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 failedworking-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 consistencyocaml/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-155CompactJws.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, boolint; 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 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 (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 (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 (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/:259scanCompact/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 (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). ADR 0016 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 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 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, -00, 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 chainhash), 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_onen_areasons 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:netisIP, replacing a crude charset check), consumption-entry and boundary-anchor genesis bindings,check_chainpositive-first_sequenceand canonical-rehash (hashing the canonical re-encode, not raw row bytes, matchingConsumptionChain.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). SDKCERTIFIED_INDEX_SHAconstants (Python hex + TypeScript base64url) rebind to the newindex.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), sopublic_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_atinvalid_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) 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). 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) 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. 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). 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) 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), 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; ADR 0011) — a companion republication of the charter § Governance, which remains the authoritative source. SECURITY.md cross-references the verdict-change rule; the errata registry 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; charter § The evolution contract + § Governance; 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 and enforced by the @compiled_export_allowances architecture-gate pin; ADR 0008), 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.