You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The accepted RFC requires both registrar verbs to be rate-limited, and specifies the limiter's shape as per-(verb, outcome-class) buckets "so refusals throttle without starving accepted mints". Decomposing that requirement surfaced an ordering conflict rather than a preference: the limiter exists to prevent the invocation's durable intent record from being written, so it has to be charged before that write — and at that moment the invocation's outcome class does not exist yet. Keying a bucket on the outcome class would therefore require either charging after the very write the limiter exists to prevent, or predicting the outcome.
The sibling limiter issue is specified against a different shape as a result: per-(client identity, verb, bucket) buckets over a two-value enum, predecision_refusal and admission. That shape delivers the part of the RFC's guarantee that matters most — the free flood path, malformed input costing no OpenBao work, cannot starve legitimate mints — and openly does not deliver the rest: a caller able to produce well-formed but refusable requests still competes with real mints for admission budget.
The departure has a second half, and it is not a consequence of the first. The RFC's caller-facing language is written as though every limited invocation is throttled: its rate-limit decision says "a throttled invocation returns RegistrarBusy { retry_after } — a retryable error", and its acceptance criterion restates that for the pre-derivation arm specifically. The plan's limiter returns that only from the admission check point. On the pre-derivation check point an invocation whose bucket is empty has its two audit records suppressed but is still given its real, permanent refusal, on the reasoning that the request is invalid on its face, no later attempt can succeed, and telling such a caller to retry manufactures the retry storm the limiter exists to stop. That is a defensible reading — arguably the only sound one — but it is a separate departure from the bucket-key question: one could ratify the two-bucket shape and still hold that every limited invocation must answer RegistrarBusy, or reject the two-bucket shape and still agree that a determined refusal is never retryable. Two independent questions, and leaving the second unasked would let an implementation ticket settle by writing code what the RFC states in acceptance language.
So an accepted RFC and the issue tree built from it disagree on two points the RFC states as guarantees. This issue is where a human resolves both, and both are now resolved. The RFC's owner ratified the bucket key as the two-value shape and the caller answer as the admission-only throttle, with the reasoning recorded under Questions 1 and 2 below. Neither is this issue's to choose any longer — both are this issue's to transcribe into the RFC, in the wording those two sections fix. What remains is therefore an editing job with a precise brief, not a decision.
Its deliverable is an edit to one document — the RFC — plus the reasoning recorded beside it. It ships no code, and it is not implementation work: ruling on an accepted RFC is a decision for that RFC's owner, not something an implementation ticket or an autonomous agent may settle by writing the code one way and calling the RFC stale. What is fixed and checkable is which sentences must change together, what each ratified answer obliges the author to write down, and what must be handed on to the issues that depend on it. Those are the acceptance criteria below.
Scope
Two questions are settled here, and they were settled independently. Both now have their answers and both need transcribing. Record both in the RFC, which is the artifact that currently carries the contradictions.
The RFC carries the disputed language in four places and they must move together, or the amendment is partial: §5.6's rate-limit sizing decision (the bucket sentence and the RegistrarBusy sentence), §5.6's pre-derivation-arm decision (which already calls a records-suppressed invocation "throttled"), §6's acceptance criterion for the verb rate limit (which restates both the retryable RegistrarBusy and the pre-derivation arm), and §7's issue-decomposition entry for the verb-level audit record (which restates "per-(verb, outcome-class) buckets … and a retryable RegistrarBusy { retry_after }" in one line).
Question 1 — the bucket key: answered; amend the RFC to the two-bucket shape
The RFC's owner ratified per-(client identity, verb, bucket) buckets over the two-value enum predecision_refusal / admission. This section is no longer a choice; it is the wording the amendment has to carry, and all four RFC locations move together as stated above.
Rewrite the bucket sentence and the acceptance criterion that restates it so they describe the two-bucket key rather than per-(verb, outcome-class) buckets.
Carry this reasoning, and not a stronger claim. The reasoning is narrower than "the RFC's shape is not implementable as written", and the narrower statement is the one the amendment must carry: the two-value enum is the maximal refinement knowable at the point the limiter is charged. A pre-derivation refusal is knowable there — that arm fires only on refusals, and label validation and multiplicity-class resolution both precede the check point — while the outcome of an admitted invocation is not, because spec-conflict, name-collision and host-mismatch are settled by OpenBao work that follows the intent write. Charging on the outcome class would therefore require charging after the durable write the limiter exists to prevent, or predicting the outcome, and no third shape is specified today. Do not write that the RFC's shape "is not implementable" — that overstates it, and a later reader who finds a workable provisional-class scheme would be entitled to treat the amendment as wrong.
State the delivered guarantee positively, not merely as a lost one. The amended text must say what the limiter does isolate: the cheapest flood path — malformed input and unknown components, refused before any OpenBao work — cannot starve accepted mints. That is the path §5.6's own threat rationale already names when it calls ServiceInstanceMismatch the cheapest refusal, and both of that error's causes are pre-derivation, so the amendment is consistent with the section's existing threat argument rather than in tension with it.
State the residual with its bound. The expensive refusals are no longer isolated: a caller able to produce well-formed, derivable requests consumes admission budget with invocations that end in refusal. Say that this residual is bounded by attacker cost rather than by the limiter, since every such attempt spends OpenBao work. An amendment that drops the "without starving accepted mints" phrasing without stating what replaced it is not sufficient; both the delivered guarantee and the bounded residual have to be legible to a later reader who was not in this conversation.
Treat the guarantee sentence as an obligation, not a residual. §5.6 states the per-(verb, outcome-class) shape as a guarantee, so until this edit lands the merged document asserts something the implementation will not deliver. Discharging that is the point of this issue, and it is why the dependent issues stay unfiled until it is done.
Why the alternative is not re-opened here. Holding the RFC's wording would mean designing a provisional-outcome-class-then-reconcile scheme, or a second post-outcome bucket that shapes only future admission, before the limiter issue could be implemented — and the change would reach the record store's serde encoding and golden fixture, the reader's pairing over coalesced records, and the cargo arm's bucket-isolation tests, all of which are written against the two-bucket shape today. That cost is why the ruling went the other way; record it beside the amendment so the trade is visible.
Question 2 — what a limited invocation tells its caller: answered; amend the RFC to the split answer
The RFC's rate-limit decision and its acceptance criterion both say a throttled invocation returns retryable RegistrarBusy { retry_after }, with no distinction between check points. The RFC's owner ratified the plan's split: the throttle comes from the admission check point only, and a limited pre-derivation invocation keeps its real, permanent refusal while its records are suppressed. This section is no longer a choice; it is the wording the amendment has to carry.
Rewrite both places, not one. The RegistrarBusy sentence in the rate-limiting section and the acceptance criterion that restates it must each say which check point produces the throttle and why the other does not. Leaving either unqualified while the code splits them reproduces this contradiction one layer down, in the sentence a later reader will quote at an implementer.
Carry the type-level ground, not the ergonomic one. The correction is not that RegistrarBusy is the unfriendly answer on that arm; it is that RegistrarBusy is the error family's only retryable member, so putting it on a determined refusal is a contradiction in the type rather than a poor choice of words. The whole of this ground is already in the document being amended, and the amendment should cite it rather than reach outside the repository for it. RFC 0001 §5.6 says, of RegistrarBusy { retry_after }, that it is "a retryable error distinct from RegistrarUnavailable, whose four reasons all mean permanent until an operator acts", and continues that without a distinct type the error arrives as generic, "review classifies generic as transient and retries (RFC-D2 §4b), and the retry storm feeds the limiter". §5.6's own pre-derivation-arm decision then describes exactly what that arm refuses — a service_name or host that is not a DNS label, and a component with no multiplicity entry — which are deterministic faults in the request or the configuration, not conditions that clear on their own. Put those together and answering RegistrarBusy on the pre-derivation arm hands a caller the one value in the set whose contract says this clears on its own, for a request that can never succeed, and it does so on the path §5.6 already names as the cheapest to flood. The per-identifier retryable/permanent split itself is transcribed into this repository's plan by the sibling issue "Settle and transcribe the registrar endpoint's caller-facing wire contract", from aicers/review-protocoldocs/rfcs/0001-node-package-and-enroll.md ("RFC-C") §5 and §8; read it there if the split needs checking identifier by identifier.
Optional, and explicitly not required to complete this issue: the operator-facing consequence. The set's operator/UI companion, cited in RFC 0001 only as RFC-E §9, is understood to require the UI to render RegistrarBusy as an in-progress wait honouring retry_after, never as a failure, and to offer no retry button — under which a malformed service_name would sit on screen as "waiting" indefinitely with its real cause unreachable. That is vivid, but RFC 0001's header block attributes no repository to the letter RFC-E, so this issue cannot make it verifiable from here. It is nonetheless attested: the RFC owner stated it as part of the ruling this issue transcribes, so it may be written on that authority — cite it as the ruling's reasoning rather than as a direct reading of a document nobody in this repository can open, or cite RFC-E §9 directly if you can reach it. It stays optional because the ruling does not rest on it — the type-level ground above stands on RFC 0001's own §5.6 text — and an amendment without it is complete. What is not acceptable is paraphrasing it into the RFC as an unattributed claim, or attributing a direct quotation to a section you did not read.
Note how it composes with Question 1. The predecision_refusal bucket exists precisely because that arm is the cheapest, most floodable path; answering RegistrarBusy on it would have the limiter feed the traffic it was added to damp. The two questions remain independent — either bucket shape composes with either caller answer — but the two ratified answers reinforce each other, and the amendment should say so rather than leaving a reader to notice it.
Keep what the suppression must not drop. §5.6 already says a throttled invocation on this arm "costs one coalesced counter increment rather than a durable record". The amendment ratifies suppressing the two audit records; it must not be read as suppressing the counter. Say plainly that the coalesced increment still happens and the anomaly definition still counts it — otherwise a flood becomes least visible exactly while it is worst, and these records are the only detection RFC 0001's own "detected, not prevented" argument rests on — that phrasing is §5.6's, standing on the per-(component, host) ceiling bootler RFC-A §6 deferred.
Fix the vocabulary as part of the amendment, because the ambiguity is what let the two departures look like one, and under the ratified split the two words no longer coincide. Reserve throttled and RegistrarBusy for the outcome a caller actually receives — which, after Question 2, means the admission check point alone. Use limited for any invocation whose records the limiter suppressed at either check point. Every throttled invocation is limited; a limited pre-derivation invocation is not throttled and keeps its permanent refusal. Apply that distinction across all four places above, and say it once explicitly rather than leaving a reader to infer it from usage — the RFC's pre-derivation-arm decision currently calls a records-suppressed invocation "throttled", and that sentence is the one the new vocabulary has to correct.
What this issue hands on
This issue does not edit any other issue. It amends the RFC and, in the same change, records an itemised follow-up requirement: for each dependent issue named below, which part of its specification the chosen answer invalidates and what it must say instead. Re-specifying those bodies is the work of whoever owns them, done before they are filed for implementation; this issue's obligation is to make that list explicit and complete rather than to leave "update the children if they need it" as an inference. The dependent issues are named in Dependencies.
Acceptance criteria
Both questions are discharged, not just one. Both answers are already ruled — the bucket key and the admission-only throttle — and each needs transcribing in the wording Questions 1 and 2 fix. An amendment that lands one and leaves the other's language unqualified has discharged half of the disagreement, and the half most easily missed is the caller answer, because it appears in an acceptance criterion as well as in the section text.
The RFC states one bucket shape — the ratified per-(client identity, verb, bucket) key — and carries all four things Question 1 requires: the maximal-refinement-at-charge-time reasoning rather than a "not implementable" claim, the delivered guarantee stated positively, the residual with its attacker-cost bound, and no surviving sentence asserting the per-(verb, outcome-class) guarantee anywhere in the document.
The RFC states one caller answer — the ratified split — and carries the three things Question 2 requires: both the RegistrarBusy sentence and the acceptance criterion that restates it name which check point produces the throttle and why the other does not; the ground is the type-level one — RegistrarBusy is the set's only retryable member, against pre-derivation refusals that are deterministic faults — rather than an ergonomic one, and it is grounded in §5.6's own existing sentences rather than in a document the deliverer cannot open; and the text states that suppressing the two audit records does not suppress the coalesced counter increment or its place in the anomaly definition. The operator-facing rendering consequence is optional and its absence does not fail this criterion.
All four RFC locations agree after the edit — the rate-limit sizing decision, the pre-derivation-arm decision, the verb-rate-limit acceptance criterion, and the issue-decomposition entry that restates the bucket shape and RegistrarBusy in one line. No disputed sentence is left standing in a location the amendment did not visit.
The RFC uses limited and throttled consistently with the ratified split — limited for suppressed records at either check point, throttled and RegistrarBusy for the admission-side outcome a caller receives — and states the distinction explicitly at least once, so the two departures cannot be mistaken for one again.
Which outcome was ruled for each question, and the reasoning, is recorded in the RFC itself rather than only in an issue thread.
The amendment carries an explicit, itemised follow-up requirement naming each dependent issue listed in Dependencies and stating what must change in it, or stating that it is unaffected by the ratified answers. It is a list, not a caveat: a reader must be able to work through it without re-deriving which issues the answers touch.
Each answer is written so that it is unambiguous which option won, without anyone re-deriving it from the amended prose. The two rulings were recorded in the plan under the identifiers limiter-bucket-shape-departs-from-rfc (question 1) and limiter-predecision-caller-answer (question 2); naming them in the amendment lets a later reader tie the RFC text to the ruling it transcribes. Their bookkeeping in the plan is the plan maintainer's and is not part of this issue; see Out of scope.
No dependent issue is filed for implementation until its own body matches the amended RFC. This issue records that requirement; it does not perform those edits.
No code, no configuration key and no test is added or changed by this issue.
Constraints
This issue is decided by the RFC's owner — the maintainer who accepted RFC 0001 — and must not be handed to an autonomous agent to settle. Amending an accepted RFC is a human decision by project convention. Both outcomes have already been ruled, and Questions 1 and 2 state them; an agent may draft the amendment's wording from that brief, but it may not re-open either ruling, soften it, or substitute a reading inferred from what the sibling issues say.
Do not resolve it by editing the implementation issues to match one outcome while leaving the RFC as it stands. That reproduces the contradiction one layer down, where the next reader of the RFC will hit it.
Do not widen the scope into the rate limiter's sizing, its configuration keys, its defaults or the audit store's capacity work. The questions here are the bucket key and the caller answer for a limited pre-derivation invocation; nothing else in that section is in dispute.
Do not present the two answers as one ruling in the amended text. They were ruled independently, and they are independent on the merits — the two-bucket shape composes with either caller answer, and either caller answer holds under either bucket shape. Collapsing them is how the second question went unasked in the first place, and an amendment that derives the caller answer from the bucket key would re-create that failure in the document.
Do not fold in the per-(component, host)ceiling the RFC defers. A ceiling bounds how many instances a deployment may legitimately run; a rate limit bounds how fast one client may drive the verbs. They are different objects and the deferral of the first is not in question.
Out of scope
Editing the bodies of the dependent issues named in Dependencies. This issue names what must change in them; their own authors make the change.
Resolving the RFC-E label to a repository, and adding its §9 operator-facing rendering consequence to the amendment. That sentence is optional colour on a ruling that stands without it, and this issue cannot make it verifiable; whoever can resolve the set index may add it later. Blocking this amendment on that lookup would hold a discharged contradiction open for a decoration.
Editing the plan's own metadata. Both questions were carried as cross-cutting decisions on the effort's root umbrella rather than on this issue, because each changes the specification of several sibling issues and the plan keeps such a decision on the nearest common ancestor. This issue is where the rulings are written down permanently, in the RFC; their state in the plan is the plan maintainer's bookkeeping. A deliverer who touches no plan metadata at all has still delivered this issue in full.
Implementing the limiter, the counted record, the throttle identifier or the health member — all of that belongs to the two limiter issues, once this lands.
Any change to the reserved audit store, its capacity probe or its low-water alarm.
The deferred per-(component, host) ceiling.
Re-litigating whether the verbs need a rate limit at all. They do, for the reason the RFC gives: with a record required for every invocation including refusals, an unlimited verb makes the record store the resource an attacker exhausts, on a host whose OpenBao fails requests it cannot audit.
Test plan
No code, so no automated test. Verification is a read-through of the amended RFC: its rate-limiting section, its pre-derivation-arm decision, its verb-rate-limit acceptance criterion and its issue-decomposition entry describe one bucket shape and one caller answer, with no residual disagreement and no unqualified RegistrarBusy sentence left standing beside a split implementation.
A reviewer who reads only the RFC can state what the limiter isolates and what it does not, and what a caller is told at each check point, without consulting an issue thread.
The follow-up list is checked against the dependent issues named in Dependencies: every issue on that list is either accounted for with a concrete required change or explicitly marked unaffected, and no dependent issue is missing from it.
Dependencies
No prerequisite — this can be settled on day one, and should be, because it blocks the issues below.
Directly blocked, and re-specified against the ratified answers:
Bound both registrar verbs with two token buckets — the limiter itself: its bucket key, its two check points, its retry arithmetic and its pre-derivation throttle rule.
Record limited registrar invocations and report the throttle — the coalescing key, the domain of the limited_bucket field, and the criteria mapping a limited invocation to what the caller is told.
Blocked transitively, because limited_bucket is a variant inside their serde types or their assertions:
Add the append-only record store and its on-disk format for registrar audit records — the encoding and the golden fixture.
Scan the audit store for anomalies and report them on bootroot status — the pairing rule over coalesced records.
Add the cargo acceptance arm for the registrar surface — the bucket-isolation and counted-record tests, and the case asserting what a limited pre-derivation invocation reports.
Part of the registrar verb rate-limiting umbrella.
Pointers
docs/rfcs/0001-registrar-role-and-non-self-propagation.md §5.6, the rate-limit sizing decision ("There is exactly ONE registrar identity…") — carries both disputed sentences: the per-(verb, outcome-class) bucket shape and the unqualified "a throttled invocation returns RegistrarBusy { retry_after }".
The same file, §5.6, the pre-derivation-arm decision ("There is a PRE-DERIVATION arm, and the rate limiter runs BEFORE any write on it") — already uses "throttled" for an invocation whose record was suppressed, which is the vocabulary collision Question 2 has to fix.
The same file, §6, the verb rate limit + reserved audit capacity criterion — restates the retryable RegistrarBusy for the pre-derivation arm specifically, and so moves with Question 2.
The same file, §7, item 1a of the issue decomposition — restates "per-(verb, outcome-class) buckets … and a retryable RegistrarBusy { retry_after }" in a single line, and is the location most easily missed.
The two rulings this issue transcribes were recorded in the plan as cross-cutting decisions on the effort's root umbrella, under the identifiers limiter-bucket-shape-departs-from-rfc (the bucket key) and limiter-predecision-caller-answer (what a limited pre-derivation invocation tells its caller). Both are now decided, and Questions 1 and 2 above carry their outcomes and reasoning in full — this issue does not depend on those entries still being readable, and does not edit them.
Resolving the companion-document labels
RFC 0001 is written as part of an ecosystem install/update RFC set and cites its companions by letter. Question 2's ground touches four of them, so they are resolved here; only the first is in this repository.
RFC-F is docs/rfcs/0001-registrar-role-and-non-self-propagation.md itself — its own title line says so, and its header block (the "Consumed by" paragraph near the top) is where the set's members are enumerated. Start there when a letter in the RFC text needs resolving.
RFC-C — aicers/review-protocol, docs/rfcs/0001-node-package-and-enroll.md ("RFC-C: review-protocol — node.package delivery and node.enroll registration", accepted). §5 defines the node.enroll error family, including RegistrarBusy { retry_after }, and §8 carries the identifier count and the retryable/permanent split. This is the source of the "only retryable member" claim. In this repository, the sibling issue "Settle and transcribe the registrar endpoint's caller-facing wire contract" is the one that transcribes that family and exposes retryability as a classification method on the error type; read it for the transcribed form rather than re-deriving it here.
RFC-A — the bootler RFC named in RFC 0001's header block ("bootler RFC-A §6 (which provisions the registrar and states …)"). Question 2 touches it only at one remove: the "detected, not prevented" framing is RFC 0001 §5.6's own, and what it rests on from RFC-A is §6's deferral of a per-(component, host) ceiling, which RFC 0001 cites in §5.6 as the reason repeated mints are detected rather than prevented. Nothing in the amendment needs to quote RFC-A directly; cite RFC 0001 §5.6 for the argument and RFC-A §6 only for the deferral.
RFC-D2 — the review RFC named in RFC 0001's header block ("review RFC-D2 §4d (registrar orchestration)"). §4b is the retry-classification section; RFC 0001 already cites it in §5.6, in the sentence explaining that review classifies a generic error as transient and retries, which is the corroboration available without leaving this repository.
RFC-E — the operator/UI-facing companion, whose §9 is understood to carry the per-identifier rendering and remedy rules. This label is deliberately not load-bearing here. RFC 0001's header block enumerates the set's other members by repository but attributes none to this letter, and nothing else in this repository or in the plan resolves it, so this issue does not make any required assertion depend on it: Question 2's ground is grounded instead in RFC 0001 §5.6's own text, and the one RFC-E-derived sentence is marked optional there. RFC 0001 does cite RFC-E §9 twice — in §5.1, where the CredentialInvalid remedy names bootrootd, and in §5.6, where an RFC-E §9 record is noted as written by REView and therefore omittable by a compromised REView — which is enough to confirm the label refers to a real accepted companion, and not enough to verify its §9 rendering rules. Resolving that document, and deciding whether its rendering consequence belongs in the RFC, is a follow-up for someone with access to the set index; see Out of scope.
Settle the limiter's bucket shape against the RFC
Context
The accepted RFC requires both registrar verbs to be rate-limited, and specifies the limiter's shape as per-
(verb, outcome-class)buckets "so refusals throttle without starving accepted mints". Decomposing that requirement surfaced an ordering conflict rather than a preference: the limiter exists to prevent the invocation's durable intent record from being written, so it has to be charged before that write — and at that moment the invocation's outcome class does not exist yet. Keying a bucket on the outcome class would therefore require either charging after the very write the limiter exists to prevent, or predicting the outcome.The sibling limiter issue is specified against a different shape as a result: per-
(client identity, verb, bucket)buckets over a two-value enum,predecision_refusalandadmission. That shape delivers the part of the RFC's guarantee that matters most — the free flood path, malformed input costing no OpenBao work, cannot starve legitimate mints — and openly does not deliver the rest: a caller able to produce well-formed but refusable requests still competes with real mints for admission budget.The departure has a second half, and it is not a consequence of the first. The RFC's caller-facing language is written as though every limited invocation is throttled: its rate-limit decision says "a throttled invocation returns
RegistrarBusy { retry_after }— a retryable error", and its acceptance criterion restates that for the pre-derivation arm specifically. The plan's limiter returns that only from the admission check point. On the pre-derivation check point an invocation whose bucket is empty has its two audit records suppressed but is still given its real, permanent refusal, on the reasoning that the request is invalid on its face, no later attempt can succeed, and telling such a caller to retry manufactures the retry storm the limiter exists to stop. That is a defensible reading — arguably the only sound one — but it is a separate departure from the bucket-key question: one could ratify the two-bucket shape and still hold that every limited invocation must answerRegistrarBusy, or reject the two-bucket shape and still agree that a determined refusal is never retryable. Two independent questions, and leaving the second unasked would let an implementation ticket settle by writing code what the RFC states in acceptance language.So an accepted RFC and the issue tree built from it disagree on two points the RFC states as guarantees. This issue is where a human resolves both, and both are now resolved. The RFC's owner ratified the bucket key as the two-value shape and the caller answer as the admission-only throttle, with the reasoning recorded under Questions 1 and 2 below. Neither is this issue's to choose any longer — both are this issue's to transcribe into the RFC, in the wording those two sections fix. What remains is therefore an editing job with a precise brief, not a decision.
Its deliverable is an edit to one document — the RFC — plus the reasoning recorded beside it. It ships no code, and it is not implementation work: ruling on an accepted RFC is a decision for that RFC's owner, not something an implementation ticket or an autonomous agent may settle by writing the code one way and calling the RFC stale. What is fixed and checkable is which sentences must change together, what each ratified answer obliges the author to write down, and what must be handed on to the issues that depend on it. Those are the acceptance criteria below.
Scope
Two questions are settled here, and they were settled independently. Both now have their answers and both need transcribing. Record both in the RFC, which is the artifact that currently carries the contradictions.
The RFC carries the disputed language in four places and they must move together, or the amendment is partial: §5.6's rate-limit sizing decision (the bucket sentence and the
RegistrarBusysentence), §5.6's pre-derivation-arm decision (which already calls a records-suppressed invocation "throttled"), §6's acceptance criterion for the verb rate limit (which restates both the retryableRegistrarBusyand the pre-derivation arm), and §7's issue-decomposition entry for the verb-level audit record (which restates "per-(verb, outcome-class)buckets … and a retryableRegistrarBusy { retry_after }" in one line).Question 1 — the bucket key: answered; amend the RFC to the two-bucket shape
The RFC's owner ratified per-
(client identity, verb, bucket)buckets over the two-value enumpredecision_refusal/admission. This section is no longer a choice; it is the wording the amendment has to carry, and all four RFC locations move together as stated above.(verb, outcome-class)buckets.ServiceInstanceMismatchthe cheapest refusal, and both of that error's causes are pre-derivation, so the amendment is consistent with the section's existing threat argument rather than in tension with it.(verb, outcome-class)shape as a guarantee, so until this edit lands the merged document asserts something the implementation will not deliver. Discharging that is the point of this issue, and it is why the dependent issues stay unfiled until it is done.serdeencoding and golden fixture, the reader's pairing over coalesced records, and the cargo arm's bucket-isolation tests, all of which are written against the two-bucket shape today. That cost is why the ruling went the other way; record it beside the amendment so the trade is visible.Question 2 — what a limited invocation tells its caller: answered; amend the RFC to the split answer
The RFC's rate-limit decision and its acceptance criterion both say a throttled invocation returns retryable
RegistrarBusy { retry_after }, with no distinction between check points. The RFC's owner ratified the plan's split: the throttle comes from the admission check point only, and a limited pre-derivation invocation keeps its real, permanent refusal while its records are suppressed. This section is no longer a choice; it is the wording the amendment has to carry.RegistrarBusysentence in the rate-limiting section and the acceptance criterion that restates it must each say which check point produces the throttle and why the other does not. Leaving either unqualified while the code splits them reproduces this contradiction one layer down, in the sentence a later reader will quote at an implementer.RegistrarBusyis the unfriendly answer on that arm; it is thatRegistrarBusyis the error family's only retryable member, so putting it on a determined refusal is a contradiction in the type rather than a poor choice of words. The whole of this ground is already in the document being amended, and the amendment should cite it rather than reach outside the repository for it. RFC 0001 §5.6 says, ofRegistrarBusy { retry_after }, that it is "a retryable error distinct fromRegistrarUnavailable, whose four reasons all mean permanent until an operator acts", and continues that without a distinct type the error arrives as generic, "review classifies generic as transient and retries (RFC-D2 §4b), and the retry storm feeds the limiter". §5.6's own pre-derivation-arm decision then describes exactly what that arm refuses — aservice_nameorhostthat is not a DNS label, and a component with no multiplicity entry — which are deterministic faults in the request or the configuration, not conditions that clear on their own. Put those together and answeringRegistrarBusyon the pre-derivation arm hands a caller the one value in the set whose contract says this clears on its own, for a request that can never succeed, and it does so on the path §5.6 already names as the cheapest to flood. The per-identifier retryable/permanent split itself is transcribed into this repository's plan by the sibling issue "Settle and transcribe the registrar endpoint's caller-facing wire contract", fromaicers/review-protocoldocs/rfcs/0001-node-package-and-enroll.md("RFC-C") §5 and §8; read it there if the split needs checking identifier by identifier.RegistrarBusyas an in-progress wait honouringretry_after, never as a failure, and to offer no retry button — under which a malformedservice_namewould sit on screen as "waiting" indefinitely with its real cause unreachable. That is vivid, but RFC 0001's header block attributes no repository to the letter RFC-E, so this issue cannot make it verifiable from here. It is nonetheless attested: the RFC owner stated it as part of the ruling this issue transcribes, so it may be written on that authority — cite it as the ruling's reasoning rather than as a direct reading of a document nobody in this repository can open, or citeRFC-E §9directly if you can reach it. It stays optional because the ruling does not rest on it — the type-level ground above stands on RFC 0001's own §5.6 text — and an amendment without it is complete. What is not acceptable is paraphrasing it into the RFC as an unattributed claim, or attributing a direct quotation to a section you did not read.predecision_refusalbucket exists precisely because that arm is the cheapest, most floodable path; answeringRegistrarBusyon it would have the limiter feed the traffic it was added to damp. The two questions remain independent — either bucket shape composes with either caller answer — but the two ratified answers reinforce each other, and the amendment should say so rather than leaving a reader to notice it.(component, host)ceiling bootler RFC-A §6 deferred.Fix the vocabulary as part of the amendment, because the ambiguity is what let the two departures look like one, and under the ratified split the two words no longer coincide. Reserve throttled and
RegistrarBusyfor the outcome a caller actually receives — which, after Question 2, means the admission check point alone. Use limited for any invocation whose records the limiter suppressed at either check point. Every throttled invocation is limited; a limited pre-derivation invocation is not throttled and keeps its permanent refusal. Apply that distinction across all four places above, and say it once explicitly rather than leaving a reader to infer it from usage — the RFC's pre-derivation-arm decision currently calls a records-suppressed invocation "throttled", and that sentence is the one the new vocabulary has to correct.What this issue hands on
This issue does not edit any other issue. It amends the RFC and, in the same change, records an itemised follow-up requirement: for each dependent issue named below, which part of its specification the chosen answer invalidates and what it must say instead. Re-specifying those bodies is the work of whoever owns them, done before they are filed for implementation; this issue's obligation is to make that list explicit and complete rather than to leave "update the children if they need it" as an inference. The dependent issues are named in Dependencies.
Acceptance criteria
(client identity, verb, bucket)key — and carries all four things Question 1 requires: the maximal-refinement-at-charge-time reasoning rather than a "not implementable" claim, the delivered guarantee stated positively, the residual with its attacker-cost bound, and no surviving sentence asserting the per-(verb, outcome-class)guarantee anywhere in the document.RegistrarBusysentence and the acceptance criterion that restates it name which check point produces the throttle and why the other does not; the ground is the type-level one —RegistrarBusyis the set's only retryable member, against pre-derivation refusals that are deterministic faults — rather than an ergonomic one, and it is grounded in §5.6's own existing sentences rather than in a document the deliverer cannot open; and the text states that suppressing the two audit records does not suppress the coalesced counter increment or its place in the anomaly definition. The operator-facing rendering consequence is optional and its absence does not fail this criterion.RegistrarBusyin one line. No disputed sentence is left standing in a location the amendment did not visit.RegistrarBusyfor the admission-side outcome a caller receives — and states the distinction explicitly at least once, so the two departures cannot be mistaken for one again.limiter-bucket-shape-departs-from-rfc(question 1) andlimiter-predecision-caller-answer(question 2); naming them in the amendment lets a later reader tie the RFC text to the ruling it transcribes. Their bookkeeping in the plan is the plan maintainer's and is not part of this issue; see Out of scope.Constraints
(component, host)ceiling the RFC defers. A ceiling bounds how many instances a deployment may legitimately run; a rate limit bounds how fast one client may drive the verbs. They are different objects and the deferral of the first is not in question.Out of scope
(component, host)ceiling.Test plan
RegistrarBusysentence left standing beside a split implementation.Dependencies
No prerequisite — this can be settled on day one, and should be, because it blocks the issues below.
Directly blocked, and re-specified against the ratified answers:
limited_bucketfield, and the criteria mapping a limited invocation to what the caller is told.Blocked transitively, because
limited_bucketis a variant inside theirserdetypes or their assertions:bootroot status— the pairing rule over coalesced records.Part of the registrar verb rate-limiting umbrella.
Pointers
docs/rfcs/0001-registrar-role-and-non-self-propagation.md§5.6, the rate-limit sizing decision ("There is exactly ONE registrar identity…") — carries both disputed sentences: the per-(verb, outcome-class)bucket shape and the unqualified "a throttled invocation returnsRegistrarBusy { retry_after }".RegistrarBusyfor the pre-derivation arm specifically, and so moves with Question 2.(verb, outcome-class)buckets … and a retryableRegistrarBusy { retry_after }" in a single line, and is the location most easily missed.limiter-bucket-shape-departs-from-rfc(the bucket key) andlimiter-predecision-caller-answer(what a limited pre-derivation invocation tells its caller). Both are now decided, and Questions 1 and 2 above carry their outcomes and reasoning in full — this issue does not depend on those entries still being readable, and does not edit them.Resolving the companion-document labels
RFC 0001 is written as part of an ecosystem install/update RFC set and cites its companions by letter. Question 2's ground touches four of them, so they are resolved here; only the first is in this repository.
docs/rfcs/0001-registrar-role-and-non-self-propagation.mditself — its own title line says so, and its header block (the "Consumed by" paragraph near the top) is where the set's members are enumerated. Start there when a letter in the RFC text needs resolving.aicers/review-protocol,docs/rfcs/0001-node-package-and-enroll.md("RFC-C: review-protocol —node.packagedelivery andnode.enrollregistration", accepted). §5 defines thenode.enrollerror family, includingRegistrarBusy { retry_after }, and §8 carries the identifier count and the retryable/permanent split. This is the source of the "only retryable member" claim. In this repository, the sibling issue "Settle and transcribe the registrar endpoint's caller-facing wire contract" is the one that transcribes that family and exposes retryability as a classification method on the error type; read it for the transcribed form rather than re-deriving it here.(component, host)ceiling, which RFC 0001 cites in §5.6 as the reason repeated mints are detected rather than prevented. Nothing in the amendment needs to quote RFC-A directly; cite RFC 0001 §5.6 for the argument and RFC-A §6 only for the deferral.RFC-E §9twice — in §5.1, where theCredentialInvalidremedy names bootrootd, and in §5.6, where an RFC-E §9 record is noted as written by REView and therefore omittable by a compromised REView — which is enough to confirm the label refers to a real accepted companion, and not enough to verify its §9 rendering rules. Resolving that document, and deciding whether its rendering consequence belongs in the RFC, is a follow-up for someone with access to the set index; see Out of scope.