Skip to content

Release 2.0.0: CLI correctness fixes, AP2 format pin, single-source versioning, hardened release gates - #104

Merged
EauDoon merged 18 commits into
mainfrom
upgrade/v2.0.0
Oct 9, 2026
Merged

EauDoon merged 18 commits into
mainfrom
upgrade/v2.0.0

Conversation

@EauDoon

@EauDoon EauDoon commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Summary

This PR releases 2.0.0. It cuts the 169 commits that have accumulated on main since v1.2.0, plus the 18 commits below.

It is a major release because 1.2.0 shipped the public CreateApiServerOptions.allowRemote option and the serve --allow-remote flag, and both are gone on main. This PR also changes some CLI exit codes and accepted inputs on purpose. Protocol 1.0.0, engine 1.0.0, native artifact bytes, schemas and decision pins do not change.

The PR does four things:

  • Fixes CLI correctness. operator compare now fails when the current case is invalid. decide and preview apply the API's complete-case gate. Stopping serve with Ctrl+C no longer leaves a stale lock. serve host, port and scenario mistakes get proper usage and availability codes, and replay and appeal envelopes are validated strictly.
  • Removes the coupling that blocked any release. Every AP2 artifact carried the package release, so a version bump would have invalidated retained Packs. That label is now a frozen format release, AP2_DISPUTE_FORMAT_RELEASE = "1.2.0". Two frozen Packs written by the released 1.2.0 code pin what that buys. The release label alone never invalidates a retained Pack, and a 1.2.0 Pack's digest and anchor still check out. Verification re-derives the resolution under the current gates, though, so a 1.2.0 Pack whose delegated Mandate payload has no exp now verifies as unresolved: delegated payloads require expiry since fc27d70. The upgrade notes say so.
  • Makes one source of truth for the version. package.json drives a new scripts/version.mjs. Its check command fails CI on drift. Its sync command runs from the npm version hook. Its notes command prints the GitHub Release notes. The CHANGELOG now follows Keep a Changelog.
  • Hardens the gates and docs.
    • The license gate fails closed when node_modules is missing.
    • Conformance now runs and publishes the external review fixtures.
    • The release workflow verifies without write tokens and runs a packed-content private-key scan.
    • Windows CI runs the full suite, Node 26.11.1 is in the matrix, and the CLI module is inside the coverage gate.
    • There is a new CLI reference, and the docs no longer state input limits the code does not enforce.

Review findings addressed

ID Severity Finding Commit
F1 high operator compare exits 0 with ok: true when the current assessment is invalid 47cf3e6
F2 high Ctrl+C on serve --store leaves a stale .lock, and later writes fail with exit 5 "state conflict" 607ec56
F3 medium CLI decide/preview skip the API complete-case gate and persist fabricated malformed-case decisions with exit 0 3eb18d5
F4 medium examples/README.md runs decide on a bundle file that nothing produces 3eb18d5
F5 low Invalid serve --host reports "Protocol artifact is invalid" (exit 3) after locking the store 0a61412
F6 low An unknown simulate scenario reports "Artifact validation failed." and never lists valid names 0a61412, 6e5fc77
F7 medium replay/appeal accept unvalidated envelopes and echo arbitrary checkpoint values 87ca43b
F8 medium The license gate passes vacuously without node_modules e949b85
F9 medium Four doc lines state input caps the code does not enforce ed659ff
F10 medium review, serve, help, version and the full exit-code table are undocumented ed659ff, 6e5fc77
F11 medium Conformance command and declaration omit the external review fixtures; nothing detects drift d2a16ee
F12 high The release label is coupled into the AP2 Pack wire format b81432f
F13 high 169 commits, including a removed public option and flag, unreleased under 1.2.0; CHANGELOG not Keep a Changelog a9655ae, 9158024, f5ec9d2
F14 low Version metadata confusing or unmaintained; only one drift test 9158024, b81432f
F15 medium Release workflow grants OIDC/attestation write to the job running dev tooling and tests 69502ca
F16 medium SECURITY.md claims a release secret scan no automation performs 69502ca
F17 low Coverage gate excludes the CLI, which already clears it 298d2c1
F18 low Windows CI runs 4 of 40 test files; no CI leg on the current Node line 298d2c1
F19 low AP2 timeline HTML lacks the case report's table semantics and CSP locks 88b8118
F20 low Legacy issue templates bypass the synthetic-data warning ed659ff
F21 low Docs hard-code release-specific names and claims f5ec9d2
F22 low Engine/AP2 console diagnostics break the CLI's JSON stderr on decide {} 3eb18d5 (CLI path closed; library behavior unchanged by design)
F23 low serve on a busy port exits 70 instead of unavailable 0a61412
F24 low Release job configures an npm registry for a publish that never happens 69502ca
F25 low @types/node targets Node 26 while the floor is 22.12 298d2c1
F26 high The 2.0.0 upgrade notes, docs/V1_2.md and ADR 0003 promise every Pack retained from 1.2.0 verifies unchanged; a Pack the 1.2.0 code writes from its own test helpers is unresolved under 2.0.0 377c376, e10709f

