All notable changes to bounded_authority_protocol are documented here.
[Unreleased]
[0.1.2] — 2026-08-20
Changed — release/verification gate hardening (no wire, bound, or verdict change)
mix conformance.verifypins 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 shippedcorpus+schemasbut notvectors, 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_protocol0.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. Thev0.1.0tag moved to the published commit so hexdocssource_reflinks 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 missingpreviousHash, empty anchor) passed the gate and the clause-3 hoist then derefed the missing field — aTypeErrorescape pasttrying(); the nested members are now fully specified (chain/anchor/transition field shapes,archived.chunksas a bytes sequence), red-leg + mutation proven (opaque specs → RED). Claude (should-fix): the Python and Rust hoists walkedexpected.transitionsper-element BEFORE thekey_transitionscount 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-levelbytearraytolerance 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_bytesgates (round 12) and allocation bounds (round 15) on untouched surfaces. - ADR 0018's named
assemble_compactdivergence closed: caller bounds threaded through assembly in all three SDKs. The reference takes limits at assemble (runtime.ex:147-155→CompactJws.assemble— encoded-segment bounds, compact_bytes, and the kind re-parse againstBounds.coerce(limits)); the SDKs hardcoded maximum. Each SDK now takes an optional bounds parameter (Pythonbounds: Bounds | None = None, TypeScriptbounds?: Bounds, Rustbounds: Option<&Bounds>— an additive public-API change; absent bounds = maximum, backward compatible) and threads it through the reference's gates.signature_bytescarries 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/TypeErrorpast the closedResult, and TypeScript — whose coercion the ADR had classed as a "disclosed margin" — silently acceptedrequestDigest(123/true/null/{})(digesting the coerced text) and threwTypeErrorpasttrying()on 109 parameter positions. Both dynamic SDKs now gate every façade argument's shape before the body runs (Python: annotation-driven_closed_shapeover the declared dataclass shapes,bool≠int; TS:closedShapewith per-façade shape specs). Rust excludes the class by typing. The per-SDK family-sweep batteries intests/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/3facade over the existing bounded runtime primitive;/2remains exactly/3with 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 ofba_dlg/ba+cap-delegatedin 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_compactmaximum-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 atd9df0bfon Node 20 EOL) and its@noble/curves"optional browser-build path" claim withdrawn (never present insdks/history); ADR 0008's BAP-07 "publishes the exact candidate" consequence annotated with the maintainer's Hex-publication deferral (thev0.1.0git tag atc65d3beis 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-stringrequest_digestoperation /ConsumptionEntry.chain_idraisesAttributeErrorpast 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, mirroringruntime.ex:237/:259—scanCompact/scan_compactintentionally stays shape-only, mirroringCompactJws.scan); (2) canonical-form equality for boundary-anchor + key-transition compacts — the protected header AND payload segments must equal the exact JCS re-encoding (mirroringboundary_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_bytesgates 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_compactstays 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
checkChainre-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_exportnow enforces the Elixir reference producer's FULL validation contract (anchored_export_codec.exencode): expected-side consistency (chain_id binding of both anchors + all transitions; the start/end sequence + hash bindings), a fullcheck_chainre-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-tightenedexpected.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).
- 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
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 fourdecode_*_partsfns (REQ1-BOUNDS-fixed-widths, mirroringruntime.ex:237/:259,boundary_anchor_codec.ex:88,key_transition_codec.ex:120) — public verdict flips ondecode_grant/decode_proof(accepted a wrong-width signature at decode) andencode_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 assertjcs_encode(value) == segment_bytesfor the protected header AND payload (mirroringboundary_anchor_codec.ex:95-96,118-119+key_transition_codec.ex:127-128,151-152), so a non-canonical (member-reordered) segment is rejected acrossassemble_compact,verify_historical_anchor,verify_key_transition,encode_anchored_export, andverify_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 viaanchor_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_exportgates 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_offlinegrant-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 (anoffline_eligibleflag +winonly — magnitudes are read from the decoded grant, not redacted facts), malformed⇒:invalid(online-only is the absent default), a wire-layermax × cntceiling (the cross-language SDKs compute exposure in fixed-width integers), theba_dlgattenuation composition, and the freshness scoping. Activation is a successor contract-major — the closed v1 profile rejectsba_offlinetoday (thev0.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: zerolib//docs/protocol-v1.md/priv/conformance/wire-behavior change (mirror BAP-11/BAP-14); an R-BAP-2 legacy-rejection tripwire is added undertest/. The offline runtime arc (privatebounded_authorityBA-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.rsis 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 INjcs_encode's recursion (depth + total_nodes + a per-nodejcs_bytesearly bail), so a hand-built value passed directly to the public primitive cannot force unbounded recursion, traversal, or intermediate allocation. This narrowsjcs_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.shenforce the lib-path purity invariant (no I/O/clock/RNG/network/env insrc/;#![forbid(unsafe_code)]is the compile-timeunsafehalf) and the runtime dependency-license allowlist (15 runtime deps, all permissive; dev-deps excluded as non-consumer-facing) — both shellcheck-clean and red-capable. Therust-conformanceCI job (.github/workflows/sdks.yml) runs fmt + clippy + the fullcargo test(unit + permissiveness + 283-vector conformance + census) + purity + license on the MSRV 1.81 toolchain. The publish guard now scanssdks/*/Cargo.tomland blockscargo publish/cargo release publish/cargo-release publish/crate-ci/cargo-release(ADR 0015). New docs:sdks/rust/README.mdand the deployment guide (docs/deployment/rust-sdk.md) (AWS Lambdaprovided.al2023; PostgreSQLplrust— ed25519-dalek-based verification is NOT plrust-trusted-mode-compatible as built). The three signed anchored-export rows indocs/design/conformance-contract.mdare 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.mdgains a normative JCS string and number serialization subsection transcribing RFC 8785 §3.2.2.2–3 (control-range escapes, raw DELU+007F, ECMAScriptNumber::toStringfloat thresholdse < -6/e >= 21,-0→0, shortest-round-trip digits — both ECMA §7.1.12.1 and TC39 §6.1.6.1.20 cited). The conformance corpus grows 259 → 280 cases: 5 JCS float cases pinning both threshold sides, a raw-DEL bare-string case, an astral-codepoint (U+10000) raw-emit case, a floatcast_argumentsrequest-digest case, malformed-UTF-8-member-name and float-magnitudejson.decoderejects, three malformed-IPv6uri.normalizerejects, a key-locator protected-only (empty-segment) valid case, threecheck_chainrejects (canonical re-encode, sequence-zero, genesis previous-hash forge), the byte-level cross-vendor findings forencode_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, isolatingstart.sequence == first_sequence-1), andverify_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_exportcases exercising ADR 0004's authenticated key-transition path:#2non-monotone chronology (two transitions with non-monotoneeffective_at→invalid_time),#2fingerprint cycle (B→C→B →invalid_key), and#3one-key/zero-transition valid (the equal start/end-time same-key case). Three new seeded Ed25519 keys joinpublic_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). SDKCERTIFIED_INDEX_SHAconstants (Python hex + TypeScript base64url) rebind to the newindex.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 sourcescripts/check_sdk_publish_infra.sh, installed viascripts/install-hooks.sh) and thesdk-publish-guardCI 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+ Pythonbounded-authority-verifier) undersdks/— 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 corpusindex.jsonSHA 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 packagefiles:(they are not Elixir). Thesdks-conformanceCI job (.github/workflows/sdks.yml) gates everysdks/**/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. Identifierio.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 theba_*/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, anddocs/design/standards-track.mdjoin.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-256c9b5b0cf…), SLSA build-provenance attestation verified constrained to this repository, the supply-chain workflow,refs/heads/main, that source digest, and GitHub-hosted runners; BAP-064c64be3joins 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 undersdks/(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 thev0.1.0git tag atc65d3be, not a registry pin). SECURITY.md now cites the most recent package-bearing verified head (BAP-064c64be3) 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.mdfloor corrected to Node >= 22 with the not-published disclaimer.docs/design/offline-authorization-requirements.mdstatus 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 theassemble_compactmaximum-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_protocol0.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/2andBoundedAuthorityProtocol.V1.Base64Url.decode/2surfaces, 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
f322e08bba665374599b9f53c362966b6b59710aafter 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
c4d7716de6499f29524e60638207b1c36e9484b3after 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-authextensions 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 aninvalid_selectorcase, teach the independent Node verifier to evaluate grant selectors, and add aselector-rejectmutation — so a verifier that ignores grant selectors now fails the corpus. Uses two new deterministic conformance keypairs (census 6→8); a companionproof_signing_inputvalid 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 fourinvalid_claimcases (each a one-defect variant of a shared valid base, with the named binding as the sole rejecter) and four matching mutation-battery entries. Theba_opcase needs a hand-built proof payload: the request digest is computed over the server-derived operation, never over the proof's ownba_opclaim, 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_selectorcase 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
cnfmap; 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 onverify_grant, which reaches the same official decode path (corpus 220→247;check_envelope/invalid_encoding6,verify_grant/invalid_encoding5). - Add
.gitleaks.toml: thejwtandgeneric-api-keyrules are allowlisted for the conformance corpus and vector paths only, where all 283 findings are JWT-shaped high-entropy public test material (109jwt, 174generic-api-key, the latter entirely key fingerprints). Every other default rule still applies in those trees — aghp_…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
.rawsidecar, 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}, whichJson.decodeandJcs.encodeboth 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 suiteBAP1-Ed25519-SHA256with 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-delegatedreserved, 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,
.rawsidecars for oversize wire inputs) and the pureConformance.Corpus/Runner/Reportcore 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 everyjson.decodestructural 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 DIRrequired, exits 0/1/2) with an exact-path purity carve-out (File/IO incli.ex,System.haltincli/main.exonly) 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
iatof 2^53 is rejected); the fullrequest_digestgate oncast_arguments— operation validity, per-node bounds,total_nodes, andjcs_byteson 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 siblingjsonDecodeper-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-carriedcast_argumentsnode/byte bounds are inline-expressible and now tested; only compact-carried whole-payloadtotal_nodesand an inline 65-memberobject_membersremain 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_allowancesarchitecture-gate pin; ADR 0008), add therelease.candidatereproducibility gate (two cache-isolated builds, byte-equal SHA-256, wired intomix 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.