# Changelog

All notable changes to `bounded_authority_protocol` are documented here.

## [Unreleased]

## [0.2.0] — 2026-08-26

### Changed — release 0.2.0

The spec-decoupling program's public surface, released: the specification
`spec/bap-v1.md` is the single normative authority (ships in the package);
`docs/protocol-v1.md` is its generated view; the spec-facts drift gates, the
corpus tooling (six-pin digest rotation, revision sidecar, re-derivability
generator, vendored-snapshot sync), the formal model (ProVerif, P1–P4 proven),
the IANA templates, the guides set, and the interoperability report land
together. Zero wire, bound, verdict, or SDK behavior change — every entry
below is the 0.2.0 content.

### Fixed — program closeout cross-vendor review findings

The closeout review (two peer families over the full program range) returned two blocking and
several note findings, all in surfaces this program touched. All fixed:

- **[blocking] .gitleaks.toml silently disabled ALL default detection rules** — a config file
  defining only an allowlist replaces gitleaks' rule set entirely. The config now
  `[extend] useDefault = true` plus scoped, documented allowlists for the public-key-only
  fixture surfaces (the generated spec documents, the corpus/vectors trees, the vendored SDK
  snapshots, SDK test sources, and the frozen .forge historical transcripts whose "PATs" are
  canonical reverse-alphabet gate self-test fakes). Verified both directions: a fake PAT in a
  scratch tree is DETECTED (3 findings) under the config, and the tracked tree scans clean
  (the only residuals are untracked gitignored build artifacts).
- **[blocking] supply-chain checksum step always failed** — `working-directory: artifacts`
  plus `cd artifacts` targeted a nonexistent `artifacts/artifacts`. The double-entry removed.
- **[note] Python/TS runners now check the revision sidecar's index entry declares zero
  cases** (Rust already asserted it) — a nonzero declaration fails all five consumers now.
- **[note] CI pin consistency** — `ocaml/setup-ocaml` SHA-pinned like every other action;
  the cddl gem checksum-pinned (`scripts/cddl-gem.sha256`) like kramdown-rfc per the A2
  amendment.
- **[note] comment anchors garbled by the spec-path rewrite** ("spec/bap-v1.md,319") fixed to
  section citations in the Python and TypeScript SDK sources.
- Residual, stated: a duplicate-member revision sidecar would pass the four SDK runners'
  permissive host JSON parsers (only the Elixir bounded decoder and the independent Node
  checker reject duplicates). The certified index SHA pin (any byte change fails all six
  pins), the corpus sync gate, and the two duplicate-rejecting consumers bound this state to
  "unreachable without simultaneously defeating the corpus identity architecture";
  duplicate-rejecting JSON parsers in four host languages for that state is disproportionate,
  recorded as accepted residual.

### Added — the successor-major charter + governance single-home + ROADMAP backfill (design-only)

- **`docs/design/successor-major-charter.md`**: the consolidated successor-major scope — delegation
  with attenuation (ADR 0010), offline floor limits (ADR 0016), suite succession and
  cross-suite evidence (ADR 0009 + 0026), the revocation/freshness posture (what a successor
  major MAY standardize at the interface — freshness-bearing claim shapes with stateless
  window semantics — vs what stays runtime-private permanently: live revocation lookup,
  replay reservation, trust selection, per-invocation accounting), and — per the owner's
  2026-08-26 directive — the DECIDED selector-expressiveness posture: per-request range
  selector kinds are successor-major scope (statelessly verifiable inequality kinds); a
  naive cumulative `budget` selector is explicitly rejected as a silent false security
  property, with budgets routed to issuer-attestation/runtime accounting. The parked fork in
  the AP2 mandate-mapping document is replaced by a pointer here. The absolute constants
  (stateless verifier, non-authorizing facts, closed rejection, named-suite succession) head
  the charter; an activation checklist closes it. Activates nothing.
- **Governance single-normative-home**: docs/governance.md is the one home for policy
  statements; the standards-track charter's duplicate-content claim is replaced by a
  citation — the line-cite dual-source drift class is dead. No line-number citations between
  the two remain.
- **ROADMAP backfill (additive, ADR 0023 — no rewrites)**: BAP-07's closeout evidence now
  records the index-SHA-pin deferral's double closure (the 0.1.2 pin landing + the
  spec-decoupling program's six-pin one-command rotation gate and the tamper-covered corpus
  revision sidecar); BAP-18's records the authority swap's documentation catch-up (spec as
  single authority, derived view, bounds gated at all poles).
- **Vendor-neutrality sweep re-proven live**: a planted topology canary in the new tracked
  charter file reds the privacy gate ("found forbidden generic topology", exit 2) and the
  clean file passes — rule 9's mechanism covers every program-touched tracked file.

### Added — ADR 0026: the post-quantum successor-suite statement (design-only, no wire change)

- **ADR 0026 (accepted)**: the concrete successor statement — the `BAP2-*` candidate family
  (ML-DSA-65 baseline, ML-DSA-87 higher tier; RFC 9964's JOSE mapping as the wire vehicle),
  hybrid-composite posture room reserved (draft-ietf-jose-pq-composite-sigs, decided at
  successor definition time), the quantum-readiness statement (NIST IR 8547 IPD 2030/2035
  horizon; long-lived chain/archive evidence should plan re-anchoring under cross-suite
  attestation), and the recorded bets with revisit triggers (pq-composite RFC; IR 8547
  final). Activation stays successor-major only (ADR 0009); every name here remains
  reserved-and-rejected in v1 with the 283-green corpus proving it.
- **Registries + standards-track reconciled**: the anticipated-suite row now names the
  `BAP2-*` family with ADR 0026; the charter's succession paragraph cites it.

### Added — the interoperability report (no wire change)

- **`docs/design/interoperability-report.md`**: the cross-validation evidence document for
  the frozen v1 profile — the reference Elixir verifier, the four cross-language SDKs (each
  authored from the spec + corpus alone per ADR 0014's derivation-hygiene rule), and the
  THREE independent Node second-implementation runners (corrected count:
  corpus/chain-archive/grant-proof), each 283/283 agreed with the two-boundary key census
  two-way equal (11 keys). Framed as TEST VECTORS + INDEPENDENT CROSS-VALIDATION (the IETF
  evidence norm), explicitly NOT an implementer's list; methodology, per-implementation
  corpus bindings (in-place vs vendored snapshot, both startup-digest-asserted), and the
  reproduction path. Stated limitations: no registry publication yet (ADR 0015), v1 only.
- **No hand numbers**: the report's certified digest (both encodings), the corpus revision
  integer, the case/surface/key counts are pinned to the LIVE corpus identity by a new
  docs-currency leg — a rotated corpus without a report update reds with the expected value
  named.

### Added — docs-currency gate + the upgrading contract (no wire change)

- **`test/docs_currency_test.exs`**: pins the public documentation surfaces to CURRENT facts —
  the changelog/version cross-reference, the four-SDK listing (case-insensitive stale-count
  check), the 283-case corpus counts, the supply-chain artifact-filename DERIVATION (no
  hardcoded versioned filename may return), the derived view's spec-revision footer, and the
  spec Doc-Revision pin shared by its formal companions. All three mutation legs executed
  red and restored: a version/changelog drift, a stale SDK count (this leg immediately caught
  a real defect — the ticket-18 README edit had landed "All four" without the Go bullet,
  now fixed), and a returning hardcoded supply-chain filename.
- **`docs/guides/upgrading.md`** (replacing its placeholder): the published compatibility
  contract — per-release-kind labels (patch/minor/major), what may never change inside v1,
  what a minor may do (corpus growth with revision bump + the six-pin rotation), the
  three-step unaffectedness check, and the deprecation pointer. The 0.1.0 RC contract remains
  the historical record.
- `/doc/` (generated ExDoc output) is confirmed gitignored — the doc/ vs docs/ hazard stays
  dead.

### Changed — housekeeping: SDK README, supply-chain derivation, dependabot, deployment guides

- **`sdks/README.md`**: the Go SDK entry and the four-SDK count (the stale "all three" is
  gone); per-SDK deployment-guide links.
- **`supply-chain.yml`**: the artifact filename is DERIVED from `mix.exs` `@version` at run
  time — the hardcoded `0.1.0` filename (a quiet-mislabel class the moment the version moves)
  is gone from the workflow; actionlint clean.
- **`dependabot.yml`**: pip (sdks/python) and gomod (sdks/go) ecosystems join mix,
  github-actions, npm, and cargo — all four SDKs now covered.
- **Deployment guides**: `docs/deployment/{typescript,python,go}-sdk.md` join the existing
  Rust guide — runtime posture, deployment targets, supply-chain posture, and the
  verification-is-not-authority boundary per SDK; the Rust guide's spec pointer repointed at
  the normative authority.

### Added — the Livebook walkthrough (no wire change)

- **`docs/livebooks/bap-walkthrough.livemd`**: the end-to-end produce → assemble → verify →
  reject notebook. It mints its OWN ephemeral Ed25519 keys at runtime via `:crypto` (zero
  tracked private material — critical rule 6; the package accepts public keys only and the
  notebook does the signing), builds a grant and proof through the deterministic producers,
  assembles both compacts, verifies the grant and the full envelope with caller-supplied
  trusted context, and then fails three classic attacks closed (a tampered signature byte, a
  selector-disallowed argument swap, a stale proof outside the max-age+skew window). The
  notebook's cells were executed VERBATIM (extracted and run as a script): all three attacks
  return exactly `{:error, :invalid}`. Two authoring bugs found and fixed by that verbatim
  run: a mis-grouped expiry literal and a `defp` helper cell that cannot exist at Livebook
  top level.
- **`test/bap_walkthrough_test.exs`** (the notebook's mirror): re-executes the walkthrough's
  exact code shape against the real package — if the notebook's code stops running, the test
  fails (a non-running livebook is worse than none) — and a shape-parity leg pins the
  notebook's code markers to the test (mutation leg executed: breaking the notebook's code
  shape reds with the marker named; restored green).

### Added — corpus vendored-snapshot sync gate (no wire change)

- **`scripts/check_corpus_sync.exs` + `mix corpus.sync` (quality leg)**: byte-compares the
  certified corpus (`priv/conformance/v1/corpus/`) against BOTH vendored SDK snapshots
  (Rust, Go) — every file, both directions, content equality. A snapshot drift is caught at
  COMMIT time instead of as a red SDK suite or a confusing digest rotation. Within ADR 0019
  (an intra-repo drift check, not a distribution artifact). Mutation leg executed: a flipped
  byte in a vendored revision.json reds with the file named; restored green.
- The CI quality job already runs `mix quality`, so the new leg is enforced there without a
  new workflow.

### Added — the implementer's guide (fifth-SDK recipe; no wire change)

- **`docs/guides/implementers-guide.md`** (replacing its placeholder): everything a fifth
  implementer needs without reading a sibling runner's source — the conformance-runner
  contract (startup corpus-identity assertion, full integrity verification, total verdict
  comparison: crashes are failures, never skips); the 28-surface dispatch table with each
  surface's role; the two-boundary key census (discovery == verify-import == index set,
  and the two runner cheats it catches); the corpus formats including the revision sidecar;
  the permissiveness-mutation expectation with the six language trap classes
  (duplicate-member rejection, null-prototype equivalence, the raw-lexeme ceiling,
  single-value/trailing, the integer/float tag distinction, decode-depth scalar asymmetry)
  each required red-capable in the implementer's own language; and the facts-anchor contract
  for embedding the spec.
- **Acceptance leg**: `test/docs_guides_test.exs` cross-checks the guide's dispatch table
  against the LIVE corpus surface enumeration, both directions named on drift (a corpus
  surface without a guide row, or a phantom guide row, reds).

### Added — code of conduct, guides taxonomy, README restructure (no wire change)

- **`CODE_OF_CONDUCT.md`** (Contributor Covenant v2.1, canonical text) at the root, linked
  from CONTRIBUTING.md.
- **Guides taxonomy** under `docs/guides/`: getting-started.md (zero to a verified envelope,
  the three rules that surprise newcomers) and a curated-order README; wired as ExDoc extras
  in reading order (implementers-guide and upgrading land with their own slices as wired
  placeholders).
- **README restructure**: a Documentation section leading with the guides index; a "When NOT
  to use this" section (authorization decisions, transport/confidentiality/storage,
  permissive parsing, online revocation/replay — each pointing at what to use instead); and a
  Named-misuses section (facts-as-authority, facts-as-runtime-input, kid-as-key-selector,
  bound-loosening, client-derived request context) — the ash_onetime anti-pattern format,
  product-neutral.

