All notable changes to bounded_authority_protocol are documented here.

[Unreleased]

[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.