Give AI autonomy without giving it authority
Human Agency Protocol — v0.8 Changelog
This document is the backward-looking record of v0.8: what was promoted into the binding surface, under which rule, on the strength of which recorded review and which implementation evidence, what was retired, and what was found while writing it. The forward ledger — open directions and deviation rules — is review.md. Implementation status is in neither (governance.md → Reference Conformance → Implementation reports).
What v0.8 is
v0.8 is the first version written from use. v0.7 was published on 2026-09-03 and the reference implementation switched to its wire on 2026-10-09. Between the two dates and for five weeks after, fifteen entries were added to the v0.7 ledger — each found in a demo, a gated release, a team setup, or an audit of a live Authority Server’s stored data, each with a stated direction, and twelve of them built end to end before this text was drafted. v0.8 promotes what was built and reviewed, pins the four details the ledger had left open in the shape the implementation chose, and adds two things no one has built, because the public ticket page has been a standing violation since v0.7 and only they close it.
It is a re-grant release (decided 2026-10-30). No field is renamed or retired and every new signed field is optional or conditional, but a v0.8 Authority Server no longer honours v0.7 mandates: every Gatekeeper upgrades and every owner re-grants, because v0.8 changes what a live mandate means — who counts as an approver, what the owner and the approver sign. v0.7 artifacts stay verifiable as evidence (protocol.md → Version negotiation, rule 3).
One sentence summarises the release: the ticket now names the request it answers, admits one execution, and never required the Authority Server to read what the action contained.
The promotion rule (unchanged from v0.6)
A direction is promoted when (1) a reference implementation exercises it end-to-end and (2) the design has survived recorded adversarial review; or (3) specification-led, where the design is additive, optional, and its absence changes nothing for existing artifacts — marked as such and tracked in review.md until closed. External-integrator dependence remains a maturity marker (absent, ecosystem-wide). The rule text and its rationale are in the v0.6 changelog.
Promoted into v0.8
None has an external integrator yet (maturity marker: absent, ecosystem-wide). Dates are the day the ledger entry was written and the day the reference implementation exercised the direction end to end; the code is not named here (Implementation status left the specification, v0.7 changelog).
| Direction (v0.7 ledger title) | Landed as | Basis |
|---|---|---|
| Scope values must not travel in the execution context | protocol.md → Ticket Request Schema, rules 7–8; Validation Steps, step 11; Privacy Invariant | rule 1 + 2 — ledger 2026-09-04; implemented before the v0.7 wire switch (allow-list of boundType.of fields on every AS call); reviewed in the 2026-10 ledger round. Ledger gap pinned: the ledger’s “plus any field a declared content binding requires” contradicted Content Binding (“the AS receives only the hash, never the content”) and is corrected: a binding contributes the hash and the declaration, never a value |
| One execution per ticket | protocol.md → One execution per ticket (Gatekeeper conformance); Enforcement classes | rule 1 + 2 — ledger 2026-09-04 after a double execution in a gated release; durable execution journal, replay-as-recovery, per-proposal serialization built and tested end to end in September 2026. Ledger gap pinned: the escape hatch “unless the profile or manifest declares the action idempotent under its ticket id” is dropped — no such declaration exists and an undeclared declaration is the hazard the manifest rules forbid |
| A ticket names the request it answers | protocol.md → Ticket Payload Schema, idempotencyKey + rule 4; Pre-flight Ticket Request; code TICKET_REQUEST_MISMATCH | rule 1 + 2 — ledger 2026-09-04; the AS copies the key and the Gatekeeper refuses a mismatch and a stale timestamp since the v0.7 wire switch (2026-10-09); tested end to end |
| Per-transaction bounds must be able to require their value | protocol.md → Bounds Schema, rule 8 | rule 1 + 2 — ledger 2026-09-30; both enforcement points refuse an absent value since early October 2026; adopted by five published profiles. Ledger gap pinned: the ledger did not say where the key goes; every profile placed it inside boundType, beside of — the specification pins that placement and refuses it elsewhere |
| A profile can restrict the commitment modes of its mandates | protocol.md → Commitment Modes, rule 8; code COMMITMENT_MODE_NOT_ALLOWED | rule 1 + 2 — ledger 2026-10-05; the AS refuses an unlisted mode and two published profiles declare ["review"]. Two ledger gaps pinned: no error code was named — one is; and review_above_cap “counts as its own mode” left a contradiction with the group mechanism that issues an automatic request as review_above_cap — resolved by the rule that automatic admits it and review alone does not |
| The owner’s disclosure choice can be standing | protocol.md → Identity Assurance → Standing disclosure choice; governance.md → Invariant 2 wording | rule 1 + 2 — ledger 2026-10-06 after 27 of 29 tickets in a run lost the owner’s name; AS-side setting live since 2026-10-06 |
| A profile can declare a maximum for a bound | protocol.md → Bounds Schema, rule 9 | rule 1 + 2 — ledger 2026-10-06; AS refusal and the first declaring profile (reporting@0.2, read_max_age_days.maximum: 366) shipped the same week |
| Group roles: who may grant, who approves above the cap | protocol.md → Group roles, approval count, and hard caps; AS Group Configuration; above_cap_approvals in mandate and projection; codes GROUP_CAP_EXCEEDED, APPROVAL_IMPOSSIBLE, PROPOSAL_APPROVERS_INVALID | rule 1 + 2 — ledger 2026-10-14 from a team of ten; grantor gate, approval count, proposer exclusion, hard caps, and the automatic → review_above_cap promotion built mid-October 2026. The specification keeps the ledger’s rule 6 (the count is signed) and adds the rule the review found missing: the decider set comes from the signed mandate, never from the caller |
| A group can cap reads and capability switches | protocol.md → Read Authorization → Group ceilings; Bounds Schema, rules 10–11 (enum order, enforcedBy) | rule 1 + 2 — ledger 2026-10-14; hard caps on Gatekeeper-enforced and enum bounds, and the Gatekeeper reading up to the signed value in a team, built mid-October 2026. Ledger gap pinned: the ledger’s “sign the group cap into the mandate as a ceiling” would have been a second signed field; the implementation needed none, because the AS refuses to sign above the cap and the signed value is the ceiling. The simpler shape is specified; it also gives enforcedBy: "gatekeeper" — specified since v0.6, though no published profile yet carries it (a new reporting version is the first that should) — its first definition. The enum order is stated as every published profile already has it (most permissive first); the ledger’s gates declaration is not promoted (below) |
| The Authority Server never sees what an action contains | protocol.md → Proposals — what the Authority Server receives; Privacy Invariant; proposalContentHash in the ticket; codes PROPOSAL_NOT_ENCRYPTED, PROPOSAL_CONTENT_MISMATCH; governance.md → Invariant 4, companion note | rule 1 + 2 — ledger 2026-10-12 from a demo run; content encrypted to its readers, fingerprint compared at ticket time, decisions carrying the fingerprint, “encrypted for another device” — built in the second week of October 2026. The fingerprint’s copy into the signed ticket (ledger rule 3 said “bind by fingerprint”) is specified as the rule requires, although the implementation compared without signing; see Found while writing |
| System data on an approval surface | protocol.md → Content Binding → What may be shown next to what is bound | rule 1 + 2 — ledger 2026-10-13; three-origin display (sealed / from the system / the AI’s claim), read time, re-read before execution, built the same week |
| What was shown can be bound to the decision; the request an action answers | protocol.md → Content Binding → The request an action answers, and binding what was shown; Tool-Gating Manifests, rule 5 extended | rule 1 + 2 — ledger 2026-10-14; the agent’s reference labelled as the agent’s, gateway-read history on declared keys, shown_hash per decider, built 2026-10-14 → 10-20. The rule that a reserved key enters neither the content binding nor the Executor’s call is the review’s addition |
| ”Gateway” appears in normative text without a definition | editorial — ten occurrences replaced by the defined term each stood for | editorial, not a promotion — no requirement changes; ledger 2026-09-10; landed with the vocabulary item below, as the ledger asked |
| Owner Interface, approval surface, and the local custody domain | protocol.md → Roles (three rows replace Local App); Approval surfaces | rule 3 — specification-led: a remote approval surface is optional, and an implementation without one is unaffected; tracked in review.md until one exists. Undated in the ledger; written now because the proposal-content work gave it its first case (a remote surface that must verify before it shows) and because the “gateway” fix needed to know what Local App becomes. Device enrolment stays open |
| Dual-signed public ticket projection | protocol.md → Public ticket projection — HAP-ticket-projection; vector ticket-projection | rule 3, specification-led. Targeted v0.7, slipped to v0.8 for a dependency v0.7 then supplied (disclose_fields, issuer). Promoted under rule 3, without an implementation, on the record: it is additive and optional, a vector pins its bytes, and the public ticket page’s “valid” indicator has been a stated v0.7 violation for seven weeks that nothing else closes. Tracked in review.md until an implementation serves it |
| Verifier policy for external relying parties | protocol.md → Owner Signatures → Verifier policy — HAP-verifier-policy; code POLICY_UNMET | rule 3, specification-led, landing with the projection as the ledger asked. A declarative document with one failure code per line; never sent to the AS |
Found by the implementation and not in the ledger — recorded and, where it changes a rule, promoted:
- Revocation by membership change. Joining or leaving a group MAY revoke the member’s mandates under it (built 2026-10-11, decided the same day). v0.7 allowed revocation by the owner or a group admin only. One sentence in Revocation, plus a rule that every revocation record names its cause.
- Outcome after the ticket. A failed execution keeps its consumption (decided 2026-10-14). One rule: cumulative state counts issued tickets, never outcomes (Execution Context Schema, rule 6). Reporting outcomes to the AS at all stays open (
review.md). - Agent-prepared mandate requests. A non-normative sentence under Human-Gated Actions, so the pattern is not read as agent-assigned authority.
- AS-signed decision records. One row in Enforcement classes: an AS signature over an approval it recorded is the AS vouching for the AS, and MUST NOT be presented as the owner’s approval.
mandateIdon the ticket request. Two mandates of one owner can share abounds_hash(a revoked one and its replacement — the reason v0.7 addedmandateIdto the ticket), so an AS resolving a ticket request by the hash alone has to guess. v0.8 lets the request name the mandate, with the hash as a mandatory cross-check. This retires a listed v0.7 non-conformance by legitimising the shape the implementation needed, which is the right outcome when the specification’s own text concedes the ambiguity.
Pinned where the ledger left a detail open
Each of these is a place where the ledger stated a direction, the implementation had to choose a detail to build it, and v0.8 pins the implemented choice because it was reviewed and because a second implementation would otherwise choose differently:
requiredForlives insideboundTypeon a bound (besideof) and insideconstrainton a scope field — in each case next to the thing it qualifies.- A profile that lists
automaticadmitsreview_above_cap; one that lists onlyreviewdoes not. The condition is the actions below the caps. - The group read ceiling is the signed bound value itself; no second field. The AS refuses to sign above the cap and refuses the field’s omission where a cap exists.
- A content binding contributes the hash and the declaration to the ticket request, never a field’s value (the ledger sentence that said otherwise is corrected).
- The one-execution rule has no idempotent-action escape hatch.
- Error codes for every promoted refusal:
COMMITMENT_MODE_NOT_ALLOWED,GROUP_CAP_EXCEEDED,APPROVAL_IMPOSSIBLE,PROPOSAL_CONTENT_MISMATCH,PROPOSAL_NOT_ENCRYPTED,PROPOSAL_APPROVERS_INVALID,TICKET_REQUEST_MISMATCH,POLICY_UNMET. The reference implementation emitted several of these conditions under non-canonical names; those are implementation work for its report, not specification text (the v0.7 advisor round made the same ruling). - The read cap applies in every mode. The ledger capped reads at the signed value in group mode only, which needs a signal the Gatekeeper cannot read from the mandate. v0.8 caps at the signed value of an
enforcedBy: "gatekeeper"bound in every mode; in personal mode the owner raises it by re-signing (Read Authorization → Group ceilings, rules 2–3). reviewin a group carries the above-cap rule. The ledger promoted onlyautomaticrequests, so a grantor choosingreviewapproved above the group’s cap alone. v0.8 signs the group’s above-cap fields on areviewmandate too (Group roles, rule 7).- Required owners are a third role. Splitting grantors from approvers left the multi-owner coverage rule pointing at a list that no longer existed. v0.8 names required owners — default none beyond the granting owner (Group roles, rule 8).
- The proposer never counts, in a group or not (decided 2026-10-29). v0.7 let an owner outside a group name themselves as their own sole above-cap approver, so that above the caps the agent had to ask them. v0.8 keeps one rule for every mandate: such a mandate is refused at signing (
ABOVE_CAP_CONFIG_INVALID). An owner who wants to approve their agent’s actions themselves usesreview;review_above_capoutside a group needs an approver other than the owner. This is a change in behaviour from v0.7 for that one configuration.
Decided in the first external round (2026-10-29)
- The ticket checks run in stages, in a stated order, and a replay is answered first: the same key and the same request — or the same executed proposal and the same request — get the original ticket back, served only to the request context that obtained it. One state table covers every proposal state in every mode; a proposal committed with the above-cap approvals lifts the cap for that action and nothing else.
- Values a bound reads are finite and non-negative, for every action type. An action that gives value back is its own action type with its own bound.
- The owner signs
binding,signing_surfaceandsubjects_disclosed. The binding label stays the owner’s device’s claim, not custody proof; that is said where the label is defined, and custody evidence is an open direction. - Coverage requires identical approval conditions — the same caps, approvers and count — within one group.
- An unreachable approval count: outside a group the owner chose it, and the request is refused; in a group the group chose it, and the caps are hard for that owner’s mandate. A grantor who is also one of too few approvers still gets a mandate below the caps.
- The review path needs a v0.8 Gatekeeper; a v0.8 AS never accepts clear content to bridge the transition. Gatekeepers upgrade first. (Superseded in scope on 2026-10-30: every mandate is re-granted under v0.8 — Decided before publication, item 3.)
valid_untilgoverns acceptance as authorization, not evidence. A public page or an auditor reads a projection without it; an effector acting on one applies it.- One execution per ticket holds per Gatekeeper, and the text says so; shared records across Gatekeepers and consumption by approval are open directions.
- Proposal encryption is pinned byte for byte to the construction the implementation uses — HPKE base mode, empty
infoand associated data, IV ‖ ciphertext ‖ tag — with a vector. Proposal content is one object, the argument object, and everyHAP-approvalsigns its fingerprint asproposal_content_hashbesidecontent_hash. - The signing input is the JCS bytes, signed directly; RFC 8785 governs the canonicalization, and the vector set now exercises its edge cases.
- The v0.6 vocabulary is a structural test, not a publication date: a profile that carries
contextSchemaordecision_owneris read under the v0.7 names; the published ones are listed (review.md→ D4). - Migrating a v0.7 group sets all three roles to the old list with a count of the list minus the proposer; a required owner must be a grantor. The v0.8 default — no required owner beyond the grantor — is weaker than v0.7’s, and is stated as such.
Decided before publication (2026-10-30)
- What a decision does. A
rejectfrom any signed decider is final; eachcommitcounts once; “send back” is a reject with a note, and a revised action is a new proposal; the proposer may withdraw an undecided proposal; the AS refuses and does not count decisions from non-deciders, departed deciders, repeat deciders, or on decided proposals (DECIDER_INVALID,PROPOSAL_ALREADY_DECIDED,PROPOSAL_WITHDRAWN). - A missing amount fails safe. Under an approval cap an absent value counts as above the cap and needs the approval; under a
cumulative_sumbound that governs the action type it is refused, unless the profile excludes the action type throughappliesTo. Consequence: a profile whose sum bound has noappliesTo—charge@0.5among them — refuses its amount-less tools inautomaticmode until a new profile version excludes them. - Every mandate is re-granted under v0.8. A v0.8 Authority Server issues no tickets against v0.7 mandates (
VERSION_UNSUPPORTED) and keeps verifying v0.7 artifacts as evidence; Gatekeepers upgrade, owners re-grant. This replaces the transition by TTL that v0.7 used, because v0.8 changes what a live mandate means.
Deviation rules
- D1 — in-place profile edits is restated: the v0.7 text grandfathered four mutations; the profiles’ own history shows seven more published versions edited in place, all before 2026-08-03 and none since. The rule is unchanged; the grandfather clause now reads “every in-place edit before 2026-08-03”, with the list left to the git history where it already is.
- D2 — the field-name fallback for cumulative bounds is closed for v0.8 mandates: a v0.8 mandate names a profile version that declares
appliesTo, so no enforcement point may select a cumulative bound by field name for one. With every mandate re-granted under v0.8, no path remains for it. - D3 — scope enum values in
field.enumis new. Every published profile with enum scope fields still places allowed values in the top-levelfield.enumthat v0.5 retired; none usesconstraint.values. The v0.5 MUST stands. D3 grandfathers the published versions and requires each profile’s next version to move the values. Re-admittingfield.enumwas considered and rejected: two read locations for one declaration is the kind of ambiguity theboundTyperule exists to forbid.
Found while writing
Writing v0.8 against the running implementation found eight places where the implementation and the ledger disagreed. Each is listed in the implementation’s own report in the specification’s terms; none changed the specification, and they are recorded here only as the fact that the review happened and what kind of thing it found. Three of them touched rules with a security consequence — what travels to the Authority Server, where the decider set comes from, and what is signed — and the text of those rules is stated more plainly in v0.8 because of it.
The lesson is the one the v0.7 changelog already recorded for appliesTo, now with a sharper edge: a ledger entry written the day a thing is built is an invitation to build the entry, and the implementation will meet it in its own words. Where the implementation’s words were better — requiredFor inside boundType, no second ceiling field — the specification adopted them. Where they were not, it did not.
Not promoted, with reasons
gateson enum bounds (ledger, group caps item, rule 4) — not built; stays inreview.md.- Attachments by reference — two published profile versions bind attachment fingerprints and declare attachment sources, with no specification text and no recorded review. Recorded in
review.mdas an open direction with a ledger entry rather than written into v0.8 on one implementation’s say-so. - The owner-signature curve (P2) — no P2 work in this version; the decision stays with whoever starts it.
- Device enrolment and revocation for remote approval surfaces — the open half of the vocabulary item; stays in
review.md. - Outcome reporting to the Authority Server — only the rule that outcomes never release consumption is normative; the report itself is open.
- A profile’s name is its area (ledger, 2026-10-17) — reached the ledger after this text was drafted, with no recorded review; carried in
review.md.
Retired in v0.8
The role name Local App — replaced by Owner Interface, approval surface, and local custody domain. The word gateway in normative text — ten occurrences replaced. The ledger’s idempotent-action escape hatch on the one-execution rule — dropped, never defined. The v0.7 sentence that a public projection is “a tracked direction, not a v0.7 guarantee” — replaced by the projection.
Considered and rejected
- A second companion specification for proposal content (
proposal-disclosure@0.1) besideintent-disclosure@0.1. One construction with two names is the drift the single-vocabulary release removed; the core section applies the companion’s cryptography and says so. - Re-admitting
field.enum— see D3. - Making
gatesa SHOULD before it is built — a SHOULD nobody has implemented is a sentence, not a rule. - Deciding the P-256 question in this version — nothing in v0.8 depends on it.
- Renaming or retiring fields in v0.8 — nothing promoted needed a rename, so v0.7 artifacts stay verifiable as evidence. Live v0.7 mandates are nevertheless not honoured: v0.8 changes what a live mandate means, so it is a re-grant release (What v0.8 is;
protocol.md→ Version negotiation, rule 3). - Treating the workspace model (an account is personal or in one team) as protocol — the v0.7 worked example of one person in two groups remains conformant; the implementation’s restriction is policy.
Review record
The recorded review behind this version, per rule 2:
- Ledger round (the v0.7
review.md, 2026-09-04 → 2026-10-14, thirteen commits): each promoted direction was carried there with a stated finding, a direction, a conformance test, and a target, and each was built against that entry before promotion. The entries are reproduced in substance inprotocol.mdand summarised in the table above; the two 2026-10-14 entries (group roles; group caps) were reviewed together and merged on 2026-10-15. - Implementation review (2026-10-27 → 10-28): a read of the reference implementation — Gatekeeper, core library, Authority Server, and every published profile — against v0.7 and the ledger, by a reviewer who had not written either; findings as described under Found while writing; the proposed scope, the pinned details, the deviation rules, and ten questions put to the author. The author’s decisions on all ten are reflected above (one core section; sign the fingerprint;
mandateIdon the request; the three terms;gates, attachments, and the curve stay open; D3; promote the projection and the policy specification-led). - Author’s adoption (2026-10-28): accepted as proposed.
- Draft review (2026-10-28): an independent read of the whole v0.8 draft — text, ledger and vectors — for contradictions, gaps and security consequences of the new text. Three blockers and twenty-three further findings. Resolved in the text: required owners named as a third role;
reviewin a group carries the above-cap rule; the decider set is exactly the signed approvers, never narrowed;HAP-approvalnames its approver (approver_did) and a ticket carries above-cap approvals asapprovalSignatures; a ticket verifier rejects any object carryingtyp; one review-path flow; an idempotency key on every request without a proposal; replays exempt from the freshness check;APPROVAL_IMPOSSIBLEdefined against approvers who can no longer decide; D2 closed for v0.8 mandates only; scope values removed from every example and vector. - Independent re-derivation of the vectors (2026-10-29): a second implementation, in another language and sharing no code, computed every value from the specification text and the vector inputs with the answers removed. Every value agreed. It also showed that four vector inputs contradicted the text — a synchronous ticket under a
reviewmandate, incompletelimits, a content binding the profile does not declare, the Authority Server’s own key as an above-cap approver — and that several rules were stated only once, or not at all. Fixed in the text: one order of checks at ticket time; the approval count against approvers other than the owner, everywhere;OWNER_NOT_COVEREDandCOVERAGE_INSUFFICIENTtold apart; the short profile identifier in signed and hashed fields; profiles in pre-v0.7 vocabulary read under their v0.7 names; value types and list-valued scope fields in the bounds and scope canonicalization; RFC 8785 governing the signing canonicalization; a malformedcommitment_modesasPROFILE_INVALID; every refusal given its code in the checklist and in the error table. No canonicalization result in the vector set changed. - External reviews, first round (2026-10-29): two independent reviews of the draft as it then stood — one adversarial, one from an implementer’s view — each reproducing every vector with its own code, plus a third independent re-derivation over the final text. Twelve blockers between them, merged: the review path had no complete state table and no exit for an approved above-cap proposal; a negative value could return consumed authority; the owner did not sign
binding, somin_bindingrested on the operator’s word; coverage ignored the owners’ approval conditions; three rules disagreed on an unreachable approval count; the review path was not, as claimed, compatible with v0.7 Gatekeepers; the public projection’s validity window defeated its purpose as evidence; “one execution per ticket” was claimed beyond one Gatekeeper; the encryption of proposal content was not pinned to the byte; “content” had two definitions; the signing input was misstated;execution_context_hashwas unchecked. Resolved in the text, with the decisions below (Decided in the first external round), and the should-fix items with them. - Re-derivation over the final text (2026-10-29): a fourth independent implementation — its own RFC 8785 serializer and its own HPKE from RFC 9180 — computed every value of the revised set from the text, the proposal-encryption envelope included; every value agreed. It found three places where the refusal code was not unique (a present non-number under
requiredFor, the fields compared on an executed proposal’s replay,mandateIdwith an unknownboundsHash) and a set of smaller gaps; resolved in the text without changing a vector value. - External reviews, second round (2026-10-29): two fresh reviews — adversarial and implementer — each reproducing every byte-level vector with its own code, the encryption envelope included. Blockers: two refusal rows contradicted the text on a non-numeric value; on the review path the AS did not compare the ticket’s content hash with the approved proposal’s;
action_typeon the wire contradicted itself; an approval cap could be bypassed by leaving the value out; the decision step had no defined outcomes. The first three and most should-fix items are resolved in the text without changing a byte-level vector; the last two await the author’s decision, and the remaining items are on the ledger with targets (review.md). - External reviews, second round — outside models (2026-10-29): the same brief, given to reviewers of another model family over the package as it stood before item 8’s fixes; each reproduced every byte-level vector. Both found, independently, that an owner-signed approval bound the arguments but not the operation — an AS could show “archive” and issue “delete” for the same record id.
HAP-approvalnow signsactionandaction_type, and a Gatekeeper verifies the approval signatures a ticket carries. Also resolved: a verifier accepting a ticket as authorization checks itscontentBindingagainst the profile the mandate pins; the absent-appliesTodefault stated once; version-2 bindings arejcsonly; tickets use the key current at issuance; the privacy invariant is stated as a custody boundary; cumulative bounds are checked over the union of the buckets they govern; an atomic execution claim before every call; the effector’s idempotent exception removed; overclaims in the security summaries narrowed. The approval and review-ticket signatures changed. - Final re-derivation (2026-10-30): a fifth independent implementation re-derived the complete set over the text with the decisions of 2026-10-30 in it; all 868 values agreed. It found five places where a refusal code was not unique — the fields a replay compares, the content binding on the review path, approvals for an absent capped value, the operation a surface shows — resolved in the text without changing a byte-level value.
Every v0.7 direction appears in exactly one v0.8 bucket: promoted (above), open (review.md), or retired (here).