Commits

  1. a9655ae docs(changelog): adopt Keep a Changelog with dated, linked releases
  2. 9158024 build(version): single-source the release version and gate drift in CI
  3. 47cf3e6 fix(cli): exit 3 from operator compare when the current case is invalid
  4. 3eb18d5 fix(cli): require a complete evaluation case before decide or preview
  5. 607ec56 fix(cli): release the store lock on SIGINT/SIGTERM and report held locks as unavailable
  6. 0a61412 fix(cli): report serve host, port and scenario mistakes as usage or availability errors
  7. 87ca43b fix(cli): validate appeal and replay envelopes and replay checkpoints
  8. e949b85 fix(scripts): fail the license gate closed without a dependency tree
  9. d2a16ee fix(conformance): run and publish the external evidence review fixtures
  10. b81432f fix(ap2-dispute): pin the AP2 Pack format release apart from the package
  11. 88b8118 fix(ap2-dispute): make the evidence timeline accessible and tighten its CSP
  12. 6e5fc77 feat(cli): list subcommand actions, scenarios and exit codes in --help
  13. ed659ff docs: add a CLI reference, correct input limits, and route issues through the privacy-notice forms
  14. 298d2c1 ci: run the full suite on Windows, test Node 26, gate CLI coverage, type against the floor
  15. 69502ca ci(release): verify without write tokens, attest separately, scan packed content
  16. f5ec9d2 chore(release): 2.0.0
  17. 377c376 test(ap2-dispute): pin the unresolved outcome for a 1.2.0 Pack without delegated expiry
  18. e10709f docs: correct the retained 1.2.0 Pack compatibility claim

Each commit builds and passes the full suite on its own.

Versioning and release plan

These surfaces all read 2.0.0, and node scripts/version.mjs check --tag v2.0.0 passes:

  • package.json, and both version fields in package-lock.json;
  • RELEASE_VERSION in src/version.ts;
  • conformance/v2.0/capabilities.json release;
  • openapi/openapi.json info.version;
  • the CHANGELOG ## [2.0.0] - 2026-10-09 heading and its compare links.

These stay unchanged by design: PROTOCOL_VERSION 1.0.0, ENGINE_VERSION 1.0.0, and AP2_DISPUTE_FORMAT_RELEASE 1.2.0 (see docs/adr/0003-version-layers.md). --version now prints:

{"name":"MandateBound","version":"1.0.0","protocolVersion":"1.0.0","releaseVersion":"2.0.0","engineVersion":"1.0.0","ap2PackFormatRelease":"1.2.0"}