### Added — ProVerif model + CI harness + findings ledger (no wire change)

- **`spec/formal/proverif/bap-core.pv`**: the stateless-core model under ProVerif 2.05 —
  Dolev-Yao network attacker plus a malicious holder with a fully-controlled signing oracle;
  the typed projection and compact framing as free constructors (the recorded symbolic
  assumptions); every verifier step annotated with its REQ id. **All four properties PROVEN
  TRUE**: P1 (a proof binds exactly the `ath`-named grant — the verifier event carries the
  digest of the grant IT was presented), P2 (request-digest equality with the verifier-derived
  pair), P3 (context-tuple equality), P4 (the server-derived cast arguments stay secret — the
  facts output constructor takes only declared fields).
- **Model non-vacuity proven by mutation**: removing the `ath` check makes P1 unprovable; the
  strengthened query and per-issuance grant freshness were themselves the ledger's first
  (refuted) finding — the initial P1 was too weak to see cross-grant replay.
- **`scripts/run_formal.sh` + `mix formal` (quality leg) + a CI job**: pinned ProVerif
  re-run with the RESULT-line diff against the frozen `expected-summary.txt` (a
  verdict-changing model edit reds — leg executed), REQ-id coverage with a pinned step-
  annotation set (dropping an annotation reds — leg executed), and the findings-ledger schema
  check (an undispositioned entry reds — leg executed). An escalated disposition prints
  loudly and blocks the program done-claim (ADR 0025).
- **`spec/formal/FINDINGS.md`**: the ledger, one terminal disposition per finding
  (refuted/documented/escalated); first entry dispositioned refuted; none escalated.
- The model joins the rule-8 companion pin (spec Doc-Revision); `spec/formal/` ships in the
  package; census extended exactly.

### Added — formal attacker model + ADR 0025 + gate rule 8 (no wire change)

- **`spec/formal/attacker-model.md`**: the formal attacker — Dolev-Yao network attacker plus a
  malicious holder attempting cross-grant/cross-operation/cross-endpoint/cross-invocation
  confusion — and the four modeled properties: P1 (a proof binds exactly the `ath`-named
  grant), P2 (the request digest binds the verifier-derived operation + typed arguments
  exactly; typed-projection injectivity is the load-bearing lemma), P3 (context-binding
  completeness across method/URI/invocation/nonce, with time abstracted symbolically and the
  assumption recorded), P4 (facts disclose nothing beyond declared fields). Scope and
  symbolic assumptions stated; self-contained and vendor-neutral; pinned to the spec's
  Doc-Revision.
- **ADR 0025 (accepted)**: the formal-analysis program — ProVerif chosen for automation fit on
  the stateless core (decision recorded as decided-with-open-evidence: no published
  machine-checked DPoP-family model exists; FAPI 2.0 CCS 2024 defers formal models), Tamarin
  named follow-on with a recorded trigger (first chain/archive ordering or timepoint lemma
  need), the standing CI posture, and the three-valued findings-ledger disposition contract —
  `escalated` routes to the owner via SECURITY.md and blocks the program's done-claim until
  the owner rules; the coupling is stated, not hidden.
- **Spec-facts rule 8**: every spec/formal companion names the revision it was written
  against; a spec revision bump without the companion (or vice versa) reds with both values
  named. Mutation leg executed: a lone spec rev bump reds, restored green. (The ProVerif model
  joins the companion list at its own landing.)

### Changed — docs/protocol-v1.md becomes a generated view; repoint census; ADR 0024

- **`spec/tools/render_derived.exs` + `mix spec.render` (quality leg)**: the package-facing
  `docs/protocol-v1.md` is no longer authored — it is generated from the normative
  `spec/bap-v1.md` (front matter stripped, citations normalized, generation footer naming the
  authority and revision). Hand edits are gate-red; the write is idempotent.
- **Repoint census**: the living normative citations now point at the spec — README, the
  requirement map, registries, standards-track charter, conformance contract, and the four SDK
  sources' docstrings (comment-only; zero behavioral SDK change; all four suites re-verified
  283/283 + census). Historical records (ADR bodies, ROADMAP rows, errata) keep their
  closure-time citations as history.
- **ADR 0004** gains the supersession note (the spec is the normative home of the byte
  definitions; the ADR remains the decision record). **ADR 0024** (accepted) records the
  decoupling decision: the single-authority swap, the certification-not-trust posture, the
  derived view, the ADR 0022 same-major-document-relocation amendment, toolchain provenance
  (kramdown-rfc 1.7.40 version+checksum-pinned, cddl 0.12.14, both executed locally), and the
  I-D-shaped-readiness venue posture.
- **Package census**: `spec/` ships in the package (the specification, its CDDL, its facts
  baselines, and its three generator tools); `check_package.exs` @expected_files extended
  exactly; the architecture-gate package boundary green.
- **The one-time full comparative read** of the new spec against the old authority was
  performed with written dispositions (mechanical parity: 76/76 REQ ids, 37 bounds rows,
  byte-identical constructions; one real gap found and fixed — the frozen façade listing now
  restored verbatim into informative Appendix C). Disposition record: the swap's review
  artifact; the empty-delta certification continues to run in `mix quality`.

### Added — generated worked examples (spec Appendix A; no wire change)

- **`spec/tools/build_examples.exs` + `mix spec.examples` (quality leg)**: Appendix A of the
  specification is now GENERATED from the conformance corpus — thirteen representative cases by
  id (a valid grant/proof/envelope/chain/anchored-export per surface family, plus rejected
  exemplars across the invalid-class families), each embedding the exact compact bytes, the
  public key material, and the single expected outcome (accepted-with-redacted-facts or the one
  closed error), labeled with its corpus case id. Corpus PUBLIC keys only — no private material
  can appear (the corpus is public-only by construction and the tool copies verbatim).
- The regeneration gate reds on a hand-edited example byte (mutation leg executed: flipped
  compact byte caught, restored) and on a deleted/renamed cited case id (mutation leg executed:
  the rebuild raises naming the missing id, restored) — the appendix cannot drift from the
  corpus and cannot go vacuous.
- The write path is idempotent (byte-identical across consecutive regenerations); the
  empty-delta certification is untouched (the appendix carries no facts anchors and no
  capitalized keywords).

### Added — Security and Privacy Considerations + spec-facts rule 11 (no wire change)

- **Spec section 21 (Security considerations, RFC 3552 grade)**: assets and goals (integrity,
  not confidentiality — every wire object is plaintext by design), adversaries and the exact
  trust boundary (raw bytes in, redacted facts out), the per-control mapping to REQ ids
  (parsing-before-crypto, exact-byte constructions, holder/grant/request bindings, temporal
  and nonce semantics, authenticated transitions, complete-scan archive acceptance),
  verification-is-not-authority stated as a security property, explicitly out-of-scope
  operational controls, and residual risks (in-window replay, compromised issuer, correct-but-
  wrong trusted keys, malicious archive controllers, implementation error). Self-contained;
  the threat model, charter, and extension security sections remain as extended records.
- **Spec section 22 (Privacy considerations, RFC 6973 shape)**: the correlation surfaces
  (`jti`, `ba_inv`, `aud`, issuer identifiers), chains and archives as deliberately durable
  longitudinal evidence with the retention-policy tension stated, nonce linkability and the
  optionality-as-privacy-feature note, and redacted facts as the profile's loudest privacy
  control. Deployment recommendations carry NO capitalized RFC-2119 keywords.
- **Spec-facts rule 11 (keyword census)**: every capitalized MUST/MUST NOT/SHOULD/SHOULD
  NOT/REQUIRED/SHALL/SHALL NOT in the spec is either a quoted mention, or shares its sentence
  with a `REQ1-*` id, or sits under an explicit informative marker — a naked keyword in
  unanchored prose reds with the sentence quoted. Mutation leg red-quoted (planted naked MUST
  caught) and restored; every mapped id exists in the requirement map (rule 3).

### Added — IANA registration templates + spec-facts rule 7 (no wire change)

