Gate decision log
Status, CoS locked 2026-10-07. Companion to Satoshi Hike v1.35 section 15. Locked by CoS on 2026-10-07. Not folded into that specification. Part B numeric thresholds stay in that file. They are not restated here. Each row in CoS locked 2026-10-07 is the log contract.
Aligned to hike
4ca480e, checked on main0e4503a. Line cites in this file are4ca480e. Main0e4503alocks section 18 item 9 and does not move those section 15 rows. Custody, the friendly entry, caps, and the cycle follow that text, including the 2026-10-07 00:15 re-verify lock at lines 139 and 1714.v1. An unsigned append-only hash-chained decision log, plus the saved PCOC, the verify record, and the profile bytes. v1 has no signed
gate.head, no signed receipt, and nogate_key. Those are Later. The public tip hint is/latest.json. It is unsigned.
Status: Locked, CoS 2026-10-07.
The admission service (Satoshi Hike v1.35 section 15), deployed as the Grounding Lab Admission Gate, appends a line when it decides. Its Sign-in door is POST /v1/sign-in. This file does not define POST /v1/admit.
This file is the Gate decision log. Section 15 is the policy. Section 14 is the measurements. This file is the log of those decisions, the friendly-list changes, and the manual strike. v1 has no head line and no receipt.
This file is not the hike construction. It is not the producer’s live event log in section 13.5 (hike.produce.*). A line here is not a produce record. A produce record is not a line here.
The Sign-in challenge bytes, the origin, and the issuing routes are section 15.1. This file uses submission_sha256 as section 15.1 defines it. It does not restate those bytes. v1 does not publish the challenge bytes or the signature bytes. The binding on the line is submission_sha256.
Sections marked Normative are the log contract. Sections marked Informative explain that contract and do not add a second one.
The verify CLI exits are hike section 18 item 9, locked on main 0e4503a. This file cites them and does not implement them. Exit code 0 means the record was produced. Exit code 1 means the object file cannot be read or decoded. Exit code 2 means a usage or invocation error. The verifier field names the implementation. It does not carry those exit codes.
1. Status
The status prose in this section is informative.
The log starts empty. seq starts at 0 on the first line. That line is a gate.decision, a gate.list, or a gate.strike. It is not a head. v1 has no gate.head. An earlier decision is not copied in.
Profile mode provisional (section 15.4) is not an event name and not a field of a line. The profile key is provisional. Its value is on or off. The prose provisional: off names that mode when the value is off. It is not a key name. Section 10 states the file. The decision token provisional is a fast-line decision. It is not that mode.
/latest.json is an unsigned tip hint. It is not evidence. Trust is the hash chain. The hint carries feed (pcoc-gate-feed-v1), last_seq, last_event_hash, today, published_unix_s, and policy_current (id, version, sha256). It carries no signature and no gate_key.
2. What a line decides
Normative.
The words in a decision line are provisional, accept, refuse, and revoke. The log does not spell deny. The log does not spell fail. Section 15 allows a deployment to spell refuse as deny on its wire. This log still spells refuse.
The fast phase never issues accept. A fast line’s decision is provisional or refuse. revoke is not a fast decision. A line with decision accept and phase fast is invalid.
A fast line is refuse when a fast-phase rule fails on the object’s own bytes. That set is decode (the object bytes fail to decode as a hike object), waypoint rehash, fold mismatch, shape mismatch including shape_item height_range, seed-value mismatch, a plant or header mismatch confirmed by a fresh read from {index-url} that bypasses the local cache, or an object-settled refuse-at-once. Decode, waypoint rehash, fold mismatch, shape mismatch, seed-value mismatch, and a confirmed plant or header mismatch are Rule B9. They are not gated on a lock. CoS locked 2026-10-07. A fold mismatch is the section 12.2 fold recomputed from stored checkpoints, compared with the fold bound into the stored waypoint. The reason is B9 fold. When no grant was issued it is an invariant fast final refuse. phase is fast. fast_seq is omitted. There is no thorough run and no grant. It is not the Rule A4.3 checkpoint-digest check, and the reason is not B9 checkpoints. When a grant was ever issued and a later measurement records that fold mismatch, the line is revoke, phase thorough, reason B9 fold. The object-settled refuse-at-once is Rule B0, reason B0 no_pair_outcome. It is not Rule B9. A cap on a signed submission is Rule B7. CoS locked 2026-10-07. It is not a decision line, it is not Rule B9, and it is not published. The Sign-in response is refuse. The fast refuse line is one final refuse of that submission_sha256 when no grant was issued. phase is fast. fast_seq is omitted. It is not encoded as null. There is no thorough run. There is no grant.
A BIP-340 failure is an error named bad_signature. It is not a section 15 decision and it is not a fast refuse. There is no decision line. The response is that error. A private ops counter records it. v1 does not publish that counter. A decision line does not carry msg or sig.
Fast-phase not read goes to held. It is not a line.
Otherwise the fast phase writes provisional. That token is a reversible fast grant. The line omits fast_seq.
A thorough line decides accept, refuse, or revoke. It does not decide provisional. Section 15.3: when thorough settles and the accept stands, the line is phase thorough, decision accept. held is not a line. Thorough refuse is the settle when no grant was issued. Thorough revoke is the settle when a grant was issued.
A move from provisional to held ends the provisional grant. It does not resume. No grant remains for use. The ended grant still counts: a later negative thorough outcome is revoke, because a grant was issued in the cycle. The Gate invalidates the pair’s grant before it returns or logs a revoke.
A grant was issued when a provisional line or a thorough accept exists for that pair in the cycle, including one already ended by the move from provisional to held. A fast refuse is not a grant. A B7 cap hit is not a grant and not a line.
A revoke comes only from a measurement. A negative thorough outcome, or a policy re-verify that records a measured mismatch, is revoke if a grant was ever issued (provisional or accept, including one already ended by the move from provisional to held). The line cites that grant in fast_seq or prior_seq. It is refuse if no grant was ever issued. Every revoke is phase thorough and hashes the full section 14 record. No revoke carries the fast record. A report does not revoke. A report feeds research and may lead to a new policy version. Holder-behaviour moderation belongs to the integrating site. It is not a Gate decision and not a reason in this file.
Under provisional: off there is no fast provisional line. The first thorough line has fast_seq omitted. It is not encoded as null. Its decision is accept or refuse. A fast refuse may still be written. It omits fast_seq.
fast_seq is a thorough-line field. It is never JSON null. CoS locked 2026-10-07. Omitting fast_seq is not a null citation. A line that encodes fast_seq as JSON null is invalid at checker step 1. A checker does not treat that null as an omission, does not drop the key, and does not read it as no grant. Omission means the line cites no fast provisional. A fast line omits fast_seq. A thorough line that does not cite prior_seq cites the latest fast provisional for that submission_sha256 among lines before this line, or omits fast_seq when there is none.
A line that cites prior_seq carries fast_seq exactly when the cited line carries fast_seq. The value equals that line’s fast_seq. It omits fast_seq exactly when the cited line omits it.
A thorough refuse omits fast_seq when no provisional grant was issued. A thorough revoke that does not cite prior_seq has fast_seq when a provisional grant was issued on a line before this line.
A second thorough line for the same fast_seq is invalid unless the later line cites prior_seq. A second thorough line for the same submission_sha256 when fast_seq is omitted is invalid unless the later line cites prior_seq. A fast line after a thorough line for the same submission_sha256 is invalid.
When fast_seq is present and the line does not cite prior_seq, that thorough line’s policy_profile_id, policy_profile_version, and policy_profile_sha256 equal the fast line’s.
A later thorough line for a submission already accepted is valid only as revoke, or as a policy-version re-verify, and cites the earlier thorough line in prior_seq. The profile-match rule applies only against fast_seq.
prior_seq is an integer on a thorough re-verify line. It is omitted on an admission line. CoS locked 2026-10-07. A re-verify record is the pair (submission_sha256, prior_seq). prior_seq omitted is the admission record for that submission_sha256. Two decision lines with the same pair are invalid. A line that cites prior_seq is exempt from G2.7 and G2.8. It carries its own policy_profile_* fields.
prior_seq cites the latest thorough line for that (public_key, object_sha256) before this line. On a policy-change re-check of the same submission, including a service-started re-verify, submission_sha256 equals the cited line’s submission_sha256. The field is present. It is not omitted. A service-started re-verify does not mint a challenge. That pair is its own record. It does not collide with admission finality for that submission_sha256, and it does not collide with a B7 cap. A B7 cap is final only as a Sign-in response for that submission_sha256 and writes no line. A client Sign-in that mints a new challenge, under a different policy_profile_id or a higher version of the same id, carries the submission_sha256 section 15.1 computes for that challenge. When that sign-in is that policy-change re-verify, prior_seq still cites the pair’s earlier thorough accept. It is not the same-policy new measurement. The accept’s submission_sha256 may differ. The record key is this line’s (submission_sha256, prior_seq).
A prior_seq line is valid only in one of three cases. CoS locked 2026-10-07.
- Policy re-verify, higher version.
policy_profile_idequals the prior line’s, andpolicy_profile_versionis higher. - Policy re-verify, different id.
policy_profile_iddiffers from the prior line’s. The version integers may be equal. Two ids are different policies. - Same-policy new measurement.
policy_profile_id,policy_profile_version, andpolicy_profile_sha256equal the prior line’s, andverify_record_sha256differs. The service appends this line only for the trigger in the next paragraph.
Any other prior_seq line has result invalid. That includes a revoke whose verify_record_sha256 and profile fields both equal the prior accept. It includes a lower policy_profile_version on the same policy_profile_id. It includes the same id and the same version with a different policy_profile_sha256.
CoS locked 2026-10-07. A same-policy new measurement is service-started, and only to fill a prior not read. A client Sign-in, a check-in, and a signature-only return do not run section 14 and do not append this line. The service starts it when a prior thorough verify record for that (public_key, object_sha256) under that same profile contains a not read, a later {index-url} read fills that measurement, and the new record’s SHA-256 differs. The line cites that prior thorough line in prior_seq. It never issues provisional. A negative thorough outcome is revoke, because a grant was issued. An outcome that meets policy is accept, and the add follows section 2.2. The checker accepts that line shape: the profile triple equals the cited line, and verify_record_sha256 differs. The checker does not observe the trigger. The service does not append the line for any other same-policy reason.
An admission refuse or revoke omits prior_seq. It is final for that admission record. The log does not append another decision line with the same submission_sha256 and prior_seq omitted. A (submission_sha256, prior_seq) re-verify is a different record. It is not that later admission line.
CoS locked 2026-10-07. A B7 cap hit (verify_cap_per_object or verify_cap_per_door_session) is not a logged event. The Sign-in response is refuse. Nothing is appended. It never revokes. It does not change the friendly entry. It does not end the cycle. The pair’s state stays received. It is final only for that submission_sha256: a resubmit of that hash returns refuse and writes no line. A later policy-change re-check of that same hash, keyed by (submission_sha256, prior_seq), is not that resubmit. A second admission line that omits prior_seq, for a (public_key, object_sha256, policy_profile_id, policy_profile_version) whose cycle is already closed refused or revoked, is invalid even when submission_sha256 is new. A different policy_profile_id is a different cycle, even when the version integers are equal. A line that cites prior_seq is not that second admission.
2.1 Cycle, caps, and new submissions
Normative.
CoS locked 2026-10-07. The cycle key is (public_key, object_sha256, policy_profile_id, policy_profile_version). The version integer alone is not the cycle key. Two profiles that share a version integer and differ in id do not share a cycle, a cap count, or a closed outcome. On this log, hike policy_version is that pair. In section 2.4, policy_version means (policy_profile_id, policy_profile_version).
An admission cycle runs from received for a (public_key, object_sha256) under one (policy_profile_id, policy_profile_version) until a thorough accept, or a refuse or revoke of the pair (including reason held_bound). A B7 cap hit is not a line and does not end the cycle. A later re-verify of an accepted object under a different policy_profile_id, or under a higher policy_profile_version of the same id, is not a new admission cycle and never issues provisional (hike 4ca480e lines 140, 1726, and 1743). A pair that is refused or revoked under an old pair, submitted under a different id or a higher version of the same id, starts a new admission cycle and may issue provisional under the same rules as any fresh cycle (hike 4ca480e lines 90, 133, 1728, 1739, and 1740). A lower version of the same id does not start that cycle.
The service keeps one atomic per-cycle record keyed on (public_key, object_sha256, policy_profile_id, policy_profile_version). That record is not the nonce row. The nonce compare-and-set stays for single-use nonces only. It does not admit the provisional grant. The record admits at most one provisional grant in the cycle. Concurrent submissions of the same pair cannot each receive one. A second provisional line for that quadruple before the cycle ends is invalid.
A new submission while the cycle is open returns the current state, with no grant and no new run. A new submission while the cycle is closed refused or revoked under this (policy_profile_id, policy_profile_version) returns that outcome and does not open a new cycle. While the pair is accepted and this object is the key’s current friendly entry, friendly-list and signature-only rules apply.
Row order. The new-submission rows are checked before the cap rows. A cap row applies to the submission the per-cycle record has admitted. It does not apply to a new submission of a pair whose cycle is already open. A cap hit stays in received. The submission response is refuse. It does not end the cycle. The pair’s state does not move to refused.
verify_cap_per_object is a candidate of 1, keyed on (public_key, object_sha256, policy_profile_id, policy_profile_version). The figure is not locked. It is a compute limit, not a ban. Service-started re-verifies and held retries are not in the count. A new (policy_profile_id, policy_profile_version) is a new count. Two ids that share a version integer do not share the count. There is no verify_cap_per_key. verify_cap_per_door_session is not applied until a profile sets a figure. The hike candidate is 3, 3, and 5 (strict, default, lenient), at lines 65, 106, and 1746 on 4ca480e. Not locked. This file does not lock a figure. When the profile sets a figure, a hit on a valid signature and a fresh challenge is not a logged event. The Sign-in response is refuse. Nothing is appended. The hit publishes no object and no verify record. It does not start a thorough run and does not issue a grant. It never revokes. It does not change the pair’s state or the friendly entry. The pair stays in received. It does not end the cycle. It is not the pair’s thorough decision. It is final only for that submission_sha256. A resubmit of that hash returns refuse and writes no line. A later (submission_sha256, prior_seq) re-check of that same hash is not that resubmit. A private ops counter cap records the hit. v1 does not publish that counter. A decision line whose reasons contain a B7 name is invalid. An unauthenticated rate limit is not this rule. A transport rate limit stays outside this policy. It writes no line.
A pair accepted under any (policy_profile_id, policy_profile_version) never issues provisional again. Under a different id, or a higher version of the same id, a client submission of that pair is a re-verify: straight to thorough, subject to verify_cap_per_object on (public_key, object_sha256, policy_profile_id, policy_profile_version). The result is logged under the new pair.
At most one provisional grant is issued in the cycle. A hold entered from fast_accept_bound or from async_catch_budget does not return to provisional. The held to provisional row applies only when no provisional has been issued yet in that cycle, provisional is on, the hold was not entered from fast_accept_bound or from async_catch_budget, and the fast inputs are read on retry. v1 does not publish a hold line. The checker enforces the one-provisional line. The service enforces the no-return from those two holds.
2.2 Friendly list, strike, and custody
Normative.
Satoshi Hike v1.35 states this lock. This companion writes the strike event. The hike does not.
MVP only, not law. One key, one PCOC. A decision binds to
(public_key, object_sha256), never to the key alone.revoke_scopeis that pair.verify_cap_per_keyandintegrity_mismatch_capare not parameters. There is no automatic key strike and nokey_struckreason. A decision does not strike the key. A key strike is only an explicit manual operator action, logged as an event. This file does not write the event. A refused or revoked object stays refused or revoked under that policy version. The same key may submit a new object hash at any time.
That paragraph is the hike file at 4ca480e, line 95, in the 2026-10-06 22:23 block. “This file does not write the event” means the hike file. This companion writes gate.strike. There is no automatic key strike. This file has no key_struck reason. A decision does not strike the key.
A friendly-list entry is added only at a thorough accept. One entry per key, and that entry is one accepted object. A provisional line does not add. The add is a gate.list line with action add. It cites accept_seq, the seq of that thorough accept. The policy_profile_* fields on the add equal that accept.
Newer is the decision-log seq of the thorough accept. A higher seq is newer. It is not the submission time and it is not a hike Bitcoin time. Only a thorough accept of a newer object replaces the entry. The replaced decision line stays unchanged. A second add does not rewrite the old decision. A refuse or a revoke of any other object does not change the entry. A re-verify of an older object does not displace the current entry. A re-verify accept keeps the entry only when this object is already the key’s current friendly entry. If a newer accept B later loses its accept, A is not restored.
The current entry is the gate.list add that replaced every earlier add for that public_key and has not itself been ended. An add replaces every earlier add for that key when its accept_seq is greater. The replaced add is not current. That holds when the object hashes differ and when they match. Ending the current add does not restore a replaced add. After that end there is no current entry until a later thorough accept adds again. A later gate.strike of the current pair ends the current add. A later revoke of that same object_sha256 ends the current add only when this object is still the current entry: compare-and-remove on object_sha256, only if the current entry’s object hash still matches this pair. A revoke of any other object does not end it. A not read never ends an add and never changes sign-in state. v1 writes no gate.list remove and no reverify_not_read line. gate.strike is the only removal line this file writes. The revoke removal is the effect of that decision line, not a second event. This is hike 4ca480e lines 135, 139, 1714, 1778, and 1779. The current entry is not the unended add with the greatest accept_seq.
An admission thorough accept omits prior_seq and closes the cycle. The next line is exactly one gate.list add. That add cites that accept’s seq as accept_seq. Its own seq is that accept’s seq plus 1. A re-verify accept writes an add only when this object is already the current entry before that accept. That add is the next line. When this object is not current, an add is invalid, because it would displace the newer entry, and no add is required. An add whose accept_seq is lower than the accept_seq of an add for the same key that is still current is invalid.
The writer appends the accept, fsyncs it, appends the required add, and fsyncs that add before the entry is current. If the add write fails after the accept is durable, the writer retries that add before any other append. A complete accept without its required add is invalid. The writer does not append a second accept to repair it.
A check-in is a signature-only return for the same already-accepted object hash under the current policy, while the entry is current and the state is accepted. A check-in is not a line. v1 publishes no check-in counter.
Replaced object. Hike 4ca480e line 1741 returns the logged accepted decision and states that this object is not the key’s friendly entry. The hike file does not name a field. This file carries that statement beside the decision. It does not add a decision token and it does not add a head field. CoS locked 2026-10-07. The field spelling below is the log contract.
- The Sign-in
decisionisaccept. The state staysaccepted. Those words are the #159 outcome and its state. This path does not add a token. - The response field
replaced_byis theseqof the thoroughacceptthat replaced this object. It is not a log key and not adecision. - No grant. No new run. No signature-only sign-in through that object. The submission does not displace the current friendly entry. v1 writes no decision line and no
gate.listline on this path. - A new object hash that has no
acceptis a new submission. The same key may submit that new hash at any time.
gate.strike is the manual operator event. It is not a decision and not a revoke. CoS locked 2026-10-07. It ends the current friendly entry for that (public_key, object_sha256) only. It does not strike the key. It does not ban the key. It does not write key_struck. It does not end another object’s grant. The accept line stays. The pair’s admission state stays accepted. Signature-only sign-in and check-in stop, because the entry is no longer current. The struck object does not become current again. A later gate.list add for that same public_key and object_sha256 is invalid. A re-verify accept of the struck object writes no add. A thorough accept of a different object hash may add, and that new entry is current. Ending that newer entry does not restore the struck object. The same key may submit a new object hash at any time. A strike whose object is not the current entry, or a strike when there is no current entry, is invalid.
A policy-version re-verify that stays not read uses reverify_max_attempts and max_held_blocks counted from the first not read on that re-verify, not from the version change. Until either budget is hit, the earlier accept stands. There is no suspend, no held_bound, and no decision line. Friendly returns continue. The re-verify is retried later. A private ops gauge reverify_pending counts those entries. v1 does not publish the gauge.
When either budget is hit on that not read, this file writes no decision line and no gate.list line. The hit is not a revoke. It does not remove the friendly entry and does not change sign-in state. The accept line stays. Signature-only sign-in continues on the earlier accept. After the hit, the Gate keeps retrying on each later {index-url} tip advance: at most one retry per strict increase in that tip height. A jump of several blocks is one retry. An equal or lower tip, including a reorg, is not a retry. These post-budget retries do not restart the spent budget and do not re-arm held_bound. The number of such retries is unbounded. A retry that is still not read writes no line. This is hike 4ca480e lines 48, 139, 1714, and 1781.
There is no reverify_not_read removal path. reverify_not_read is not a reason and not a line.
A re-verify that refuses, other than a B7 cap hit, is a revoke. phase is thorough. The line hashes the full section 14 record. It cites the new policy version. It cites prior_seq. It carries fast_seq exactly when the cited line carries fast_seq. That revoke removes the friendly entry only when this object is the key’s current friendly entry, by the compare-and-remove above. A B7 cap hit is not a logged refuse, never a revoke, and never removes a friendly entry. A not read is not a refuse and does not revoke. This is hike 4ca480e lines 46, 139, and 1779.
The Gate keeps the accepted object bytes. This file publishes every decided object. If those bytes are missing when a re-verify is needed, that is not not read. The next sign-in requires a full section 15.1 submission of the object. It is not a signature-only return treated as not read. This is hike 4ca480e lines 89 and 1712. The hike does not name a state token for the missing-bytes case beyond that next submission.
The wording for a refused object is “refused under policy version X”, where X is policy_profile_version. There is no ban and no flag. The same object submitted again under a new policy version starts a new admission cycle and may issue provisional, and it may be accepted.
2.3 Budgets
Normative.
max_held_blocks applies to every hold, whatever the cause: a fast not read, a thorough not read, fast_accept_bound, or async_catch_budget. The count starts at the Gate’s {index-url} tip height at the first entry into held in the cycle. It does not reset, including across held to provisional to held. held_bound fires when a check finds that tip ≥ start + max_held_blocks, even when no not read has occurred. The candidate is 6. The number is not locked.
reverify_max_attempts counts not read retries only, fast or thorough. A hold with no not read, including fast_accept_bound and async_catch_budget, does not count toward it. It is hit when that many retries are still not read. The candidate is 3. The number is not locked. The count starts at the first not read in the cycle for an admission hold, and at the first not read on a policy re-verify for that re-verify. A later provisional does not reset it. The first still-not read record is count 0. Each later retry that is still not read adds 1. The budget is hit when the count ≥ reverify_max_attempts.
The profile keys are lowercase reverify_max_attempts and max_held_blocks. This file does not spell those names in capitals.
On an admission hold, whichever budget is hit first, the outcome is refuse or revoke with reason held_bound. detail names the budget, reverify_max_attempts or max_held_blocks. When both are hit on the same check, detail is reverify_max_attempts. The outcome is revoke if a grant was ever issued, and refuse if none was. It does not stay held. A re-verify budget, counted from the first not read on that re-verify, is not this rule. A hit on that budget writes no line, is not held_bound, is not a revoke, and is not re-armed.
phase on a held_bound refuse is the phase running when the budget ran out, and the line carries that phase’s record. No grant, fast phase still running: refuse, phase fast, fast_seq omitted, fast record. No grant, thorough phase running, including provisional: off: refuse, phase thorough, fast_seq omitted, thorough record. A held_bound revoke, and every other revoke, is phase thorough with the full section 14 record. A held_bound revoke cites fast_seq only when a provisional was issued in that admission cycle. It does not cite prior_seq. An accept ends the cycle, so a held_bound line is not a re-verify of a thorough accept.
{index-url} is the upstream. The local mirror is always a cache of {index-url} responses. It is never the {index-url}. An operator’s own Bitcoin-built index counts as its {index-url}. The normative held clock is that tip. A log line records the tip height as bitcoin_tip_height. That name is the log entry’s name for the tip. The entry is unsigned. It is not a head field. v1 has no head. This file does not define a second clock.
The per-cycle record stores the tip height at the first entry into held. A held_bound line’s bitcoin_tip_height is the tip at the check that fired. The checker does not read the per-cycle record.
A vector may supply holds, an array of objects beside the log. Each object has submission_sha256 (hex64), start_tip (the tip height at the first entry into held), not_read_count (an integer), and the two budgets when the profile sets them: max_held_blocks and reverify_max_attempts, each an integer in 0 … 2^53 − 1 or omitted when unset. No other key. No JSON null. held_late runs only when the vector supplies holds. When holds is absent, this check does not run.
held_late looks only at admission lines for that submission_sha256, the lines that omit prior_seq. A line that cites prior_seq is never held_late. A (submission_sha256, prior_seq) re-verify is never held_late. The post-budget re-verify that stays open with no closing line is not held_late. For one holds object, the result applies when a later line exists, no closing admission decision for that submission_sha256 (an accept, refuse, or revoke that omits prior_seq) has been appended, and either max_held_blocks is present and that later line’s bitcoin_tip_height is ≥ start_tip + max_held_blocks, or reverify_max_attempts is present and not_read_count ≥ reverify_max_attempts. A log that ends at the closing held_bound line is not held_late. An unrelated line that does not show the budget already hit does not by itself flag held_late. A provisional is not a closing line and does not reset the budgets. A budget key omitted on the holds object does not run that comparison.
An unresolved hold has no decision line. Its budgets come from the active published profile, the profile in force at the first entry into held, stored on the per-cycle record. A decision line’s limits come from the profile that line names. A head names no profile, because v1 has no head. A vector may supply the profile bytes.
On the per-cycle record, hold_kind is fast_phase when the hold began from the fast phase and no thorough not read has been recorded. hold_kind is thorough_not_read when a thorough measurement in this hold is not read, including six-leg and a hold entered from provisional. Within one hold, hold_kind may change from fast_phase to thorough_not_read only. It does not change back. entered_from is set at the first entry into held and does not change. Its values are fast_not_read, fast_accept_bound, async_catch, and thorough_not_read. Those two fields are service state. They are not keys on a v1 log line.
2.4 Transitions
Normative.
The table maps each section 15.2 row this file logs to a line or not. “No line” means the log is not appended. v1 returns no receipt. An HTTP 503 writes no line and consumes no seq. The nonce stays consumed. An unauthenticated rate limit writes no line.
{index-url} is the upstream, as section 2.3 states.
| From | When | To | Log |
|---|---|---|---|
received |
A new submission of this pair under this policy_version while the cycle is already open in received. Return the current state. No grant. No new run |
received |
No line |
provisional |
A new submission while the cycle is open. Return the current state. No grant. No new run | provisional |
No line |
held |
A new submission while the cycle is open. Return the current state. No grant. No new run | held |
No line |
refused |
A new submission under this policy_version. The cycle is closed refused. Return that outcome. No new cycle |
refused |
No line |
revoked |
A new submission under this policy_version. The cycle is closed revoked. Return that outcome. No new cycle |
revoked |
No line |
refused |
This pair is refused under an old policy_version. A client submits it under a new policy_version. The submission starts a new admission cycle under the new version. That cycle may issue provisional under the same rules as any fresh cycle. The pair may be accepted |
received |
No line on the open. A later fresh-cycle row may write provisional |
revoked |
This pair is revoked under an old policy_version. A client submits it under a new policy_version. The submission starts a new admission cycle under the new version. That cycle may issue provisional under the same rules as any fresh cycle. The pair may be accepted |
received |
No line on the open. A later fresh-cycle row may write provisional |
accepted |
A newer accept has replaced this object. Return decision accept. The state stays accepted. The response field replaced_by is the seq of the thorough accept that replaced it. No grant. No new run. No signature-only. Does not displace the current entry |
accepted |
No line |
accepted |
This object is the key’s current friendly entry. Friendly-list and signature-only rules. Not a new cycle. Never issues provisional |
accepted |
No line |
accepted |
A client submission of a pair accepted under any policy_version. Never issues provisional. Under a new policy_version, re-verify straight to thorough |
accepted |
Thorough line under the new version when thorough settles. No provisional line |
received |
provisional is on, no provisional has been issued yet in that cycle, the fast measurements meet the profile, and every fast measurement the profile needs is present as an integer or a token other than not read or not defined. At most one provisional in the cycle |
provisional |
Fast line, decision provisional. Omits fast_seq |
received |
The profile sets a figure and verify_cap_per_object is hit on a valid signature and a fresh challenge. The new-submission rows were checked first. The pair stays in received. The Sign-in response is refuse. Nothing is logged. The hit never revokes, does not change the friendly entry, and does not end the cycle. It is final only for that submission_sha256 |
received |
No line. Private counter cap. A resubmit of that hash returns refuse and writes no line |
received |
The profile sets a figure and verify_cap_per_door_session is hit. Same cap rule. The hike candidate is 3, 3, and 5 (strict, default, lenient). Not locked. This file does not lock a figure |
received |
No line. Private counter cap. Same finality for that submission_sha256 |
received |
A plant or header mismatch is read from the local cache and is not yet confirmed by a fresh {index-url} read that bypasses the cache |
held |
No line |
received |
That mismatch is confirmed by a fresh {index-url} read. Rule B9. phase fast. No thorough run. No grant |
refused |
Fast refuse. Omits fast_seq |
received |
The object bytes fail to decode. Rule B9 | refused |
Fast refuse. Reason B9 decode. Omits fast_seq |
received |
Shape mismatch, including shape_item height_range, seed-value mismatch, or waypoint rehash mismatch. Rule B9. Not gated on a lock |
refused |
Fast refuse. Omits fast_seq |
received |
Fold mismatch from stored checkpoints under section 12.2. Rule B9. Reason B9 fold. phase fast. No thorough run. No grant. Not B9 checkpoints |
refused |
Fast refuse. Omits fast_seq |
received |
A fast not read other than a six-leg read, including an unreadable header, when the profile needs that measurement |
held |
No line. hold_kind fast_phase |
received |
Fewer than two waypoints among W_1 … W_16 would be anchored even if every not read plant or path read among them came back anchored, or N is not defined from the object’s own bytes, or fewer than six legs. If a re-read could bring that count to two or more, this row does not apply |
refused |
Fast refuse. Reason B0 no_pair_outcome. Omits fast_seq |
received |
A re-read could bring that waypoint count to two or more, or S is not read |
held |
No line. The measurement is not read |
received |
provisional: off. A thorough not read, including a short six-leg path. When the draw is not read, six_leg_runs is the string not read |
held |
No line. hold_kind thorough_not_read. Not a fast received to held row |
received |
provisional is on, and the fast phase has not left received within fast_accept_bound. No grant. This timeout hold does not return to provisional |
held |
No line. entered_from fast_accept_bound |
received |
provisional: off. Thorough decides accept |
accepted |
Thorough accept. fast_seq omitted. gate.list add cites it |
received |
provisional: off. Thorough negative. No grant was issued |
refused |
Thorough refuse. fast_seq omitted. Full record |
provisional |
async_catch_budget is hit before thorough settles. The move ends the grant. It does not resume. This hold does not return to provisional. A later thorough negative is revoke |
held |
No line. entered_from async_catch |
provisional |
Thorough decides accept |
accepted |
Thorough accept. fast_seq set. gate.list add |
provisional |
Thorough negative. A grant was issued. phase thorough |
revoked |
Thorough revoke. Cites fast_seq. Full record |
provisional |
A thorough not read, including short six-leg bytes. six_leg_runs is not read when the draw is not read |
held |
No line. The move ends the grant. hold_kind thorough_not_read |
held |
provisional is on, no provisional has been issued yet in that cycle, and this hold was not entered from fast_accept_bound or async_catch_budget. The fast inputs are read |
provisional |
Fast provisional. Omits fast_seq. The only provisional in the cycle |
held |
Entered from fast_accept_bound or async_catch_budget. Thorough decides accept. Does not return to provisional |
accepted |
Thorough accept. fast_seq omitted when none was issued |
held |
Entered from fast_accept_bound or async_catch_budget. Thorough negative. No grant was issued. phase thorough. fast_seq omitted |
refused |
Thorough refuse. Full record |
held |
Entered from fast_accept_bound or async_catch_budget. Thorough negative. A grant was issued. A later thorough negative is revoke. phase thorough |
revoked |
Thorough revoke. Cites fast_seq. Full record |
held |
Thorough decides accept |
accepted |
Thorough accept. fast_seq is the provisional, or omitted when there is none. gate.list add |
held |
Thorough negative, and a grant was ever issued (provisional or accept, including one already ended by the move from provisional to held). phase thorough |
revoked |
Thorough revoke. Cites fast_seq or prior_seq. Full record |
held |
Thorough negative, and no grant was issued | refused |
Thorough refuse. Omits fast_seq. Full record |
held |
A thorough not read and neither budget is hit. six_leg_runs is not read when the draw is not read |
held |
No line. hold_kind may change to thorough_not_read only |
held |
An admission budget is hit and a grant was ever issued. Reason held_bound. phase thorough. A re-verify budget hit is not this row |
revoked |
held_bound revoke. Full record. Cites fast_seq only when a provisional was issued in that admission cycle. Does not cite prior_seq |
held |
No grant, and the budget ran out while the fast phase was still running. Reason held_bound. phase fast. fast_seq omitted |
refused |
held_bound refuse. Fast record |
held |
No grant, and the budget ran out while thorough was running, including provisional: off. Reason held_bound. phase thorough. fast_seq omitted |
refused |
held_bound refuse. Thorough record |
held |
A cache-sourced plant or header mismatch is confirmed, and no grant was ever issued. Rule B9. phase fast |
refused |
Fast refuse. Omits fast_seq |
held |
That confirmed mismatch, and a grant was ever issued. phase thorough. The revoke cites fast_seq |
revoked |
Thorough revoke. Full record |
accepted |
Policy version changes. Re-verify the stored object. Thorough accept. The friendly entry is kept only when this object is already the current entry. If a newer accept B later loses its accept, A is not restored. A re-verify of an older object does not displace a newer entry. Not a new cycle. Never provisional |
accepted |
Thorough accept. prior_seq set. fast_seq copied only when the cited line has it. gate.list add is the next line only when this object is already current |
accepted |
That re-verify refuses under the new policy version. The refuse is a measured mismatch or any B-rule outcome other than a B7 cap hit. A not read is not this row. A B7 cap hit is never this row. phase thorough. The revoke cites the new policy version and prior_seq. It carries fast_seq exactly when the cited line carries fast_seq. Removes the friendly entry only when this object is current, by compare-and-remove on object_sha256 |
revoked |
Thorough revoke via prior_seq. Full record. No reverify_not_read line |
accepted |
That re-verify is not read and neither budget, counted from the first not read on that re-verify, is hit. The accept stands. No suspend. No held_bound. Friendly returns continue. The re-verify is retried later |
accepted |
No line. reverify_pending gauge |
accepted |
That re-verify is still not read and a budget counted from that first not read is hit. The hit is not a revoke, does not remove the friendly entry, and does not change sign-in state. Signature-only sign-in continues. After the hit, at most one retry per strict increase in the {index-url} tip height. A jump of several blocks is one retry. An equal or lower tip, including a reorg, is not a retry. Those retries do not restart the spent budget and do not re-arm held_bound |
accepted |
No line. The accept stays. No reverify_not_read line |
accepted |
Signature-only sign-in for the same already-accepted object, state accepted |
accepted |
No line |
refused |
Any later check of that same submission_sha256 |
refused |
No line |
revoked |
Any later check of that same submission_sha256 |
revoked |
No line |
| — | Manual operator strike of the current friendly entry | — | gate.strike. Not a revoke. Not a decision |
| — | A BIP-340 failure | — | No line. Error bad_signature. Private counter only |
| — | An unauthenticated rate limit, or HTTP 503 | — | No line. Private counter only. 503 consumes no seq. The nonce stays consumed |
| — | Same submission_sha256 already decided or already answered |
— | No line. Return the original outcome. No grant. Checked before the nonce compare-and-set |
Policy refuses at once only when the waypoint count is settled by bytes already in hand: fewer than two waypoints among W_1 … W_16 would be anchored even if every not read plant or path read among them came back anchored. If a re-read could bring that count to two or more, the measurement is not read and the state is held. N not defined from the object’s own bytes. Fewer than six legs is not defined from the object’s own bytes and is that same refuse-at-once. S not read stays held. A short or missing six-leg read, a short-byte six-leg draw, and short path bytes for a six-leg run are thorough-only. They are not read. When the six-leg draw is not read, six_leg_runs is the JSON string not read, not an empty array. The state is held from received when provisional is off, from provisional, or from held. They are not a fast received to held row.
An integer above 2^53−1 in the object’s own bytes, including stored_height and P_b when those bytes are the object’s and the height is not inside a hash-verified path body, is shape mismatch, shape_item height_range, reason B9 shape. It is a fast refuse. It is not not read. An index-document height above that bound is an unsuccessful read: not read. A hash-verified path body with a height above 2^53−1 is plant mismatch. claimed_block is not defined. It is not height_range and it is not a whole-object decode failure.
The service looks up submission_sha256 before the nonce check. A resubmit returns the original outcome and does not mint a grant. v1 has no receipt. A resubmit while the cycle is held returns that state and writes no line. After a decision line exists, the resubmit returns that outcome. There is no receipt log_seq. The line seq is the order. The nonce compare-and-set is for single-use nonces only. It does not admit the provisional grant. The per-cycle record does.
The default profile leg_rate_floor is locked at 1/2 in the hike file. This file cites that lock and does not restate the comparison. Candidate min_anchored_paths is 8, 7, and 7. Candidate min_last_bookend is 14, 14, and 13. Those candidates are the hike file’s. This file does not lock them. Only the letter k, the budgets, and the numeric thresholds wait on Christian’s lock. Rule B9 does not wait.
3. Encoding
Normative.
One writer appends the log. A line is not rewritten and is not reordered. A complete line is not deleted.
Each line is one compact JSON object, UTF-8, no byte-order mark, no insignificant whitespace, keys in the order this file lists for that line, then one newline (U+000A). The hashed bytes are the object bytes without that newline. Checker step 5 checks that key order.
Integers are plain decimal JSON numbers. No exponent. No leading zeros. No spelling -0. A negative integer that section 14 allows uses a leading - and then digits, and the value is not zero.
Hashes and keys in the JSON are lowercase hexadecimal, byte 0 first, with no 0x prefix. In this log the spelling is part of the line bytes. Uppercase hex is a different line. hex12 is 12 lowercase hexadecimal characters. hex64 is 64 lowercase hexadecimal characters, 32 bytes. A field typed hex12 or hex64 has that many characters. A wrong-length hex field is invalid at checker step 1. Key order is not that step.
Tokens are lowercase strings. A token that section 4 or section 14 spells with spaces keeps those spaces.
JSON strings escape " and \ and escape each code point U+0000 through U+001F as \u00XX with lowercase hex. / is not escaped. U+007F is written raw. It is not a \u escape. A character above U+007F is raw UTF-8. It is not a \u escape.
There is no JSON null. A field that does not apply is omitted. The fields that may be omitted are:
fast_seqon a fast line, on a thorough line that has noprovisionalgrant to cite, and on aprior_seqline whose cited line omitsfast_seqprior_seqon an admission line. A re-verify line does not omit it, including when no earlier thorough line shares thissubmission_sha256checkpoints_mismatch,six_leg, andsix_leg_runsinsideverify_summaryon a fast line, including aheld_boundrefusewhose phase isfastdetailon agate.decisionline whosereasonsdo not containheld_bound
Every other key this file lists for that object is present.
seq is an integer, 0 ≤ seq ≤ 2^53 − 1. time_unix_s is an integer, 0 ≤ time_unix_s ≤ 2^53 − 1. bitcoin_tip_height is an integer in that same bound. A height the service would record above 2^53 − 1 is not written as a decision. The fast outcome for an object byte above that bound is the height_range refuse in section 2.4.
Log lines are not RFC 8785. Their key order is the order in this file. A log-line integer stays a JSON number inside the bounds above. Cite section 14 for the verify-record bound: plain JSON numbers in ±(2^53−1). The decimal-string list is empty. This file names no decimal-string key and does not restate a decimal-string procedure.
4. gate.decision
Normative.
| # | Key | Type | Meaning |
|---|---|---|---|
| 1 | event |
string | gate.decision |
| 2 | seq |
int | Position in this log. Starts at 0. Increases by 1 on every line, including gate.list and gate.strike. |
| 3 | prev_event_hash |
hex64 | event_hash of the line seq − 1. For seq 0, the genesis value in section 7. |
| 4 | time_unix_s |
int | Wall clock at the decision, Unix seconds. A presentation and ordering aid. Not evidence. |
| 5 | bitcoin_tip_height |
int | The {index-url} tip height when this line is appended. The log entry’s name for the tip. Not a head field. |
| 6 | gate_version |
string | The admission service’s version. |
| 7 | gate_commit |
hex12 | Build commit. |
| 8 | policy_profile_id |
string | The profile id. |
| 9 | policy_profile_version |
int | Monotonic for that id. This is the cycle’s policy_version. |
| 10 | policy_profile_sha256 |
hex64 | SHA-256 of the profile file in section 10. |
| 11 | phase |
string | fast or thorough. |
| 12 | decision |
string | provisional, accept, refuse, or revoke. Fast: provisional or refuse. Thorough: accept, refuse, or revoke. |
| 13 | fast_seq |
int | Omitted when section 2 omits it. Never JSON null. |
| 14 | prior_seq |
int | Omitted unless section 2 requires it. |
| 15 | submission_sha256 |
hex64 | The value section 15.1 defines. |
| 16 | public_key |
hex64 | The object’s 32-byte x-only key. Human copy may show npub. The log field is this hex. |
| 17 | object_sha256 |
hex64 | SHA-256 of the submitted object bytes, exactly as received. |
| 18 | object_published |
bool | true on every decision line, including a fast refuse. A B7 cap hit is not a decision line and publishes no object. |
| 19 | verifier |
string | The verify implementation and its version. The reference CLI name is satoshi-hike-verify. |
| 20 | verify_record_sha256 |
hex64 | Section 4.1. Present on every decision line. |
| 21 | verify_summary |
object | Section 4.2. Present on every decision line. |
| 22 | reasons |
array of strings | Section 4.3. |
| 23 | detail |
string | Present only when reasons contains held_bound. reverify_max_attempts or max_held_blocks. |
event_hash is not a key. The next line carries it in prev_event_hash.
v1 does not put msg or sig on the line. A reader checks the published object, the published verify record, and the published profile. The submission signature stays off the line.
On a thorough line that has fast_seq and does not cite prior_seq, the line at fast_seq is an earlier gate.decision with phase fast, decision provisional, and the same submission_sha256, and no later fast provisional exists for that submission before this line.
4.1 Verify record hash
Normative.
On a fast line, verify_record_sha256 is SHA-256 of the fast-phase record. That record is the section 14 object. It keeps the checkpoints key. It drops only these:
checkpoints[*].legs[*].result. Those values are the Rule A4.3 checkpoint-digest recompute. Thecheckpointskey stays.checkpoints[*].gstays.checkpoints[*].legs[*].jstays.resultis the only checkpoint field dropped.six_leg. This removessix_leg.indicesandsix_leg.results.six_leg_runs. This removes every pathsix_leg_runs[*].
No other section 14 key is absent. The record keeps waypoints, including waypoint result, paths, headers, shape, seed_value, bookends, anchored_total, analysis, and profile. The fast checkpoint-binding checks are the fold and the waypoint rehash under section 12.2 from stored checkpoints. The fast phase never recomputes a leg. A not read value the fast phase measured stays in the record.
A held_bound refuse whose phase is fast hashes that fast record. A held_bound refuse whose phase is thorough hashes the thorough record as measured, including not read. Every revoke, including a held_bound revoke, hashes the full section 14 record. No revoke hashes the fast record.
A B7 cap hit is not a line. It publishes no object, no public_key, and no verify record.
The byte form of both records is RFC 8785 JSON Canonicalization Scheme, the encoding section 14 states as the default for verify_record_sha256. The profile file uses the same scheme. Every integer in that record is a plain JSON number in ±(2^53−1). The decimal-string list is empty. The checker hashes the published record bytes. A vector supplies those published bytes. It does not supply values for the checker to encode.
CoS locked 2026-10-07. Canonicality is an in-place check of the published bytes. The checker hashes those bytes. It does not hash a re-encoded form. A failure is invalid at checker step 8 for a verify record, and at step 9 for a profile file. The published bytes pass when all of the following hold:
- The bytes are UTF-8, with no byte-order mark and no insignificant whitespace.
- Each object’s keys are in RFC 8785 order: sorted by UTF-16 code units. A shorter key that is a prefix of a longer key comes first. Duplicate keys fail the check.
- Arrays keep the published order.
- Numbers are plain decimal JSON numbers. No exponent. No leading zero. No spelling
-0. No fraction. The integer is inside ±(2^53−1). A negative integer is-and then digits, and the value is not zero. - Strings escape only what RFC 8785 escapes:
"as\",\as\\, and each code point U+0000 through U+001F as\u00xxwith lowercase hex./is not escaped. U+007F and every character above U+001F are raw UTF-8.
An implementation may serialize RFC 8785 and compare those bytes with the published bytes. That comparison rejects the line when the bytes differ. The hash input remains the published bytes.
verify_summary is a display copy. It is not an input to a decision. CoS locked 2026-10-07. The checker rejects a summary that contradicts the published record. Each count and token in section 4.2 that is derived from the record equals that record. A mismatch is invalid at step 8. A field the fast record omits is omitted on the fast summary. The checker does not invent it. A stranger’s re-verify that differs is shown as measurements, not as match or mismatch. Expected differences are a not read later filled, plants confirmed later, and a direct Bitcoin read.
When the six-leg draw is not read, the thorough record’s six_leg_runs is the JSON string not read, not [].
4.2 verify_summary
Normative.
Keys that are present stay in the order below. A fast line, including a fast held_bound refuse, omits checkpoints_mismatch, six_leg, and six_leg_runs. A thorough line, and every revoke, carries all of them. profile is present on both. Values are integers, tokens, or the objects named here. There is no ratio. There is no null. A value section 14 records as not defined or not read is that token string.
| # | Key | Value |
|---|---|---|
| 1 | profile |
production or conformance-small. |
| 2 | shape |
match or mismatch. |
| 3 | seed_value |
match or mismatch. |
| 4 | headers_mismatch |
Count of header index_comparison mismatch. |
| 5 | headers_not_read |
Count of header index_comparison not read. |
| 6 | waypoints_mismatch |
Count of waypoint objects whose result is mismatch. |
| 7 | checkpoints_mismatch |
Count of checkpoint leg objects whose result is mismatch. Thorough lines and every revoke. |
| 8 | anchored_total |
Section 14 anchored_total. |
| 9 | plant_mismatch |
Count of plants whose comparison is mismatch. |
| 10 | skip_count |
Count of plants whose path state is skip. |
| 11 | a |
Section 14 a. |
| 12 | b |
Section 14 b. |
| 13 | N |
Section 14 N. |
| 14 | S |
Section 14 S. |
| 15 | delta_h |
Section 14 delta_h. |
| 16 | six_leg |
Section 14 six_leg. Thorough lines and every revoke. |
| 17 | six_leg_runs |
Section 14 six_leg_runs. The string not read when the draw is not read. Thorough lines and every revoke. |
checkpoints_mismatch and waypoints_mismatch are counts. They are not the section 14 arrays.
Log lines are not RFC 8785. Nested keys use the order stated here. These tokens are hike 4ca480e lines 1314, 1316, and 1320.
a and b are JSON numbers in 0 … 2^53 − 1, or the string not defined. When a dependent read is short or missing, each is the string not read. N is a JSON number in 0 … 2^53 − 1, or the string not defined. When a dependent read is short or missing, N is the string not read. S is a JSON number in ±(2^53−1), or the string not read, or the string not defined. delta_h is a JSON number in ±(2^53−1), or the string not defined. When a dependent read is short or missing, delta_h is the string not read.
six_leg, when it is an object, has keys indices then results. indices is six JSON numbers. results is six strings, each match or mismatch. When fewer than six legs exist, six_leg is the string not defined. When the byte source cannot supply the next 8 bytes, six_leg is the string not read. six_leg_runs is an array of those objects in request order, or the JSON string not read. That string is not []. When the six-leg draw is not read, both six_leg and six_leg_runs are the string not read.
4.3 reasons
Normative.
reasons is a JSON array of strings. On decision provisional or accept the array is empty.
On refuse or revoke, each string is "<rule> <name>" except the held bound. The held-bound string is held_bound. It has no rule prefix. The array order is the profile’s check order.
| Rule | Names that may appear |
|---|---|
B0 |
no_pair_outcome |
B9 |
decode, shape, seed_value, waypoints, fold, headers, plant, checkpoints, six_leg |
B2 |
leg_rate_floor |
B3 |
span_sanity_bound |
B4 |
min_anchored_paths, min_last_bookend |
B5 |
plant_gap_floor |
B8 |
None. Rule B8 sets no leg-count threshold. |
| — | held_bound |
Rule B9 is normative. It does not wait on a locked threshold. shape includes shape_item height_range. CoS locked 2026-10-07. fold is the fast fold mismatch in section 2. It is not checkpoints. A cap hit is Rule B7, and only when the profile sets that figure. It is not a reason on a decision line. A line whose reasons contain B7 verify_cap_per_object or B7 verify_cap_per_door_session is invalid. not_friendly_entry is not a reason.
CoS locked 2026-10-07. The reason tokens are B3 span_sanity_bound and B5 plant_gap_floor. This file does not lock either numeric threshold. plant_gap_ceiling stays record-only and is not a reason. A line carries B3 span_sanity_bound only when the published profile sets span_sanity_bound and checks includes that string. A line carries B5 plant_gap_floor only when the published profile sets plant_gap_floor and checks includes that string. Otherwise the line is invalid. Both tokens are thorough-phase reasons. The fast phase does not refuse on either token. When the measurement those rules read is not read, the state is held and there is no line. When the profile sets the figure and the measured number fails the rule, no grant was issued means a thorough refuse with that reason, and a grant that was ever issued means a thorough revoke with that reason.
This file has no reason token for a per-key verify cap, for an integrity-mismatch cap, or for a key strike. Those names are not v1 fields. Hike 4ca480e line 95 says they are not parameters and that there is no automatic key strike.
plant_gap_ceiling is record-only. It is not a fast final refuse.
5. gate.list
Normative.
| # | Key | Type | Meaning |
|---|---|---|---|
| 1 | event |
string | gate.list |
| 2 | seq |
int | As section 4. |
| 3 | prev_event_hash |
hex64 | As section 4. |
| 4 | time_unix_s |
int | As section 4. |
| 5 | bitcoin_tip_height |
int | As section 4. |
| 6 | action |
string | add. v1 has no remove. |
| 7 | public_key |
hex64 | The key. |
| 8 | object_sha256 |
hex64 | The object. |
| 9 | accept_seq |
int | The thorough accept this add cites. |
| 10 | policy_profile_id |
string | Equal to that accept. |
| 11 | policy_profile_version |
int | Equal to that accept. |
| 12 | policy_profile_sha256 |
hex64 | Equal to that accept. |
An add whose accept_seq is not an earlier thorough accept for that public_key and object_sha256 is invalid. An add whose profile fields differ from that accept is invalid. Any other action is invalid. A friendly-list removal other than gate.strike is not a v1 line. A revoke of the current entry’s object_sha256 removes that entry by compare-and-remove. That removal is not a gate.list line. A not read does not remove the entry. There is no reverify_not_read line. Section 2.2 states which accept requires an add, that the required add is the next line, and that ending the current add does not restore a replaced add. An add after a re-verify accept of an object that is not already current is invalid.
6. gate.strike
Normative.
| # | Key | Type | Meaning |
|---|---|---|---|
| 1 | event |
string | gate.strike |
| 2 | seq |
int | As section 4. |
| 3 | prev_event_hash |
hex64 | As section 4. |
| 4 | time_unix_s |
int | When the operator action is logged. |
| 5 | bitcoin_tip_height |
int | As section 4. |
| 6 | public_key |
hex64 | The struck entry’s key. |
| 7 | object_sha256 |
hex64 | The struck entry’s object. |
| 8 | prior_accept_seq |
int | The thorough accept that created the entry. |
| 9 | note |
string | A short operator note. No ban wording. No flag wording. |
The strike is manual. CoS locked 2026-10-07. It ends that pair’s current friendly entry only. It does not strike the key. It does not ban the key. It does not rewrite the accept line. It is not a revoke. The struck object does not become current again, as section 2.2 states. A later add for that same public_key and object_sha256 is invalid.
7. Hash chain and durability
Normative.
event_hash = SHA-256( ASCII "pcoc-gate-log-v1" ‖ 0x0A ‖ line_bytes ). line_bytes exclude the trailing newline. The domain is 16 bytes.
Genesis, the prev_event_hash of seq 0, is SHA-256( ASCII "pcoc-gate-log-v1 genesis" ‖ 0x0A ). No key is hashed into genesis.
The checker hashes bytes. It does not parse a line in order to check the chain. It recomputes each event_hash and compares it with the next line’s prev_event_hash. seq is consecutive from 0.
One writer. Two different line_bytes at the same seq under the same genesis are a fork. The checker does not merge logs.
Durable means the line, including its trailing newline, is fsynced before the line is committed. A line that has a Sign-in response is fsynced before that response. A line with no Sign-in response, including a service-started thorough settle, a held_bound line, and a re-verify revoke, is committed only after that fsync. A failed write returns HTTP 503 when a response is due, consumes no seq, and publishes no partial line. The nonce stays consumed. A failed provisional write that lands no line does not consume the cycle’s one provisional. The required gate.list add after an accept follows section 2.2: the writer retries that add before any other append, and a complete accept without its required add is invalid. A revoke is durable before the relying party learns of it. The grant is already invalid before that return.
The service truncates a torn tail, including a missing final newline, before the next append. The checker does not repair the file. A tail still present is truncated, with ignored_bytes equal to the tail length. invalid is a broken chain or a broken rule on a complete line, including an encoding defect on that line. A torn tail is not invalid.
/latest.json is rewritten as a hint after the lines it names are durable. It is not a link in the chain. A mirror checks the chain, not the hint.
8. Tip height on the log entry
Normative.
bitcoin_tip_height on a log line is the {index-url} tip height at that line. It is the log entry’s name for the tip. It is unsigned. It is not a proxy stored on a head, because v1 has no head. The checker requires the value not to decrease from one line to the next. A decrease is tip_mismatch. CoS locked 2026-10-07. That decrease includes a lower tip after a reorg. The service does not append a line whose bitcoin_tip_height is lower than the previous line’s. An equal tip is not a decrease and is not tip_mismatch. A reorg that lowers the observed tip is not a retry and writes no line, as section 2.2 states. It does not authorize a lower bitcoin_tip_height on a later line. The checker does not rewrite the log.
max_thorough_gap_blocks is a lowercase profile key. The candidate is 6. It is not locked. The check runs only when the published profile or the vector states a number. It is measured from the bitcoin_tip_height of the first fast provisional for that submission_sha256 to the first thorough line for that submission_sha256 that omits prior_seq. It is not measured from a later provisional, from a fast refuse, or from a line that cites prior_seq. A line that cites prior_seq is not thorough_late. When that submission has no fast provisional, including provisional: off, the check does not run for it. A gap above the number on that first thorough line is thorough_late.
max_head_gap_decisions and max_head_gap_blocks are not v1 checks. v1 has no head. max_fast_lines_per_submission is lowercase. This file states no number. The check runs only when the published profile or the vector states a number. A submission_sha256 with more fast lines than that number is invalid. When no number is present, the check does not run.
9. Privacy
Normative.
The log does not contain an IP address, a Cloudflare ray ID, a user agent, a country, a session id, a cookie, or a site account id.
Every decision refuse is published with public_key and the object bytes. The public wording is “refused under policy version X”. There is no ban and no flag. The sign-in page says the submitted object is published. A B7 cap hit is not a decision. It publishes no object.
Private ops counters are not published. They include malformed, unknown_challenge, expired, reused, bad_signature, object_mismatch, rate_limit, cap, check_in, resubmit, and the gauges held and reverify_pending. A 503, a B7 cap hit, and an unauthenticated rate limit increment a private counter only.
10. Published bytes
Normative.
Object bytes for every decision line, including every refuse and every revoke, are published. Verify-record bytes are published for every decision line. A B7 cap hit publishes no object and no verify record. Profile bytes are published. policy_profile_sha256 is SHA-256 of the profile file defined below.
A request that is not a decision is not published as an object.
min_retention_days is a candidate 365. It is not locked. Its home is the profile key of that name. The key is omitted when unset.
The profile file is one RFC 8785 JSON object. That encoding fixes key order. This file does not state a second order. Arrays keep the order below. There is no JSON null. An unknown key is invalid. An uppercase key is invalid, including MAX_HELD_BLOCKS. A key not in the lists below is invalid.
Required keys:
| Key | Type |
|---|---|
id |
string. The policy_profile_id on a line. |
version |
integer, 0 … 2^53 − 1. The policy_profile_version on a line. |
provisional |
string on or off. The prose provisional: off is this key with value off. |
checks |
array of strings. The check order for reasons. Each string is a section 4.3 reason, "<rule> <name>" or held_bound. |
leg_rate_floor |
object. Keys p then q under RFC 8785. Each is an integer in 1 … 2^53 − 1. The hike comparison is q·N ≥ p·S_eff (hike 4ca480e line 1866). The default-profile lock 1/2 is p = 1 and q = 2. This file does not restate the comparison. |
Optional keys are omitted when unset. Each integer is in 0 … 2^53 − 1.
| Key | Type |
|---|---|
max_held_blocks |
integer. Candidate 6. Not locked. |
reverify_max_attempts |
integer. Candidate 3. Not locked. |
max_thorough_gap_blocks |
integer. Candidate 6. Not locked. Section 8. |
max_fast_lines_per_submission |
integer. This file states no number. Section 8. |
min_retention_days |
integer. Candidate 365. Not locked. |
verify_cap_per_object |
integer. Candidate 1. Not locked. Omitted means the cap is unset. |
verify_cap_per_door_session |
integer. Unset until present. The hike candidate figures are not locked. |
fast_accept_bound |
integer. No number is locked. |
async_catch_budget |
integer. No number is locked. |
min_anchored_paths |
integer. |
min_last_bookend |
integer. |
thorough_six_leg_runs |
integer. |
span_sanity_bound |
integer seconds. |
span_scale_seconds |
integer. |
plant_gap_ceiling |
integer. Record-only. |
plant_gap_floor |
integer. |
span_endpoint_mode |
string raw or neighbour. |
fixed_start |
boolean. |
reasons on a refuse or revoke line is a subsequence of that profile’s checks, in the same order. When the profile sets max_held_blocks or reverify_max_attempts, checks includes held_bound. A line that carries held_bound when checks does not include it is invalid. checks does not contain a B7 name. A B7 cap hit is not a line.
Informative. Feed paths and cache lifetimes are not this file. The bytes and the line are the contract. The unsigned /latest.json hint is section 1.
11. Checker
Normative. The checker verifies bytes as published. It never re-encodes a line or a verify record.
The result set is ok, invalid, thorough_late, truncated, fork, tip_mismatch, and held_late. The checker returns every result that applies. ok means the set is empty. Fork is decided before the increasing-seq check. v1 has no receipt input and no head line. v1 does not emit stale. There is no head gap to apply.
- Split on U+000A. Bytes after the last newline, including a missing final newline, are a torn tail:
truncated, withignored_bytes. The checker does not repair the file. On a complete line,null, an exponent, a leading zero,-0, uppercase hex, an extra space, or a wrong-length hex field isinvalid. Key order is not this step. - The key set and the types match section 4, section 5, or section 6. There is no
gate.headline. There is no receipt. A fastdecisionisprovisionalorrefuse. A missing required key, an extra key, or a wrong type isinvalid. - Two different
line_bytesat onesequnder the same genesis arefork. This step runs before the increasing-seqcheck. seqstarts at 0 and increases by 1. A failure isinvalid.seq0 is not required to be a head.- Key order, then
event_hashand genesis from section 7. A swapped key isinvalid. Do not re-encode. - Section 2. A fast
acceptisinvalid. A secondprovisionalfor the same(public_key, object_sha256, policy_profile_id, policy_profile_version)before the cycle ends isinvalid. A fast line after a thorough line for the samesubmission_sha256isinvalid. A B7 cap hit is not a line. A second admission line that omitsprior_seq, for a(public_key, object_sha256, policy_profile_id, policy_profile_version)whose cycle is already closedrefusedorrevoked, isinvalideven whensubmission_sha256is new. A differentpolicy_profile_idat the same version integer is not that closed cycle. A(submission_sha256, prior_seq)re-verify is a different record and is not that second admission. Aheld_boundrefuseuses the running phase and that phase’s record. Aheld_boundrevokedoes not citeprior_seq. Everyrevokeisphasethoroughwith the full record.fast_seqis omitted, nevernull, where section 2 omits it. JSONnullis not that omission. Aprior_seqline meets section 2, including the three valid cases. The same-policy trigger is a service rule. The checker checks the line shape and does not observe the trigger. A failure isinvalid. submission_sha256ishex64. v1 has nomsgorsigon the line, so this step does not verify BIP-340 from the line. A BIP-340 failure is not a line.- Every decision line has
verify_record_sha256andverify_summary. Hash the published record bytes. Canonicality is the in-place check in section 4.1. Do not hash a re-encoded form. A summary that contradicts the published record isinvalid. A fast summary omitscheckpoints_mismatch,six_leg, andsix_leg_runs. Arevokesummary does not. Nestedsix_legkeys areindicesthenresults, as section 4.2 states. A failure isinvalid. reasonsis empty onprovisionaland onaccept. Otherwise every string is a name in section 4.3, and the array is a subsequence ofcheckson the published profile file whoseSHA-256is that line’spolicy_profile_sha256. The profile file passes the same in-place RFC 8785 check.B3 span_sanity_boundrequires the profile to setspan_sanity_bound.B5 plant_gap_floorrequires the profile to setplant_gap_floor. A B7 name is not a name in section 4.3. A failure isinvalid.- An admission
refuseorrevokeomitsprior_seqand has no later decision line with that samesubmission_sha256andprior_seqomitted. A later line isinvalid. Two decision lines with the same(submission_sha256, prior_seq)areinvalid. A second admission line that omitsprior_seqfor a closedrefusedorrevoked(public_key, object_sha256, policy_profile_id, policy_profile_version)isinvalideven under a newsubmission_sha256. A different id at the same version integer is not that closed cycle. A line that citesprior_seqis not that second admission. A B7 cap hit is not a line. Its finality for thatsubmission_sha256is the Sign-in response only. thorough_lateas section 8 states, when a number is present. A line that citesprior_seqis notthorough_late. Otherwise this step does not run.held_lateas section 2.3 states. The step runs only when the vector suppliesholds. A(submission_sha256, prior_seq)re-verify is neverheld_late.- Section 5 and section 6, including the current-entry rule and the required
addin section 2.2. Ending the currentadddoes not restore a replacedadd. A lateraddfor a struck(public_key, object_sha256)isinvalid. A failure isinvalid. - A decrease in
bitcoin_tip_heightistip_mismatch, including a decrease after a reorg. An equal tip is nottip_mismatch. max_fast_lines_per_submission, when a number is present. Otherwise this step does not run.
12. Rule index
Normative. Classes are INVARIANT, CONSTANT, RANGE, and POLICY THRESHOLD. A candidate number is not a locked CONSTANT.
| Rule ID | Class | One-line rule | Locus |
|---|---|---|---|
| G2.1 | INVARIANT | Decision words are provisional, accept, refuse, revoke. No deny. No fail. |
§2 |
| G2.2 | INVARIANT | The fast phase never issues accept. A fast line decides provisional or refuse. |
§2 |
| G2.3 | INVARIANT | A thorough line decides accept, refuse, or revoke. held is not a line. |
§2 |
| G2.4 | INVARIANT | revoke if a grant was ever issued, including one already ended by the move from provisional to held; otherwise refuse. Every revoke is phase thorough with the full record. |
§2 |
| G2.5 | INVARIANT | provisional: off writes no fast provisional. That thorough line omits fast_seq. |
§2 |
| G2.6 | INVARIANT | A prior_seq line carries fast_seq exactly when the cited line does. A fast line omits fast_seq. Omission is not a null citation. JSON null is invalid. CoS locked 2026-10-07. |
§2 |
| G2.7 | INVARIANT | A second thorough line for the same fast_seq is invalid unless it cites prior_seq. |
§2 |
| G2.8 | INVARIANT | The profile-match rule applies only against fast_seq. A prior_seq line is exempt. |
§2 |
| G2.9 | INVARIANT | An admission refuse or revoke has no later admission line for that submission_sha256. A (submission_sha256, prior_seq) re-verify is a different record. The closed cycle is (public_key, object_sha256, policy_profile_id, policy_profile_version). A B7 cap hit is not a line and does not close the cycle. |
§2 |
| G2.10 | INVARIANT | A check-in is not a line. A policy-version re-verify of an accepted object is thorough, with prior_seq, and never provisional. A refused or revoked object under a new policy_version is a new cycle and may issue provisional. |
§2.1, §2.2 |
| G2.11 | INVARIANT | A list add cites accept_seq of a thorough accept for that key and object. |
§5 |
| G2.12 | INVARIANT | A prior_seq line is valid as a higher version of the same id, a different id, or a same-policy new measurement whose record hash differs. CoS locked 2026-10-07. |
§2 |
| G2.13 | INVARIANT | There is no key-strike revoke. A manual strike is gate.strike. A revoke comes only from a measurement. |
§2, §6 |
| G2.14 | INVARIANT | The same submission_sha256 returns the original outcome, checked before the nonce compare-and-set. v1 has no receipt. |
§2.4 |
| G2.15 | INVARIANT | A B7 cap hit is a non-event: Sign-in refuse, no line. It stays in received and is final only for that submission_sha256. CoS locked 2026-10-07. An unauthenticated rate limit writes no line. |
§2.1 |
| G2.16 | INVARIANT | On an admission hold, max_held_blocks applies from the first entry into held in the cycle, with no reset. reverify_max_attempts counts not read retries only. A re-verify budget hit is not held_bound, writes no line, and is not re-armed. A held_bound refuse uses the running phase. A held_bound revoke is phase thorough, cites fast_seq only when that admission cycle issued a provisional, and does not cite prior_seq. |
§2.3 |
| G2.17 | INVARIANT | Section 2.4 maps each logged section 15.2 row to a line or no line. New-submission rows are checked before cap rows. | §2.4 |
| G2.18 | INVARIANT | A revoke comes only from a measurement. A report does not revoke. |
§2 |
| G2.19 | INVARIANT | A friendly-list add happens only at a thorough accept. |
§2.2, §5 |
| G2.20 | INVARIANT | Re-verify not read budgets start at the first not read on that re-verify. Exhaustion is not a revoke, does not remove the friendly entry, and writes no line. After the hit, at most one retry per strict {index-url} tip increase. A re-verify refuse, other than a B7 cap hit, is a revoke and removes the entry only when this object is current. It carries fast_seq only when the cited line does. No reverify_not_read path. |
§2.2 |
| G2.21 | INVARIANT | One key, one PCOC. No automatic key strike. No key_struck reason. gate.strike ends the current pair only. The struck object does not become current again. CoS locked 2026-10-07. |
§2.2, §6 |
| G2.22 | INVARIANT | A fast line after a thorough line for the same submission_sha256 is invalid. |
§2 |
| G2.23 | INVARIANT | At most one provisional per cycle. Timeout and async holds never return to provisional. held to provisional only when no provisional has been issued yet. The nonce compare-and-set is for single-use nonces only. |
§2.1 |
| G2.24 | INVARIANT | Newer is the thorough-accept seq. The current entry is the add that replaced every earlier add and has not itself been ended. Ending it does not restore a replaced add. |
§2.2 |
| G2.25 | INVARIANT | Object-cap and door-session-cap hits are Sign-in refuses. They write no line, stay in received, and do not close the cycle. They are final only for that submission_sha256. New-submission rows are checked first. |
§2.1 |
| G2.26 | INVARIANT | A replaced object resubmitted returns decision accept. The state stays accepted. Response field replaced_by is the seq of the thorough accept that replaced it. No grant. No run. No log line. The decision set is unchanged. CoS locked 2026-10-07. |
§2.2 |
| G2.27 | INVARIANT | The cycle key is (public_key, object_sha256, policy_profile_id, policy_profile_version). The version integer alone is not the key. CoS locked 2026-10-07. |
§2.1 |
| G2.28 | INVARIANT | A same-policy new measurement is service-started only, to fill a prior not read. A client Sign-in does not trigger it. CoS locked 2026-10-07. |
§2 |
| G3.1 | INVARIANT | One compact JSON object per line, fixed key order, no null, no -0. Key order is checker step 5. |
§3 |
| G3.2 | RANGE | String escaping is section 3. U+007F is raw. / is not escaped. |
§3 |
| G3.3 | RANGE | 0 ≤ seq ≤ 2^53 − 1 and 0 ≤ time_unix_s ≤ 2^53 − 1. |
§3 |
| G3.4 | INVARIANT | A wrong-length hex field is invalid at checker step 1. |
§3, §11 |
| G4.1 | INVARIANT | v1 does not publish msg or sig. The line binds submission_sha256. |
§4 |
| G4.2 | INVARIANT | A fast record keeps checkpoints, including g and legs[*].j, and drops legs[*].result, six_leg, and six_leg_runs. Record integers are plain JSON numbers. The decimal-string list is empty. |
§4.1 |
| G4.3 | INVARIANT | verify_summary is a display copy. |
§4.1 |
| G4.4 | INVARIANT | checkpoints_mismatch and waypoints_mismatch are counts. |
§4.2 |
| G4.5 | INVARIANT | A fast summary omits checkpoints_mismatch, six_leg, and six_leg_runs. A revoke summary does not. |
§4.2 |
| G4.6 | INVARIANT | reasons strings are "<rule> <name>", or bare held_bound, from section 4.3. |
§4.3 |
| G4.7 | INVARIANT | A stranger’s re-verify that differs is shown as measurements. | §4.1 |
| G4.8 | INVARIANT | Rule B9 names the invariant refuses, including fold. They are not gated on a lock. A cap is Rule B7 and is not a reason on a line. |
§4.3 |
| G4.9 | INVARIANT | A fold mismatch with no grant is a fast refuse, reason B9 fold. It is not B9 checkpoints. CoS locked 2026-10-07. |
§2, §4.3 |
| G4.10 | INVARIANT | Reason tokens B3 span_sanity_bound and B5 plant_gap_floor exist. The numeric thresholds are not locked. A line carries one only when the profile sets that key. CoS locked 2026-10-07. |
§4.3 |
| G5.1 | INVARIANT | gate.list v1 action is add only. It cites a thorough accept. |
§5 |
| G5.2 | INVARIANT | gate.strike is the manual strike of the current pair only. It is not a revoke. A later add of that same object is invalid. CoS locked 2026-10-07. |
§6 |
| G6.1 | CONSTANT | The v1 log domain is pcoc-gate-log-v1. Genesis hashes pcoc-gate-log-v1 genesis and a newline. |
§7 |
| G6.2 | INVARIANT | Durable means fsync before the line is committed. A failed write consumes no seq. A failed provisional that lands no line does not consume the one provisional. The required add is retried before any other append. |
§7 |
| G6.3 | INVARIANT | The service truncates a torn tail. The checker does not repair it. A tail is truncated with ignored_bytes. |
§7, §11 |
| G6.4 | INVARIANT | One writer. A second writer is a fork. | §7 |
| G6.5 | INVARIANT | event_hash is SHA-256 of pcoc-gate-log-v1, one newline, and line_bytes. |
§7 |
| G6.6 | INVARIANT | Genesis does not hash a key. | §7 |
| G7.1 | INVARIANT | v1 has no signed head and no gate_key. |
§1, Later |
| G7.2 | INVARIANT | v1 publishes no head preimage and no head counters. Ops counters stay private. | §9 |
| G7.3 | INVARIANT | A Gate-log vector carries no hiker signature and no aux_rand. submission_sha256 in a vector is an opaque hex64. |
§13 |
| G7.4 | RANGE | max_head_gap_decisions and max_head_gap_blocks are not v1 checks. v1 has no head. |
§8 |
| G7.5 | RANGE | max_thorough_gap_blocks is measured from the first fast provisional to the first thorough line that omits prior_seq. A prior_seq line is not thorough_late. No fast provisional: the check does not run. Candidate 6. Not locked. |
§8 |
| G7.6 | INVARIANT | Ops counters are private. They are not a log object. | §9 |
| G7.7 | INVARIANT | bitcoin_tip_height does not decrease, including after a reorg. A decrease is tip_mismatch. An equal tip is not. CoS locked 2026-10-07. |
§8 |
| G7.8 | INVARIANT | seq starts at 0. The first line is not a head. |
§1 |
| G7.9 | RANGE | On an admission hold, max_held_blocks starts at the first entry into held in the cycle and does not reset. reverify_max_attempts counts not read retries only. A re-verify budget hit is not held_bound. Both comparisons are ≥. Candidates 3 and 6. Not locked. |
§2.3 |
| G7.10 | RANGE | max_fast_lines_per_submission has no number in this file. The check runs only when a number is present. |
§8, §11 |
| G7.11 | INVARIANT | reverify_pending is a private gauge of friendly entries waiting on a policy re-verify. It is not a published head counter. |
§2.2, §9 |
| G8.1 | INVARIANT | v1 has no receipt. A 503 and an unauthenticated rate limit write no line. | §2.4, §7 |
| G8.2 | INVARIANT | v1 defines no receipt signing bytes. | Later |
| G8.3 | INVARIANT | A decision is the log line. There is no receipt hash to fork against. | §11 |
| G8.4 | INVARIANT | v1 has no held_id. Hold state lives on the per-cycle record. |
§2.3 |
| G8.5 | INVARIANT | hold_kind moves only from fast_phase to thorough_not_read. It is service state, not a v1 line key. |
§2.3 |
| G9.1 | INVARIANT | Never publish an IP address, a ray ID, a user agent, or a session. | §9 |
| G10.1 | INVARIANT | Object bytes and verify-record bytes are published for every decision line. A B7 cap hit publishes neither. | §10 |
| G10.2 | INVARIANT | The profile file is RFC 8785 with the section 10 keys. Canonicality is the in-place check in section 4.1. The checker does not hash a re-encoded form. CoS locked 2026-10-07. | §10, §4.1 |
| G10.3 | RANGE | min_retention_days. Candidate 365. Not locked. |
§10 |
| G11.1 | INVARIANT | A checker hashes published bytes and does not re-encode. | §11 |
| G11.2 | INVARIANT | Checker step 3: duplicate seq with different line_bytes is fork. |
§11 |
| G11.3 | INVARIANT | Checker step 4: seq starts at 0 and increases by 1. The first line is not a head. |
§11 |
| G11.4 | INVARIANT | Checker step 5: key order, then event_hash and genesis. |
§11 |
| G11.5 | INVARIANT | Checker step 7: submission_sha256 is hex64. BIP-340 is not re-checked from the line. |
§11 |
| G11.6 | INVARIANT | Checker step 8: every decision line has both verify fields. Canonicality is in-place. A summary that contradicts the record is invalid. Nested six_leg key order is section 4.2. CoS locked 2026-10-07. |
§11 |
| G11.7 | INVARIANT | Checker step 9: reasons match section 4.3, including B9 fold, B3 span_sanity_bound, and B5 plant_gap_floor when the profile sets them. |
§11 |
| G11.8 | INVARIANT | v1 has no receipt-signature step. | §11, Later |
| G11.9 | INVARIANT | Checker step 12: held_late runs only when the vector supplies holds, and only on admission lines that omit prior_seq. A re-verify is never held_late. |
§2.3, §11 |
| G11.10 | INVARIANT | The checker returns every applicable result. ok means the set is empty. |
§11 |
| G11.11 | INVARIANT | v1 has no receipt past a head. A torn tail is truncated at step 1. |
§11 |
| G11.12 | INVARIANT | Checker step 2: key set and types. No head. No receipt. | §11 |
| G11.13 | INVARIANT | Checker step 1: a torn tail is truncated with ignored_bytes. |
§11 |
| G11.14 | INVARIANT | v1 does not apply a head gap. thorough_late uses bitcoin_tip_height on lines. |
§8 |
| G11.15 | INVARIANT | An unrelated line does not by itself flag held_late. A post-budget re-verify that stays open is not held_late. |
§2.3 |
| G13.1 | INVARIANT | This file governs. A conflicting vector is a defect. The runner marks it known-defect. Release stays blocked. |
§13 |
13. Vectors
Normative. Published vectors are normative worked examples of this file. Where a vector conflicts with a sentence here, this file governs. The conflict is a defect in this specification. It blocks release until a SPEC change settles it. An implementer reports the conflict and does not pick a side. A runner marks the disputed vector known-defect and excludes it from the pass count. The vector does not settle the conflict. This file does not contain the pack. This revision does not regenerate one.
Informative. The classes below are the list a pack has to cover. They are not the pack.
- Chain:
seq0 is the first line, not a head. Genesis is the domain string and a newline, with no key. Each line hasevent_hashas section 7 states. - Encoding: an extra space, a
null, an exponent,-0, uppercase hex, a wrong-length hex field, or a\/escape on a complete line isinvalidat step 1. A swapped key is step 5. A missing final newline istruncatedwithignored_bytes. - Tamper: a deleted line, an edited line, two lines swapped, and a fork. A torn tail is
truncated. The checker does not repair it. - Decisions: fast
provisional; fastrefusewithfast_seqomitted; thoroughaccept,refuse, andrevoke; everyrevokeisphasethoroughwith the full record; aheld_boundrefuseuses the running phase; aheld_boundrevokedoes not citeprior_seq; oneprovisionalper cycle; a fastacceptisinvalid. While a numeric threshold stays unlocked, the required refuse lines areB0 no_pair_outcomeand the Rule B9 names, includingfold,height_range, and a confirmed plant.B3 span_sanity_boundandB5 plant_gap_floorare thorough reasons when the profile sets those keys. A line whose reason is a B7 name isinvalid. JSONnullforfast_seqisinvalid. A vector does not lock an unlocked number. A vector may supplyholdsas section 2.3 states.held_lateruns only then.thorough_latedoes not flag a line that citesprior_seq. A non-canonical verify record isinvalid. A summary that contradicts the record isinvalid. - Custody: a newer thorough
acceptreplaces the friendly entry and leaves the old decision line unchanged. Ending that newer entry does not restore the replaced object. The requiredaddis the next line, as section 2.2 states. A replaced-object submission returnsdecisionaccept, stateaccepted, with response fieldreplaced_byset to the replacing accept’sseq. It writes no line, issues no grant, and starts no run. The cycle key includespolicy_profile_idandpolicy_profile_version. A refused or revoked object under a different id or a higher version of the same id starts a new cycle and may issueprovisional. Two ids that share a version integer are different cycles. A re-verify of an accepted object writes noprovisional. Its record key is(submission_sha256, prior_seq). A same-policy new measurement is the service fill of a priornot read. A re-verifynot readdoes not remove the friendly entry. A re-verify refuse removes it only when this object is current. - Non-events: BIP-340 failure, HTTP 503, an unauthenticated rate limit, and a B7 cap hit write no line. The cap’s Sign-in response is
refuse. It is final only for thatsubmission_sha256. A resubmit returns the original outcome and writes no line. A resubmit of a cappedsubmission_sha256returnsrefuseand writes no line. A lower tip, including a reorg, writes no line. gate.strikeends that pair’s current entry only. The struck object does not become current again. A lateraddfor that same object isinvalid. Agate.listline whoseactionis notaddisinvalid. No vector writes areverify_not_readline. There is no such removal path.
Every record integer in a vector is a plain JSON number. The decimal-string list is empty. A Gate-log vector carries no hiker signature and no aux_rand. submission_sha256 in a vector is an opaque hex64.
Later
Informative. Not v1. No rule here is required for a conforming v1 log. The absence of a signature is not invalid.
- Signed
gate.headlines, a Gate signing key, and a head preimage. - Signed receipts, including any
pcoc-gate-receipt-v1bytes. - Gate-key rotation and an anchored list of Gate keys.
- Outside witnessing of the immutable list. Nostr publication of tip hashes is one idea. This file does not specify those bytes.
- Head-gap checks that need a head line.
v1 remains the unsigned hash chain, the saved objects, the verify records, the profile bytes, and the unsigned /latest.json hint.
CoS locked 2026-10-07
Normative. Each row is the log contract. This section does not amend the hike file. Numeric Part B thresholds, the letter k, and the budgets stay unlocked.
| Item | Lock |
|---|---|
| B7 non-event | A B7 cap hit (verify_cap_per_object or verify_cap_per_door_session) is a Sign-in refuse and writes no line. It never revokes, does not change the friendly entry, does not end the cycle, and is final only for that submission_sha256. A decision line whose reasons contain a B7 name is invalid. The cap figures stay unlocked. |
replaced_by |
A replaced object resubmitted returns decision accept. The state stays accepted. The response field replaced_by is the seq of the thorough accept that replaced it. No grant. No run. No log line. The decision set is unchanged. |
| Re-verify key | A re-verify record is (submission_sha256, prior_seq). prior_seq omitted is the admission record for that submission_sha256. |
| Cycle key | The cycle key is (public_key, object_sha256, policy_profile_id, policy_profile_version). The version integer alone is not the key. Two ids that share a version integer do not share a cycle, a cap count, or a closed outcome. A different id, or a higher version of the same id, is a policy change. An accepted pair under that change is a re-verify and never issues provisional. A refused or revoked pair under that change starts a new cycle and may issue provisional. A lower version of the same id does not. |
| RFC 8785 | Canonicality is the in-place check in section 4.1. The checker hashes the published bytes. It does not hash a re-encoded form. A failure is invalid. A verify_summary that contradicts the published record is invalid at step 8. The summary is not an input to the decision. |
| Fast fold mismatch | A fold mismatch with no grant is a fast refuse, reason B9 fold, fast_seq omitted, no thorough run. It is not B9 checkpoints. After a grant, the same mismatch is a thorough revoke with reason B9 fold. |
| B3 and B5 tokens | The reason tokens are B3 span_sanity_bound and B5 plant_gap_floor. The numeric thresholds are not locked. A line carries a token only when the profile sets that key and checks includes the string. Both are thorough-phase reasons. plant_gap_ceiling stays record-only. |
| Tip decrease | A decrease in bitcoin_tip_height is tip_mismatch, including after a reorg. The service does not append that line. An equal tip is not tip_mismatch. A lower observed tip writes no line and is not a retry. |
| Strike scope | gate.strike ends the current (public_key, object_sha256) entry only. It does not strike the key, ban the key, or write key_struck. The accept line stays. The struck object does not become current again. A later add for that same object is invalid. A new object hash remains a new submission. |
Omitted fast_seq |
Omitting fast_seq is not a null citation. JSON null is invalid at step 1. A checker does not treat null as omitted. |
| Same-policy new measurement | The service appends a same-policy prior_seq line only to fill a prior thorough not read under that same profile, when the new verify_record_sha256 differs. A client Sign-in, a check-in, and a signature-only return do not trigger it. The checker checks the line shape and does not observe the trigger. |
Open
Informative.
- Game Theory section 8 (a) is the hike file’s open section 8 question. This log does not decide it.
- The hike change-note at
4ca480eline 90 still says “Custody candidate, not locked” for a refused or revoked object that starts a new cycle under a newpolicy_version. Section 15.2 lines 1739 and 1740 state that behavior. This log follows those rows, withpolicy_versionread as(policy_profile_id, policy_profile_version). This file does not edit the hike file. - The hike does not name a state token for missing accepted-object bytes. It says the next sign-in is a full section 15.1 submission and that the miss is not
not read(lines 89 and 1712). This lock does not add a state.