Tag to create after merge: v2.0.0, lightweight like v1.0.0 and v1.2.0, created by gh release create.

  1. Merge with a merge commit (gh pr merge --merge) so all 18 commits stay on main. Do this only after every CI leg is green, because main has no branch protection.
  2. On the merge commit, run the gates:
    git fetch && git checkout <merge-sha>
    npm ci --ignore-scripts
    npm run verify
    node scripts/version.mjs check --tag v2.0.0
    
    If the merge lands after 2026-10-09, you can keep the cut date or change the [2.0.0] date first. The check accepts any valid date.
  3. Write the notes to a file. On Windows, do not use process substitution:
    node scripts/version.mjs notes 2.0.0 > <scratch>/mandatebound-2.0.0-notes.md
    
  4. Create the release:
    gh release create v2.0.0 --repo EauDoon/mandatebound --target <merge-sha> --title "MandateBound v2.0.0" --notes-file <scratch>/mandatebound-2.0.0-notes.md --latest
    
  5. Publishing the release runs release.yml, then gh run watch follows it:
    • build runs read-only. It verifies the tag and every version surface, runs npm run verify, then packs the tgz and writes the SPDX SBOM and SHA256SUMS.
    • publish holds the write scopes. It runs sha256sum -c, attests provenance and the SBOM, then runs gh release upload --repo.
  6. Confirm the release:
    • gh release view v2.0.0 --json assets shows three assets.
    • Download the assets, then run sha256sum -c SHA256SUMS and gh attestation verify mandatebound-2.0.0.tgz -R EauDoon/mandatebound.

Nothing is published to npm. The package is not on the registry, and merging deploys nothing.

You can also run an optional smoke test before the real release: gh workflow run release.yml -f release_tag=v1.2.0. It must fail in build at the tag-diff or version check, and publish must be skipped.

Verification

Run on Windows 11 with Node 24.18.0 and npm 12.0.2, on the final branch head:

Command Result
npm ci --ignore-scripts ok, 0 vulnerabilities
npm run verify exit 0 (details below)
npm run conformance 76 of 76 pass (was 60; adds the 11 external review tests and 5 new parity and golden-Pack tests)
npm run build then node --test (the new Windows CI step) 405 tests: 398 pass, 0 fail, 7 platform skips (symlink EPERM, POSIX FIFO, npm bin symlinks, POSIX signal test)
node scripts/version.mjs check --tag v2.0.0 exit 0
node scripts/version.mjs notes 2.0.0 prints the [2.0.0] section, with relative links pinned to blob/v2.0.0
Release-label probe before the bump (RELEASE_VERSION set to 9.9.9, then reverted) only 6 release-label tests fail; every AP2 test passes, including the frozen 1.2.0 Pack
Retained-Pack check (tag v1.2.0 rebuilt in scratch, Pack from its unmodified helpers) the 1.2.0 verifier reports verified; this head reports unresolved with AP2_PACK_RESOLUTION_UNRESOLVED, digestValid and anchorMatched true. Adding only exp to the delegated Checkout Mandate makes it verified at this head
py -I privacy_screen.py <repo> origin/main --extra-file <this body> clean
Workflow YAML parse (PyYAML in an external venv) ci.yml, release.yml and dependabot.yml load; release job permissions are as designed
PR CI, run 37952427035 on e10709f all 7 legs green. Static checks pass on 22.12.0, 24.18.0 and 26.11.1. Coverage and packaging passes on all three: 405 of 405 tests, with the SIGINT/SIGTERM lock test executed. Coverage is about 96.2% lines, 91.4% branches and 97.0% functions. Windows full suite: 402 pass and 3 skip, then the package check (163 files).

npm run verify ran these steps:

  • the version check;
  • lint over 182 files;
  • licenses and dependency advisories (12 packages each);
  • typecheck against @types/node 22.20.5;
  • coverage with dist/cli.js included: 96.12% lines, 91.38% branches and 96.83% functions, against thresholds of 90, 85 and 90;
  • examples/case-study.mjs;
  • the package check: 163 files, plus the installed-consumer acceptance run.

The end-to-end SIGINT/SIGTERM lock test runs only on the Linux CI legs. Windows cannot deliver a catchable SIGINT to a child process. The handler logic has a cross-platform unit test, and an in-process scratch run on Windows confirmed the lock is removed and the exit code is 130.