- **`docs/design/iana/`**: the machine-readable registration sources and rendered ready-to-file
  markdown for the profile's IANA entries. JSON Web Token Claims Registry (its published
  four-field format, Specification Required): `ba_inv`, `ba_op`, `ba_req` ready to file;
  `ba_dlg`, `ba_offline`, `ba_sut` reserved-marked and NOT filed. Media types (RFC 6838
  section 5.6 field template, LIMITED USE): `application/ba-cap+jwt`,
  `application/ba-chain-anchor+jwt`, `application/ba-key-transition+jwt` ready to file;
  `application/ba-cap-delegated+jwt`, `application/ba-suite-attestation+jwt` reserved. The
  drafted `ba+*` wire `typ` values are NOT registrable as-is (`+cap` is not a registered
  structured suffix; `+jwt` is) — **the wire `typ` values are unchanged**; the media-type
  namespace is distinct. Filing is externally gated (the BAP-08 official-submission
  preconditions); the templates make in-repo readiness verifiable.
- **Spec §IANA considerations** (section 20): the registration requests, the reserved names,
  and the namespace distinction, per RFC 8126 guidance (read first-hand).
- **registries.md typ table gains the media-type column**; purposes are now byte-exact shared
  facts between the registries document and the IANA sources.
- **Spec-facts rule 7**: registries.md == IANA template sources, exact names/statuses/purposes
  both directions, with two documented allowsets (`ba_obo`: reserved without a template by
  design; `dpop+jwt`: registered by RFC 9449) — BAP-12's acceptance criterion mechanized.
  Mutation leg proven red (flipped template status caught, named) and restored.

### Added — the v1 specification `spec/bap-v1.md`; authority swap certified with an EMPTY delta

- **`spec/bap-v1.md`** (kramdown-rfc conventions, `[@RFC2119]`-style citations, no IETF
  stream/IPR title-block fields — in-repo readiness, zero submission intent): the complete
  normative v1 profile in one language-neutral document — conformance language and the
  closed-rejection invariant, suite identity, normative references without repo paths, the
  abstract tagged data model and typed projection, JSON decoding and JCS serialization,
  base64url, wire objects (headers, claims, selectors, URI normalization), signing and digest
  inputs with the `BAP1-*` domain separators, the consumption chain / boundary anchors /
  authenticated key transitions / anchored export framing folded NORMATIVE from ADR 0004, the
  public verification contract as algorithm prose (the Elixir bindings and frozen façade moved
  to informative Appendix C), hard maxima, the untrusted key locator, and the `typ` registry.
  Informative appendices: B the CDDL summary, C the reference mappings and tagged-algebra
  binding table, D the requirement-inventory pointer.
- **The authority swap is certified**: the spec-facts extractor now reads `spec/bap-v1.md`
  (all ten anchors in the one normative document) and `extract(new spec) ==
  spec/facts/baseline-v1.json` byte-for-byte — the frozen pre-swap extraction, EMPTY delta.
  `docs/protocol-v1.md` keeps its anchors but is no longer read (the derived view lands with
  its own landing); ADR 0004 and registries.md remain extended records.
- **Framing oracle** (new spec-facts rule): a corpus archive-bearing valid case's real bytes
  are re-parsed against the EXTRACTED framing facts (archive prefix, UINT32_BE nonzero-length
  frames, exact EOF) — a spec whose framing prose drifts from accepted bytes reds even when
  the delta gate happens to pass. Proven red twice during construction (wrong prefix form,
  wrong chunk decoding) and green on the shipped corpus.
- **`spec/cddl/bap-v1.cddl`** (informative): the closed wire objects in CDDL, honest about
  CDDL-inexpressible invariants (duplicate names, canonical bytes, tagged integer/float,
  binary framing). **CI coherence job** (`scripts/check_cddl_coherence.rb`, pinned cddl
  validator 0.12.14, executed locally at authoring): every valid corpus case's CDDL-covered
  artifact validates (grant/proof/anchor/transition payloads, consumption rows), and four
  PINNED invalid exemplars (regexp/size/closed-set violations) reject — the pinned list cannot
  go vacuous. A pinned-and-checksummed kramdown-rfc 1.7.40 render job renders the spec to
  xml2rfc v3 (verified locally: renders clean).
- The spec-facts battery entries re-anchored at the spec (a digit drift or deleted anchor in
  `spec/bap-v1.md` reds). `mix spec.facts` green against the NEW authority in ~0.1s.

### Added — spec-facts mutation battery (no wire, bound, or verdict change)

- **`scripts/check_spec_facts_mutations.exs` + `mix spec_facts.mutations` (wired into
  `mix quality`)**: the spec-facts gate's own red-capability battery, mirroring the conformance
  mutation-gate doctrine — scratch-copy isolation, one anchored source mutation per entry,
  baseline-green non-vacuity (a deleted target can never score as "caught"), and one
  inverted-assertion calibration self-proof executed at authoring (under a neutered rule-1
  assertion the bounds mutation SURVIVES green — the exact condition the battery raises on).
  Seven entries, each proven red in-landing: spec bounds digit, live Bounds digit, requirement
  statement softening, cited-count drift, revision-citation drift, deleted anchor, and a
  renamed optional-unobserved coverage mark (the marks file is load-bearing). The privacy-canary
  mutation stays outside the battery by design — the canary sweep needs the real git worktree
  and carries its own calibration tests.

### Added — spec-facts drift gate against the current authority (no wire, bound, or verdict change)

- **`spec/tools/extract_facts.exs` + `spec/facts/baseline-v1.json`**: the normative facts of the
  CURRENT authority (`docs/protocol-v1.md` + ADR 0004 byte definitions + the registries typ
  table) are now machine-extracted from ten closed `<!-- facts:key -->` anchor regions (bounds,
  header members, grant/proof claims, selector kinds, typ values, domain separators, digest
  constructions, archive framing, error shape) and frozen as a byte-deterministic baseline. A
  future editor who changes any normative fact — spec, bound, corpus, requirement map, or
  registry — is stopped by `mix spec.facts` naming the divergent pole pair.
- **`scripts/check_spec_facts.exs` + `mix spec.facts` (wired into `mix quality`)**: rule 1b
  (extraction == frozen baseline), rule 2 (spec closed sets ⊇ the corpus's valid-case member
  unions, direction-aware, with justified optional-unobserved marks in
  `spec/facts/coverage-v1.json`), rule 3 (spec REQ-id set == requirement-map set, each defined
  once), rule 4 (requirement-statement hashes == `spec/facts/requirement-statements-v1.json` —
  silent softening reds), rule 5 (map-cited counts re-derived from the live corpus index),
  rule 6 (map cites the corpus revision integer), rule 9 (every authority file git-tracked and
  thus inside the public-surface privacy gate's full-tree canary sweep — the ADR 0023 topology;
  no term list is tracked), rule 10 (anchor completeness: every normative table inside exactly
  one anchored region, with the JSON-algebra binding table's coverage mechanically verified
  against the typed-projection facts). Rule 1's bounds-dump equality lives as
  `test/spec_facts_test.exs` (needs the compiled Bounds module).
- The gate ships GREEN against the old authority — the guards are proven against the world they
  protect BEFORE the authority swap. Every rule proven red-capable at landing by a named
  mutation (spec digit, Bounds digit, map statement softening, cited count, revision citation,
  a planted privacy-canary term in an authority doc, a deleted anchor — each caught, each
  restored). Gate runtime ~0.2s; rules 7 (IANA), 8 (formal companions), and 11 (keyword census)
  arrive with their owning landings.

### Added — corpus revision sidecar + re-derivability generator (no case byte or verdict change)

- **`priv/conformance/v1/corpus/revision.json`**: the corpus's monotone revision integer (1)
  with a generated-from provenance note, hash-covered as an entry in `index.json`'s per-file
  SHA `files` set and enforced by exact file-set equality in every consumer — a tampered,
  deleted, malformed, or wrongly-shaped sidecar fails every loader closed. `index.json` bytes
  change once (the single files entry; 283 cases, verdicts, counts, and applicability are
  byte-identical). The corpus-index schema's file-path pattern admits exactly the one reserved
  root path. All six certified-index-SHA pins rotated in the same commit via the regeneration
  script (ADR 0019 atomic-landing template), and both vendored SDK snapshots re-copied.
- **`conformance/generators/`**: the corpus re-derivability tooling (authoring tooling, not a
  runner, not in the published package). `build_corpus.mjs --verify` proves the shipped corpus
  equals a rebuild from its frozen case files plus the shipped curated inputs (n_a reasons +
  the public-key fingerprint census), including re-derivation of every tamper case's verbatim
  artifact; `--rebuild-index`/`--bump-revision` are the amendment path. The README records the
  honest provenance: the signed fixtures were minted with ephemeral keys at authoring time and
  cannot be re-minted; everything derived is machine-rebuilt and byte-verified.
