Admission Process
Responsibility. Generic, value-free process: consume a measurement record + a policy instance → decide/log. Downstream of Measurement. Used by an admission-gate implementer. Policy values live outside this pack (e.g. gl-admit-*).
Source. Satoshi Hike 2.3 (tag spec-2.3.0). Section numbers are the specification section numbers:
- §15.1 Inputs
- §15.2 Outputs and states
- §15.3 Two-phase async
- §15.4 Admission levers
- §15.5 Thorough draw and the floor
FRAMING (2026-10-24). Rule A5 unknown-kind measurement lives in Measurement Record; Admission only judges with reason unknown_kind (Rule B9). Gate/sign-in wire and groundinglab Gate deployment detail in this file are ops-shaped; the SPEC intent of this document is the generic consume-then-decide process. Lever values are policy. Core: a measurement record plus a policy instance → decide. This specification defines no tip-versus-last-beacon submit deadline.
15. Admission service
Normative for the admission service only. Part B. This section defines the admission levers and the comparisons that use them. A lever is a name, a unit, an allowed range, and a measuring or comparison rule (section 15.4). A policy sets each lever to a value inside its range. This file sets no lever value and gives no lever a default. An admission service with no policy file, or whose policy file is refused, admits nothing. It has no built-in lever values. A policy is a separate document. It names the version of this file it targets, its own policy_version, and the value of each lever it sets. A value outside a lever’s allowed range does not conform to this file. Admission reads only a section 14 record whose profile is production, and an admission service on the live plane compares against the mainnet portable index only (Rule B9). Those are rules of this file, not levers, and no policy sets them aside. Rules B0, B2, B3, B4, B6, B8, and B9, the Rule B7 caps and fake-leg ignore, and the re-verify rule in section 15.1 Binding are normative. Integrity outcomes beyond Rule B9, any plant-gap comparison, and friendly-entry custody are policy, not rules of this file. A provisional state grants no action (provisional_action_cap); that is a rule of this file, not a lever. The section 15.1 sign-in wire is normative: the challenge link, challenge_bytes, the BIP-340 message, the origin display, the challenge window, the submit request, the client response codes, and the website outcome tokens. The signed origin is the requesting site’s own origin, compared exactly (section 15.1 Origin); https://groundinglab.io is an example deployment. That wire adds no leg-rate value and no admission bar. Section 15.5 states the thorough leg draw and the floor’s measuring method.
Part A records and measures. This section is the only judgment. The producer in section 13 records evidence and does not judge. The verifier in section 14 outputs a verify record of measurements and does not judge. The admission service applies this section.
The admission service is the actor in this section. Any service that meets this section conforms to it. The groundinglab Admission Gate (Gate) is one implementation, run for the example deployment. It is not a second specification. FRAMING: Gate/sign-in service detail in this section is ops-shaped; treat the pack intent of Admission Process as value-free consume-measurement-then-decide, with policy instances outside. The signing client uses one route, POST /v1/sign-in, at the default door named in section 15.1. POST /v1/sign-in/challenge, POST /v1/challenge, and POST /v1/admit are not routes. The website mints the challenge link and owns the browser session. That session is not a field of the hike object and not a field of the verify record. The grant named later in this section is not placed in the link and is not returned to the signing client. A deployment of the admission service may spell refuse as deny on its wire. The word in this file is refuse.
A threshold here can change without changing section 13 and without changing the measurements in section 14. No order among the admission rules in this section is fixed.
The Gate decision log is the companion specification Gate decision log, outside this file. This file does not write that log and does not write log JSON. Thorough outcomes are: thorough decides accept and the accept stands; revoke; refuse; or held. held is a thorough not read or a retry. It is not a negative settle. A negative thorough outcome, or a failed policy-version re-verify, is revoke if a grant was ever issued (provisional or accept, including one already ended by the move from provisional to held), and refuse if none was. provisional then held then a negative thorough outcome is revoke. A later revoke cites a new policy version or a new measurement. Admission outcomes inside this section are provisional, accept, refuse, revoke, and held. held is an admission outcome. It is not a decision-log event. Section 15.1 names the signing client's response and the website outcome.
15.1 Inputs
Normative. The admission service runs its own conforming section 14 verifier on the hike object it receives. It never takes a verify record supplied by the submitter. The record it produces is bound to the object hash: SHA-256 of the exact object bytes.
The inputs are:
- The hike object, its bytes.
- The object’s
public_key. - The object hash,
SHA-256of those bytes. - The challenge defined in this section. The website mints it as the link in this section.
- A BIP-340 signature over that challenge, checked against
public_key. Produce and verify do not check a signature (section 2). The signature check is not a section 14 measurement. - A policy profile (section 15.4). The profile may name a friendly list. The friendly list is not a field of the hike object and not a field of the verify record.
Challenge
The Sign-in door accepts only the challenge this section defines. It does not accept a signature over a display-order index header hash or any other challenge layout.
Domain. The domain is the 19 ASCII bytes pcoc-gate-signin-v1. There is no trailing NUL in the tag.
Origin. origin is the origin of the requesting site: the site that minted the challenge link and that the signature is for. It is the ASCII serialization of a web origin under RFC 6454 section 6.2: the lowercase scheme https, then ://, then the host in lowercase (an internationalized host in its A-label form), then : and the port in shortest decimal only when the port is not 443. It has no userinfo, no path, no query, no fragment, and no trailing slash. n is its byte length, from 1 through 255.
Comparison is exact. Two origins are the same only when their serialized bytes are equal. Nothing is case-folded, trimmed, decoded, or completed with a default port before the comparison.
- A signing client MUST take
originfrom the scheme, host, and port of the challenge link it signs, serialized as above. It MUST NOT substitute a built-in value. It MUST NOT sign when the link's origin is not in serialized form. - Any operator MAY run an admission service. An admission service MUST accept a signed
originonly when it is byte-for-byte equal to an origin that service is built or set to serve. It MUST NOT accept any other value. It MUST NOT normalize the received value first. - A received
originthat is not equal to a served origin is check-order step 2: clientmalformed, unchanged from the check order below.
https://groundinglab.io is one example deployment. It is not a requirement and is not special: for it, n is 23. https://www.groundinglab.io, https://groundinglab.io/, http://groundinglab.io, and https://groundinglab.io:443 are not equal to it. The door host is not the Sign-in origin. Vectors: spec/conformance/pack/sign-in-origins.json signs the same challenge for https://example.com and for near misses, and gives what a service that serves only https://example.com returns for each.
Other fields. nonce is 32 bytes. The website draws them from a CSPRNG when it mints the link. expiry_unix_s is a u64 count of Unix seconds, not milliseconds. object_sha256 is the 32-byte SHA-256 of the exact object bytes that are submitted. The preimage does not carry an IP address, a Cloudflare token, a session cookie, a user-agent, or a second copy of public_key.
Lifetime and clock skew. The lifetime is the lever challenge_lifetime_seconds (section 15.4). expiry_unix_s = issue_unix_s + challenge_lifetime_seconds. issue_unix_s is the integer Unix second at which the website mints the link, the floor of the millisecond clock divided by 1000. The clock-skew allowance is the lever challenge_clock_skew_seconds (section 15.4). The lifetime is not part of the hash formula. The Gate compares the integers with a wide sum so the addition does not wrap. The Gate accepts the challenge when now_unix_s < expiry_unix_s + challenge_clock_skew_seconds, expiry_unix_s <= now_unix_s + challenge_lifetime_seconds + challenge_clock_skew_seconds, and expiry_unix_s − challenge_lifetime_seconds ≥ nonce_store_start_unix_s + challenge_clock_skew_seconds. nonce_store_start_unix_s is the integer Unix second, on the Gate’s clock, at which the current nonce-row store started: the latest time at which the store that the service’s nonce compare-and-set uses could have lost rows, by a restart, a flush, a failover, or any other loss. Every process answering the door uses that one value, read from or with that store; a process does not substitute its own start. A single-process Gate that holds the nonce rows in its own memory uses its own process start. The third comparison refuses every challenge issued before the current store start, so a challenge issued before the store could have lost rows is never valid after it. Otherwise the client code is malformed. There is no expired code. The signing client does not enforce the window. There is no grace after expiry.
Bytes. Integers are big-endian.
challenge_bytes =
ASCII "pcoc-gate-signin-v1"
‖ 0x00
‖ uint8(n)
‖ origin_utf8[0 .. n)
‖ nonce[32]
‖ uint64_be(expiry_unix_s)
‖ object_sha256[32]
The length is 93 + n bytes. Examples: https://groundinglab.io gives n = 23 and 116 bytes; https://example.com gives n = 19 and 112 bytes.
BIP-340 message.
bip340_message_32 = SHA-256(challenge_bytes)
That 32-byte digest is the BIP-340 message. The signer must not hash it again. SHA-256(bip340_message_32) is not the message. The signature is 64 bytes under the object's x-only public_key. Verify is bip340_verify(public_key, bip340_message_32, signature).
Challenge link. One canonical form. The Android QR encodes that same UTF-8 string, with no byte-order mark. The link starts with the requesting site's origin, exactly as serialized under Origin; groundinglab.io is the example deployment's host. The path is /sign-in/ with the trailing slash. The query order is fixed. There is no extra parameter, no fragment, no userinfo, and no port other than one that is part of the origin. The link does not name the door. The link does not name an object.
<origin>/sign-in/?v=1&nonce=<64 lowercase hex>&exp=<decimal>
Example deployment: https://groundinglab.io/sign-in/?v=1&nonce=<64 lowercase hex>&exp=<decimal>.
v stays 1. It names the challenge-bytes domain pcoc-gate-signin-v1. The object hash is not a link parameter. The client binds object_sha256 from the hike object it chooses. nonce is 64 lowercase hex digits of the 32 nonce bytes. exp is expiry_unix_s in the shortest decimal form: no sign, and no leading zero except the value 0. A paste trims only leading and trailing ASCII whitespace: space, tab, CR, and LF. The remainder equals that string. Uppercase hex, http, an origin that is not in serialized form, an extra parameter, a parameter named object, a swapped parameter order, a fragment, a bare 64-hex string, a leading zero on exp, an exp that is not a u64, and a leading U+00A0 are not links. The client stops before any POST. The website does not emit object=. A link that carries object= is not a link. Clients do not accept it. The form <origin>/sign-in/?h= plus 64 hex digits is not a link form. The link classes in sign-in.json are given for the example origin https://groundinglab.io; there, www.groundinglab.io is another origin, not that site's link. A signature over a display-order index header hash is not this wire.
Origin display. Before the signature is produced, the client shows the origin taken from the link (example deployment: https://groundinglab.io). It shows no path, no query, and no door host. On Android that origin is the only confirmation. The client then signs and POSTs, with no second control. On desktop the sign control stays disabled until the origin is shown. One press signs and POSTs once. The client chooses the hike object. That object is the current or selected produced object on the device. When the device holds several, the client lets the user pick. The chosen object's SHA-256 is object_sha256 in challenge_bytes and in the POST body. When the device holds none, the client does not sign and does not POST. That stop is local. It is not a Gate token.
Submit. Each signing client is built for the deployments it serves. The Sign-in door URL for each origin the client accepts is part of the client's build configuration. A client MUST NOT derive or guess a door URL from the origin. A client MUST refuse a link whose origin has no configured door; it does not sign and does not POST. Every signing client POSTs once to the configured Sign-in door for the link's origin. In the example deployment that door is POST https://admission.groundinglab.io/v1/sign-in. The content type is application/json. The client does not follow a redirect. The POST target is that door. It is not the scanned URL. The body is one JSON object with each of these keys once:
Example deployment:
{"origin":"https://groundinglab.io","nonce":"<64 lowercase hex>","exp":<u64>,"object":"<64 lowercase hex>","object_b64":"<standard base64, with padding, no whitespace>","signature":"<128 lowercase hex>"}
exp is a JSON integer in the u64 range, not a string. object_b64 is standard base64 of the exact object bytes, with = padding and with no whitespace. Any other key, a missing key, a repeated key, exp as a string, or uppercase hex is malformed. The Gate rebuilds challenge_bytes from origin, nonce, exp, and object. The signature origin is accepted only under Origin. The origin is the body field. It is not the caller's Origin header.
Client response. The client class is one of three. These sentences are the only sign-in sentences the client shows: Sign-in submitted, bad signature, and malformed.
bad_signature. HTTP 400. The body is{"error":{"code":"bad_signature"}}and no other field. The request was not submitted. The client showsbad signature.malformed. HTTP 400. The body is{"error":{"code":"malformed"}}and no other field. The request was not submitted. The client showsmalformed.- Delivery OK. Any other completed HTTP response, including 200, 413, 415, 429, and 503, except a redirect. The client shows
Sign-in submitted. On the check-order success path the body is{"delivery":"ok"}and no other field. The POST body does not carryoutcome. The client ignoresdecision,reason,reasons,outcome, andverifier_ran.
A redirect (HTTP 3xx) is not followed and is not delivery OK. An HTTP 400 whose body is not one of the two objects above is not delivery OK. For a redirect, for that other HTTP 400, and when no HTTP response arrives, the client does not show Sign-in submitted and does not show a Gate outcome. The client does not poll. The client does not call the result read. The client does not show accept, refuse, provisional, held, revoke, or a policy sentence. unknown_challenge, expired, and reused are not client codes.
A BIP-340 failure is an error named bad_signature. It is not a section 15 decision and it is not a fast final refuse. There is no decision line. An append-only hash-chained log entry may record it. This version has no signed head and no signed receipt. This file does not write the entry.
Website outcome. The website reads GET /v1/sign-in/result/<64 lowercase hex nonce>. The response is HTTP 200 and the body is {"outcome":"<token>"} with no other field. An unknown nonce, and a nonce the Gate has not stored, is pending. That read does not create a row. The signing client does not call this route. The website of the requesting origin is the reader (example deployment: https://groundinglab.io).
outcome |
Words on the website |
|---|---|
pending |
Progress. The word pending is not the result |
accepted |
Open /welcome/. The website mints the browser session. This token is not a hike grade |
malformed |
malformed |
bad_signature |
bad signature |
too_short |
too short |
rejected |
rejected |
unknown_kind |
unknown kind |
too_few_waypoints_anchored |
too few waypoints anchored |
rejected has no extra field. too_short is a decoded object shorter than 32 bytes. Those bytes cannot hold an x-only key, so no signature check runs. The POST is delivery OK. The signing client shows Sign-in submitted.
unknown_kind is an object whose kind text is not KIND, after a valid signature. That outcome is not a fake-leg ignore. accepted is the thorough accept. While admission is unsettled, including a thorough not read, the website outcome stays pending. An unfinished page wait is not rejected. The website session lifetime is the website's own lifetime. It is not this challenge lifetime.
too_few_waypoints_anchored is the words for the already-written refuse-at-once: fewer than 6 of W_1 through W_16 could be anchored, wherever those waypoints sit. A build that does not enforce that row does not emit the phrase. An object of KIND that the Gate refuses, when the refusal has no phrase in the table, is rejected. A later version of this file might add a plain phrase to the table. Until the phrase is in the table, the website outcome is rejected. The leg-rate floor the deployed policy sets stays enforced. The website and any public reason string do not show the fraction, a threshold, a verifier note, or a retry instruction. This table adds no leg-rate value and no admission bar.
Check order. The door applies these steps in order and returns on the first step that decides.
- The body is not one JSON object, or
nonceis not 64 lowercase hex digits. The client code ismalformed. There is no result row. The nonce is not consumed. originis not byte-for-byte equal to an origin this service serves (see Origin), a field breaks the Submit grammar, or the window does not accept the challenge. The client code ismalformed. The nonce is not consumed. When the object bytes hash toobjectand this nonce has no consumed row, the website outcome ismalformed. Otherwise the website outcome stays as it was.- SHA-256 of the object bytes is not
object. The client code isbad_signature. The body does not say which check missed. The nonce is not consumed. The website outcome stays as it was. - A consumed row exists for this nonce. The same
objectand the same signature return the original client class and the original website outcome. Policy does not run again. Any other object or signature is clientmalformed, and the stored website outcome stays. Asubmission_sha256of the same submission returns the original outcome and does not mint a grant. - The object is shorter than 32 bytes. The website outcome is
too_short. The client class is delivery OK. The nonce is consumed. - The bytes yield no 32-byte public key, or the signature does not verify over
bip340_message_32. The client code isbad_signature. The body does not say which check missed. The nonce is not consumed. The website outcome isbad_signature. A later consuming submit replaces this recorded outcome. - The public key is on this gate’s persistent fake-leg ignore record. The website outcome is
rejected. The client class is delivery OK. The nonce is consumed. Section 14 does not run. There is no new draw. The same refusal is returned for every later submission from that key at this gate. - The object is Rule A5 under the section 7.3 order: one well-formed CBOR map whose
kindtext is notKIND. The website outcome isunknown_kind. The client class is delivery OK. The nonce is consumed. This step does not add the key to the fake-leg ignore record. It does not fill terrain. - The object does not decode as a hike object: a section 7.3 decode failure (one well-formed data item that is not a map, or a map with no
kindtext). The website outcome isrejected. The client class is delivery OK. The nonce is consumed. This step does not add the key to the fake-leg ignore record. - The object is a hike object of
KIND, or a malformed CBOR value (section 7.3), which section 14 records as shapemismatch(Rule B9). The client class is delivery OK. The nonce is consumed when this step starts. The website outcome ispendinguntil policy settles, thenaccepted, ortoo_few_waypoints_anchoredwhen that refusal is the one this section names, orrejected. A later policy refusal does not free the nonce. A cheap-check failure on this object is a plain refusal and does not add the key to the fake-leg ignore record.
Transport edge. Before the check order, the door may refuse a request as oversize, as the wrong content type, or as over its transport rate. Each of those is a completed HTTP response, and the client class is delivery OK. The wrong content type writes no website outcome. Oversize and the transport rate write website rejected, and do not consume the nonce, only when the body is the JSON object, nonce is 64 lowercase hex digits, the nonce has no consumed row, and the object bytes hash to object. Otherwise the website outcome stays as it was. An unavailable door writes no website outcome. This rule does not change the door's transport figures and does not put those figures on the sign-in wire.
Single-use. The service stores a row keyed by nonce: origin, expiry_unix_s, object_sha256, the signature, the client class, and the website outcome. The row is written when the nonce is consumed. Two racing submits of one fresh nonce must not both consume the nonce. The set is an atomic compare-and-set. The nonce compare-and-set does not admit the provisional grant. A result row stays readable for result_retention_seconds (section 15.4) after the website outcome becomes something other than pending. Within one nonce-row store, whatever result_retention_seconds is, a consumed-nonce row is kept at least until now_unix_s ≥ expiry_unix_s + challenge_clock_skew_seconds, so a replay while the window still accepts the challenge finds that row (check-order step 4). After that time the window check (step 2) answers a replay malformed. A pending row stays until it settles or the store loses it. No row needs to survive a restart or a flush of the store. The store may lose every row, because the window refuses every challenge issued before the current store start (Lifetime and clock skew): a replay, after the store lost rows, of a submission consumed before that is client malformed at check-order step 2, consumes nothing, and runs no policy. The user scans a new challenge. After the row is gone, the result read is pending and creates no row. A late replay whose window has closed is malformed.
submission_sha256 = SHA-256(
object_sha256 ‖ public_key ‖ bip340_message_32 ‖ signature)
That preimage is 32 ‖ 32 ‖ 32 ‖ 64 = 160 bytes. The Gate decision log names how a submission is logged. This file does not write its JSON. A negative thorough outcome in that log, or a failed policy-version re-verify, is revoke if a grant was ever issued (provisional or accept, including one already ended by the move from provisional to held), and refuse if none was. A later revoke cites a new policy version or a new measurement.
verify_cap_per_door_session counts verifies at this door. It is not a cap on a website session. A provisional state grants no action (provisional_action_cap). No irreversible action is allowed while provisional, in every profile. That value is fixed in this file. It is not a profile knob. A provisional grant is short, read-only access until thorough settles or async_catch_budget is hit. The friendly-list entry is added only at a thorough accept.
Informative. EXAMPLE, not production. Origin https://groundinglab.io, nonce 0123456789abcdef repeated to 32 bytes, expiry_unix_s 1893456000, object_sha256 32 bytes of 0xaa. That object_sha256 is the 32-byte field the client binds from the object it chooses. It is not a link parameter. It is not the hash of a file of 32 0xaa bytes. The link is https://groundinglab.io/sign-in/?v=1&nonce=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef&exp=1893456000. challenge_bytes hex:
70636f632d676174652d7369676e696e2d7631001768747470733a2f2f67726f756e64696e676c61622e696f0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0000000070dbd880aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
bip340_message_32 hex: 90e38d7538d543aff5148318b7cb310595def390dac199c99bb10bc9ead5e0c2. The vector spec/conformance/pack/sign-in.json signs that digest once under the fixed test key, with 32 aux bytes of zero. SHA-256 of the digest does not verify. The example expiry lies outside a window near 2026, so a live-clock submit of this example is malformed on the window. The test key is not a production key.
Friendly list
A friendly-list entry is added only at a thorough accept. A friendly list maps a public_key to an accepted object_sha256 and a policy version. A listed key may sign in again only for that same already-accepted object hash. While a later-policy re-verify is not read, including after either reverify budget is hit, signature-only sign-in continues on the earlier accept. Only a re-verify that refuses, other than a B7 cap refuse, ends that sign-in as a revoke, and it removes the friendly entry only when this object is the key's current friendly entry. That sign-in does not run section 14 again. A new object hash is a new section 15.1 submission. When the policy version changes, the Gate re-verifies the accepted object. The Gate keeps those object bytes. The decision-log companion 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. One entry per key, and that entry is one accepted object. Which accepted object holds the entry when a key has more than one is friendly-entry custody, set by the policy in force; this file does not define it. A re-verify of an older object does not displace the current entry. A logged manual operator strike of the key is the other removal. A revoke in the object's own admission cycle, from provisional or from held, when thorough falls short of policy, ends that pair's grant and any signature-only sign-in for the object. Signature-only sign-in requires state accepted. Who edits the list by hand is a deployment concern. The list has no time expiry.
Normative. Re-verify is the re-check rule of this section. An accept stands. A fake-leg ignore is not cleared by a policy-version change and is not cleared by a restart of a gate process. “That gate” is the admission service’s persistent per-key record, shared by every process of that service. A re-verify of a key on that record does not draw again. A policy-version change means the Gate re-verifies. If that re-verify refuses, other than a B7 cap refuse, it is a revoke. A B7 cap refuse is never a re-verify refuse, never a revoke, and never removes a friendly entry. The key's friendly entry is removed only when this object is the key's current friendly entry, as with any revoke. The removal is a compare-and-remove on object_sha256: the Gate removes the entry only if the current entry's object hash still matches this pair. A not read never removes the entry and never changes sign-in state. Before either reverify_max_attempts or max_held_blocks is hit, the Gate retries within the existing reverify budget. After either budget is hit on a later-policy re-verify not read, the hit is not a revoke, does not remove the friendly entry, and does not change sign-in state. 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.
Binding
The fast decision and a later revoke bind the same submission: the object bytes, the object hash, the challenge, and the signature. This file does not mint a website session and does not add a session field to the hike object or the verify record. The grant defined above is the only session handle this section names. A provisional grant is short, read-only access until thorough settles or async_catch_budget is hit. The friendly-list entry is added only at a thorough accept.
15.2 Outputs and states
The HTTP response outcomes are provisional, accept, refuse, revoke, and held. Those words are admission outcomes. The signing client does not receive them. Section 15.1 is the client response and the website outcome. States are received, provisional, accepted, refused, revoked, and held. accept is the thorough outcome whose state is accepted. The fast decision token is provisional. A fast-phase accept is invalid. The fast phase never issues accept. It may issue a final refuse when a fast rule fails on the object's own bytes: decode (the object bytes fail to decode as a hike object), waypoint rehash, 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, an object-settled refuse-at-once, or a cap on a signed submission. Otherwise the fast phase gives provisional. A fast-phase not read is held. Only the thorough phase issues accept. refuse is the response whose state is refused. revoke is the response whose state is revoked. held is both a response outcome and a state. held is not a decision-log event. Thorough outcomes are: thorough decides accept and the accept stands; revoke; refuse; or held. held is a thorough not read or a retry. It is not a negative settle. A negative thorough outcome, or a failed policy-version re-verify, is revoke if a grant was ever issued (provisional or accept, including one already ended by the move from provisional to held), and refuse if none was. provisional then held then a negative thorough outcome is revoke. A later revoke cites a new policy version or a new measurement. Thorough decides accept; the accept stands. Sections 8–14 do not use these words as outcomes. This section does not say that a hike object passes or fails. This file does not write the decision-log JSON.
A client cannot trigger revoke. revoke_scope is the pair (public_key, object_sha256). A revoke binds to (public_key, object_sha256). It does not end another object's grant. A revoke in that object's own admission cycle ends that pair's grant and any signature-only sign-in for the object. A later-policy re-verify that refuses, other than a B7 cap refuse, is a revoke and removes the friendly entry only when this object is the key's current friendly entry, by a compare-and-remove on object_sha256, only if the current entry's object hash still matches this pair. A not read does not. The Gate invalidates the pair's grant before it returns or logs a revoke. Every grant use or signature-only sign-in checks the pair's current state. How relying parties hear of a revoke stays a deployment concern.
An admission cycle runs from received for a (public_key, object_sha256) under a policy_version until a thorough accept, or a refuse or revoke of the pair (including reason held_bound). A cap refuse (verify_cap_per_object or verify_cap_per_door_session) does not end the cycle. A later policy-version re-verify is not a new admission cycle and never issues provisional.
The service keeps an atomic per-cycle record keyed on (public_key, object_sha256, policy_version). That record is not the nonce row. The nonce compare-and-set stays for single-use nonces only. The record admits at most one provisional grant in the cycle. Concurrent submissions of the same pair cannot each receive one. 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_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. A client submission of a pair that is accepted, under a new policy_version, is a re-verify under the re-verify rule. A pair that is refused or revoked under an old policy_version and is submitted under a new policy_version, and a pair whose object no longer holds the key's friendly entry, are custody cases set by the policy in force. This file does not define them. A key on the gate’s persistent fake-leg ignore record never starts a new cycle.
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.
| From | When | To |
|---|---|---|
received |
A new submission of this (public_key, object_sha256) under this policy_version while the admission cycle is already open in received. The atomic per-cycle record is keyed on (public_key, object_sha256, policy_version). It is not the nonce row. Return the current state. No grant. No new run |
received |
provisional |
A new submission of this pair under this policy_version while the admission cycle is open. Return the current state. No grant. No new run |
provisional |
held |
A new submission of this pair under this policy_version while the admission cycle is open. Return the current state. No grant. No new run |
held |
refused |
A new submission of this pair under this policy_version. The cycle is closed refused. Return that outcome. No new cycle |
refused |
revoked |
A new submission of this pair under this policy_version. The cycle is closed revoked. Return that outcome. No new cycle |
revoked |
accepted |
A new submission of this same pair under this policy_version, and this object is the key's current friendly entry. Friendly-list and signature-only rules apply. This is not a new admission cycle and it never issues provisional |
accepted |
accepted |
A client submission of a pair that is accepted under any policy_version. It never issues provisional. Under a new policy_version it is a re-verify of that pair: straight to thorough, subject to verify_cap_per_object on (public_key, object_sha256, policy_version). The result is logged under the new version. A refuse is a revoke and removes the friendly entry only when this object is the key's current friendly entry, by a compare-and-remove on object_sha256. A not read keeps the entry and sign-in state. Signature-only sign-in continues. After either reverify budget is hit on a not read, 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. |
accepted |
received |
provisional is on, no grant was issued in that admission cycle, the fast measurements and the signature meet the profile, and every fast measurement the profile needs for provisional is present as an integer or a token other than not read or not defined. At most one provisional grant is issued in that cycle |
provisional |
received |
The policy in force sets verify_cap_per_object, and on a valid signature and a fresh challenge it is hit. A verify_cap_per_object refuse is final for that submission_sha256 only. It does not change the pair's state or friendly entry. It is not the pair's thorough decision. A cap refuse (verify_cap_per_object or verify_cap_per_door_session) does not end the cycle. The pair's state does not move to refused. The submission response is refuse. Service-started re-verifies and held retries are not in the count. An unauthenticated rate limit is not this row. A transport rate limit is not this row |
received |
received |
The policy in force sets verify_cap_per_door_session, and on a valid signature and a fresh challenge it is hit. A cap refuse (verify_cap_per_object or verify_cap_per_door_session) does not end the cycle. The pair's state does not move to refused. The submission response is refuse. Held retries do not count. An unauthenticated rate limit is not this row. A transport rate limit is not this row |
received |
received |
A plant or header mismatch is read from the local cache and is not yet confirmed by a fresh read from {index-url} that bypasses the local cache |
held |
received |
That same plant or header mismatch is confirmed by a fresh read from {index-url} that bypasses the local cache. Invariant fast final refuse. No thorough run. No grant. phase fast |
refused |
received |
The object bytes fail to decode as a hike object. Invariant fast final refuse. No thorough run. No grant |
refused |
received |
A fast not read other than a six-leg read, including an unreadable header or a height or plant the mirror does not hold, when the profile needs that measurement for provisional |
held |
received |
Settled by bytes already in hand: fewer than min_anchored_paths waypoints among W_1 … W_16 would be anchored, wherever those waypoints sit, even if every not read plant or path read among them came back anchored, or the section 14.6 draw is not defined because fewer than six legs exist. Bookend N not defined is not this row. If a re-read could bring that waypoint count to 6 or more, the measurement is not read and the state is held, and this row does not apply. A short-byte six-leg draw is not this row. hike_span not read is not this row. Bookend S not read is not this row |
refused |
received |
hike_span is not read. The state enters held and the read is retried. Bookend S not read is not this row |
held |
received |
Shape mismatch, including shape_item height_range, seed-value mismatch, or waypoint rehash mismatch is measured from the object. Waypoint rehash is the recomputed waypoint compared with the stored waypoint. Invariant fast final refuse. No thorough run. No grant. Not gated on a lock |
refused |
provisional |
A thorough not read, including a short or missing six-leg read, a short-byte six-leg draw, or short path bytes for a six-leg run. When the six-leg draw is not read, six_leg_runs is not read. The service re-verifies |
held |
received |
provisional: off. A thorough not read, including a short or missing six-leg read, a short-byte six-leg draw, or short path bytes for a six-leg run. When the six-leg draw is not read, six_leg_runs is not read. The service re-verifies |
held |
received |
provisional is on, and the fast phase has not left received within fast_accept_bound. No grant. The state enters held. This timeout hold does not return to provisional |
held |
received |
provisional: off. Thorough decides accept; the accept stands |
accepted |
received |
provisional: off. Thorough negative. No grant was issued |
refused |
provisional |
async_catch_budget is hit before thorough settles. The move from provisional to held ends the grant. It does not resume. No grant remains. The state enters held. This hold does not return to provisional. A later thorough refuse is revoke, because a grant was issued in the cycle |
held |
provisional |
Thorough decides accept; the accept stands |
accepted |
provisional |
Thorough negative. A grant was issued (provisional or accept, including one already ended by the move from provisional to held). phase thorough. The revoke cites fast_seq |
revoked |
held |
provisional is on, no grant was issued in that admission cycle, and this hold was not entered from fast_accept_bound or from async_catch_budget. The fast inputs are read on retry |
provisional |
held |
This hold was entered from fast_accept_bound or from async_catch_budget. Thorough decides accept; the accept stands. This hold does not return to provisional |
accepted |
held |
This hold was entered from fast_accept_bound or from async_catch_budget. Thorough negative, and no grant was issued in that cycle. phase thorough. fast_seq is omitted. This hold does not return to provisional |
refused |
held |
This hold was entered from fast_accept_bound or from async_catch_budget. Thorough negative, and a grant was issued in the cycle. A later thorough refuse is revoke. phase thorough. The revoke cites fast_seq. This hold does not return to provisional |
revoked |
held |
This hold was entered from fast_accept_bound or from async_catch_budget. reverify_max_attempts or max_held_blocks is hit, whichever comes first. Same budgets, no reset. When both are hit on the same check, the detail is reverify_max_attempts. Reason held_bound. No grant was issued in that cycle. phase is the phase running when the budget ran out. This hold does not return to provisional |
refused |
held |
This hold was entered from fast_accept_bound or from async_catch_budget. reverify_max_attempts or max_held_blocks is hit, whichever comes first. Same budgets, no reset. When both are hit on the same check, the detail is reverify_max_attempts. Reason held_bound. A grant was issued in that cycle. phase thorough. The revoke cites fast_seq. This hold does not return to provisional |
revoked |
held |
Thorough decides accept; the accept stands |
accepted |
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. The revoke cites fast_seq |
revoked |
held |
Thorough negative, and no grant was issued | refused |
held |
A thorough not read, including a short or missing six-leg read, a short-byte six-leg draw, or short path bytes for a six-leg run. When the six-leg draw is not read, six_leg_runs is not read. Neither reverify_max_attempts nor max_held_blocks is hit |
held |
held |
reverify_max_attempts or max_held_blocks is hit, whichever comes first, and a grant was ever issued (provisional or accept, including one already ended by the move from provisional to held). Reason held_bound. Detail names the budget. When both budgets are hit on the same check, the detail is reverify_max_attempts. phase thorough. The revoke cites fast_seq |
revoked |
held |
No grant was issued, and the budget ran out while the fast phase was still running. Reason held_bound. Detail names the budget. When both budgets are hit on the same check, the detail is reverify_max_attempts. phase fast. No thorough line. The line carries the fast record. fast_seq is omitted |
refused |
held |
No grant was issued, and the budget ran out while the thorough phase was running. That includes provisional: off, and a fast-entered hold that later ran thorough when the budget hit. Reason held_bound. Detail names the budget. When both budgets are hit on the same check, the detail is reverify_max_attempts. phase thorough. fast_seq is omitted. It is never encoded as JSON null. The line carries the thorough record |
refused |
held |
A cache-sourced plant or header mismatch is confirmed by a fresh read from {index-url} that bypasses the local cache, and no grant was ever issued. Invariant fast final refuse. phase fast. No thorough run. No grant |
refused |
held |
A cache-sourced plant or header mismatch is confirmed by a fresh read from {index-url} that bypasses the local cache, and a grant was ever issued (provisional or accept, including one already ended by the move from provisional to held). Invariant. phase thorough. The revoke cites fast_seq. The reason is the confirmed plant or header mismatch |
revoked |
revoked |
Any later check of that same submission | revoked |
accepted |
The policy version changes. Re-verify the stored object under the new policy version. While that re-verify is not read, including after either reverify budget is hit, signature-only sign-in continues on the earlier accept. Thorough decides accept; the accept stands. The friendly entry is kept 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. A re-verify of an older object does not displace a newer entry. This re-verify is not a new admission cycle and never issues provisional |
accepted |
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 refuse. A not read is not this row. The outcome is revoke. phase thorough. The revoke cites the new policy version and fast_seq. The Gate invalidates the pair's grant before it returns or logs the revoke. When this object is the key's current friendly entry, the revoke removes that entry by a compare-and-remove on object_sha256, only if the current entry's object hash still matches this pair. A re-verify of an older object does not displace a newer entry. If a newer accept B later loses its accept, A is not restored |
revoked |
accepted |
That re-verify is not read, and neither reverify_max_attempts nor max_held_blocks counted from the first not read on that re-verify is hit. The earlier accept stands. No suspend. No held_bound. Friendly returns continue. The re-verify is retried later |
accepted |
accepted |
That re-verify is still not read, and reverify_max_attempts or max_held_blocks counted from the first not read on that re-verify is hit, whichever comes first. The hit is not a revoke, does not remove the friendly entry, and does not change sign-in state. 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 |
accepted |
accepted |
The state is accepted. A listed key signs in again for the same already-accepted object hash. While a later-policy re-verify is not read, including after either reverify budget is hit, signature-only sign-in continues on the earlier accept. Only a re-verify that refuses, other than a B7 cap refuse, ends that sign-in as a revoke, and it removes the friendly entry only when this object is the key's current friendly entry. Section 14 does not run again. Signature-only sign-in requires state accepted |
accepted |
refused |
Any later check of that same submission | refused |
Normative. Policy refuses at once only when the waypoint count is settled by bytes already in hand: fewer than min_anchored_paths waypoints among W_1 … W_16 would be anchored, wherever those waypoints sit, even if every not read plant or path read among them came back anchored. If a re-read could bring that count to min_anchored_paths or more, the measurement is not read and the state is held. Bookend N not defined is not, by itself, an admission refuse. Fewer than six legs for the section 14.6 draw is not defined from the object's own bytes and is that same refuse-at-once. That draw is not a leg-count floor. hike_span not read stays held. Bookend S not read is not this hold. A fast not read, including an unreadable header, is 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 not read. The state is held from received when provisional is off, from provisional, or from held. They are not a fast received to held row. The service re-verifies. A short-byte six-leg draw stays re-readable.
A move from provisional to held ends the grant. It does not resume. No action is allowed while held. held leaves to provisional only when provisional is on, no grant was issued in that admission cycle, the hold was not entered from fast_accept_bound or from async_catch_budget, and the fast inputs are read on retry. A hold entered from fast_accept_bound or from async_catch_budget does not return to provisional. It leaves only to a thorough accept, to refuse, to revoke, or to refuse/revoke with reason held_bound when reverify_max_attempts or max_held_blocks runs out. Those are the same budgets, with no reset. At most one provisional grant is issued in that admission cycle. held leaves to accepted when thorough decides accept, to revoked on a negative thorough outcome when a grant was ever issued (provisional or accept, including one already ended by the move from provisional to held), or to refused on a negative thorough outcome when no grant was issued. provisional then held then a negative thorough outcome is revoke. While neither reverify_max_attempts nor max_held_blocks is hit, a thorough not read is retried and the state is held. Exhausting either budget is refuse/revoke with reason held_bound: revoke if a grant was ever issued (provisional or accept, including one already ended by the move from provisional to held), and refuse if none was. It does not stay held. That held is not a negative settle. held for a missing measurement is only for not read. Policy refuses at once only when the waypoint count is settled by bytes already in hand: fewer than min_anchored_paths waypoints among W_1 … W_16 would be anchored, wherever those waypoints sit, even if every not read plant or path read among them came back anchored. If a re-read could bring that count to min_anchored_paths or more, the measurement is not read and the state is held. Bookend N not defined is not, by itself, an admission refuse. Fewer than six legs for the section 14.6 draw is not defined from the object's own bytes and is that same refuse-at-once. That draw is not a leg-count floor. hike_span not read stays held. Bookend S not read is not this hold. That not defined does not go to held. A fast not read, including an unreadable header, is 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 not read. The state is held from received when provisional is off, from provisional, or from held. They are not a fast received to held row. A short-byte six-leg draw stays re-readable. A Rule A4.3 checkpoint digest that has not been recomputed does not put an honest object in held.
A height or plant the mirror does not hold is not read, and the state is held. A plant or header mismatch read from that mirror is held until a fresh read from {index-url} that bypasses the local cache confirms the same comparison. {index-url} is the upstream. The local mirror is a cache of {index-url} responses. An operator's own Bitcoin-built index counts as its {index-url}. Anyone may still check that index against Bitcoin. Only a mismatch confirmed by that fresh read can refuse.
Normative. A fake leg is a section 14.6 mismatch on a recomputed leg. The gate refuses that submission, and that gate ignores every later submission from that public key. The ignore is per key and that gate only. Its duration is the lever fake_leg_ignore_seconds (section 15.4); when that lever is unset the ignore has no expiry. Every later submission from that key at that gate gets the same refusal, for any object, with no new draw and no new section 14 run. A cheap-check failure, including a Rule A4.3 checkpoint-digest mismatch, a shape mismatch, a waypoint rehash mismatch, a seed-value mismatch, and an unknown_kind refuse, is a plain refusal. It does not add the key. The ignore record is persistent. A policy-version bump does not clear it and does not open a new cycle for that key. A gate restart does not clear it. A re-verify does not give a free redraw. A cap refuse under Rule B7 is not this ignore. The caps are not a ban. A refuse or a revoke that is not a fake leg still binds to (public_key, object_sha256) under that policy version. The same submission_sha256 does not become accepted under that policy version. A resubmit of the same submission_sha256 returns the original outcome. This version does not issue a signed receipt. It does not mint a grant. A new submission needs a new challenge. held is not a sticky refuse and it is not accept. The decision-log companion publishes the refuse. This file does not write that log. Cross-gate sharing of the fake-leg record is out of scope.
Signature-only sign-in requires state accepted. A listed key may sign in again only for that same already-accepted object hash. While a later-policy re-verify is not read, including after either reverify budget is hit, signature-only sign-in continues on the earlier accept. Only a re-verify that refuses, other than a B7 cap refuse, ends that sign-in as a revoke, and it removes the friendly entry only when this object is the key's current friendly entry. That sign-in does not run section 14 again. A new object hash is a new section 15.1 submission. When the policy version changes, a re-verify that refuses under the new policy version revokes an accepted object. That refuse is a measured mismatch or any B-rule outcome other than a B7 cap refuse. A not read is not a refuse and does not revoke. That revoke cites the new policy version. While that re-verify is not read and neither reverify_max_attempts nor max_held_blocks counted from the first not read on that re-verify is hit, the earlier accept stands. It does not suspend the accept, it does not apply held_bound, and friendly returns continue. The re-verify is retried later. Both reverify_max_attempts and max_held_blocks for that re-verify are counted from the first not read on that re-verify. max_held_blocks starts at the Gate’s {index-url} tip height at that first not read. It is hit when a retry finds that tip ≥ start + max_held_blocks and the re-verify is still not read. reverify_max_attempts is hit when that many retries of this re-verify, counted from that first not read, are still not read. Before either budget is hit, the Gate retries within the existing reverify budget. After either budget 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 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 re-verify that refuses, other than a B7 cap refuse, is a revoke and removes the friendly entry only when this object is the key's current friendly entry. When that re-verify decides accept, the entry is kept 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. A re-verify of an older object does not displace a newer entry. This file does not write that log. An index outage does not revoke that accepted object. Thorough decides accept and the accept stands.
Rule B9. Invariant refuses
Normative. These outcomes are not Rule B0–B8 candidates. They do not wait on a configured threshold. A revoke in this list is phase thorough. A grant means the fast phase already finished. fast_seq is omitted when the line does not cite one. It is never encoded as JSON null.
- The object bytes fail to decode as a hike object. Invariant fast final
refuse. No thorough run. No grant.phasefast. - Waypoint rehash is the recomputed waypoint compared with the stored waypoint. That
mismatchis an invariant fast finalrefuse. No thorough run. No grant.phasefast. - Shape
mismatch, includingshape_itemheight_range, and seed-valuemismatch, are invariant fast finalrefuses. No thorough run. No grant.phasefast. - A plant or header
mismatchconfirmed by a fresh read from{index-url}that bypasses the local cache is an invariant. No grant was ever issued:refuse,phasefast, no thorough run. A grant was ever issued (provisionaloraccept, including one already ended by the move fromprovisionaltoheld):revoke,phasethorough, and therevokecitesfast_seq. The reason is the confirmed plant or headermismatch. - A checkpoint-digest
mismatchis an invariant. The thorough phase measures it. No grant was ever issued:refuse,phasethorough. A grant was ever issued:revoke,phasethorough, and therevokecitesfast_seq. It is a cheap-check failure. It does not add the public key to the fake-leg ignore record. - A six-leg comparison of
mismatchis an invariant. The thorough phase measures it. No grant was ever issued:refuse,phasethorough. A grant was ever issued:revoke,phasethorough, and therevokecitesfast_seq. When the six-leg draw isnot read,six_leg_runsisnot read. That is a thoroughnot read. The state isheld. Thatmismatchis a fake leg. Rule B7 and section 15.5 then ignore later submissions of that public key at that gate. - A cap hit is not in this list. Rule B7 states it. It applies only when the policy in force sets that cap.
- The admission service runs its section 14 verifier under the profile
production, the section 6 constants, and reads only a section 14 record whoseprofileisproduction. It never runsconformance-smalland never reads a record of any other profile. An object whosefillor size fields differ from section 6 is then shapemismatchwithshape_itemconstant(Rule A4.1.2), an invariant fast finalrefuse. No thorough run. No grant.phasefast. An admission service on the live plane compares headers and plants against the Bitcoin mainnet portable index only: its{index-url}is a mainnet index. This is not a lever. - When the section 14 record’s
kindisunknown kind, the admission service refuses with reasonunknown_kind. That reason is this policy. It is not a section 14 token. Verify does not record it. No thorough run. No grant.phasefast. The key is not added to the ignore record.
15.3 Two-phase async
- Fast phase. The fast list is the section 14.1–14.5 measurements, and the cheap checks include the Rule A4.3 checkpoint digest on every checkpoint: shape, the seed, waypoint and fold recompute from stored checkpoint bytes, path and plant comparison, bookends, and the anchored count, plus the signature. Header and plant facts come from the service's local mirror of the portable index.
{index-url}is the upstream. The local mirror is a cache of{index-url}responses and serves the section 9 body rules. The mirror holds only{index-url}responses. It is filled from the portable index only. A height or plant the mirror does not hold isnot read, and the state isheld. The fast phase does no other network read. A plant or headermismatchread from that mirror ishelduntil a fresh read from{index-url}that bypasses the local cache confirms the same comparison. An operator's own Bitcoin-built index counts as its{index-url}. Anyone may still check that index against Bitcoin. Only amismatchconfirmed by that fresh read canrefuse. 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. Leg recompute stays thorough-only. The fast phase does not turn a partial view into the finished verify record. Branch on phase. There is no new token for the phase. The Rule A4.3 checkpoint digest is a cheap check on every checkpoint. It hashes the stored sample. It does not recompute a leg. The admission service runs it with the other cheap checks. It is not thorough-only. The fast record is the section 14 record withsix_legandsix_leg_runsomitted.checkpoints[*].legs[*].resultstays, because the Rule A4.3 digest is a cheap check. Every other section 14 key stays, includingshape_item,checkpoints[*].g,checkpoints[*].legs[*].j,waypoints[*].result,seed_value,paths,anchors, the bookend keys, andanalysis. Omitted keys are absent. They are not filled withnot defined. The full section 14 record, after thorough, has every key present, andsix_leg_runsholds the thorough draws. Both records are encoded with RFC 8785. The fast record has no placeholders. It does not fill an omitted key withnot defined. It is not the finished record. The fast decision token isprovisional. A fast-phaseacceptis invalid. The fast phase never issuesaccept. It may issue a finalrefusewhen a fast rule fails on the object's own bytes: decode (the object bytes fail to decode as a hike object), waypoint rehash, shapemismatch, includingshape_itemheight_range, seed-valuemismatch, a plant or headermismatchconfirmed by a fresh read from{index-url}that bypasses the local cache, an object-settled refuse-at-once, or a cap on a signed submission. Otherwise the fast phase givesprovisional. A fast-phasenot readisheld. When profile modeprovisionalison, it returns withinfast_accept_bound. That bound is a lever (section 15.4). Whenprovisionalisoff, the fast phase does not grantprovisional.provisional: offis that mode. It is not a value offast_accept_bound. - Held.
heldfor a missing measurement is only fornot read. When a fast measurement the active profile needs forprovisionalisnot read, the fast phase does not issueprovisionaland does not record a stickyrefuse. The state isheld. Policyrefuses at once only when the waypoint count is settled by bytes already in hand: fewer thanmin_anchored_pathswaypoints amongW_1…W_16would be anchored, wherever those waypoints sit, even if everynot readplant or path read among them came back anchored. If a re-read could bring that count tomin_anchored_pathsor more, the measurement isnot readand the state isheld. BookendNnot definedis not, by itself, an admission refuse. Fewer than six legs for the section 14.6 draw isnot definedfrom the object's own bytes and is that same refuse-at-once. That draw is not a leg-count floor.hike_spannot readstaysheld. BookendSnot readis not this hold. Thatnot defineddoes not go toheld. A fastnot read, including an unreadable header, isheld. 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 arenot read. When the six-leg draw isnot read,six_leg_runsisnot read. The state isheldfromreceivedwhenprovisionalis off, fromprovisional, or fromheld. They are not a fastreceivedtoheldrow. A short-byte six-leg draw stays re-readable. A missing Rule A4.3 checkpoint digest is not thatnot definedcase: the digest is not yet run. A height or plant the mirror does not hold isheld, as section 15.2 states. A cache-sourced plant or headermismatchishelduntil a fresh read from{index-url}that bypasses the local cache confirms it.{index-url}is the upstream. The local mirror is a cache of{index-url}responses. An operator's own Bitcoin-built index counts as its{index-url}. Anyone may still check that index against Bitcoin. The fast phase must not issueprovisionalon a floor that isnot readornot defined. A fast-phasenot readisheld. A plantmismatchconfirmed by a fresh read from{index-url}that bypasses the local cache is a fast finalrefuse: no thorough run and no grant. - Thorough phase. The thorough phase is the leg sample in section 15.5:
D = 6 · thorough_roundsrandom legs asthorough_roundsrounds of 6, each round’s 6 legs run in parallel. The Rule A4.3 digest is a cheap check and is not this phase. Leg recompute (section 14.6) is thorough-only. A short or missing six-leg read, a short-byte six-leg draw, and short path bytes for a six-leg run are that thoroughnot read. When the six-leg draw isnot read,six_leg_runsisnot read. The state isheldfromreceivedwhenprovisionalis off, fromprovisional, or fromheld. They are not a fastreceivedtoheldrow. Only the thorough phase issuesaccept. The fast phase never issuesaccept. The thorough phase issuesrefuseorrevokeby the grant-ever-issued rule. A fast finalrefuseis the case named in the fast phase. Thorough outcomes are: thorough decidesacceptand the accept stands (accepted);revoke;refuse; orheld.heldis a thoroughnot reador a retry. It is not a negative settle. A negative thorough outcome, or a failed policy-version re-verify, isrevokeif a grant was ever issued (provisionaloraccept, including one already ended by the move fromprovisionaltoheld), andrefuseif none was.provisionalthenheldthen a negative thorough outcome isrevoke. A laterrevokecites a new policy version or a new measurement.heldis an HTTP response outcome. It is not a decision-log event. If the object wasprovisional, a move toheldends the grant. It does not resume. A grant was issued (provisionaloraccept, including one already ended by the move fromprovisionaltoheld). A later negative thorough outcome isrevoke. It isrefuseonly when no grant was issued. - Provisional. The fast-phase outcome is
provisional, a reversible grant, until thorough settles. It is neveraccept. Aprovisionalstate grants no action (provisional_action_cap). No irreversible action is allowed whileprovisional, in every profile. That value is fixed in this file. It is not a profile knob. Aprovisionalgrant is short, read-only access until thorough settles orasync_catch_budgetis hit. The friendly-list entry is added only at a thoroughaccept. The window before thorough settles is the attacker surface of a provisional grant. A provisional grant is not a finished admission. The residual risk is QR relay phishing: a victim scans a QR that an attacker relayed. Informative. Although aprovisionalstate grants no action, a forged object can still reachprovisionalfor free: the fast phase has no leg, and the plant gap is record-only. That grant is short, read-only access until thorough settles orasync_catch_budgetis hit.verify_cap_per_door_sessionis the main limit.
Held bound. Normative. reverify_max_attempts and max_held_blocks are levers (section 15.4). 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. It starts at the Gate’s {index-url} tip height at the first entry into held in the cycle. It does not reset. When no tip has been read at the first entry into held, max_held_blocks starts at the first successful tip read. That start tip height is the normative clock. An append-only hash-chained log entry may record that 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 signed head. This version has no signed head and no signed receipt. This file does not define a second clock. It is hit when a check finds that tip ≥ start + max_held_blocks, even when no not read has occurred. 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 counted retries have been made. A held submission is retried at least once every held_retry_seconds (the lever held_retry_seconds, section 15.4), whether or not the tip advanced. Each such retry counts under reverify_max_attempts by the rules in this paragraph. That timer schedules retries only and is not a clock for any budget. Whichever budget is hit first, the outcome is refuse/revoke with reason held_bound. The detail names the budget that hit, reverify_max_attempts or max_held_blocks. This file names that detail. It does not write the decision-log JSON. Exhausting either budget is revoke if a grant was ever issued (provisional or accept, including one already ended by the move from provisional to held), and refuse if none was. It does not stay held. phase is the phase running when the budget ran out, and the line carries that phase's record. No grant was issued and the fast phase was still running: the outcome is refuse, reason held_bound, the detail names the budget that hit, phase fast, no thorough line, and the line carries the fast record. No grant was issued and the thorough phase was running, including provisional: off and a fast-entered hold that later ran thorough: the outcome is refuse, reason held_bound, the detail names the budget that hit, phase thorough, fast_seq is omitted, and the line carries the thorough record. fast_seq is omitted. It is never encoded as JSON null. A grant was ever issued (provisional or accept, including one already ended by the move from provisional to held): the outcome is revoke, phase thorough, and that revoke cites fast_seq. Every revoke is phase thorough. When both reverify_max_attempts and max_held_blocks are hit on the same check, the detail is reverify_max_attempts. This file does not write the line. After a grant was issued (provisional or accept, including one already ended by the move from provisional to held), a held_bound outcome is revoke, not refuse. That revoke binds to (public_key, object_sha256). It does not end another object's grant. A revoke in that object's own admission cycle ends that pair's grant and any signature-only sign-in for the object. A later-policy re-verify that refuses, other than a B7 cap refuse, is a revoke and removes the friendly entry only when this object is the key's current friendly entry, by a compare-and-remove on object_sha256, only if the current entry's object hash still matches this pair. A not read does not.
Which named checks are fast and which are thorough is the profile schema in section 15.4. The fast list does not include a leg.
The hit rule, the {index-url} tip clock, and the phase of a held_bound line are the Held bound paragraph above. An append-only hash-chained log entry may record that 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 signed head. This version has no signed head and no signed receipt. That held is not a negative settle. The fast phase itself does not refuse for a not read gap, or for a height or plant the mirror does not hold. Policy refuses at once only when the waypoint count is settled by bytes already in hand: fewer than min_anchored_paths waypoints among W_1 … W_16 would be anchored, wherever those waypoints sit, even if every not read plant or path read among them came back anchored. If a re-read could bring that count to min_anchored_paths or more, the measurement is not read and the state is held. Bookend N not defined is not, by itself, an admission refuse. Fewer than six legs for the section 14.6 draw is not defined from the object's own bytes and is that same refuse-at-once. That draw is not a leg-count floor. hike_span not read stays held. Bookend S not read is not this hold. That not defined does not go to held. A short-byte six-leg draw stays re-readable. The state is held. The service re-verifies.
15.4 Admission levers
FRAMING. Lever ranges below are what a policy file may set; values belong in a policy instance outside this pack.
Normative. A policy profile names its policy_version, the version of this file it targets, the value of each lever it sets, and its fast checks and its thorough checks. The fast list is the cheap checks: shape, header match, the Rule A4.3 checkpoint digest on every checkpoint, the seed, waypoint and fold recompute from stored checkpoints, path and plant comparison, bookends, the anchored count, and the signature. The thorough list is the section 15.5 leg sample. The fast checkpoint-binding checks are the fold and the waypoint rehash under section 12.2 from stored checkpoints. The Rule A4.3 checkpoint digest hashes the stored sample and does not recompute a leg. The fast list never recomputes a leg.
Every lever is below. A lever has a name, a unit, an allowed range, and a measuring or comparison rule. A policy sets a value inside the range. This file gives no lever a default. The policy in force sets every lever. The one exception is a lever whose range lists unset: the policy may leave it absent, which means unset. “Unset” is an allowed value only where the range lists it. A lever marked gate-scoped has one value per admission service, not per profile. Integers are compared exactly, with no rounding. A value that disables a comparison is out of range.
| Lever | Rule | Unit | Allowed range | Measured or compared as |
|---|---|---|---|---|
min_anchored_paths |
B4 | waypoints | integer 2 … 16 |
anchored_total (Rule A4.5) over W_1 … W_16, anywhere among them. Compared anchored_total ≥ min_anchored_paths. The lower bound 2 is the two anchored waypoints the bookend pair needs (Rule A4.6). hike_span does not read those waypoints. |
leg_rate_floor |
B2 | checkpoints per second, as p/q |
integers p ≥ 1, q ≥ 1 |
(hike_legs, hike_span) from Rule A4.10, hike_span the raw header nTime span in seconds, nTime(I_17) − nTime(I_0). Compared q · hike_legs ≥ p · hike_span. Section 15.5 is the measuring method a policy uses to choose p/q. |
held_retry_seconds |
15.3 | seconds | integer ≥ 1; gate-scoped |
Wall-clock interval between retries of a held submission. A retry schedule only; not a clock for any budget. |
reverify_max_attempts |
15.3 | counted retries | integer ≥ 1 |
Counted retries as the section 15.3 Held bound defines. Hit when that many have been made. |
max_held_blocks |
15.3 | blocks | integer ≥ 1 |
{index-url} tip height minus the start tip of the hold, as the section 15.3 Held bound defines. Hit when the difference is ≥ max_held_blocks. |
thorough_rounds |
B7, 15.5 | rounds of 6 legs | integer ≥ 1 |
The thorough draw size is D = 6 · thorough_rounds (section 15.5). |
provisional |
15.3 | mode | on or off |
off means no provisional state. |
fast_accept_bound |
15.3 | seconds | positive; used only when provisional is on |
Wall-clock time of the fast phase. |
async_catch_budget |
15.3 | seconds | positive | Wall-clock time of the parallel checkpoint-digest pass. It does not include recomputing legs. |
verify_cap_per_object |
B7 | verifies per (public_key, object_sha256, policy_version) |
integer ≥ 1, or unset |
Client-submitted verifies of that triple. Service-started re-verifies and held retries are not counted. |
verify_cap_per_door_session |
B7 | client submissions per door session | integer ≥ 1, or unset |
Client submissions in one door session. held retries are not counted. |
fake_leg_ignore_seconds |
B7 | seconds | integer ≥ 1, or unset; gate-scoped |
Time since the fake-leg refuse that entered the key on the ignore record. Unset means no expiry. |
challenge_lifetime_seconds |
15.1 | seconds | integer ≥ 1 |
expiry_unix_s − issue_unix_s of a minted challenge. The website and the Gate use the same value. |
challenge_clock_skew_seconds |
15.1 | seconds | integer ≥ 0 |
Allowance in the section 15.1 challenge window comparison. |
result_retention_seconds |
15.1 | seconds | integer ≥ 0 |
Time a settled result row stays readable. It never shortens a consumed-nonce row, which is kept within one nonce-row store until now_unix_s ≥ expiry_unix_s + challenge_clock_skew_seconds; when the store could have lost rows, nonce_store_start_unix_s moves forward and every earlier challenge is refused (section 15.1 Lifetime and clock skew, Single-use). |
Policy file encoding. A policy file that an admission service reads is a TOML v1.0.0 document. It has a top-level string spec, the version of this file it targets ("2.3", or "2.2" for a policy written for Satoshi Hike 2.2, which this file reads by the rules of this section, so an absent key is never read as a 2.2 default), and a table [levers]. Each key in [levers] is exactly a lever name from the table above, with no alias, prefix, suffix, or unit added: fast_accept_bound, not fast_accept_bound_seconds. An integer lever is a TOML integer. leg_rate_floor is a TOML table with exactly the integer keys p and q, for example { p = 1, q = 5 }. It is not a string and not a float. provisional is the string "on" or "off". A lever whose range is positive seconds (fast_accept_bound, async_catch_budget) is a TOML integer or float with at most 3 fractional digits. The service reads it as integer milliseconds, the nearest integer to the value times 1000, and compares in those milliseconds; a value that reads as 0 milliseconds or less is out of range. An absent key means unset where the lever’s range lists unset; there is no other spelling of unset. Any other absent key makes the service refuse the whole file. A key in [levers] that is not a lever name, a value of the wrong TOML type, or a value outside the lever’s range makes the service refuse the whole file. It does not run with part of a file. Keys outside [levers], other than spec, belong to the policy, and this file does not read them.
These are rules of this file, not levers: provisional_action_cap (a provisional state grants no action, and no irreversible action is allowed while provisional); revoke_scope, the pair (public_key, object_sha256); the round size of 6 legs (the section 14.6 draw count); the friendly-list rules in section 15.1 other than custody. revoke_scope binds a revoke to that pair. It does not end another object's grant. A later-policy re-verify that refuses, other than a B7 cap refuse, is a revoke and removes the friendly entry only when this object is the key's current friendly entry, by a compare-and-remove on object_sha256. A not read does not.
Rule B0. Which rate the admission service reads
Normative. The admission service reads (hike_legs, hike_span) from Rule A4.10. leg_rate = hike_legs / hike_span. That rate is the admission rate. The pair starts at the first interrupt I_0 (stages[1].beacon) and ends at last_interrupt (I_17). It does not start at seed_beacon. Stage 0 checkpoints are not in hike_legs. The bookend pair (N, S) is a section 14 measurement. It is not this rate. whole_hike_legs and whole_hike_span stay analysis names for the same integers as hike_legs and hike_span. When hike_span is not read, there is no numeric rate. The state is held and the read is retried (no_pair_outcome). Bookend S not read is not this hold. Bookend N not defined is not, by itself, an admission refuse. When fewer than min_anchored_paths waypoints among W_1 … W_16 would be anchored, wherever those waypoints sit, even if every not read plant or path read among them came back anchored, a re-read cannot change that result. Policy refuses it at once. It does not go to held. If a re-read could bring that count to min_anchored_paths or more, the measurement is not read and the state is held. A short or missing network read is not read. The state is held. The service re-verifies.
Rule B2. Leg-rate floor
Normative comparison. leg_rate_floor is the lever p/q (section 15.4) in checkpoints per second, hike_legs over hike_span, with hike_span in Unix seconds. One checkpoint is one leg. Section 15.5 states the measuring method. The comparison is integer-only: a ratio p/q meets the floor when hike_span is an integer greater than 0 and q · hike_legs ≥ p · hike_span. The comparison result is meets when that holds and refuse when it does not (section 4, Comparison result). hike_span ≤ 0 means the rate is undefined. The outcome is refuse. It is not held. held is only hike_span not read. The policy supplies p and q. The comparison does not round. Rule B3 does not change hike_span. There is no comparison when hike_span is not read. The fast phase does not issue provisional on that gap. The state is held. A short or missing network read stays not read and the state is held. A numeric rate that falls short of the floor is refuse. The bookend pair is not an input to this comparison.
Rule B3. Span sanity
Normative. Rule B3 is not the admission rate and is not an admission input. No span adjustment is applied to bookend S for admission, and none is applied to hike_span. delta_h stays the Rule A4.8 measurement.
Rule B4. Paths minimum
Normative. The comparison is anchored_total ≥ min_anchored_paths, with min_anchored_paths the lever in section 15.4. When anchored_total ≥ min_anchored_paths, the comparison result is meets (section 4, Comparison result). The anchored waypoints sit anywhere among W_1 … W_16. W_17 is not one of the 16. The count is Rule A4.5. A count settled below min_anchored_paths by bytes already in hand is refuse at once. If a re-read could bring the count to min_anchored_paths or more, the measurement is not read and the state is held.
Rule B6. Fixed start
Normative. fixed_start is not an admission input. Section 14 may still record fixed_start.N, fixed_start.S, and fixed_start.delta_h when bookends are defined. The admission service does not read those three for the rate, for the anchor comparison, or for any other admission comparison. Part A still records the actual a, b, N, and S. A fixed_start field of not read or not defined does not, by itself, refuse or hold the submission.
Rule B7. Resubmits
Normative. verify_cap_per_object (section 15.4) counts verifies per (public_key, object_sha256, policy_version). It is a compute limit, not a ban. A hit's refuse is final for that submission_sha256 only. It does not change the pair's state or friendly entry. It is not the pair's thorough decision. A cap refuse (verify_cap_per_object or verify_cap_per_door_session) does not end the cycle. Service-started re-verifies and held retries are not in the count. The same object under a new policy version may be verified again. verify_cap_per_door_session (section 15.4) counts client submissions in the session, so it bounds the fast verifies and the thorough runs those submissions trigger. Held retries are Gate-internal and do not count. It is not a cap on a website session. A transport rate limit stays outside this policy. These caps are compute limits. They do not ban a key. They are not the fake-leg ignore.
Normative. The admission service recomputes D = 6 · thorough_rounds distinct legs, drawn once and split into thorough_rounds rounds of 6, in that draw order. Each round’s 6 legs run in parallel. Section 15.5 is that rule, including the case 6 ≤ leg_total < D. A fake leg, a section 14.6 mismatch, refuses that submission. That gate then ignores every later submission from that public key, for fake_leg_ignore_seconds, or with no expiry when that lever is unset, on the gate’s persistent per-key record. A cheap-check failure does not. The caps above are not this ignore. Cross-gate sharing of the record is out of scope.
Normative. When the policy in force sets a value for verify_cap_per_object or verify_cap_per_door_session (section 15.4), a hit on that cap, on a valid signature and a fresh challenge, is a fast final refuse of that attempt. A verify_cap_per_object refuse is final for that submission_sha256 only. It does not change the pair's state or friendly entry. It is not the pair's thorough decision. A cap refuse (verify_cap_per_object or verify_cap_per_door_session) does not end the cycle. A verify_cap_per_door_session hit stays in received. The submission response is refuse. verify_cap_per_door_session (section 15.4) counts client submissions in the session, so it bounds the fast verifies and the thorough runs those submissions trigger. Held retries are Gate-internal and do not count. These are compute limits. They do not ban the key. The hit does not start a thorough run and does not issue a grant. An unauthenticated rate limit is not this rule. A transport rate limit stays outside this policy.
Rule B8. Leg count
Normative. There is no minimum or maximum leg count as policy. There is no per-stage admission minimum and no whole-hike leg-count minimum. leg_count ≥ 1 per stage follows from Rule A1.1 and is recorded under shape (Rule A4.1). It is the section 13.2 loop. It is not an admission threshold. A shape mismatch is Rule B9. It does not delete the draw rule below. The section 14.6 token not defined, when fewer than six checkpoints exist, is the draw’s own token. It is not a leg-count floor. The sample size D is a count of legs to recompute. It is not a leg-count floor. When leg_total < 6 the section 14.6 draw is not defined and the admission service refuses at once. When leg_total ≥ D the admission service draws D distinct global indices once, by the section 14.6 unbiased draw, discarding an index already chosen, and splits that sequence into thorough_rounds rounds of 6 in draw order. The 6 indices of one round are distinct. No index repeats across the D. When 6 ≤ leg_total < D the same draw continues for thorough_rounds rounds of 6. An index is not repeated while an unused index remains. When no unused index remains, a later draw may repeat an index, and it still discards an index already chosen in the current round. The 6 indices of the current round stay distinct. Each index is one section 14.6 unbiased draw. A discarded candidate is not used and the draw continues. A mismatch refuses that submission. The gate does not start the next round. The index draw is one sequence. The rounds are that sequence split into sixes. They are not independent draws.
15.5 Thorough draw and the floor
Normative. D = 6 · thorough_rounds (section 15.4).
Cheap checks run on every checkpoint and every waypoint. Those checks are shape, header match, the seed, the Rule A4.3 checkpoint digest on every checkpoint, and the section 12.2 fold and waypoint rehash. The Rule A4.3 digest hashes the stored sample. It does not recompute a leg. A cheap-check failure refuses the submission and does not add the public key to the ignore record. After the cheap checks, the gate draws distinct global indices once and runs them as thorough_rounds rounds of 6. When leg_total ≥ D the draw is D distinct indices, split in draw order into rounds [6r : 6r + 6] for r = 0 … thorough_rounds − 1. When 6 ≤ leg_total < D the draw still fills thorough_rounds rounds of 6, indices stay distinct while an unused index remains, and a later draw may repeat an index only after every index has been used. The current round’s 6 stay distinct. When leg_total < 6 the draw is not defined and the gate refuses. The 6 legs of a round run in parallel. A mismatch in a round refuses that submission. The gate does not start the next round. Legs of the current round that are already running finish only for the record; their results do not turn the refuse into an accept.
Normative. The current group of 6 is the indices in phase from index 6 times the number of complete groups already in phase, through the end. It is empty when the length of phase is a multiple of 6, including at the start. A candidate already in that group is discarded. The draw has not stopped while phase holds fewer than D indices and the section 14.6 byte source still yields a candidate. The draw stops when phase holds D indices. The draw also stops when that byte source is exhausted before D indices. Exhaustion is not read. Rounds already filled stay recorded. The unfinished group is not a round. The state is held. It is not a mismatch refuse.
if leg_total < 6:
the draw is not defined
refuse
else:
D = 6 * thorough_rounds
phase = []
# one draw, then split. Not three independent draws.
while phase has fewer than D indices and the draw has not stopped:
# stopped: D indices are in phase, or the byte source is exhausted before D
q = one section 14.6 unbiased draw from 0 … leg_total − 1
if q is already in the current group of 6:
discard q and continue
if q is already in phase and some index in 0 … leg_total − 1 is not in phase:
discard q and continue
append q to phase
rounds = the complete groups of 6 in phase, in order
# a trailing group with fewer than 6 indices is not a round
for round in rounds: # up to thorough_rounds
start all 6 legs before recording any of them
if any of the 6 is mismatch:
refuse
do not start the next round
record the round after all 6 have a result
Informative. Detection at a fake-leg fraction f = 0.25, for n sampled legs, is 1 − (1 − f)^n.
| Rounds finished | Legs n |
Detection |
|---|---|---|
| 1 | 6 | 82.2% |
| 2 | 12 | 96.8% |
| 3 | 18 | 99.44% |
Those three figures are 1 − (1 − 0.25)^n at n = 6, n = 12, and n = 18. They are not a second sample size.
Normative. A fake leg is a section 14.6 mismatch on a recomputed leg. The gate refuses that submission, and that gate ignores every later submission from that public key. The ignore is per key and that gate only. Its duration is the lever fake_leg_ignore_seconds; unset means no expiry. Later submissions receive that same refusal, with no new draw. A cheap-check failure does not add the key. “That gate” is the admission service’s persistent per-key record. The record survives a policy-version bump and a restart of the gate. A re-verify never gives a free redraw. Cross-gate sharing of the record is out of scope. An admission service that runs one round’s 6 legs one after another does not follow this section. Rule A4.4.4 still applies to the section 14 measurement record: the record does not depend on finish order, and a verifier that is not running this thorough draw may recompute its six legs one at a time. That permission does not apply to thorough_rounds. The admission service starts all 6 legs of the round before any of those 6 is recorded, and it records the round only after all 6 have a result.
Floor. leg_rate_floor is the lever in section 15.4. This file gives it no default. The policy in force sets it.
Measuring method. Measure on an owned commodity host. The low end of that set is the low-end owned host (an Intel Core i7-10610U laptop, no SHA-NI). Use a conforming SDK at the cut tag of this file (section 16.2) that passes the whole pack, including every permanent negative. Use the fastest such producer build available for that host, and record with the rate whether its fold ran vectorised or scalar and whether its SHA-256 ran in hardware or software. Run a real beacon-paced hike under this file’s producer rules: nothing pauses a leg (Rule A6), and there is no wait before a path ask. The rate is the first-interrupt to last-interrupt rate, hike_legs / hike_span, Rule A4.10. The first interrupt is I_0 (stages[1].beacon). The last interrupt is I_17 (last_interrupt). The seed beacon is not the start. hike_span is the raw header nTime difference of those two beacons, header.time of I_17 minus header.time of I_0. Median-time-past is not this span. The floor is calibrated on the best legal packed rate per machine. That rate is N parallel PCOCs with distinct keys. Distinct keys make distinct terrains under this seed. An interleave of those hikes is the packed rate. Shared-terrain packing across distinct PCOCs is impossible under this seed. It is not a legal schedule. Record the host, the CPU, the implementation, the commit, and that packed rate beside the single-hike rate. A policy that follows this method sets the floor a little below the low-end host’s rate. It does not set the floor above the rate that the released conforming SDKs reach on the low-end host. That constraint binds whoever sets or raises the floor. It is not a comparison the admission service makes. The floor must be measured under this file. A rate measured with a producer that paused the walk (for example, with a wait before path asks or a blocking post) is not a measurement for this file: those pauses sat inside hike_span and understate the rate a producer of this file reaches at the same cost per leg. This method informs the value a policy sets. It does not set one.
Republished from pcoc 1b9f632 (spec/ six-doc pack; same bytes at e8030ba). Raw markdown.