Deviations from the plan

  • Golden Pack (commit 10). The tag was extracted with git archive into a scratch directory outside the repo, rather than with git worktree. It was then built at v1.2.0 (b2ca2c5). The Pack was produced by that released code from the tag's own makeFixture/makePackInput, with one change: the Checkout Mandate carries iat/exp. Since fc27d70 (after 1.2.0), main requires expiry on delegated payloads. Without that change, the current verifier reports the pure-tag Pack as unresolved, although its digest and anchor still verify. The test comment records this. Commit 17 adds that pure-tag Pack as a second frozen fixture and pins its unresolved outcome, and commit 18 corrects the upgrade notes, docs/V1_2.md and ADR 0003, which had promised that every retained 1.2.0 Pack verifies unchanged.
  • Signal handling (commit 5). No change to src/store.ts was needed. The handler is exported from the CLI module as closeServerOnSignals, with an injectable emitter and exit setter, so it can be unit-tested on every platform. The entrypoint also keeps a signal received during startup from being overwritten by the command's success code.
  • serve validation (commit 6). --port is now also validated before the store opens. An invalid --host changes from exit 3 to exit 2, and the upgrade notes record this as breaking.
  • Envelope errors (commit 7). A rejected appeal envelope reports Appeal input is invalid.. A non-object, non-array replay input now reports Replay input is invalid., where it used to report the generic shape message.
  • Conformance count (commit 9). It reached 72 at that commit, not the planned 71, because the new parity test adds one. It is 75 at the head.
  • CLI reference (commit 13). CLI_INPUT_LIMITS is exported from dist/cli.js only, so the docs drift test reads the real limits; it is not part of any package entry point. SECURITY.md now points to the exact serve boundary rules.
  • Windows CI (commit 14). The job runs node --test after the guarded build, not npm test, which would build a second time. A CHANGELOG bullet records the CI changes.
  • Release commit (commit 16).
    • conformance/v1.2/README.md returns to its 1.2.0 text with a historical note, because commit 9 had extended it while it was still the current declaration. conformance/v2.0/README.md carries the current list.
    • The upgrade notes also cover the serve --host, operator compare, replay and appeal behavior changes, and docs/ADOPTER_WORKFLOW.md lists the CI Node versions.
    • The CHANGELOG is dated 2026-10-09, the day this PR was cut.
  • scripts/version.mjs. check also rejects impossible dates and requires the [Unreleased] link to compare from v<version>. notes rewrites relative links to the tag's blob URLs, so they resolve on the release page.
  • Dependabot PR build(deps-dev): bump @types/node from 26.6.2 to 26.6.4 in the npm-routine-updates group across 1 directory #101. It was neither merged nor closed. It is superseded: commit 14 moves @types/node to ^22.20.5 and ignores its semver-major updates. build(deps-dev): bump @types/node from 26.6.2 to 26.6.4 in the npm-routine-updates group across 1 directory #101 conflicts in package-lock.json and should be closed.
  • Node 26.11.1. The new legs cannot run on this machine, so the PR CI run is their first execution. If one fails for a runtime reason, the plan says to drop that leg rather than weaken tests.

EauDoon added 18 commits October 9, 2026 01:34
Merge the four undated candidate sections into one [Unreleased] section
with Added, Changed, Removed, Fixed and Security subsections, keeping every
bullet's wording. The remote-binding removal moves from Security to Removed
and is marked breaking, naming the CreateApiServerOptions.allowRemote option
and the serve --allow-remote flag that 1.2.0 shipped, with a migration note.

Released headings become dated, linked [X.Y.Z] - YYYY-MM-DD headings, and
compare-link references are added at the bottom. The drift test now parses
the bracketed, dated heading and requires [Unreleased] above it.
Add scripts/version.mjs with three commands. check fails, naming the file,
when the lockfile, the RELEASE_VERSION literal, the conformance declaration
for the release major.minor, OpenAPI info.version, the CHANGELOG headings and
links, or an optional release tag disagree with package.json. sync edits those
targets in place with one targeted replacement each and refuses to write if a
replacement would change anything but the version field, because openapi.json
does not survive a JSON.stringify round trip. notes prints a CHANGELOG
section for the GitHub Release with relative links pinned to the tag.