- Every corpus consumer recognizes the sidecar fail-closed (Elixir loader + CLI, the
  independent Node runner, and the TypeScript/Python/Rust/Go SDK runners — the two loaders that
  previously skipped case-free files silently now enforce the sidecar's SHA and closed shape).
- `docs/design/requirement-map.md` cites the revision integer (the `format`-string citation
  was constant across revisions and could detect nothing); the five consumer-doc digest
  citations refreshed to the rotated values.

### Added — corpus digest regeneration gate (no wire, bound, or verdict change)

- **`scripts/regen_corpus_digests.exs` + `mix corpus.digests`**: one command regenerates every
  certified corpus index-SHA machine pin — the four SDK conformance runners in their native
  encodings (base64url: TypeScript, Go; hex: Python, Rust) plus the two Elixir pins (the CLI's
  fail-closed certified-corpus assertion and its test mirror) — and the check leg, wired into
  `mix quality`, fails red the moment any pinned constant drifts from the live `index.json`
  digest (corrupt-one-constant and scratch-index-byte mutation legs proven red at landing). The
  Elixir-side pin itself landed in the 0.1.2 hardening; this closes the tooling gap the BAP-07
  gate-integrity review recorded and makes every future corpus rotation a single-command,
  same-commit affair (ADR 0019 atomic-landing template). No corpus byte changes in this landing:
  all six constants keep their current values, and `--write` is byte-idempotent today.

### Fixed — public-history privacy boundary

- Public documentation and reachable Git history no longer identify private product repositories
  or their deployment topology. ADR 0023 records the durable boundary and the all-ref rewrite.
- `bounded_authority` is explicitly a private commercial application: it must never be published
  to public Hex, is not currently distributed through private Hex, and any future private-Hex
  release requires both a paid subscription and fresh owner approval for that exact release.

### Fixed — v1 selector contract reconciliation

- The protocol and both shipped selector schemas now describe the released `all` behavior: `all`
  accepts any of the three recognized member sets and treats `path`/`value(s)` as inert. Go,
  TypeScript, Python, and Rust now reject every other member combination, matching the Elixir
  reference and independent runner. ADR 0021 records the zero-verdict-change erratum.

### Added — Go verifier SDK (BAP-16; no Elixir package change)

- **`sdks/go/` — the typed Go verifier SDK** for the frozen v1 profile, authored from the spec +
  ADRs + conformance corpus alone (ADR 0014 D5): the 17-function façade plus versioned primitives,
  zero runtime dependencies (stdlib `crypto/ed25519` + `crypto/sha256` only), Go floor 1.25. Passes
  all 283 conformance vectors from a vendored corpus snapshot with startup index SHA-256 assertion
  and the two-boundary key census (`agreed=283 disagreed=0 census=11`). The per-language
  permissiveness battery ships with per-clause red-capable legs for every closure and gate (no F1
  debt: 14 mechanical mutation probes proven RED at authoring), including ADR 0017's five clauses
  (closed Result panic guard, pre-digest hoist with a zero-hash work pin via the internal
  `archiveDigest` seam, canonical byte-equality, signature width at decode, role-bounded frame
  reads) and ADR 0018's bounds threading incl. `assemble_compact` caller limits and nested-pins
  identity semantics. Purity vet + zero-dependency license gate (each red-capable) and a
  `go-conformance` CI job. Not in the Hex `files:` list; no registry-publish infrastructure
  (ADR 0015 graduation posture).
- **Cross-vendor hardening (codex + claude review, fix pass `fbff228`):** proof-claim presence
  tracking, export anchor-chain cross-binding, standalone genesis zero-hash, port-overflow
  closure, embedded-IPv6 group counting with dotted-form preservation, post-decode host
  classification with lowercased decoded bytes, tightened integer-magnitude enforcement,
  UTF-8 object-version validation, chunk-vs-byte bound separation at encode, a digest-deriving
  export producer, proof-producer nonce support, constant-time nonce comparison, and
  runner/purity-gate hardening — each behavioral fix pinned by a red-capable
  review-regression leg.

## [0.1.2] — 2026-08-20

### Changed — release/verification gate hardening (no wire, bound, or verdict change)

- **`mix conformance.verify` pins the certified corpus index SHA-256** (ADR 0014 D4). The Elixir
  verifier CLI now fails closed unless the loaded corpus is the exact certified snapshot — closing
  the gap where a self-consistent but shrunken corpus (regenerated index) passed integrity and
  agreement. Parity with the three SDK runners, which already pin the identical value.
- **The architecture gate asserts every pinned beam is present** (keys-⊆-present). A pinned public
  surface deleted or renamed without updating the allowance now reds the gate instead of vanishing
  silently.
- **The SDK publish guard scans SDK scripts, Makefiles, justfiles, and composite actions**, not
  only workflows and top-level manifests — a registry-publish command can no longer hide in an SDK
  release script or a composite action.

## [0.1.1] — 2026-08-20

### Added

- **Ship `priv/conformance/v1/vectors/` in the Hex package.** The four named vectors
  (`grant-holder-proof`, `chain-semantic-edge`, `consumption-chain-archive`, `manifest`)
  are the acceptance oracle a holder-side consumer verifies its envelope production against
  (ADR 0013's corpus-as-oracle posture). 0.1.0 shipped `corpus` + `schemas` but not
  `vectors`, so a Hex consumer could not reach the oracle vector; 0.1.1 ships it. Public
  test data, same class as the already-shipped corpus — no private material (verified: the
  manifest carries only discovery roots and public-key fingerprints). The exact-file
  package census is extended to match. Zero wire byte, bound, or verdict change.

## [0.1.0] — 2026-08-20

### Added

- **First public release (BAP-07, executed 2026-08-20 by owner decision).** The exact reviewed
  candidate published to Hex as `bounded_authority_protocol` 0.1.0. Connected verification: the
  private runtime's PostgreSQL 18 gate passed 996/996 against the pinned candidate, and the
  consumer's gates passed 819 tests including the immutable authority-contract bundle's 15
  consumer cases; the opt-in live-endpoint consumer gate was provisioned and run for this
  closeout. Fresh correctness, security, and gate-integrity reviews closed with their findings
  fixed in this landing; the cross-vendor peer (codex) returned no findings, and the third-family
  GLM lens was a named sensitivity-policy skip. The publication sweep in this landing: the
  private-strategy links left the README, every unpublished/deferred claim in the shipped docs
  became the published truth, ADR 0008 gained the 2026-08-20 amendment lifting the deferral, the
  consumer-seams design note left the package and hexdocs, the mutation batteries gained
  baseline-green runs (an entry now fails if its target test cannot run green unmutated), the
  bounds-aware facade gained widening/malformed-bounds rejection legs, and `.gitignore`/dependabot
  gained the public-repo hygiene the security review named. The `v0.1.0` tag moved to the
  published commit so hexdocs `source_ref` links resolve.

### Fixed

- **BAP-07 readiness reconciled across the repo docs.** The ROADMAP row's acceptance still named
  the retired private-runtime PG 16/17/18 matrix (the private runtime's ADR 0010 made PostgreSQL 18
  the sole supported major); the ROADMAP Next-action note, the release-candidate contract's Status,
  the README Status, and SECURITY.md's supported-versions note each described BAP-07 as gated on the
  private runtime's connected gates —
  private BA-14 completed 2026-08-18, so BAP-07 is fully unblocked (the Hex-publication half stays
  deferred by maintainer decision). Docs-only: zero code, wire, bound, or verdict change.
- **Cross-vendor review round 18 (codex blocking + claude should-fix/notes, all closed).**
  Codex (blocking): the TS shape gate's nested expected-export members were opaque `"object"`
  specs, so a malformed nested struct (chain missing `previousHash`, empty anchor) passed the
  gate and the clause-3 hoist then derefed the missing field — a `TypeError` escape past
  `trying()`; the nested members are now fully specified (chain/anchor/transition field shapes,
  `archived.chunks` as a bytes sequence), red-leg + mutation proven (opaque specs → RED). Claude
  (should-fix): the Python and Rust hoists walked `expected.transitions` per-element BEFORE the
  `key_transitions` count ceiling, regressing the round-3 ceiling-first invariant the TS sibling
  kept; the ceiling now runs at the top of the hoist in both (the dead later duplicate removed).
  Claude (notes, reconciled): the entry-position shape gate's sequence walks before count
  ceilings are a documented accepted margin (typeof-only per element); Python's body-level
  `bytearray` tolerance was dead under the exact-bytes gate and is reconciled to bytes-only.
  The timed-out fable peer's transcript was mined: it was converging on the ordering finding
  (fixed) and an API-shape note that is benign (the SDKs' optional bounds default to maximum —
  behavior-identical to the reference's public façade).
- **Round-12..14 per-clause pin debt paid on the touched surfaces (ADR 0017's Honest limit,
  amended).** The 2026-08-18 cluster's legs double as the owed per-clause pins: the pre-digest
  export gates (version shape/equality, key-count, key-id charset, key magnitude) are now pinned as
  WORK legs in the Python battery (reject with zero sha256 calls — all five mutation-proven
  load-bearing) with TS/Rust verdict matrices. Root cause of the "unpayable" debt: each gate is
  verdict-subsumed by a later gate, so verdict legs were structurally impossible; the work form is
  the red-capable pin. Still owed and disclosed: the standalone anchor-path `anchor_bytes` gates
  (round 12) and allocation bounds (round 15) on untouched surfaces.
- **ADR 0018's named `assemble_compact` divergence closed: caller bounds threaded through
  assembly in all three SDKs.** The reference takes limits at assemble (`runtime.ex:147-155` →
  `CompactJws.assemble` — encoded-segment bounds, compact_bytes, and the kind re-parse against
  `Bounds.coerce(limits)`); the SDKs hardcoded maximum. Each SDK now takes an optional bounds
  parameter (Python `bounds: Bounds | None = None`, TypeScript `bounds?: Bounds`, Rust
  `bounds: Option<&Bounds>` — an additive public-API change; absent bounds = maximum, backward
  compatible) and threads it through the reference's gates. `signature_bytes` carries no
  assemble-time gate — fixed-width at Bounds construction (the reference's check is subsumed).
  Mutation-proven per SDK (reverting the threading reddens each battery's tightened-bounds legs).
- **ADR 0017 exception 2 closed: the expected-anchor identity ordering divergence (all three
  SDKs).** The reference validates the expected struct (chain + both anchors' identity/binding
  well-formedness + transitions) BEFORE hashing the archive chunks (`anchored_export_codec.ex:88-104`);
  the SDKs ran those gates only post-digest. Verdict-invariant by subsumption — the fix restores the
  clause-3 work ordering: malformed caller metadata now rejects without hashing the archive. The
  Python battery proves it behaviorally (sha256 call-count == 0 on malformed-expected rejections,
  mutation-proven); TS and Rust pin the ordering structurally (hoist block before the digest site,
  mutation-proven) plus verdict-matrix legs.
- **ADR 0017 exception 1 closed: the SDK closed-Result escape family (Python + TypeScript).** A
  mechanical family sweep (every façade × every parameter × wrong-typed values, then every struct
  field the same way) proved the 2026-08-17 ledgered Python escapes were not two instances but a
  total class: 31/34 Python parameter sites and every caller-supplied struct field raised
  `AttributeError`/`TypeError` past the closed `Result`, and TypeScript — whose coercion the ADR
  had classed as a "disclosed margin" — silently accepted `requestDigest(123/true/null/{})`
  (digesting the coerced text) and threw `TypeError` past `trying()` on 109 parameter positions.
  Both dynamic SDKs now gate every façade argument's shape before the body runs (Python:
  annotation-driven `_closed_shape` over the declared dataclass shapes, `bool` ≠ `int`; TS:
  `closedShape` with per-façade shape specs). Rust excludes the class by typing. The per-SDK
  family-sweep batteries in `tests/test_permissiveness.{py,ts}` are the pre-fix red run and are
  mutation-proven per façade. Exception 2 (expected-anchor identity post-digest) remains open.

### Added

- **Bounds-aware public compact assembly and current-major issuer posture.** Add the public
  `BoundedAuthorityProtocol.V1.assemble_compact/3` facade over the existing bounded runtime
  primitive; `/2` remains exactly `/3` with profile maxima. Native tests cover byte equality for
  grant, proof, boundary-anchor, and key-transition compacts; tightened encoded-segment and final-
  compact rejection; and continued rejection of `ba_dlg` / `ba+cap-delegated` in current v1.
  [ADR 0020](docs/adr/0020-bounds-aware-assembly-and-issuer-reauthorization-posture.md) records that
  a private authority may issue an independently valid, narrower current-v1 grant to a new holder,
  while lineage remains runtime-only and portable holder-signed delegation remains successor-major.

- **The SDK contract ADRs (BAP-15 documentation slice).** Three new accepted ADRs record contracts
  that had shipped as code + ROADMAP evidence amendments only: [ADR 0017](docs/adr/0017-inter-sdk-behavioral-contract.md)
  (the inter-SDK behavioral contract the 17-round cross-vendor hardening arc converged on — closed
  Result surface, type strictness, pre-hash validation, canonical-form byte equality +
  signature-width gates, role-bounded frame reads), [ADR 0018](docs/adr/0018-sdk-bounds-contract.md)
  (the caller-tightenable bounds contract through the expected structs, including the
  nested-pins identity semantics and the named `assemble_compact` maximum-bounds divergence), and
  [ADR 0019](docs/adr/0019-corpus-artifact-distribution.md) (ADR 0015 Decision 6's deferred
  corpus-artifact question settled: per-SDK binding until the first SDK graduation, on the traced
  two-SDK bump-amplitude evidence). Fact corrections in the same landing: ADR 0014's Node floor
  corrected to >= 22 (raised at `d9df0bf` on Node 20 EOL) and its `@noble/curves` "optional
  browser-build path" claim withdrawn (never present in `sdks/` history); ADR 0008's BAP-07
  "publishes the exact candidate" consequence annotated with the maintainer's Hex-publication
  deferral (the `v0.1.0` git tag at `c65d3be` is the internal pin). Zero wire-behavior change. The
  ADR-0017 authoring review (cross-vendor) surfaced two verified SDK contract exceptions, disclosed
  as ADR 0017's named exceptions and routed as SDK-code fixes: a Python closed-Result escape
  (non-string `request_digest` operation / `ConsumptionEntry.chain_id` raises `AttributeError` past
  the façade), and all three SDKs validating the expected-anchor identity fields post-digest where
  the reference validates them pre-digest.

- **BAP-09 SDK conformance hardening (TypeScript + Python).** The TS + Python verifier SDKs now
  enforce two checks the Elixir reference has and the SDKs were missing: (1) a decoded
  **signature-width gate** in `parseCompact`/`parse_compact` (`len(signature) == signature_bytes`,
  mirroring `runtime.ex:237`/`:259` — `scanCompact`/`scan_compact` intentionally stays shape-only,
  mirroring `CompactJws.scan`); (2) **canonical-form equality** for boundary-anchor + key-transition
  compacts — the protected header AND payload segments must equal the exact JCS re-encoding
  (mirroring `boundary_anchor_codec.ex:95-96,118-119` + `key_transition_codec.ex:127-128,151-152`),
  so a non-canonical encoding (e.g. reordered members) is rejected. New red-capable tests cover the
  anchor + transition canonical paths (header + payload) and the signature-width gate. Verified: TS
  92 unit + conformance 283/283; Python 62 unit + conformance 283/283. (The Rust/Go SDKs — BAP-15/16
  — may carry the same gaps; a separate check is owed.)

- **All-SDK pre-hash validation hardening (BAP-15, cross-vendor rounds 11-15).** The archive
  verify paths across Rust/TS/Python now validate the full caller-context shape BEFORE the
  digest: the object versions (string, non-empty, UTF-8 bytes <= 512, well-formed, equal),
  the key chain (exact count, key-id ASCII-unreserved class + width), key windows
  (integral + magnitude + ordering), and identifier well-formedness — malformed metadata
  no longer forces maximum-sized hashing, ill-formed strings fail closed in every SDK
  (Python's UnicodeEncodeError escape closed; TS's silent U+FFFD replacement closed), and
  frame reads are role-bounded per chain_row_bytes/anchor_bytes.

- **Result-contract fail-closure + ChainInput type strictness (BAP-15, cross-vendor rounds 16-17).**
  Every path where a caller-supplied Python value could raise out of the closed Result API
  now fails closed instead *(2026-08-17 delta: "every" was overclaimed — two further escape paths
  were found by the ADR-0017 review and routed; see the SDK contract ADRs row below)*: ill-formed
  or non-str identifier strings (`_utf8_bytes` gates
  chain_ids, key_ids, versions, and every expected-side string the header construction
  encodes), non-int chain integers (gated before the sequence arithmetic), non-bytes chunk
  elements, and Boolean ChainInput integers (True == 1 no longer verifies — TS's strict
  equality and Rust's typing already rejected them). The TS chunk-type sibling gate added
  for family symmetry; the check_chain chain_id StringOrURI shape validation that only
  Rust carried is now in all three SDKs.

- **Rust SDK bounds parity (BAP-15; closes the LAST named delta).** Caller-tightenable
  bounds through the expected structs — the exact reference/sibling shape: five additive
  `Option<Bounds>` fields (None = the profile maximum), the nested-bounds pins with identity
  semantics (a present nested must coerce-equal the outer; an absent nested is valid only under
  an effectively-untightened outer — identity overrides are NOT tightening), and the resolved
  bounds threaded through every ceiling at encode AND verify + the standalone chain/anchor/
  transition entries. 27 mutation-proven legs at closeout (permissiveness 39 → 67 — the original 12 plus
  the five the four closeout lenses forced: the chain_rows count, the standalone
  transition, the verify-pin family on a REAL corpus-signed archive, and the two
  encode-pin isolations; the standalone anchor leg, the chunk-count MAGIC pin now reachable and landed, and a 7/7
  Elixir oracle receipt (local re-runnable .forge artifact). `assemble_compact` stays at maximum
  (the siblings too). (Scope driver: the 2026-08-14 session direction — the session's input,
  not this entry's verdict.)

- **TypeScript + Python encode-path validation parity (BAP-15).** Both sibling SDKs gain the
  same producer contract the Rust SDK just closed: a full `checkChain` re-check of the rows, and
  gated parses + 7-field matches for the START anchor, the END anchor, and every transition
  (their expected-side + key-path validations were already present — the deltas were the parses,
  the matches, and the row re-check; the start anchor was framed raw too, plan-review F1).
  15 legs per SDK at closeout (control + 8 tampers + the six pin/threading legs the
  delta- and diff-reviews forced; the five pin legs each isolated under their named
  mutation, the threading leg joint-by-construction per the settled diff-review record),
  each proven red-capable; TS 107 unit + conformance 283/283; Python 77 + conformance 283/283.
  mypy/ruff/purity/license clean. With this, all three shipped SDKs enforce the reference
  producer's full contract; the Go SDK (BAP-16) picks it up at authoring.

- **Rust SDK encode-path validation parity (BAP-15).** `encode_anchored_export` now enforces the
  Elixir reference producer's FULL validation contract (`anchored_export_codec.ex` encode):
  expected-side consistency (chain_id binding of both anchors + all transitions; the
  start/end sequence + hash bindings), a full `check_chain` re-check of the rows, gated parses
  + 7-field matches for BOTH anchors and every transition (the width/canonical gates now
  reached at encode), and the key-path walk (running key, strictly-after transition times,
  seen-list cycle guard, end anchor binding the final key with NON-STRICT `>=` chronology).
  19 red-capable battery legs at closeout (permissiveness 19 → 38), each mutation-proven; every leg
  verified against the Elixir reference oracle (20/20 fixtures — the 15 + control + the four
  closeout-lens additions; the receipt is a local re-runnable .forge artifact). Honest
  residuals, both NAMED: the TypeScript/Python SDKs carried the same producer permissiveness
  (CLOSED by the sibling entries above); the Rust SDK is a documented maximum-bounds posture
  at encode (the reference + siblings thread caller-tightened `expected.bounds` — carrying
  that is a public-API change, its own slice; the sibling identity fix also WIDENS TS/Python
  VERIFY-path acceptance for identity-override configurations, matching the reference; the
  Rust chunk-count parity fix is a latent verdict-tightening flip unreachable at the frozen
  maximum bounds — its pin is owed by the future caller-bounds slice).

- **Rust SDK conformance hardening (BAP-15).** The Rust verifier SDK now enforces the same two
  checks the Elixir reference has (closing the "separate check owed" note above, mirroring
  `18c6467`): (1) a decoded **signature-width gate** in all four `decode_*_parts` fns
  (`REQ1-BOUNDS-fixed-widths`, mirroring `runtime.ex:237`/`:259`,
  `boundary_anchor_codec.ex:88`, `key_transition_codec.ex:120`) — public verdict flips on
  `decode_grant`/`decode_proof` (accepted a wrong-width signature at decode) and
  `encode_anchored_export` (the start-anchor parse never width-checked its signature segment);
  (2) **canonical-form equality** for boundary-anchor + key-transition compacts — the four
  validators now assert `jcs_encode(value) == segment_bytes` for the protected header AND
  payload (mirroring `boundary_anchor_codec.ex:95-96,118-119` +
  `key_transition_codec.ex:127-128,151-152`), so a non-canonical (member-reordered) segment is
  rejected across `assemble_compact`, `verify_historical_anchor`, `verify_key_transition`,
  `encode_anchored_export`, and `verify_anchored_export`. Nine red-capable battery legs
  (4 canonical + 2 decode-width + 1 export-encode width + 2 canonical-exclusion pins + the
  four closeout-lens legs), each mutation-proven. Verified: cargo 338 unit + conformance 283/283 + permissiveness 38;
  clippy/fmt clean. Honest residuals, all
  routed: the encode path validates less than the reference producer — it frames the END
  anchor AND every TRANSITION raw without parsing them (the reference parses both anchors +
  all transitions through the width/canonical-gating codecs,
  `anchored_export_codec.ex:40-52`), binds the start anchor by sequence only (the reference
  matches all signed fields via `anchor_matches?`/`transition_matches?`), and does not
  re-check rows against the chain at encode (`ConsumptionChain.check`). A non-canonical or
  wrong-width end anchor or transition is therefore still accepted at Rust encode (probe-
  proven; caught downstream — `verify_anchored_export` gates all of them); closing these
  flips further verdict classes and is its own reviewed change. The Go SDK (BAP-16) picks
  both classes up at authoring.

- **BAP-17 — offline-eligible grant claims (reserve + specify).** Reserve the `ba_offline`
  grant-payload claim name in the [registries](docs/design/registries.md) (issuer-set offline
  floor limits: maximum value with explicit currency, maximum offline use count, offline-window
  expiry — a closed nested object; absence means online-only, per
  [R-BAP-1](docs/design/offline-authorization-requirements.md)). [ADR 0016](docs/adr/0016-offline-eligible-grant-claims.md)
  carries the activating-major mechanism to spec quality: the closed `{cnt, cur, max, win}` object,
  the non-authorizing facts contract (an `offline_eligible` flag + `win` only — magnitudes are read
  from the decoded grant, not redacted facts), malformed⇒`:invalid` (online-only is the *absent*
  default), a wire-layer `max × cnt` ceiling (the cross-language SDKs compute exposure in fixed-width
  integers), the `ba_dlg` attenuation composition, and the freshness scoping. **Activation is a
  successor contract-major** — the closed v1 profile rejects `ba_offline` today (the `v0.1.0`-amend
  alternative was considered and is the heavier path: the published governance change-class rule, the
  three corpus-SHA-pinning SDKs, and intra-major fragmentation). This is a design-only slice: zero
  `lib/`/`docs/protocol-v1.md`/`priv/conformance/` wire-behavior change (mirror BAP-11/BAP-14); an
  R-BAP-2 legacy-rejection tripwire is added under `test/`. The offline runtime arc (private
  `bounded_authority` BA-20..23) is successor-major-gated by this reservation.

- Close the **BAP-15 Rust verifier SDK** (Tasks 15–17 + closeout; the library Tasks 1–14 were already
  landed and verified green). `sdks/rust/tests/permissiveness.rs` is the named per-language permissiveness
  battery exercising all six ADR 0014 D6 closures through the public crate boundary (duplicate-reject,
  source-order preservation, raw-lexeme ceiling, single-value, int/float tag distinction, base64url
  pad-bits), each documented with its red-capable mutation. Closure #6 — the `(d)`-class per-node encode
  bounds — is now enforced IN `jcs_encode`'s recursion (depth + total_nodes + a per-node `jcs_bytes`
  early bail), so a hand-built value passed directly to the public primitive cannot force unbounded
  recursion, traversal, or intermediate allocation. This narrows `jcs_encode`'s accept set
  (verdict-preserving on every corpus case — all corpus inputs are decode-bounded within the same
  ceilings; verified lib 331/331, conformance 283/283); the depth and total_nodes guards are proven
  red-capable by live mutation. `sdks/rust/tools/purity_check.sh` + `license_check.sh` enforce the
  lib-path purity invariant (no I/O/clock/RNG/network/env in `src/`; `#![forbid(unsafe_code)]` is the
  compile-time `unsafe` half) and the runtime dependency-license allowlist (15 runtime deps, all
  permissive; dev-deps excluded as non-consumer-facing) — both shellcheck-clean and red-capable. The
  `rust-conformance` CI job (`.github/workflows/sdks.yml`) runs fmt + clippy + the full `cargo test`
  (unit + permissiveness + 283-vector conformance + census) + purity + license on the MSRV 1.81
  toolchain. The publish guard now scans `sdks/*/Cargo.toml` and blocks `cargo publish` /
  `cargo release publish` / `cargo-release publish` / `crate-ci/cargo-release` (ADR 0015). New docs:
  [`sdks/rust/README.md`](sdks/rust/README.md) and the deployment guide
  (`docs/deployment/rust-sdk.md`) (AWS Lambda `provided.al2023`; PostgreSQL
  `plrust` — ed25519-dalek-based verification is NOT plrust-trusted-mode-compatible as built). The three
  signed anchored-export rows in [`docs/design/conformance-contract.md`](docs/design/conformance-contract.md)
  are updated to their landed case ids. Zero wire byte, bound, or verdict change to the Elixir package.
- Close the BAP-15 prerequisite spec/corpus gaps (no-key half). `docs/protocol-v1.md` gains a
  normative **JCS string and number serialization** subsection transcribing RFC 8785 §3.2.2.2–3
  (control-range escapes, raw DEL `U+007F`, ECMAScript `Number::toString` float thresholds
  `e < -6` / `e >= 21`, `-0`→`0`, shortest-round-trip digits — both ECMA §7.1.12.1 and TC39
  §6.1.6.1.20 cited). The conformance corpus grows **259 → 280 cases**: 5 JCS float cases pinning
  both threshold sides, a raw-DEL bare-string case, an astral-codepoint (U+10000) raw-emit case, a
  float `cast_arguments` request-digest case, malformed-UTF-8-member-name and float-magnitude
  `json.decode` rejects, three malformed-IPv6 `uri.normalize` rejects, a key-locator
  protected-only (empty-segment) valid case, three `check_chain` rejects (canonical re-encode,
  sequence-zero, genesis previous-hash forge), the byte-level cross-vendor findings for
  `encode_consumption_entry` (seq-1 nonzero previous), `boundary_anchor_signing_input` (seq-0
  nonzero chain_hash), `encode_anchored_export` (start-anchor sequence binding — a signed start
  anchor carries a bogus sequence, isolating `start.sequence == first_sequence-1`), and
  `verify_anchored_export` (empty chunk). Every invalid case is a one-defect **skip-would-accept**
  construction (a verifier that drops its target check would accept it) — proven by defect-injecting
  each target check in the independent runner and confirming the case flips. The two
  `encode_/verify_anchored_export.maximum_plus_one` `n_a` reasons are corrected to name all three
  bounds (`archive_bytes` + `archive_chunks` + `historical_key_transitions`) and the loader
  representation constraint. The independent Node runner is strengthened to mirror the reference's
  IPv6-structure (node:net `isIP`, replacing a crude charset check), consumption-entry and
  boundary-anchor genesis bindings, `check_chain` positive-`first_sequence` and canonical-rehash
  (hashing the canonical re-encode, not raw row bytes, matching `ConsumptionChain.parse_row/1`),
  anchored-export start/end anchor bindings, and verify per-chunk emptiness — the invariants the new
  cases exposed; every new case is dual-verified (`agreed=280 disagreed=0`). SDK
  `CERTIFIED_INDEX_SHA` constants (Python hex + TypeScript base64url) rebind to the new `index.json`.
  The encode_anchored_export binding case's authoring key is seed-generated and never enters the
  import-boundary census (encode frames signatures, never verifies them), so
  `public_key_fingerprints` stays 8 and the manifest three-partition is unchanged.
- Close the BAP-15 prerequisite corpus gap (signed half). The conformance corpus grows
  **280 → 283 cases** with three signed `verify_anchored_export` cases exercising ADR 0004's
  authenticated key-transition path: `#2` non-monotone chronology (two transitions with non-monotone
  `effective_at` → `invalid_time`), `#2` fingerprint cycle (B→C→B → `invalid_key`), and `#3`
  one-key/zero-transition valid (the equal start/end-time same-key case). Three new seeded Ed25519
  keys join `public_key_fingerprints` (**8→11**) since the export verifier imports them; their seeds
  never enter the corpus. The independent Node runner is strengthened to mirror the reference's
  cross-transition invariants (`validate_expected_key_path`): strictly-increasing transition times,
  fingerprint no-cycle, and the end-anchor chronologically at-or-after the last transition — gates
  the per-element compact checks cannot express. Every new invalid case is a one-defect
  **skip-would-accept** construction (a monotone / no-cycle variant accepts); every new case is
  dual-verified (`agreed=283 disagreed=0`). SDK `CERTIFIED_INDEX_SHA` constants (Python hex +
  TypeScript base64url) rebind to the new `index.json`; the vector manifest's canonical fingerprint
  set grows 19→22 and the corpus partition 8→11.
- Record the SDK graduation and publish-topology decision: cross-language verifier SDKs are
  authored under `sdks/` (per [ADR 0014](docs/adr/0014-cross-language-verifier-sdks.md)) but each
  graduates to its own per-SDK repository (`bounded_authority_protocol_<lang>`) on first
  publication; the decision boundary is publication irreversibility, not SDK count
  ([ADR 0015](docs/adr/0015-sdk-graduation-and-publish-topology.md)). Zero wire byte, bound, or
  verdict change.
- Ship the ADR 0015 enforcement layer: a tracked pre-commit hook
  (`scripts/hooks/pre-commit`, single pattern source `scripts/check_sdk_publish_infra.sh`, installed
  via `scripts/install-hooks.sh`) and the `sdk-publish-guard` CI job
  ([`.github/workflows/sdk-publish-guard.yml`](.github/workflows/sdk-publish-guard.yml)) that together
  reject SDK registry-publish infrastructure committed to the monorepo. Honesty limit: catches
  committed publish infrastructure, not a literal ad-hoc publish run against a working tree; CI on
  main is the non-bypassable backstop, the local hook is honor-system. Bypass:
  `git commit --no-verify`.
- Add ROADMAP rows BAP-15 (Rust verifier SDK) and BAP-16 (Go verifier SDK) — typed reimplementations
  of the frozen v1 profile from spec + corpus alone, authored under `sdks/` and graduating per
  [ADR 0015](docs/adr/0015-sdk-graduation-and-publish-topology.md). Post-1.0 rows; no code shipped.
- Ship cross-language verifier SDKs (TypeScript `@bounded-authority/verifier` + Python
  `bounded-authority-verifier`) under `sdks/` — typed reimplementations of the frozen v1 profile from
  the spec + corpus alone, with no code-level derivation from the Elixir reference
  ([ADR 0014](docs/adr/0014-cross-language-verifier-sdks.md)). Each passes all 283 conformance vectors
  (recomputed from scratch), asserts the corpus `index.json` SHA at startup, and proves every
  permissiveness closure red-capable via a per-language mutation-gate. They are verifiers, not
  authority runtimes (no key selection, replay reservation, or execution grant). NOT in the Hex
  package `files:` (they are not Elixir). The `sdks-conformance` CI job
  ([`.github/workflows/sdks.yml`](.github/workflows/sdks.yml)) gates every `sdks/**` /
  `priv/conformance/**` change. Zero wire byte, bound, or verdict change to the Elixir package.
- Draft the capability-authorization extension as a pre-submission package for the MCP
  experimental-extension track (`docs/extensions/` — a draft `.mdx`, a draft
  Extensions-Track SEP, and an Agent Payments Protocol (AP2) mandate-mapping note;
  [ADR 0013](docs/adr/0013-capability-authorization-extension.md)), documenting the already-normative
  v1 protocol. Identifier `io.bounded-authority/capability-authorization`. Partial conformance to the
  MCP official-submission bar (SEP-2133); official submission is gated on external preconditions
  (a reference implementation in an official MCP SDK, a working group + Extension Maintainers +
  sponsor, SEP acceptance, and
  IANA registration of the `ba_*`/`ba+*` names via BAP-12). The extension documents are repo-tracked,
  excluded from the Hex package census (pre-submission drafts). Zero wire byte, bound, or verdict
  change.
- Publish the governance policy (change classes, change-control triggers, errata no-verdict-flip
  prohibition, deprecation windows, security-release handling) as a standalone normative project
  document ([docs/governance.md](docs/governance.md); [ADR 0011](docs/adr/0011-published-governance.md))
  — a companion republication of the charter § Governance, which remains the authoritative source.
  [SECURITY.md](SECURITY.md) cross-references the verdict-change rule; the
  [errata registry](docs/errata.md) header retargets to governance.md as the published policy home;
  `SECURITY.md`, `docs/governance.md`, and `docs/design/standards-track.md` join
  `.forge/critical-surfaces` (SECURITY.md a retroactive gap close since BAP-06). Zero wire byte,
  bound, or verdict change.
- Resolve a contradiction in the deprecation/security policy: the twelve-month deprecation-window
  minimum (`REQ1-EVO-deprecation-window-minimum`) is scoped to planned deprecations, and a security
  contract-major (one remediating a verdict-changing vulnerability) is exempt — its accelerated
  overlap window is published at announcement, proportional to severity, and a deployment-decided
  sunset, never a silent change ([ADR 0012](docs/adr/0012-security-release-accelerated-deprecation-window.md);
  charter § The evolution contract + § Governance; [governance.md](docs/governance.md) re-synced).
  Deployment-policy refinement only — zero wire byte, bound, or verdict change.

### Changed

- **Documentation corrections (2026-08-18 alignment-audit items 1, 4, 7).** SECURITY.md's "most
  recent package-bearing verified source" re-anchored from the BAP-06 head to the SDK
  behavioral-closure cluster head `c281938`, with the superlative date-bounded ("as of
  2026-08-18") so the claim expires visibly instead of silently: CI run 32118915019 and
  supply-chain run 32118915034 passed at that exact revision, and the doc's own verification
  recipe was re-run against the downloaded archive — checksum OK (ubuntu-built archive SHA-256
  `c9b5b0cf…`), SLSA build-provenance attestation verified constrained to this repository, the
  supply-chain workflow, `refs/heads/main`, that source digest, and GitHub-hosted runners; BAP-06
  `4c64be3` joins the earlier-heads list. The TypeScript conformance runner's startup comment
  miscited the corpus binding as "ADR 0014 D8" (enforcement posture) — corrected to D4, matching
  the file's own correct citations. usage-rules rule 13's era-frozen "BAP-04 verification is
  implemented" now states the v1 verification surface is implemented. Docs-only — no code, wire,
  bound, or verdict change.
- **Documentation reconciliation — status prose frozen at the BAP-10 era (alignment-audit items
  1–9).** `AGENTS.md` "Current state" rewritten from the tracker: names the closed set (BAP-00..06,
  08..11, 13..15, 17), the three verifier SDKs under `sdks/` (ADR 0014/0015 graduation topology,
  none published), ADRs 0001–0016, and the 283-case corpus + verifier CLI. README's status section
  corrected the same way; the BAP-07 framing in README, SECURITY.md, and the release-candidate
  contract now records the maintainer decision (Hex publication deferred — internal consumption via
  the `v0.1.0` git tag at `c65d3be`, not a registry pin). SECURITY.md now cites the most recent
  package-bearing verified head (BAP-06 `4c64be3`) with the BAP-05/BAP-04 history retained. The
  TypeScript and Python SDK READMEs no longer point Install at registry URLs that do not resolve —
  they carry the Rust README's reserved-identifier/not-yet-published framing, and "one of two" SDK
  prose corrected to three; `sdks/README.md` floor corrected to Node >= 22 with the not-published
  disclaimer. `docs/design/offline-authorization-requirements.md` status now points at the closed
  BAP-17 row (ADR 0016). ROADMAP repairs: BAP-17 added to the complete list, the Rust façade count
  corrected 15 → 17 (the exported public contract), and amendment #2's garbled closure/residual
  splice repaired so the `assemble_compact` maximum-bounds residual stands as its own sentence,
  consistent with amendment #3's restatement. Docs-only — no code, wire, bound, or verdict change.

## 0.1.0 release-candidate record — 2026-08-17 (published above as [0.1.0])

### Added

- Initialize the public Apache-2.0 repository and cold-start architecture authority.
- Define the deterministic verifier boundary, public/private dependency direction, protocol
  charter, threat model, conformance contract, ADR, and implementation roadmap.
- Close BAP-00 after public-remote verification, cross-repository documentation reconciliation,
  security-contract hardening, local-link/browser checks, independent reviews, and tamper-gate
  verification.
- Add the unpublished `:bounded_authority_protocol` 0.1.0 Mix package with zero production
  dependencies, no application callback, and no supervision tree.
- Enforce the pure-library boundary across source AST, compiled imports, application metadata,
  dependency declarations, and the exact packed/unpacked Hex archive.
- Add full local quality, coverage, documentation, advisory, closed license, CycloneDX,
  fresh-consumer, public CI, checksum, provenance-attestation, and SBOM-attestation gates.
- Freeze the normative v1 header, claim, selector, JSON, encoding, URI, domain-separator, bound,
  and fixed-error tables with primary RFC and OTP provenance.
- Add bounded ordered JSON decoding with recursive duplicate rejection, strict canonical
  base64url decoding, and a protected-header-only untrusted key locator.
- Add allow, deny, malformed, exact-boundary, deterministic malformed-input sweep, symmetric
  numeric-magnitude, package-consumer, and purity-boundary proof.
- Enforce raw numeric-lexeme bytes and exact decimal magnitude before OTP conversion; validate the
  companion Draft 2020-12 schemas with an independent validator and distinguish their structural
  code-point limits from the normative UTF-8 byte contract.
- Align the tracked roadmap with Forge's authored-row contract while preserving BAP-00 through
  BAP-07 identities and exact dependency labels.
- Document the exact public `BoundedAuthorityProtocol.V1.Json.decode/2` and
  `BoundedAuthorityProtocol.V1.Base64Url.decode/2` surfaces, tightening-only positive-integer
  limits, structural-schema boundary, and fixed value-free errors.
- Extend the packed external consumer to exercise both decoder success and rejection paths, and
  add mutation-red proof that escaped string content cannot hide a following over-limit number.
- Reconcile the BAP-02 final trusted-main receipts and degraded peer-review record.
- Add deterministic standard compact-JWS grant and RFC 9449 holder-proof producers, exact external
  signature assembly, and bounded grant/proof decoders.
- Add RFC 8785 canonical JSON, RFC 7638 public Ed25519 JWK thumbprints, bounded HTTPS URI
  normalization, type-preserving request digests, and conjunctive selector evaluation.
- Add standalone raw-grant verification and combined raw-envelope verification with exact
  issuer/audience/time/holder/request/nonce/operation bindings and redacted, non-authorizing
  verified facts.
- Add public-only grant/holder-proof vectors, independent Node verification, exact public-key
  census, meaningful byte-tamper and duplicate-member cases, portable timing/allocation bounds,
  architecture census updates, and unpacked external-consumer API proof.
- Close BAP-03 at package-bearing head `f322e08bba665374599b9f53c362966b6b59710a`
  after the supported CI matrix, complete quality/package boundary, independent Node verification,
  exact archive checksum, SLSA provenance, and CycloneDX SBOM attestation passed. The single final
  review admitted five findings; all five were fixed in one pass without review recursion.
- Add closed canonical consumption rows with domain-separated hashes and mandatory-boundary raw
  chain verification for genesis and continued ranges.
- Add deterministic standard-JWS boundary anchors and authenticated historical-key transitions,
  including derived RFC 7638 fingerprints and lower-inclusive/upper-exclusive validity windows.
- Add deterministic binary anchored-export framing and atomic raw-chunk verification of complete
  digest, exact out-of-band object version, exact EOF, ordered key rollover, both signed
  boundaries, and every canonical row.
- Add closed fixed-redacted non-authorizing chain, anchor, transition, and anchored-export facts;
  exact tightening-only archive bounds; constant-time fixed-width comparisons; and expanded
  source/BEAM architecture accounting.
- Add five Draft 2020-12 structural schemas and public-only same-key, rollover, shortened,
  relinked, same-ID/equal-time, signed cross-chain, signed reverse-time, and signed invalid-genesis
  evidence. The project-independent Node verifiers prove an exact two-way eleven-key census split
  into exact per-verifier sets observed at their public-key import boundaries,
  49 named decoded-byte, structural, boundary, coverage, chronology, and limit cases, two direct
  valid chain cases, and seven signed semantic-edge cases; the isolated mutation gate proves 47
  source-level invariants go red. Published fixture verdicts and complete redacted
  chain/anchor/transition/export facts are exact-checked rather than treated as commentary.
- Add the maximum-count, maximum-width worst-of-20 chain/archive resource gate, with every sample
  isolated in a fresh monitored process, plus ADR 0004, normative documentation, and packed
  external-consumer coverage.
- Validate every nested expected field and historical-key shape before archive hashing or parsing;
  reject duplicate outer conformance JSON members; and detect self-identifying Ed25519 PKCS#8 DER
  regardless of its surrounding field name.
- Make Ed25519 public-key/signature and SHA-256 digest widths immutable bounds so tightening cannot
  produce or admit a structurally nonconforming cryptographic value.
- Centralize strict StringOrURI validation across grant/proof and BAP-04 identifiers so malformed
  percent escapes and non-URI bytes cannot enter any producer or verifier path.
- Preserve exact package-boundary accounting across supported Elixir compiler versions and pin
  bitstring match sizes required by Elixir 1.20.
- Close BAP-04 at package-bearing head `c4d7716de6499f29524e60638207b1c36e9484b3`
  after the supported CI matrix, complete quality/package boundary, independent Node verification,
  47/47 mutation battery, maximum-shape resource gate, exact archive checksum, SLSA provenance,
  and CycloneDX SBOM attestation passed.
- Author roadmap rows BAP-08 (capability-authorization extension proposal for the MCP
  `modelcontextprotocol/ext-auth` extensions repository plus an AP2 mandate-mapping note;
  depends on BAP-04 only) and BAP-09 (thin TypeScript and Python verifier SDKs consuming only
  the published spec and vectors; depends on BAP-05). Each row's own ADR lands when its work
  starts; neither changes any wire format, limit, or verification rule.
- Close the check_envelope selector-binding gap: add a non-trivial (`equals`) selector valid case
  plus an `invalid_selector` case, teach the independent Node verifier to evaluate grant selectors,
  and add a `selector-reject` mutation — so a verifier that ignores grant selectors now fails the
  corpus. Uses two new deterministic conformance keypairs (census 6→8); a companion
  `proof_signing_input` valid case carries the new holder key as a labeled field so the
  cross-verifier census discovery scan finds it (corpus 212→215).
- Close the check_envelope authority-binding gap the same re-signing capability exposed: the
  holder (`cnf.jkt`), grant (`ath`), request-argument (`ba_req`), and operation (`ba_op`) bindings
  had no corpus case isolating them, so a verifier omitting any one of them still scored a perfect
  corpus run — omitting the holder binding accepts any holder's proof against any grant. Add four
  `invalid_claim` cases (each a one-defect variant of a shared valid base, with the named binding
  as the sole rejecter) and four matching mutation-battery entries. The `ba_op` case needs a
  hand-built proof payload: the request digest is computed over the server-derived operation, never
  over the proof's own `ba_op` claim, so only a dishonest producer — which the façade cannot be —
  emits a proof whose two operation fields disagree (corpus 215→219).
- Add an empty-path `invalid_selector` case so the independent runner's selector shape and width
  validation is falsifiable: the official rejects an empty selector path at grant decode, and a
  matcher treating `[]` as "the root" would accept what the official refuses (corpus 219→220).
- Close the independent runner's remaining permissiveness against the official decoder, so it can
  no longer certify a grant the reference implementation refuses. Six divergences, each now carried
  by a corpus case and a mutation entry: an extra member inside the closed `cnf` map; a
  non-printable byte in an operation name (the official requires printable ASCII); a structurally
  invalid selector on a NON-matching operation, and duplicate operation names (the official
  validates every operation and enforces global name uniqueness, not just the requested one); a
  lone surrogate in a selector path (the official requires valid UTF-8); and a `__proto__` member
  in a selector value, which the tagged projection silently dropped to the prototype setter so that
  two structurally different values compared equal — a collapse that reached the request digest as
  well as selector matching. Selector values are now also held to the protocol JSON bounds. The
  same operation validation is applied on `verify_grant`, which reaches the same official decode
  path (corpus 220→247; `check_envelope/invalid_encoding` 6, `verify_grant/invalid_encoding` 5).
- Add `.gitleaks.toml`: the `jwt` and `generic-api-key` rules are allowlisted for the conformance
  corpus and vector paths only, where all 283 findings are JWT-shaped high-entropy public test
  material (109 `jwt`, 174 `generic-api-key`, the latter entirely key fingerprints). Every other
  default rule still applies in those trees — a `ghp_…` token committed there is still caught —
  and every rule applies everywhere else. Stated residual: a credential matching ONLY those two
  rules, under those two machine-generated fixture directories, is not flagged.
- Complete the payload-field decode mirror, closing the last of the runner's permissiveness
  against the official decoder. The runner previously validated only the grant/proof fields the
  expected-context comparison happened to touch; it now mirrors decode_grant_fields /
  decode_proof_fields field for field — issuer/grant-id/audience StringOrURI validity and length,
  audience count bound and uniqueness, coherent times (iat<exp, nbf<exp), method token charset,
  invocation UUID shape, and htu normalization — wherever it reads a grant or proof payload
  (check_envelope, verify_grant, decode_grant, decode_proof). Nine cases on the DECODE surfaces
  (no expected context to mask the validator, so each is the sole reject reason) with six mutation
  entries prove them; corpus 236→247. One bound stays out of scope by construction: the aggregate
  total_nodes/depth budget the official applies across the whole payload cannot appear inline (such
  an input exceeds string_bytes) and these surfaces take no `.raw` sidecar, so it is exercised at
  the json.decode surface instead. Two further field checks the first mirror pass missed,
  found by the closeout review: the StringOrURI structural gate (the official validates
  iss/jti/aud through URI.new, so it rejects a non-numeric port, an unterminated IPv6
  literal, or a double `@`; the byte-only mirror accepted them — now matched to URI.new
  across a 56-input boundary set, using node:net for IPv6 literals so the mirror is neither
  looser nor stricter), and the optional proof nonce (present must be a 1..nonce_bytes
  well-formed string). Each with a decode case and mutation entry; corpus 236→247.
- Close three further runner/official divergences the final cross-vendor pass found in selector
  value validation, each now carried by a corpus case and a mutation entry: the magnitude bound
  (the official caps |value| at 9007199254740991 and rejects 2^53; the runner checked only
  finiteness), and a one-byte floor on object member keys that the official does not impose — the
  runner rejected `{"":1}`, which `Json.decode` and `Jcs.encode` both accept, making it STRICTER
  than the reference. Corpus 233→236 (a valid empty-object-key case pins the strictness fix).
- Adopt the standards track charter (ADR 0006, `docs/design/standards-track.md`,
  `docs/design/registries.md`, `docs/errata.md`): evolution above the permanently closed wire
  format via parallel contract-majors with published deprecation windows; the current profile
  named as cryptographic suite `BAP1-Ed25519-SHA256` with an ML-DSA succession path and
  cross-suite countersignature design for long-retention evidence; RFC 2119 requirement
  identifiers with corpus traceability and IANA registration templates as release-gating roadmap
  rows; delegation-with-attenuation decided (chained grants, `ba_dlg`/`ba_obo`/`ba+cap-delegated`
  reserved, the conjunctive selector algebra as the attenuation language, no caveat DSL);
  revocation and principal-binding deployment guidance homed; governance (change classes, errata
  registry with the no-verdict-flip invariant, comment-window triggers) published. No wire byte,
  bound, or verdict changes; new roadmap rows gate first publication on the unretrofittable items.
- Add the portable v1 conformance corpus (247 cases across 28 surfaces with a total
  surface × class applicability matrix, `.raw` sidecars for oversize wire inputs) and the pure
  `Conformance.Corpus`/`Runner`/`Report` core that loads, executes, and reports agreement.
- Harden the corpus against vacuous green: author the invalid vectors the crypto verifying
  surfaces were missing — algorithm-confusion (`alg:"none"`), meaningful-byte signature/commitment/
  anchor tampers (via a target-addressed tamper loader that binds a single-byte flip to the
  compact / grant / proof / row / chunk bytes), and archive/chain/envelope binding failures — plus
  exact-bound and maximum-plus-one pairs for every `json.decode` structural limit. Each new invalid
  vector is a one-defect construction confirmed rejected by BOTH the official facade and the
  independent Node runner; every remaining not-applicable applicability cell carries a falsifiable
  inexpressibility reason. The independent Node runner gained the request-binding (method / URI /
  invocation / operation / nonce) and object-name-byte checks the new vectors surfaced.
- Add the deterministic offline verifier CLI (escript `bounded_authority_conformance`,
  `--corpus DIR` required, exits 0/1/2) with an exact-path purity carve-out (File/IO in `cli.ex`,
  `System.halt` in `cli/main.ex` only) enforced by the architecture gate.
- Add the independent Node second-implementation runner
  (`conformance/corpus_independent.mjs`, node:* only) that recomputes every corpus verdict from
  scratch — making the corpus normative. Evolve the public-key census to three partitions
  (bap03 + chain_archive + corpus = the canonical set); the corpus self-census is hard two-way.
- Add stream_data property gates (JCS determinism/idempotence, base64url round-trip/pad rejection,
  URI normalization idempotence, facade closure totality) and a deterministic-PRNG fuzz gate.
- Add the source-isolated conformance mutation battery (proving the corpus integrity, CLI
  carve-out, and runner-verdict gates actually catch their named failures); wire both mutation
  batteries into `mix quality`. CI pins Node 20 for the quality job.
- Close BAP-05 with ADR 0005 (corpus formats, sidecar rule, published-artifacts definition,
  applicability matrix + n_a criterion, census evolution, CLI contract, carve-out shape,
  gate set). The corpus ships in the published package; the fresh-consumer check runs the packaged
  escript against the packaged corpus, proving published-set sufficiency.
- Close the remaining independent-runner permissiveness residuals surfaced by a cross-vendor design
  review (corpus grows 247 → 259 cases). The Node runner now mirrors the official on: the integer
  magnitude bound for every integer claim (a proof `iat` of 2^53 is rejected); the full
  `request_digest` gate on `cast_arguments` — operation validity, per-node bounds, `total_nodes`, and
  `jcs_bytes` on the type-tagged projection, not an unbounded digest; and whole-payload container
  depth on grant/proof payloads (a payload nested past depth 32 is rejected at parse). Fix the sibling
  `jsonDecode` per-node-type depth error (it rejected a 32-deep scalar-inner nest the official
  accepts — the too-strict direction that fails a conforming verifier). Twelve exact-bound /
  maximum-plus-one cases and seven source mutations pin each, each verdict confirmed against the
  official facade. ADR 0005 corrected: whole-payload depth and value-carried `cast_arguments`
  node/byte bounds are inline-expressible and now tested; only compact-carried whole-payload
  `total_nodes` and an inline 65-member `object_members` remain enforced-without-a-red-case.
- Close BAP-06: lock the 0.1.0 release-candidate public API surface (enumerated in the
  [release-candidate contract](release-candidate-contract.md) and enforced by the
  `@compiled_export_allowances` architecture-gate pin; [ADR 0008](adr/0008-release-candidate-contract.md)),
  add the `release.candidate` reproducibility gate (two cache-isolated builds, byte-equal SHA-256,
  wired into `mix quality`), and author the candidate-facing docs (release-candidate-contract.md,
  SECURITY.md, CHANGELOG `[0.1.0]`, README). Zero wire byte, bound, or verdict change. Published
  2026-08-20 — BAP-07 published the exact candidate after the connected gates passed.