version:check runs first in verify:static, and sync is wired as the npm
version lifecycle script. Running sync once moves OpenAPI info.version from
1.0.0 to the package release 1.2.0. CONTRIBUTING gains a Releasing section.
compareCaseAssessments reports validity as after.valid, while the targeted
comparisons report afterValid. The CLI read only afterValid, so a pair of
equally tampered revisions exited 0 with ok:true, contradicting the
documented exit table and the four sibling comparisons, which exit 3.

Derive the current-invalid flag from whichever field the comparison carries.
Precedence is unchanged: noncomparable 3, regression 5, invalid current 3,
otherwise 0. The new test fails on the previous build.
The API gated /v1/evaluations through a complete-case check, but the CLI
cast any JSON to an evaluation input. The engine then fabricated a
malformed-case decision dated 1970 that decide appended to the store and
both commands printed with exit 0, plus a raw TypeError line on stderr.

Move the gate into an internal src/evaluation-input.ts module shared by
both boundaries. The API rethrows its errors with the exact existing 422
codes and messages; the CLI applies it right after reading input, before
the engine runs or a store is opened or locked, and exits 3.

Tests that pinned {} or {pins:{}} as decide input now send the principal
scenario case so the stub-engine, stub-store and corrupt-store mappings
are still exercised. examples/README.md no longer sends users to decide
on a bundle file. Breaking for CLI callers only.
…cks as unavailable

The CLI entrypoint installed no signal handling, so stopping serve --store
with Ctrl+C orphaned the JSONL writer lock. Every later writer then failed
with exit 5 and a message blaming a state conflict, while the API called
the same condition 503.

The entrypoint now passes an onServer hook that registers once-only
SIGINT and SIGTERM handlers for a running server. Each closes the server,
which closes the store and removes the lock, and sets exit 130 or 143; a
second signal falls back to the default kill. Handlers exist only while a
server does, so other commands keep default signal behavior.

ALB_STORE_LOCKED now maps to exit 6 with a fixed, path-free hint about
stale .lock files instead of exit 5. The end-to-end signal test is POSIX
only because Windows cannot deliver a catchable SIGINT to a child; the
handler logic is unit tested on every platform.
…vailability errors

A non-loopback --host reached createApiServer after the store had been
opened and locked, and its TypeError surfaced as exit 3 'Protocol artifact
is invalid'. serve now validates the host with isLoopbackAddress and the
port before opening a store, and a bad host exits 2 naming the accepted
loopback IP literals.

A busy, unbindable or forbidden port (EADDRINUSE, EADDRNOTAVAIL, EACCES)
now exits 6 with ALB_SERVE_UNAVAILABLE instead of 70, while still closing
the owned store. An unknown simulate scenario keeps ALB_SCENARIO_UNKNOWN
and exit 3 but lists the valid scenarios instead of a generic message.
replay cast record.checkpoint straight to AppealCheckpoint, so a string,
number, array or wrongly typed object was reported as a history mismatch
with exit 5 and echoed back verbatim in completeness.checkpoint. replay
also accepted extra envelope keys, and appeal read record.event ?? input
without an exact-key check, so a misspelled decision key was dropped.

Add an exported isAppealCheckpoint guard that requires a plain object with
exactly a safe-integer sequence of at least 1 and a sha256 headDigest.
The CLI now accepts a bare events array or exactly {events, checkpoint?}
and rejects a malformed checkpoint with exit 3 and a fixed message that
never reflects the value; an event-keyed appeal envelope must be exactly
{event, decision?}. replayAppealEvents semantics are unchanged.
check-licenses.mjs returned early when node_modules was absent and then
printed 'license check: 0 installed packages use approved licenses' with
exit 0, so the release gate passed vacuously. The sibling advisory check
already fails closed in the same case.

Exit 1 with an actionable message when node_modules is missing, and also
when the tree holds no package manifests at all. Approved licenses and the
success wording are unchanged.
The runtime declares external_evidence_review_v1 as supported, but the
conformance command and the published fixtureTests omitted its fixture
file, and the parity test compared only the release and profiles, so
nothing noticed.

Append test/review-external.test.mjs to the conformance script and the
declaration, publish every capability id with its status, and extend the
parity test: the published capabilities must equal the runtime list, the
fixture list must equal the files the npm script runs, and every listed
file must exist. npm run conformance now runs 72 tests.
RELEASE_VERSION was stamped into every AP2 resolution, Evidence Pack and
Pack verification report, and Pack verification required it to equal the
running package. schemas/v1.2 pin the same field to the constant 1.2.0, so
any package release would have invalidated every retained Pack and
produced schema-violating artifacts. Setting RELEASE_VERSION to 9.9.9 broke
three AP2 tests.

Add AP2_DISPUTE_FORMAT_RELEASE, frozen at 1.2.0, and use it for the AP2
types, producers and the Pack shape check. Artifact bytes are unchanged.
The HTML timeline now labels the value 'Pack format'. CLI --version and
--help add protocolVersion and ap2PackFormatRelease and keep version as
the protocol alias.

Commit a golden Pack written by the released 1.2.0 code (tag v1.2.0,
b2ca2c5) from that tag's own test helpers, with the Checkout Mandate
iat/exp that main requires since fc27d70, and test that it verifies with
its retained digest and holds only public JWK members. With RELEASE_VERSION
at 9.9.9 every AP2 test now passes and only release-label tests fail.
ADR 0003 records the version layers.
…ts CSP

The metadata-only AP2 timeline is a reviewer-facing artifact, but its
table had no header scopes, caption or keyboard-reachable scroll region,
and its CSP lacked the base-uri and form-action locks the case report
already ships.

Add scope=col to every header, wrap the table in a focusable labelled
scroll region with a caption, add the matching focus, scroll and print
styles from the case report, and extend the CSP with base-uri 'none' and
form-action 'none'. Escaping and raw-token omission are unchanged.
--help named the commands but not their actions, the simulate scenarios
or the exit codes, and the action lists existed only as inline literals in
the requireSubcommandInput calls, so help could not be derived from them.

Hoist one frozen action list per subcommand family (casepack, policy,
ap2-dispute, and the 23 operator actions). The dispatcher validates
against those lists and CLI_COMMANDS publishes them as an optional
actions field. Help also adds scenarios (all plus every synthetic
scenario) and exitCodes derived from CLI_EXIT with a one-line meaning
each. Existing help fields are unchanged; the change is additive.
…ough the privacy-notice forms

review, serve, help and version had no user documentation, the full exit
code table and per-command mapping existed nowhere, and the store lock and
loopback Host/Origin rules were only summarized. Add docs/CLI.md covering
input conventions, every input limit, each command's input, output and
exit codes, the review input schema, extracted fields and verdicts, and
serve's bind, Host, Origin, lock and signal behavior.

Four doc lines claimed a 4 MiB cap the code does not enforce: operator
batches and actions, casepack and case-report read up to 17 MiB with depth
48 and 250,000 nodes, and receipt canonical input is bounded to 16 MiB.
Correct them in OPERATOR_WORKFLOWS and V1_2, and route the README command
map and SECURITY's API boundary summary to the reference.

A new test fails when a command, action, scenario or exit code is missing
from the page or a documented limit stops matching the values the CLI reads
with, now exported as CLI_INPUT_LIMITS from the CLI module only.

Delete the legacy Markdown issue templates so only the forms that carry the
synthetic-data warning remain; blank issues are already disabled.
…ype against the floor

Windows is a declared target, but CI ran 4 of the test files there. The
Windows job now builds and runs the whole suite before the installed
package check; tests that cannot run on Windows already skip themselves.

Add Node 26.11.1, which is in the setup-node manifest, to the static and
coverage matrices beside 22.12.0 and 24.18.0. Drop the dist/cli.js
exclusion from the coverage gate: with the CLI included the aggregate is
96.12% lines, 91.38% branches and 96.83% functions against 90/85/90.

Set @types/node to ^22.20.5 so code cannot type-check against APIs that
the Node 22.12 floor lacks, and make Dependabot ignore its semver-major
updates. The routine update groups are unchanged.
…ked content

The release workflow granted contents, id-token, attestations and
artifact-metadata write at workflow level to the one job that runs npm ci
and npm run verify, so every dev tool and test could request an OIDC
token. It also configured an npm registry for a publish that never happens,
and SECURITY.md promised a release secret scan that nothing performed.

Default the workflow to contents: read with a per-tag concurrency group.
Job build checks out the tag, runs the version check against the tag,
verifies, packs, writes the SBOM and checksums, and hands the files on as
a short-lived artifact. Job publish, the only one with write scopes,
installs nothing and runs no repository code: it downloads the files,
checks them with sha256sum -c, attests provenance and the SBOM, and runs
gh release upload with an explicit --repo because it has no checkout.
upload-artifact v7.0.1 and download-artifact v8.0.1 are pinned by SHA.

The package check now reads every packed file and rejects a PEM private
key block, reporting the path only. SECURITY.md lists the controls that
actually run.
Cut the unreleased work since 1.2.0 and this branch as 2.0.0. It is a
major release because the public CreateApiServerOptions.allowRemote option
and the serve --allow-remote flag that 1.2.0 shipped are gone, and because
CLI exit codes and accepted inputs changed (decide and preview reject
incomplete cases, held locks and busy ports exit 6, a bad serve host exits
2). It also gives builds with the RFC 8785 separator correction a release
label distinct from the regression window. Protocol, engine, native bytes
and the AP2 Pack format release are unchanged.

The conformance declaration moves to conformance/v2.0 before the bump so
the npm version lifecycle hook finds it; npm version 2.0.0 then moved
package.json and the lockfile and synced src/version.ts, the declaration
and OpenAPI info.version. conformance/v1.2/README.md returns to its 1.2.0
text with a historical note, and conformance/v2.0/README.md carries the
current fixture list including external review.

The CHANGELOG dates [2.0.0] with an Upgrading from 1.2.0 section, opens an
empty [Unreleased] and updates the compare links. SECURITY.md supports
2.0.x, and the adopter, evidence-operations, protocol and issue-template
text no longer hard-code 1.2.0. Tests derive the expected release and the
declaration path from package.json.
…t delegated expiry

Add a second frozen Pack written by the released 1.2.0 code (tag v1.2.0,
b2ca2c5) from that tag's unmodified makeFixture() and makePackInput()
helpers. Its delegated Checkout Mandate payload has no exp. The 1.2.0
verifier reports it verified; since fc27d70 the resolution gate requires
delegated expiry, so the current verifier reports it unresolved with
AP2_PACK_RESOLUTION_UNRESOLVED while digestValid and anchorMatched stay
true. The test pins that outcome, the AP2_EXPIRY_MISSING resolution issue
and schema validity of the report.

Rename the existing golden-Pack test to say its input carries delegated
expiry, and extend the public-key-material scan to both fixtures.
The 2.0.0 upgrade notes, docs/V1_2.md and ADR 0003 said every Pack
retained from 1.2.0 verifies unchanged. A Pack the released 1.2.0 code
writes from its own test helpers has no exp on its delegated Checkout
Mandate payload; since fc27d70 the resolution gate requires delegated
expiry, so 2.0.0 verifies that Pack as unresolved with
AP2_PACK_RESOLUTION_UNRESOLVED while its digest and anchor still check
out.

State what actually holds: the format label, Pack digest and anchor match
are unchanged, verification re-derives the resolution under the current
gates, and a 1.2.0 Pack without delegated exp is unresolved. Say the
release label alone no longer invalidates retained Packs, and drop the
pre-cut "Preserve 1.2.0 release pins ... historical AP2 packs" bullet
from [2.0.0] Changed: the release label moves to 2.0.0 and the upgrade
notes already list the unchanged native bytes, protocol and engine.
@EauDoon
EauDoon merged commit 32d5256 into main Oct 9, 2026
8 checks passed
@EauDoon
EauDoon deleted the upgrade/v2.0.0 branch October 9, 2026 16:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant