diff --git a/.github/workflows/review-candidate.yml b/.github/workflows/review-candidate.yml index 1171e70..ab9b0e8 100644 --- a/.github/workflows/review-candidate.yml +++ b/.github/workflows/review-candidate.yml @@ -11,7 +11,7 @@ permissions: jobs: candidate: - name: Pinned official candidate and negative control + name: Pinned official and historical candidate evidence runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -28,6 +28,8 @@ jobs: run: node --test scripts/review-candidate.test.mjs - name: Test exact upstream candidate bytes run: node scripts/review-candidate.mjs + - name: Test exact merged official bytes + run: node scripts/review-candidate.mjs --official - uses: actions/upload-artifact@v4 if: always() with: @@ -37,4 +39,8 @@ jobs: artifacts/review-candidate/*.txt artifacts/review-candidate/erc-8350.md artifacts/review-candidate/*/erc-8350/*.sol + artifacts/review-official/*.json + artifacts/review-official/*.txt + artifacts/review-official/erc-8350.md + artifacts/review-official/*/erc-8350/*.sol if-no-files-found: error diff --git a/.gitignore b/.gitignore index 80563e9..d708a20 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ artifacts/external-reproductions/ artifacts/independent-client*/ artifacts/account-conformance/ artifacts/review-candidate/ +artifacts/review-official/ account-conformance/out/ account-conformance/cache/ account-conformance/lib/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 015c219..3e06bee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ All notable implementation and protocol changes are recorded here. ### Added +- `spec-baseline-3` synchronizes official merge `8b11215c` from ethereum/ERCs#2147 + while preserving historical manifests 1/2 and the proposed-candidate record. + The explicit official-source gate checks baseline/spec/asset bindings, runs + the merged bytes and retains the old-source negative control. Draft status, + wire format, vectors, product runtime and frozen audit scope are unchanged. + - Pinned official authorization-candidate gate and CI: verifies proposed upstream bytes, runs 38 authorization/signature tests, and requires two dirty-padding failures against the historical official source as a negative control. Review diff --git a/README.md b/README.md index 3e9b96d..95b1e7c 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ prevStateRoot + ExperienceDelta v1 ## 关键文档 -- **[规范与实现基线](docs/spec-baseline/README.md):当前实现为 spec-baseline-2,历史 spec-baseline-1 原样保留。官方文本与提议中的澄清严格区分。** +- **[规范与实现基线](docs/spec-baseline/README.md):当前为 spec-baseline-3,已同步官方 #2147 合并版本;历史基线 1/2 原样保留。官方状态仍为 Draft,不代表 G4 或生产就绪。** - [真实账户验证](docs/interop/account-conformance.md):Safe 1.4.1 双签、7702 委托和本地 type-4 交易证据。 - [独立实现交接](docs/interop/independent-client-handoff.md)与 [Draft → Review 材料](docs/review/draft-to-review.md)。 - **`docs/implementing.md`: 写一个独立实现(外部实现者入口 — 什么算通过、要实现哪些面、`test-vectors/v2.json` 怎么跑)** diff --git a/SPEC.md b/SPEC.md index ee297c0..c7ce220 100644 --- a/SPEC.md +++ b/SPEC.md @@ -2,16 +2,17 @@ Status: informative overview accompanying the published ERC-8350 Draft. -The [official Draft](https://github.com/ethereum/ERCs/blob/b9651bc84714e87e6361f4f6c0c8ff39cc265d0a/ERCS/erc-8350.md) +The [official Draft](https://github.com/ethereum/ERCs/blob/8b11215cf267f710b49ea003384e678bcf953eae/ERCS/erc-8350.md) is the normative authority. [`erc/erc-8350.md`](erc/erc-8350.md) is its byte-identical local mirror. This overview is not a second specification and does not override either the official rules or their unresolved ambiguities. -Start with [spec-baseline-1](docs/spec-baseline/README.md) for source pins, +Start with [spec-baseline-3](docs/spec-baseline/README.md) for source pins, [differences](docs/spec-baseline/DIFFERENCES.md), the [authorization decision table](docs/spec-baseline/AUTHORIZATION.md) and -[open questions](docs/spec-baseline/OPEN_QUESTIONS.md). Proposed clarifications -are not upstream-approved changes. Historical pre-ERC wording remains in Git. +[open questions](docs/spec-baseline/OPEN_QUESTIONS.md). The AQ-01/AQ-02 wording and +AQ-03 official asset fix merged in ethereum/ERCs#2147; the status remains Draft. +Historical baselines and pre-ERC wording remain in Git. ## Scope diff --git a/docs/interop/account-conformance.md b/docs/interop/account-conformance.md index 297858c..4a3b08d 100644 --- a/docs/interop/account-conformance.md +++ b/docs/interop/account-conformance.md @@ -1,9 +1,10 @@ # Real-account authorization evidence This is a maintainer-run executable test package, not an independent implementation -or an external security audit. Its interpretation is the proposed -[spec-baseline-2](../spec-baseline/spec-baseline-2.json), with the official Draft -still authoritative and AQ-01/AQ-02 wording not yet approved upstream. +or an external security audit. Its current interpretation is +[spec-baseline-3](../spec-baseline/spec-baseline-3.json), aligned with official +Draft merge `8b11215c`. Historical runs retain their original baseline-2 scope; +new wording does not turn a historical run into new independent verification. ## Reproduce diff --git a/docs/review/draft-to-review.md b/docs/review/draft-to-review.md index 387a936..edcdd98 100644 --- a/docs/review/draft-to-review.md +++ b/docs/review/draft-to-review.md @@ -1,72 +1,90 @@ # Draft-to-Review preparation -Updated: 2026-11-06. Status: technical revision submitted in -[ethereum/ERCs#2147](https://github.com/ethereum/ERCs/pull/2147), not a submitted -status transition. The official ERC remains Draft. The proposed revision is -`397426c046049e1a0c3436e543f9731408708fd3`; it is not a new accepted baseline. +Updated 2026-11-06. [Technical revision #2147](https://github.com/ethereum/ERCs/pull/2147) +is merged at `8b11215cf267f710b49ea003384e678bcf953eae`. +The pinned official status is **Draft**. This packet supports a separate request +for Review; it does not itself change the upstream status. ## Review packet -- [Official Draft](https://eips.ethereum.org/EIPS/eip-8350), pinned in - [spec-baseline-2](../spec-baseline/spec-baseline-2.json). -- [Differences and disputed rules](../spec-baseline/DIFFERENCES.md). -- [Authorization decision table and proposed wording](../spec-baseline/AUTHORIZATION.md). +- [Pinned official Draft](https://github.com/ethereum/ERCs/blob/8b11215cf267f710b49ea003384e678bcf953eae/ERCS/erc-8350.md) + and [spec-baseline-3](../spec-baseline/spec-baseline-3.json). +- [Differences and resolutions](../spec-baseline/DIFFERENCES.md). +- [Authorization decision table](../spec-baseline/AUTHORIZATION.md). - [AQ-03 runtime/audit delta](../security/aq03-runtime-delta.md). -- [Real-account test command and boundaries](../interop/account-conformance.md). -- [Official-candidate reproduction and acceptance checklist](upstream-authorization.md). -- [External replay evidence](../interop/external-reproduction.md): project PR #33 - merged; issues #29/#31 closed on 2026-11-01 with preserved failure records. -- [Independent-client acceptance package](../interop/independent-client-handoff.md). -- [Scoped Python results](../interop/independent-client-results.md) and the - [merged PR #37 main acceptance](https://github.com/AwareLiquid/AgentMemoryState/pull/37#issuecomment-6679669756). -- Public external report intake in `audits/g4/external/submissions/`; acceptance - remains pending under issues #11/#22, not granted by this packet. - -## Questions for technical reviewers - -1. Should the broad empty/length prohibitions explicitly apply to direct-call - exemption and ECDSA recovery, while ERC-1271 receives arbitrary bytes? -2. Confirm the required signer for registration/rotation/transition, and that a - nonempty self-call is still validated. Keep the empty direct-call shortcut MAY. -3. Confirm ordered short-circuit validation: 1271 refusal is not a veto on a - valid retained 7702 key. Is the security boundary sufficiently prominent? -4. Confirm that noncanonical ABI padding is a failed wallet validation rather - than a decoder abort, without accepting a magic prefix with dirty padding. -5. Are immutable enforcing logic, scoped transition references and the separation - between hash compatibility, state enforcement and actual memory truth clear? - -## Remaining work, in order - -Technical: address technical/editor feedback on official PR #2147. The wording -and reference-code correction are separate commits. Number, authors, Draft -status, signing domain and wire format are preserved. After upstream merge, -refresh the official mirror and record a new baseline with the actual merged -commit. Until then, spec-baseline-2 and the original official mirror stay pinned. - -Operations: publish concise discussion updates with exact commits/results, track -dissent and external implementation responses, and link reviewer-owned audit -supplements when actually received. Do not repeatedly ping editors with claims -that passing project CI establishes consensus. - -After the draft is judged ready for peer review, request the Review transition -under [EIP-1](https://eips.ethereum.org/EIPS/eip-1#eip-process). The project treats -full independent implementation and security acceptance as important quality/ -release gates; they are not universal EIP-1 prerequisites for entering Review. -An audit does not automatically produce Review or Final. - -## Suggested discussion text - -> The focused authorization revision is now proposed in -> https://github.com/ethereum/ERCs/pull/2147 (candidate 397426c0; status remains -> Draft). We seek feedback on three specific points: ERC-1271 receives original -> empty/custom-length bytes before the ECDSA-only length guard; nonempty self-call -> signatures are validated; malformed return words fail wallet validation and -> continue to the same-byte ECDSA fallback. That fallback intentionally preserves -> an EIP-7702 account's key path even when its delegate rejects. Our pinned runner -> tests the exact candidate and reproduces the old dirty-padding failure. -> Independently maintained Python hash/state/EOA coverage is accepted only in its -> reported scope; independent ERC-1271/Safe/7702 coverage and current-source audit -> acceptance remain open. Which acceptance rule, if any, is still ambiguous for -> your implementation? This is technical feedback, not a status-change request. - -This text is a draft for community discussion, not a claim that it was posted. +- [Real-account command and boundaries](../interop/account-conformance.md). +- [Official and historical-source reproduction](upstream-authorization.md). +- [External replay evidence](../interop/external-reproduction.md): PR #33 merged; + issues #29/#31 closed with historical failed runs preserved. +- [Independent-client handoff](../interop/independent-client-handoff.md) and + [scoped Python results](../interop/independent-client-results.md). +- Public provisional external report in `audits/g4/external/submissions/`; + current-runtime acceptance remains pending in issues #11/#22. + +## Resolved rule questions + +The official merge resolves AQ-01/AQ-02 and synchronizes the AQ-03 source fix: + +1. ERC-1271 receives original empty/custom bytes before the ECDSA-only length guard. +2. Required signer roles are explicit for all three operations. Direct-empty + authorization is optional; nonempty self-call signatures still need validation. +3. Ordered fallback preserves a valid 7702 retained-key path after wallet refusal. +4. Noncanonical return padding is failed wallet validation, not an ABI decoder + abort and not authorization based only on a four-byte prefix. + +No hash, type string, signing domain, field, storage layout or interface changes +were made in the baseline synchronization. The merged wording resolves semantic +ambiguity and is not retroactively labelled an editorial-only revision. + +## Questions still useful for peer review + +- Are required-account roles, optional direct-call capability and retained-key + fallback unambiguous for independently written clients? +- Are immutable enforcing logic, fully scoped transition references and the + distinction between a commitment and memory truth sufficiently clear? +- Do consumers understand wallet-state/gas dependence and the limits of offline + audit claims? No universal gas minimum or production liveness is guaranteed. +- Can another implementation report wallet authorization outcomes against the + published scenarios without depending on project SDK code? + +## Separate standards and release tracks + +Under [EIP-1](https://eips.ethereum.org/EIPS/eip-1#eip-process), the author requests +Review when the draft is ready for peer review. A complete second implementation +and external audit are project quality/release objectives, not universal Review +prerequisites. Green CI or audit results do not themselves grant Review or Final. + +The independently maintained Python client covers hash/encoding, five-transition +fixtures, held-state rejections and EOA ECDSA under its reported pins. It does not +implement ERC-1271, Safe or 7702. Maintainer account tests are not independent +coverage. Historical Python results are not relabelled as runs of the new baseline. + +The existing external report targets the old freeze. Current-runtime addendum, +canonical G4 acceptance and new deployment evidence remain open. Do not describe +this packet or a Review transition as production-ready. + +## Publication and request checklist + +1. Merge project PR #38 and verify its main commit. +2. Publish baseline 3 and fresh clean-checkout/source/account results. +3. Notify the existing Magicians thread and issues #21/#11 with exact pins, + changed rules and unchanged external coverage limitations. +4. Submit a separate official PR changing only `status: Draft` to `status: Review`. + Link this packet and discussion; retain the number, authors and technical text. +5. Record the actual PR, notifications and replies in the public baseline-PR + conversation. Do not infer consensus, a new audit or contributor commitment. + +## Suggested community update + +> The focused authorization revision has merged in ethereum/ERCs#2147. ERC-8350 +> remains Draft. Baseline 3 pins the actual merged text/assets and preserves old +> baselines and failed regression evidence. The runner tests exact official bytes +> and the historical candidate separately. We are preparing a request for peer +> review, particularly on wallet-policy versus retained-key fallback and the +> boundary between memory commitments and memory truth. Independent Python +> evidence remains scoped to hash/state/EOA behavior; independent wallet coverage +> and current-runtime external audit acceptance are still open. Please identify +> any remaining ambiguous acceptance rule or interoperability concern. + +This is reusable draft text. Actual publication and status-request URLs belong +in the public handoff record; preparing this file alone does not publish them. diff --git a/docs/review/upstream-authorization.md b/docs/review/upstream-authorization.md index 87771f0..92a570c 100644 --- a/docs/review/upstream-authorization.md +++ b/docs/review/upstream-authorization.md @@ -1,92 +1,107 @@ # Official authorization revision evidence -Date: 2026-11-06. Technical proposal, not upstream acceptance or a release. +Updated 2026-11-06: [ethereum/ERCs#2147](https://github.com/ethereum/ERCs/pull/2147) +merged at `8b11215cf267f710b49ea003384e678bcf953eae`. +The official status remains Draft. Source acceptance is not G4, a release, +deployment verification or independent security approval. ## Exact inputs -- Official PR: [ethereum/ERCs#2147](https://github.com/ethereum/ERCs/pull/2147). -- Candidate: `397426c046049e1a0c3436e543f9731408708fd3` in `XiaoHai67890/ERCs`. -- Wording commit: `6420c36e`; runtime commit: `397426c0`. -- Accepted official text remains `b9651bc84714e87e6361f4f6c0c8ff39cc265d0a`. -- [Candidate manifest](upstream-candidate.json) pins the proposal text, all three - CC0 source files, and the old-source negative control by commit and SHA-256. -- Harness originates from merged project main `5770a9090700cc6df925b7a399e53014eac16667`; - each generated record identifies the actual runner commit and dirty status. - -The candidate's three assets match the current local CC0 assets byte-for-byte. -Neither `erc/erc-8350.md` nor either historical baseline manifest is replaced. -The normative wording is a proposal resolving conflicting readings, not an -editorial-only change or already accepted rule. +- [Baseline 3](../spec-baseline/spec-baseline-3.json) pins the actual official merge + and the byte-identical local mirror. +- [Official manifest](upstream-official.json) pins the merged text and all three + CC0 assets, and binds them to baseline 3 and the historical candidate digest. +- [Historical candidate](upstream-candidate.json) remains unchanged: + `397426c046049e1a0c3436e543f9731408708fd3` in `XiaoHai67890/ERCs`. + Its proposed status describes that historical record, not today's official state. +- Candidate and merged text SHA-256: + `295ab952357cefe141f3f1df5ceaaed2fe40826939fd22c683519c747a0fc072`. +- Candidate and merged Registry SHA-256: + `94c7f39920205ae581e6537828a80a3dcfeb0a283c833ed2b50bc10927a292c3`. +- Negative control remains official + `b9651bc84714e87e6361f4f6c0c8ff39cc265d0a`, Registry SHA-256 + `6d99fc690abd776c53f607be9da1e25c2a48a80037088a3f3fb029f54943685b`. +- Examined project main after PR #38: + `086e493d70bb07fc2e8b4bfac12282312c2cdc9e`. Each new run records its actual + harness commit and dirty-checkout status rather than inheriting a previous PASS. ## Reproduce Requirements: Node 22, Foundry 1.7.1, solc 0.8.24, GitHub/solc download access. -From a clean checkout of this PR's head: +From a clean checkout of the baseline PR commit: ```bash cd contracts forge install foundry-rs/forge-std@v1.16.2 --no-git cd .. -node --test scripts/review-candidate.test.mjs +node --test scripts/spec-baseline.test.mjs scripts/review-candidate.test.mjs +node scripts/spec-baseline.mjs --verify-upstream node scripts/review-candidate.mjs +node scripts/review-candidate.mjs --official ``` -The runner downloads pinned source bytes, verifies their digests, checks the -candidate against local CC0 assets, and explicitly remaps the test import to -the fetched directory. Candidate/control compiler outputs are isolated. It does -not execute scripts from the external repository. Its JSON/logs are saved under -`artifacts/review-candidate/` and uploaded by `Official revision evidence` CI. -The initial local run passed; the PR's clean-checkout CI is the durable run record. +The default mode preserves historical-candidate evidence. The explicit +`--official` mode fetches from ethereum/ERCs at the actual merge commit and +checks baseline identity, immutable candidate history, spec/assets and the local +mirror. Neither mode executes scripts from the downloaded repository. + +Both modes explicitly remap test imports to fetched source and isolate compiler +outputs/failure persistence. Outputs go respectively to +`artifacts/review-candidate/` and `artifacts/review-official/`. +The read-only `Official revision evidence` CI uploads raw source, outputs and +reports; each report includes the manifest, commands, source/harness commits, +Foundry version, dirty status and output digests. -Expected results: +Required outcomes: | Layer | Required result | Scope | | --- | --- | --- | -| Runner adversarial tests | 9 pass, zero skips | Hash/HTTP failure, missing suites, false approval, missing assets, reduced fuzz counts | -| Candidate authorization | 36 pass | 18 tests per Registry copy, three operations, atomic rejection; 256 padding fuzz runs per copy | -| Candidate signature vectors | 2 pass | Each test executes all 46 externally exported ECDSA cases | -| Old official negative control | Exactly 2 specified tests fail | Dirty-padding valid-key fallback, deterministic and fuzz case, using old official source | - -An empty suite, skip, fetch failure, malformed JSON or compiler failure cannot -satisfy the expected negative control. The expected failures are not unresolved -failures of the candidate. This gate proves the documented test outcomes, not -the completeness of the specification or an independent security assessment. +| Baseline/gate unit tests | 47 pass, zero skips | Historical integrity, source/status bindings, failure handling and result validation | +| Each source mode: authorization | 36 pass | 18 per Registry copy; three operations; atomic rejection; 256 padding fuzz runs per copy | +| Each source mode: signature vectors | 2 pass | Each executes all 46 independently exported ECDSA cases | +| Each source mode: historical control | Exactly 2 named tests fail | Old official dirty-padding valid-key fallback, deterministic and fuzz regression | +| Full workspace | Separate clean-checkout gate | All three Golden Vector implementations, TS, Solidity, invariants and preparation guards | +| Real accounts | Separate account gate | Pinned Safe 1.4.1 and Prague/real local type-4 execution | + +Expected old-source failures are not failures of the current implementation. +Empty suites, skips, fetch errors, malformed JSON and compiler failures cannot +satisfy the negative control. These results do not establish complete protocol +correctness or independent security acceptance. ## Evidence already merged -[PR #36](https://github.com/AwareLiquid/AgentMemoryState/pull/36) and -[PR #37](https://github.com/AwareLiquid/AgentMemoryState/pull/37) are merged. -The [main acceptance record](https://github.com/AwareLiquid/AgentMemoryState/pull/37#issuecomment-6679669756) -binds clean commit `5770a909` to full workspace/Golden Vector checks, 155 TypeScript -and 120 Solidity tests, 58 real-account tests with local type-4 receipts, scoped -Python reproduction and both live replay suites. The new runner supplements, -not replaces, those gates. Static vectors cannot demonstrate wallet execution. - -## Concrete handoff and acceptance - -| Task | Responsibility | Output | Acceptance | -| --- | --- | --- | --- | -| Official technical review | Technical | PR #2147; answers to AQ-01/AQ-02/AQ-03 feedback | Review comments addressed; upstream merge observed, not inferred from CI | -| Community discussion | Operations | Targeted questions in the existing Magicians thread | Published URL and substantive responses/dissent recorded; draft prose is not a posted comment | -| Post-merge baseline | Technical | New numbered manifest and byte-identical official mirror | Actual merged SHA/digests verified; baselines 1/2 retained; notify implementers | -| Independent wallet coverage | Operations | Optional scope agreement in issue #21 | Contributor explicitly accepts a bounded task; no assumed maintenance commitment | -| Account conformance handoff | Technical | Existing 58-case harness, 46 vectors and rule table | External account results bind wallet/version/state, commands, outcomes and rejection snapshots; unsupported rows declared | -| Audit addendum | Operations | Reviewer-owned supplement via issues #11/#22 | Exact new candidate and runtime delta, methodology, findings, risk mapping and attestation; genuine acceptance before canonical record | -| Draft-to-Review submission | Technical | Separate status-only official PR after technical revision acceptance | Author requests peer review; no false claim that audit/independent tests guarantee editor merge | - -Full independent implementation and G4 are project quality/release gates, not -universal EIP-1 requirements to request Review. ZK profiles, markets, new SDK -features and deployments are not prerequisites for this focused revision. +Project PRs [#36](https://github.com/AwareLiquid/AgentMemoryState/pull/36), +[#37](https://github.com/AwareLiquid/AgentMemoryState/pull/37) and +[#38](https://github.com/AwareLiquid/AgentMemoryState/pull/38) are merged. +The [PR #37 main acceptance](https://github.com/AwareLiquid/AgentMemoryState/pull/37#issuecomment-6679669756) +records clean main `5770a909`: 155 TS, 120 Solidity, 58 real-account tests, +local type-4 receipts, scoped Python reproduction and both live replay suites. +Those are versioned historical results, not automatically a run of baseline 3. +Fresh local/CI results are linked from the baseline PR. + +## Handoff and remaining boundaries + +| Task | Responsibility | Output / acceptance | +| --- | --- | --- | +| Official synchronization | Technical | Observed merge, baseline 3, byte-identical mirror; old baselines retained | +| Community discussion | Operations | Existing Magicians thread; actual post URL and substantive responses/dissent | +| Independent-client follow-up | Operations | Issue #21; contributor reports new pins, layer outcomes and unsupported wallet paths | +| Wallet conformance | Technical | Existing 58-case harness and 46 vectors; external results identify wallet/state/commands and rejection snapshots | +| Audit addendum | Operations | Issues #11/#22; reviewer-owned exact-runtime supplement, findings, risk mapping and attestation | +| Draft-to-Review request | Technical | Separate official status request after baseline evidence is public; no new semantics or claim of production readiness | + +No contributor has accepted new scope merely because this document names it. +A full independent implementation and G4 are project quality/release objectives, +not universal EIP-1 prerequisites for requesting Review. ## Audit and deployment boundary -The October reviewer-controlled report is archived as a provisional submission; -it is not absent, but canonical acceptance is still pending. The frozen target -`af5a75fb2db532fd5603554083d8895a825c2de2` is unchanged and does not cover AQ-03. -The [runtime delta](../security/aq03-runtime-delta.md) is the supplement input. -`pnpm security:rc` remains a failing release gate until actual current-scope -evidence and acceptance exist. CI scope checks do not mean security-ready. +The October reviewer-controlled report remains a provisional submission. The +frozen target `af5a75fb2db532fd5603554083d8895a825c2de2` is unchanged and does not +cover AQ-03. The [runtime delta](../security/aq03-runtime-delta.md) is the supplement +input. `pnpm security:rc` remains a failing release gate until genuine scope and +acceptance evidence exist; scope-only CI does not mean security-ready. -No deployment is changed by this work. An immutable existing address cannot be -patched by merging source. A future deployment needs separate bytecode/build, -Sourcify Exact Match and reachable-upgrade-path evidence for the actual network. +No deployment is changed. An immutable existing address cannot be patched by a +source merge. A future deployment needs its own build/bytecode identity, Sourcify +Exact Match and reachable-upgrade-path evidence for the actual chain. diff --git a/docs/review/upstream-official.json b/docs/review/upstream-official.json new file mode 100644 index 0000000..ef2beeb --- /dev/null +++ b/docs/review/upstream-official.json @@ -0,0 +1,23 @@ +{ + "schema": "erc8350/upstream-official/v1", + "status": "merged-upstream-draft", + "repository": "ethereum/ERCs", + "commit": "8b11215cf267f710b49ea003384e678bcf953eae", + "specSha256": "295ab952357cefe141f3f1df5ceaaed2fe40826939fd22c683519c747a0fc072", + "assets": { + "AgentMemoryStateRegistry.sol": "94c7f39920205ae581e6537828a80a3dcfeb0a283c833ed2b50bc10927a292c3", + "ECDSA.sol": "74d0a828f70af9384457e2cf5165638dcc44a7b9749b8dc80cb7c9bd2ba2ccf0", + "IAgentMemoryState.sol": "274bfee5de979ea164abcf30af6b9dace1fccdb42bee4cf59bdeb0a9621be93a" + }, + "negativeControl": { + "repository": "ethereum/ERCs", + "commit": "b9651bc84714e87e6361f4f6c0c8ff39cc265d0a", + "registrySha256": "6d99fc690abd776c53f607be9da1e25c2a48a80037088a3f3fb029f54943685b" + }, + "baseline": "spec-baseline-3", + "pullRequest": "https://github.com/ethereum/ERCs/pull/2147", + "historicalCandidate": { + "path": "docs/review/upstream-candidate.json", + "sha256": "b5145480906b4e6ca9e4addfbe1754e60908d03604fae4c19662ce1e7be762e4" + } +} diff --git a/docs/security/aq03-runtime-delta.md b/docs/security/aq03-runtime-delta.md index 20e02e7..6c6139d 100644 --- a/docs/security/aq03-runtime-delta.md +++ b/docs/security/aq03-runtime-delta.md @@ -1,14 +1,16 @@ # AQ-03 runtime change and audit delta -Date: 2026-11-01. Status: implementation fix; external acceptance pending. +Date: 2026-11-01; updated 2026-11-06. Status: implementation and official asset +fix merged; current-runtime external security acceptance pending. ## Identity and scope - Historical baseline: `spec-baseline-1`, P1a commit `71b50f98919bb4e8858a96493293b6188e8df2ab`. -- Current implementation: [spec-baseline-2](../spec-baseline/spec-baseline-2.json), - whose implementation commit pins the runtime patch and regression tests. +- Runtime-fix baseline: [spec-baseline-2](../spec-baseline/spec-baseline-2.json). + Current [spec-baseline-3](../spec-baseline/spec-baseline-3.json) synchronizes the + official merged text/assets without another runtime change. - Changed runtime files: `contracts/src/reference/AgentMemoryStateRegistry.sol` - and the proposed CC0 upstream copy `erc/assets/erc-8350/AgentMemoryStateRegistry.sol`. + and the CC0 upstream copy `erc/assets/erc-8350/AgentMemoryStateRegistry.sol`. - Frozen external review target remains `af5a75fb2db532fd5603554083d8895a825c2de2`. No old scope, report, tag, risk register or deployment record is rewritten. @@ -45,13 +47,11 @@ before release acceptance. Canonical G4 acceptance remains absent. A new deploym needs a new reviewed candidate, address and verification evidence, not an in-place upgrade or reuse of the old audit claim. -The official Draft text remains pinned; empty/custom-signature clarifications are -still proposals. The local CC0 asset fix must receive a separate upstream review -before claiming ethereum/ERCs contains it. No production readiness is asserted. - Update 2026-11-06: [official PR #2147](https://github.com/ethereum/ERCs/pull/2147) -proposes the exact local CC0 bytes at `397426c046049e1a0c3436e543f9731408708fd3`. -The [candidate gate](../review/upstream-authorization.md) verifies fetched source -digests, runs all 38 authorization/signature tests and requires two expected -dirty-padding failures against the old official source. Upstream acceptance and -the reviewer-owned current-source supplement remain pending. +merged at `8b11215cf267f710b49ea003384e678bcf953eae`. It contains the same CC0 +bytes as historical candidate `397426c046049e1a0c3436e543f9731408708fd3`. +The [source gate](../review/upstream-authorization.md) separately verifies both +revisions, runs 38 authorization/signature tests for each and requires two +expected dirty-padding failures against the old official source. +The reviewer-owned current-source supplement remains pending. No production +readiness or deployment change is asserted. diff --git a/docs/spec-baseline/AUTHORIZATION.md b/docs/spec-baseline/AUTHORIZATION.md index 63df7e3..b4f743e 100644 --- a/docs/spec-baseline/AUTHORIZATION.md +++ b/docs/spec-baseline/AUTHORIZATION.md @@ -1,13 +1,14 @@ -# Authorization decisions for spec-baseline-1 +# Authorization decisions for spec-baseline-3 -Current implementation baseline: **spec-baseline-2** (AQ-03 runtime fix). -The official text is unchanged. Historical spec-baseline-1 remains byte-identical. +Current baseline: **spec-baseline-3**, official merge `8b11215c` (still Draft). +Historical spec-baseline-1 and spec-baseline-2 remain byte-identical. See [AQ-03 change and audit delta](../security/aq03-runtime-delta.md). This table distinguishes the official Draft, optional capabilities, observed -reference choices and proposed clarifications. The source pins are in the -[baseline manifest](spec-baseline-1.json); open items are not upstream-approved -rules. Decisions assume the operation's non-authorization preconditions hold +reference choices and remaining implementation/release questions. Source pins +are in the [baseline manifest](spec-baseline-3.json). AQ-01/AQ-02 wording and the +AQ-03 asset fix are merged into the official Draft. Decisions assume the +operation's non-authorization preconditions hold and sufficient execution gas is available, unless a row says otherwise. ## Required account and digest @@ -32,16 +33,16 @@ ERC-1271 attempt and the ECDSA fallback. | ID | Condition | Result in the reference | Normative classification | | --- | --- | --- | --- | | A-01 | Caller equals required account; signature empty; direct-call capability enabled | Authorize without ERC-1271/ECDSA; still enforce every state/field precondition | Official MAY, enabled for all three reference operations | -| A-02 | Caller equals required account; signature non-empty | No shortcut; continue to ERC-1271/ECDSA, reject if neither succeeds | Reference behavior consistent with signature validation; AQ-02 makes it explicit | -| A-03 | Different caller; empty signature | No direct-call shortcut; continue to ERC-1271 if code exists | Direct identity restriction; wallet success is the AQ-01 textual conflict | +| A-02 | Caller equals required account; signature non-empty | No shortcut; continue to ERC-1271/ECDSA, reject if neither succeeds | Explicit official validation rule, AQ-02 resolved | +| A-03 | Different caller; empty signature | No direct-call shortcut; continue to ERC-1271 if code exists | Explicit direct identity restriction; wallet policy may still authorize, AQ-01 resolved | | A-04 | No account code | Skip ERC-1271 and try ECDSA | Official ordered validation | -| A-05 | Code exists; successful ERC-1271 call, canonical ABI result of at least 32 bytes with magic value | Authorize, including custom short/long/empty signatures approved by the wallet | ERC-1271 success rule; empty/length wording needs AQ-01 clarification | +| A-05 | Code exists; successful ERC-1271 call, canonical ABI result of at least 32 bytes with magic value | Authorize, including custom short/long/empty signatures approved by the wallet | Official ERC-1271 rule: first word has magic plus 28 zero padding bytes; trailing bytes ignored | | A-06 | ERC-1271 reverts, returns fewer than 32 bytes, or canonically returns wrong magic | Continue to ECDSA; the wallet's failure is not a final veto | Official fallback rule | | A-07 | Reached ECDSA with length other than 65 | Reject before recovery, including empty and EIP-2098 compact signatures | Recovery length rule; not a global rule for contract signatures | | A-08 | Reached ECDSA with 65 bytes, canonical low-s, v 27/28, recovered account equals required account | Authorize | Official canonical ECDSA rule; exact helper errors are implementation details | | A-09 | Reached ECDSA with high-s, invalid v/r/s or wrong recovered account | Reject | Official non-malleable/matching-account requirement | | A-10 | Code-present keyed account; delegate returns wrong magic but the same signature meets A-08 | Authorize through ECDSA | Intentional retained-key behavior; delegate policy is not exclusive | -| A-11 | Call succeeds but returns a 32-byte word with magic prefix and nonzero padding | Baseline 2 treats the word as invalid wallet approval, then tries the same-byte ECDSA fallback | AQ-03 implementation fix; upstream normative review and audit addendum pending | +| A-11 | Call succeeds but returns a 32-byte word with magic prefix and nonzero padding | Invalid wallet approval, then same-byte ECDSA fallback | Explicit official rule and merged asset fix; current-runtime external audit addendum pending | | A-12 | ERC-1271 runs out of forwarded gas | Fallback only if the caller retains enough gas; the whole call may fail | AQ-04; no guaranteed liveness for EOA or contract-policy signatures | **Design intent:** a 7702 delegation does not revoke the underlying key. A @@ -55,37 +56,14 @@ Direct-empty capability is optional. A portable runner records whether an implementation enables it; reference-exact tests expect it enabled. A self-call with invalid non-empty bytes does not fall back to the direct-empty shortcut. -## Proposed clarification text - -The following records the original project direction, not a replacement for the -pinned official file. The complete wording, including canonical return-word and -state-validation constraints, is now submitted in -[ethereum/ERCs#2147](https://github.com/ethereum/ERCs/pull/2147) at `397426c0`. -It remains a proposal until upstream review/merge, not a new official baseline. - -> The required account is the controller for registration, the current controller -> for an authorization update, and the current authorizer for a transition. -> A registry MAY authorize an operation without a signature when the caller is -> that required account and the supplied signature is empty. A different caller -> MUST NOT receive this direct-call exemption merely by supplying empty bytes. -> A non-empty signature MUST be validated even when supplied by the required -> account itself. -> -> Otherwise, when the required account has non-empty code, the registry MUST -> first attempt ERC-1271 validation of the operation's EIP-712 digest with the -> original signature bytes. No signature-length filter, including an empty-byte -> filter, is applied before this attempt. Successful validation MUST authorize -> the operation. If ERC-1271 validation fails, or the account has no code, the -> registry MUST attempt canonical ECDSA validation of the same digest and bytes. -> The 65-byte, non-malleable encoding requirement applies only to this recovery -> path; failure to recover the required account MUST reject the operation. - -The upstream revision should retain explicit STATICCALL, return-size and magic -value semantics. AQ-03 proposes treating non-canonical ABI data as unsuccessful -ERC-1271 validation, then continuing to ECDSA; it does not propose accepting -malformed bytes solely because their first four bytes are the magic value. This proposal -changes neither hash inputs nor the signing domain. Because it resolves conflicting -readings, it still needs normative review rather than being labelled editorial-only. +## Merged normative text + +The authoritative wording is the [pinned official Signature validation section](https://github.com/ethereum/ERCs/blob/8b11215cf267f710b49ea003384e678bcf953eae/ERCS/erc-8350.md#signature-validation), +merged through [ethereum/ERCs#2147](https://github.com/ethereum/ERCs/pull/2147). +The table is an informative implementation aid, not a second normative text. +The merged revision specifies STATICCALL, canonical first-word handling and +fallback without changing hash inputs or the signing domain. Baselines 1/2 +retain the historical context of the formerly conflicting readings. ## State safety and test mapping diff --git a/docs/spec-baseline/DIFFERENCES.md b/docs/spec-baseline/DIFFERENCES.md index ca8d521..468ff6f 100644 --- a/docs/spec-baseline/DIFFERENCES.md +++ b/docs/spec-baseline/DIFFERENCES.md @@ -1,8 +1,25 @@ # Baseline differences and dispositions -Current implementation baseline: **spec-baseline-2** (AQ-03 runtime fix). -The official text is unchanged. Historical spec-baseline-1 remains byte-identical. -See [AQ-03 change and audit delta](../security/aq03-runtime-delta.md). +Current baseline: **spec-baseline-3**, official merge `8b11215c`. +The mirror now matches the merged Draft byte-for-byte; baseline manifests 1/2 +remain unchanged. See [AQ-03 change and audit delta](../security/aq03-runtime-delta.md). + +## Baseline 3 resolution + +- AQ-01/AQ-02: official wording now distinguishes direct-empty exemption, + arbitrary ERC-1271 bytes and 65-byte ECDSA recovery, with explicit signer roles + and nonempty self-call validation. +- AQ-03: the official Registry now uses the same strict return-word comparison as + the local runtime. The historical candidate and actual merge have identical + spec/asset digests. Current-runtime external audit acceptance is still pending. +- `SPEC.md` now points to baseline 3; it remains informative. No wire format, + hash, storage, signature vector, product runtime or audit freeze changes here. +- Maintainer Safe/real-7702 tests and portable signature adapters already exist; + independent wallet execution remains outside the scoped Python evidence. + +The tables below retain **historical baseline-1/2 observations**, not unresolved +claims about today's official text. Current rule classifications are in +[AUTHORIZATION.md](AUTHORIZATION.md). Baseline: `spec-baseline-1`. Official authority and full source pins are in the [manifest](spec-baseline-1.json). Comparisons use the official Draft at @@ -32,10 +49,10 @@ changes at `891b47c70c8b45df1daaa78065b16ff73e67f8a4`. | D-12 | No universal gas allowance is specified | `staticcall` has no explicit gas cap; EIP-150 and transaction budget constrain execution | AQ-04: availability/resource risk, not a gas-induced authorization bypass | | D-13 | Core acceptance rules are specified, but most custom error selectors and their order are not | v2 includes reference selectors, multi-invalid-input precedence and a zero-Space-ID check | Separate portable rejection outcomes from reference-exact diagnostics; do not promote vector-only details to ERC requirements | -D-07 and D-08 mean the current implementation's behavior should be reported as +D-07 and D-08 meant baseline-1/2 implementation behavior had to be reported as **observed reference behavior under a proposed clarification**, not as an -unqualified resolution of the current official wording. This work does not -silently make the implementation more restrictive or rewrite the official mirror. +unqualified resolution of the then-official wording. Baseline 3 resolves that +textual conflict through the observed upstream merge, not through local inference. AQ-03 current regression tests (the original reproducer remains at commit `71b50f9`): @@ -64,7 +81,7 @@ not an unauthorized state write; this is not a completed external severity revie - New baseline tests are maintainer characterization evidence. They do not close R-002's externally maintained second-implementation objective. -## What remains unchanged +## Historical baseline-2 boundary Baseline 2 changes both local Registry source copies. The official ERC text, v1/v2 vectors, EIP-712 domain, type strings, frozen audit manifest, @@ -73,6 +90,9 @@ contains a proposed upstream fix, not bytes already merged into ethereum/ERCs. The new source of truth is not a new protocol: it is an explicit pointer to the already published official Draft, accompanied by proposed clarifications. +That paragraph records baseline 2's publication state; it is superseded for +official synchronization by baseline 3, not erased from history. + The [open-question register](OPEN_QUESTIONS.md) supplies the next action and exit criterion for each unresolved behavior. A future runtime fix must reassess current-source audit coverage instead of inheriting the frozen report implicitly. diff --git a/docs/spec-baseline/OPEN_QUESTIONS.md b/docs/spec-baseline/OPEN_QUESTIONS.md index b91128d..bc7bc8e 100644 --- a/docs/spec-baseline/OPEN_QUESTIONS.md +++ b/docs/spec-baseline/OPEN_QUESTIONS.md @@ -1,35 +1,37 @@ # Baseline open questions and handoff -Current implementation baseline: **spec-baseline-2** (AQ-03 runtime fix). -The official text is unchanged. Historical spec-baseline-1 remains byte-identical. +Current baseline: **spec-baseline-3**, official merge `8b11215c` (still Draft). +Historical spec-baseline-1 and spec-baseline-2 remain byte-identical. See [AQ-03 change and audit delta](../security/aq03-runtime-delta.md). -Update 2026-11-06: AQ-01/AQ-02 wording and the AQ-03 CC0 asset correction are -submitted in [ethereum/ERCs#2147](https://github.com/ethereum/ERCs/pull/2147), -candidate `397426c046049e1a0c3436e543f9731408708fd3`. Submission is not acceptance; -the pinned official mirror is unchanged. See the [review evidence](../review/upstream-authorization.md). +Update 2026-11-06: AQ-01/AQ-02 wording and the AQ-03 CC0 asset correction +merged in [ethereum/ERCs#2147](https://github.com/ethereum/ERCs/pull/2147), +commit `8b11215cf267f710b49ea003384e678bcf953eae`. The local mirror is synchronized. +This is Draft text acceptance, not G4 acceptance or independent wallet coverage. +See the [review evidence](../review/upstream-authorization.md). -Baseline: `spec-baseline-1`. Status: P1a inputs recorded; semantic clarifications -are proposed, not approved upstream. Technical and operations are responsibility +Baseline: `spec-baseline-3`. Status: P1a rule direction is now in the official +Draft; remaining external/release boundaries stay open. Technical and operations are responsibility categories, both currently coordinated by `@XiaoHai67890`. ## Decisions and remaining acceptance | ID | Question / current direction | Blocking scope | Responsibility | Exit criterion | | --- | --- | --- | --- | --- | -| AQ-01 | Empty/custom ERC-1271 signatures versus broad empty/length prohibitions. Preserve direct exemption, ERC-1271, then ECDSA; narrow the prohibitions | Unqualified authorization-conformance claims; not hash/encoding work | Technical | Proposed text reviewed, submitted and merged upstream; mirror and baseline updated; success/failure cases pass | -| AQ-02 | Required-account terminology, self-call with non-empty bytes, direct-empty MAY | Authorization capability reporting; direction fixed to current implementation | Technical | Table covers all three operations; portable runner reports optional capability; upstream wording clarified without changing signer roles | -| AQ-03 | Baseline 2 fixes dirty-padding abort using a strict full-word comparison; regression tests cover both copies and all three operations | External audit and official upstream asset coverage remain pending | Technical | Review the runtime delta, obtain audit addendum and upstream review; do not inherit the old frozen report | +| AQ-01 | Resolved in Draft: arbitrary ERC-1271 bytes precede the recovery-only length guard; direct-empty exemption is caller-bound | No remaining textual blocker; report each implementation's tested scope | Technical | Official merge #2147, byte-identical baseline-3 mirror, executable success/failure cases | +| AQ-02 | Resolved in Draft: required account for each operation, nonempty self-call validation and direct-empty MAY | Optional capability still needs declaration by each implementation | Technical | Merged wording and table cover all three roles without changing them | +| AQ-03 | Runtime fix and official asset synchronization complete; strict full-word comparison preserves fallback | Current-runtime external audit supplement remains pending | Technical | Obtain reviewer-owned addendum and genuine acceptance; do not inherit the old frozen report | | AQ-04 | Gas exhaustion, large returndata/resource limits and relayer underfunding | Non-blocking for implementation start; no liveness or production-readiness claim | Technical | Adequate/insufficient gas tests, rejection atomicity, RPC/client guidance; do not mandate an arbitrary universal gas allowance | | AQ-05 | Portable rejection semantics versus v2 reference error selectors/order | Reference-exact certification, not valid hash interoperability | Technical | Label each case by normative rule or reference choice; do not require unstandardized error selectors of other languages | | AQ-06 | Real Prague/type-4 harness and 46 portable ECDSA vectors/adapters merged in PRs #35/#37 | Independent external wallet execution remains separate in #21 | Technical | Maintainer account/signature gates passed on main 5770a909; retain zero-skip runs and explicit external coverage boundaries | | AQ-07 | Safe 1.4.1 source/handler/message encoding pinned and executable 2-of-2 / signed-message cases added | No claim about every Safe version or production deployment | Technical | Run `pnpm test:accounts`; inspect approved/unapproved/wrong-message and approveHash negative controls | | AQ-08 | Immutable enforcing logic and deployed-code identity | New release/deployment evidence, not a prerequisite to start an offline implementation | Technical | Reproduce bytecode; record Sourcify Exact Match, actual libraries/immutables and deployment transaction; inspect reachable upgrade paths and chain-specific SELFDESTRUCT semantics | -AQ-01/AQ-02 have a project proposal in the [decision table](AUTHORIZATION.md). -External teams can implement against that explicitly named proposal, but their -reports should not imply that the conflicting official sentences have already -changed. AQ-03 is deliberately not hidden under a green hash/vector gate. +AQ-01/AQ-02 now refer to the official merged Draft in the +[decision table](AUTHORIZATION.md). External teams should cite baseline 3 and +report any differences from their previous inputs. Historical Python results +remain scoped to the original pins until independently rerun. +AQ-03's external audit boundary is not hidden under a green hash/vector gate. ## P1b work packages diff --git a/docs/spec-baseline/README.md b/docs/spec-baseline/README.md index 10dfcbe..4e9afff 100644 --- a/docs/spec-baseline/README.md +++ b/docs/spec-baseline/README.md @@ -1,119 +1,96 @@ # ERC-8350 specification baseline -Current implementation baseline: **spec-baseline-2** (AQ-03 runtime fix). -The official text is unchanged. Historical spec-baseline-1 remains byte-identical. -See [AQ-03 change and audit delta](../security/aq03-runtime-delta.md). - -`spec-baseline-1` records the published Draft and the implementation examined on -2026-10-28. The official text is authoritative; proposed clarifications in this -directory are not merged ERC requirements. This package supplies the inputs for -authorization testing, independent implementation, audit follow-up and a future -Draft-to-Review request. It does not approve a release or complete G4. +Current baseline: **spec-baseline-3**, recorded after +[ethereum/ERCs#2147](https://github.com/ethereum/ERCs/pull/2147) merged on 2026-11-06. +The official status is still **Draft**. This is not Review, G4 or release acceptance. ## Source of authority | Artifact | Role | Pinned reference | | --- | --- | --- | -| Official `ERCS/erc-8350.md` | Normative Draft | [ethereum/ERCs at b9651bc84714e87e6361f4f6c0c8ff39cc265d0a](https://github.com/ethereum/ERCs/blob/b9651bc84714e87e6361f4f6c0c8ff39cc265d0a/ERCS/erc-8350.md) | -| Local `erc/erc-8350.md` | Byte-identical official mirror | [Local mirror](../../erc/erc-8350.md); the manifest pins its SHA-256 and official Git blob | -| `SPEC.md` | Informative implementation overview | [Current overview](../../SPEC.md); not a competing normative source | -| Reference implementation and vectors | Observed behavior and test inputs | Baseline commit `891b47c70c8b45df1daaa78065b16ff73e67f8a4` | -| Clarifications in this directory | Project proposals for discussion | Not approved upstream; tracked in [open questions](OPEN_QUESTIONS.md) | - -The official revision above is the last commit changing this ERC when checked. -Do not use a moving `master`/`main` URL as the version an implementer signs off on. -The [manifest](spec-baseline-1.json) pins independent identities for the official -text, implementation, build settings and both vector files. Its implementation -commit identifies the examined source, not a claim that new documentation or -tests already existed at that commit. - -`spec-baseline-1` is a documentation baseline ID. It does not change -`ExperienceDelta v1`, EIP-712 domain version `1`, any type string, or any hash. -A future vector-set `v3` would likewise be a test-data revision, not a protocol -version change. Baseline 1's pinned artifacts remain immutable evidence; later -baselines need new IDs and a recorded difference assessment. - -## Read the four deliverables - -1. This baseline inventory and source precedence. -2. [Text and implementation differences](DIFFERENCES.md), including resolved - documentation errors and unresolved normative ambiguity. -3. [Authorization decision table](AUTHORIZATION.md), covering all three - operations, direct-call capability, ERC-1271 and ECDSA fallback. -4. [Open questions and handoff](OPEN_QUESTIONS.md), with blocking scope, proposed - direction, responsibility and exit criteria. - -No test vector or reference implementation overrides the official text. Where -these disagree or the text is ambiguous, record the mismatch rather than silently -changing the expected output to make a test pass. The official mirror stays -unchanged while the proposed wording is discussed separately. +| Official `ERCS/erc-8350.md` | Normative Draft | [ethereum/ERCs at 8b11215cf267f710b49ea003384e678bcf953eae](https://github.com/ethereum/ERCs/blob/8b11215cf267f710b49ea003384e678bcf953eae/ERCS/erc-8350.md) | +| Local `erc/erc-8350.md` | Byte-identical official mirror | [Local mirror](../../erc/erc-8350.md); SHA-256 and Git blob are pinned | +| `SPEC.md` | Informative implementation overview | [Overview](../../SPEC.md); does not override the official text | +| Project implementation and vectors | Examined source and test inputs | Main `086e493d70bb07fc2e8b4bfac12282312c2cdc9e`, after PR #38 | +| Authorization table | Interpretation and capability inventory | [Decision table](AUTHORIZATION.md), aligned with the merged Draft | + +The [baseline-3 manifest](spec-baseline-3.json) pins the official text, runtime, +build settings and v1/v2 vectors. Its implementation commit identifies examined +source, not a claim that the new baseline documentation already existed there. +The historical overview digest likewise identifies `SPEC.md` at that commit. + +Baseline IDs are evidence revisions. `ExperienceDelta v1`, EIP-712 domain +version `1`, storage, public interfaces and the v1/v2 vectors are unchanged. +Do not use a moving branch as the version an implementer signs off on. + +## History and differences + +- [Baseline 1](spec-baseline-1.json) records the earlier official Draft and + original runtime, including the AQ-03 failure. +- [Baseline 2](spec-baseline-2.json) records the local runtime fix while official + wording was still pending. +- Baseline 3 synchronizes the accepted Draft wording and official asset identity. + The two historical manifest files and the + [candidate manifest](../review/upstream-candidate.json) remain byte-identical. +- [Differences](DIFFERENCES.md) separates the historical observations from the + merged resolution. [Open questions](OPEN_QUESTIONS.md) retains external-wallet, + current-runtime audit, resource and deployment boundaries. + +No vector or reference implementation overrides the official text. New semantic +questions should be recorded and discussed upstream, not silently turned into +new expected values. ## Verification ```bash pnpm spec:check pnpm spec:test -# Optional network check of the pinned upstream bytes, not the latest branch: pnpm spec:check --verify-upstream -cd contracts -forge test --match-path test/unit/SpecBaselineAuthorization.t.sol -vv +node --test scripts/review-candidate.test.mjs +node scripts/review-candidate.mjs +node scripts/review-candidate.mjs --official +pnpm conformance:clean +pnpm test:accounts ``` -The offline check verifies ten pinned local artifacts, the mirror's Git blob -identity and SHA-256, required metadata, and the four document entry points. -The optional upstream check compares the downloaded official bytes against both -digests. Neither check certifies the correctness of the protocol or external -review acceptance. `pnpm check` includes the offline check and its negative tests. -Full checkout validation remains `pnpm conformance:clean`. - -The characterization tests exercise the existing implementation. They are not a -new externally maintained implementation, a complete portable authorization -suite, a real Safe integration, or a real 7702 authorization-transaction test. -Separate P1b deliverables, rather than these characterization tests alone, -provide that additional coverage. - -Update 2026-11-06: separate P1b work has since merged real Safe/7702 account -scenarios, portable 46-case signature adapters, and scoped independent Python -reproduction. Those results do not expand the baseline characterization tests' -scope. [Official PR #2147](https://github.com/ethereum/ERCs/pull/2147) now proposes -AQ-01/AQ-02 wording and the AQ-03 asset fix. The [review packet](../review/draft-to-review.md) -records completed evidence and remaining upstream/external acceptance work. +The offline baseline check verifies ten protected artifacts, the official mirror's +SHA-256/Git blob, both historical manifest digests and the four document entry +points. The network check verifies the pinned official text, not today's branch. -## Confirmation and notification +The evidence runner separately checks the historical candidate and merged +official source, including all three asset digests. Each mode runs 38 +authorization/signature tests and requires exactly two expected AQ-03 failures +against the old official source. The official mode also checks its baseline +binding and the byte-identical local mirror. Reports preserve source commits, +toolchain, commands, harness commit, dirty status and raw outputs. -XiaoHai (`@XiaoHai67890`) is the project contact. Responsibility labels are -technical or operations; the current maintainer handles both. Recording this -baseline does not forge an editor approval, reviewer attestation, or partner -acceptance. +Full checkout and real-account evidence remain separate gates. None of these +tests constitutes an independently maintained client or an external audit. +See [official revision evidence](../review/upstream-authorization.md) for commands +and [Review preparation](../review/draft-to-review.md) for the remaining scope. -1. Technical: prepare the baseline, diffs, decision table and executable evidence. -2. Maintainer: review and merge the project baseline PR, recording the exact - commit being accepted for project-side coordination. -3. Operations: notify implementers through their collaboration issues and - [the second-implementation request](https://github.com/AwareLiquid/AgentMemoryState/issues/21). - Include baseline ID, full commit links, proposed-rule status and affected rows. -4. Technical: submit any normative clarification separately to `ethereum/ERCs`. - After upstream merge, create the next baseline and notify the same channels. +## Confirmation and notification -Notification text to adapt after publication: +XiaoHai (`@XiaoHai67890`) coordinates the project. Responsibility categories are +technical and operations; the current maintainer handles both. -> Baseline spec-baseline-1 pins the official Draft, implementation and vectors. -> Please cite the baseline commit in your results and distinguish observed -> reference behavior from proposed clarifications AQ-01/AQ-02. Hash/encoding -> work can proceed; authorization claims should name the rule set used. The -> table and open questions identify remaining tests and review boundaries. +1. Technical: review/merge the baseline PR and preserve exact commits/results. +2. Operations: publish the baseline in the existing Magicians thread and issues + [#21](https://github.com/AwareLiquid/AgentMemoryState/issues/21) and + [#11](https://github.com/AwareLiquid/AgentMemoryState/issues/11). +3. Technical: request Draft-to-Review separately after checking the published + packet. Any later official change requires a new assessed baseline. +4. Operations: record notification links and actual acknowledgements separately; + sending a request is not receipt of independent verification. -No notification is claimed sent by this document. A collaboration record should -link the actual baseline PR/commit, notification and implementer's acknowledgement. +The [handoff record](../review/draft-to-review.md) identifies the communication +scope. Historical independent Python results still apply to their old pinned +inputs until the contributor reports otherwise. ## Standards and release tracks Under [EIP-1](https://eips.ethereum.org/EIPS/eip-1#eip-process), Review requests -peer review. A second implementation and external audit are project quality -objectives, not universal EIP-1 prerequisites for entering Review. G4 acceptance, -production readiness, a release tag and a deployment are separate decisions. - -P1a fixes the input baseline and records directions for semantic questions. P1b -can then run testing, independent implementation, audit supplementation and -Review materials in parallel, while preserving the unresolved boundaries below. -Neither source-hash checking nor green CI automatically closes those questions. +peer review. A complete second implementation and an external audit are project +quality objectives, not universal EIP-1 prerequisites for entering Review. +Current-source G4 acceptance, production readiness, a release tag and a deployment +remain separate decisions. No approval or external attestation is inferred from CI. diff --git a/docs/spec-baseline/spec-baseline-3.json b/docs/spec-baseline/spec-baseline-3.json new file mode 100644 index 0000000..d6ee032 --- /dev/null +++ b/docs/spec-baseline/spec-baseline-3.json @@ -0,0 +1,87 @@ +{ + "schema": "erc-8350/spec-baseline/v1", + "id": "spec-baseline-3", + "recordedAt": "2026-11-06", + "status": "recorded", + "maintainer": "XiaoHai67890", + "clarifications": "merged-upstream-draft", + "upstream": { + "repository": "ethereum/ERCs", + "commit": "8b11215cf267f710b49ea003384e678bcf953eae", + "path": "ERCS/erc-8350.md", + "gitBlob": "23e1fb3d369162848162e60708000bc18aa6ba80", + "sha256": "295ab952357cefe141f3f1df5ceaaed2fe40826939fd22c683519c747a0fc072", + "status": "Draft", + "url": "https://github.com/ethereum/ERCs/blob/8b11215cf267f710b49ea003384e678bcf953eae/ERCS/erc-8350.md", + "mirror": "erc/erc-8350.md", + "pullRequest": "https://github.com/ethereum/ERCs/pull/2147" + }, + "implementation": { + "repository": "AwareLiquid/AgentMemoryState", + "commit": "086e493d70bb07fc2e8b4bfac12282312c2cdc9e", + "wireFormat": "ExperienceDelta v1", + "eip712DomainVersion": "1", + "testVectorVersions": [ + "v1", + "v2" + ] + }, + "historicalOverview": { + "path": "SPEC.md", + "commit": "086e493d70bb07fc2e8b4bfac12282312c2cdc9e", + "sha256": "9e64aec8e722845bf8e46601e96b4b4f0ede7eeaee01b9bebe1940b440c511af" + }, + "documents": [ + "docs/spec-baseline/README.md", + "docs/spec-baseline/DIFFERENCES.md", + "docs/spec-baseline/AUTHORIZATION.md", + "docs/spec-baseline/OPEN_QUESTIONS.md" + ], + "protectedArtifacts": [ + { + "path": "erc/erc-8350.md", + "sha256": "295ab952357cefe141f3f1df5ceaaed2fe40826939fd22c683519c747a0fc072" + }, + { + "path": "contracts/src/reference/AgentMemoryStateRegistry.sol", + "sha256": "de49d954c72882654dcc6ac01eeb9be451b10fa1720cc25db199b5dbc97a4cee" + }, + { + "path": "contracts/src/ECDSA.sol", + "sha256": "fdf13dd3c50b06ec54f0260dfd10f3a8c1349380ae878d73966981994cbb632d" + }, + { + "path": "contracts/src/interfaces/IAgentMemoryState.sol", + "sha256": "7307e0bd0df94d6ab6487924ec41680deaaeb7e7eecad23e9669f9babc12f963" + }, + { + "path": "erc/assets/erc-8350/AgentMemoryStateRegistry.sol", + "sha256": "94c7f39920205ae581e6537828a80a3dcfeb0a283c833ed2b50bc10927a292c3" + }, + { + "path": "erc/assets/erc-8350/ECDSA.sol", + "sha256": "74d0a828f70af9384457e2cf5165638dcc44a7b9749b8dc80cb7c9bd2ba2ccf0" + }, + { + "path": "erc/assets/erc-8350/IAgentMemoryState.sol", + "sha256": "274bfee5de979ea164abcf30af6b9dace1fccdb42bee4cf59bdeb0a9621be93a" + }, + { + "path": "contracts/foundry.toml", + "sha256": "bfbd4b78e4fcca6ed6d7bc3dab7e903b949bfde4981e0c231f79de80e12756cb" + }, + { + "path": "test-vectors/v1.json", + "sha256": "5e04cb9c1181222f483d3ad547e0a9aa301dfab74c7028f3cfa2e5dcd574fc20" + }, + { + "path": "test-vectors/v2.json", + "sha256": "4c59351ffe5317434c8ad8fda88cb95c2d2158f3dd6c147c301b7496de8dce4e" + } + ], + "predecessor": { + "id": "spec-baseline-2", + "path": "docs/spec-baseline/spec-baseline-2.json", + "sha256": "c4fe28f81708a6aa4f9e91dcef54b2d0d7a13067fd24f6286d6d25d210cc3255" + } +} diff --git a/erc/erc-8350.md b/erc/erc-8350.md index e67dfac..23e1fb3 100644 --- a/erc/erc-8350.md +++ b/erc/erc-8350.md @@ -266,22 +266,44 @@ MUST be non-zero. The nonce prevents replay of an earlier configuration. ### Signature validation +The **required account** is the supplied controller for registration, the current +controller for an authorization update, and the current authorizer for a +transition. The following rules apply to all three operations. Satisfying +authorization MUST NOT bypass any other state or field validation requirement. + A registry MUST NOT select between ECDSA and ERC-1271 validation by account code presence alone. An externally owned account delegated under [EIP-7702](./eip-7702.md) carries code of the form `0xef0100 || delegate`, and many delegates implement no signature policy; branching on code presence would reject those accounts outright. -A signature satisfying either scheme MUST be accepted, evaluated in this order. When -the authorizer has non-empty code, the registry MUST first call `isValidSignature` as -specified by ERC-1271 and accept the signature on the `0x1626ba7e` magic value. -Otherwise, or when that call reverts, returns fewer than 32 bytes, or returns any other -value, the registry MUST recover the signer from a canonical, non-malleable 65-byte -ECDSA signature and require equality with the configured account. A signature of any -other length MUST be rejected without attempting recovery. - A registry MAY accept an empty signature when `msg.sender` is exactly the account -whose authorization is required. It MUST NOT treat an empty signature submitted -by any other caller as authorized. +whose authorization is required. A different caller MUST NOT receive this +direct-call exemption merely by supplying empty bytes. A non-empty signature +MUST undergo the validation below even when submitted by the required account. + +Unless the direct-call exemption succeeds, the registry MUST evaluate the +following paths in order and stop signature validation at the first success: + +1. When the required account has non-empty code, the registry MUST first call + its ERC-1271 `isValidSignature` using `STATICCALL`, the operation's EIP-712 + digest, and the original signature bytes. It MUST NOT reject the signature + based on length before this call; empty and custom-length signatures are + subject to the account's policy. The call succeeds as authorization only if + it does not revert and returns at least 32 bytes whose first word is exactly + the ABI encoding of `bytes4(0x1626ba7e)`: the magic value followed by 28 zero + bytes. Additional return bytes MUST be ignored. Nonzero padding, short + returns, other values, and reverts MUST be treated as failed ERC-1271 + validation, not as authorization or a decoder error that prevents fallback. +2. If the account has no code or ERC-1271 validation fails, the registry MUST + attempt ECDSA validation of the same digest and signature bytes. Only this + recovery path requires a canonical, non-malleable 65-byte signature; any + other length MUST be rejected without attempting recovery. Recovery MUST + yield the required account, or the operation MUST be rejected. + +A successful path MUST satisfy the operation's authorization requirement. +ERC-1271 rejection MUST NOT be treated as a veto on an otherwise-valid ECDSA +signature. In particular, an EIP-7702 account's retained key can authorize an +operation even when its delegate rejects that signature. ### Baseline private commitments @@ -516,9 +538,21 @@ and operational recovery procedures appropriate to the value of the Space. ### Contract signature behavior ERC-1271 validation is external code execution through `STATICCALL`. The -Specification requires the exact magic value, handling of reverts and malformed -return data, and completing authorization before state is mutated; an -implementation that relaxes any of these accepts forged authorization. +Specification defines canonical return-word validation and same-byte ECDSA +fallback. Accepting a magic prefix with nonzero padding relaxes that validation; +decoding such a word as `bytes4` can instead revert before fallback. Neither +behavior implements the specified handling of malformed return data. + +Empty or custom-length signatures accepted by ERC-1271 depend on the wallet's +policy and state; they are not a direct-call exemption for the relayer. +Historical validation may therefore require historical execution state. +Authorization completes before registry state is mutated. + +The interface defines no universal gas allowance for ERC-1271. Underfunding or +resource exhaustion can deny a valid wallet-policy signature, including on a +delegated account. Fallback can complete only with sufficient remaining gas; +it does not guarantee liveness. Relayers can estimate, re-simulate, and retry, +but cannot obtain authorization merely by causing wallet validation to fail. ### Delegated accounts diff --git a/scripts/review-candidate.mjs b/scripts/review-candidate.mjs index 87cd395..5a2c66b 100644 --- a/scripts/review-candidate.mjs +++ b/scripts/review-candidate.mjs @@ -20,9 +20,20 @@ const regressions = [ export const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex"); export function validateManifest(m) { - assert.equal(m.schema, "erc8350/upstream-candidate/v1"); - assert.equal(m.status, "proposed-not-upstream-approved"); - assert.equal(m.repository, "XiaoHai67890/ERCs"); + assert.ok(["erc8350/upstream-candidate/v1", "erc8350/upstream-official/v1"].includes(m.schema), + "Unknown source evidence schema"); + const official = m.schema === "erc8350/upstream-official/v1"; + assert.equal(m.status, official ? "merged-upstream-draft" : "proposed-not-upstream-approved"); + assert.equal(m.repository, official ? "ethereum/ERCs" : "XiaoHai67890/ERCs"); + if (official) { + assert.equal(m.commit, "8b11215cf267f710b49ea003384e678bcf953eae"); + assert.equal(m.baseline, "spec-baseline-3"); + assert.equal(m.pullRequest, "https://github.com/ethereum/ERCs/pull/2147"); + assert.deepEqual(m.historicalCandidate, { + path: "docs/review/upstream-candidate.json", + sha256: "b5145480906b4e6ca9e4addfbe1754e60908d03604fae4c19662ce1e7be762e4", + }); + } assert.match(m.commit, /^[a-f0-9]{40}$/u); assert.match(m.specSha256, /^[a-f0-9]{64}$/u); assert.deepEqual(Object.keys(m.assets).sort(), [...names].sort()); @@ -34,6 +45,29 @@ export function validateManifest(m) { }); } +export function validateOfficialBaseline(m, baseline, historicalBytes) { + validateManifest(m); + assert.equal(m.schema, "erc8350/upstream-official/v1"); + assert.equal(sha256(historicalBytes), m.historicalCandidate.sha256, + "Historical candidate bytes changed"); + const historical = JSON.parse(historicalBytes); + validateManifest(historical); + assert.equal(historical.schema, "erc8350/upstream-candidate/v1"); + assert.equal(m.specSha256, historical.specSha256, "Merged text differs from tested candidate"); + assert.deepEqual(m.assets, historical.assets, "Merged assets differ from tested candidate"); + assert.equal(m.baseline, baseline.id, "Official/baseline ID mismatch"); + assert.equal(m.status, baseline.clarifications, "Official/baseline status mismatch"); + assert.equal(m.repository, baseline.upstream.repository); + assert.equal(m.commit, baseline.upstream.commit, "Official/baseline commit mismatch"); + assert.equal(m.specSha256, baseline.upstream.sha256, "Official/baseline digest mismatch"); + assert.equal(m.pullRequest, baseline.upstream.pullRequest); + assert.equal(baseline.upstream.status, "Draft"); + for (const name of names) { + const entry = baseline.protectedArtifacts.find((item) => item.path === `erc/assets/erc-8350/${name}`); + assert.equal(entry?.sha256, m.assets[name], `Official/baseline asset mismatch: ${name}`); + } +} + export async function fetchPinned(url, hash, fetcher = fetch) { const response = await fetcher(url, { signal: AbortSignal.timeout(30000) }); assert.equal(response.ok, true, `HTTP ${response.status}: ${url}`); @@ -62,10 +96,14 @@ export function checkResults(value, negative = false) { } async function main() { - assert.equal(process.argv.length, 2, "Usage: node scripts/review-candidate.mjs"); - const output = resolve(root, "artifacts/review-candidate"); + const args = process.argv.slice(2); + assert.ok(args.length === 0 || (args.length === 1 && args[0] === "--official"), + "Usage: node scripts/review-candidate.mjs [--official]"); + const official = args[0] === "--official"; + const mode = official ? "official" : "candidate"; + const output = resolve(root, `artifacts/review-${mode}`); mkdirSync(output, { recursive: true }); - const report = { schema: "erc8350/upstream-candidate-result/v1", result: "fail", commands: [] }; + const report = { schema: `erc8350/upstream-${mode}-result/v1`, result: "fail", commands: [] }; function run(command, args, allowFailure = false, env = {}) { const result = spawnSync(command, args, { cwd: root, encoding: "utf8", maxBuffer: 32 * 1024 * 1024, timeout: 300000, @@ -80,14 +118,22 @@ async function main() { report.harnessCommit = run("git", ["rev-parse", "HEAD"]).stdout.trim(); report.dirtyCheckout = run("git", ["status", "--porcelain"]).stdout.trim() !== ""; report.foundry = run("forge", ["--version"]).stdout.trim(); - const manifest = JSON.parse(readFileSync(resolve(root, "docs/review/upstream-candidate.json"))); + const manifest = JSON.parse(readFileSync(resolve(root, `docs/review/upstream-${mode}.json`))); validateManifest(manifest); + assert.equal(manifest.schema, `erc8350/upstream-${mode}/v1`, "Manifest does not match requested mode"); + if (official) { + validateOfficialBaseline(manifest, + JSON.parse(readFileSync(resolve(root, "docs/spec-baseline/spec-baseline-3.json"))), + readFileSync(resolve(root, manifest.historicalCandidate.path))); + } report.inputs = manifest; const raw = (repo, commit, path) => `https://raw.githubusercontent.com/${repo}/${commit}/${path}`; const spec = await fetchPinned(raw(manifest.repository, manifest.commit, "ERCS/erc-8350.md"), manifest.specSha256); assert.match(spec.toString(), /^status: Draft$/mu); + if (official) assert.deepEqual(spec, readFileSync(resolve(root, "erc/erc-8350.md")), + "Official text differs from the local mirror"); writeFileSync(resolve(output, "erc-8350.md"), spec); - for (const label of ["candidate", "negative-control"]) { + for (const label of [mode, "negative-control"]) { const negative = label === "negative-control"; const source = negative ? manifest.negativeControl : manifest; const assetDir = resolve(output, label, "erc-8350"); @@ -96,7 +142,7 @@ async function main() { const hash = negative && name === names[0] ? source.registrySha256 : manifest.assets[name]; const bytes = await fetchPinned(raw(source.repository, source.commit, `assets/erc-8350/${name}`), hash); if (!negative) assert.deepEqual(bytes, readFileSync(resolve(root, "erc/assets/erc-8350", name)), - `Candidate differs from tested project asset: ${name}`); + `Pinned source differs from tested project asset: ${name}`); writeFileSync(resolve(assetDir, name), bytes); } // Isolated outputs and an explicit import remapping bind tests to fetched bytes. diff --git a/scripts/review-candidate.test.mjs b/scripts/review-candidate.test.mjs index 194d290..e089be3 100644 --- a/scripts/review-candidate.test.mjs +++ b/scripts/review-candidate.test.mjs @@ -1,10 +1,46 @@ import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; -import { checkResults, fetchPinned, sha256, validateManifest } from "./review-candidate.mjs"; +import { checkResults, fetchPinned, sha256, validateManifest, validateOfficialBaseline } from "./review-candidate.mjs"; const manifest = JSON.parse(readFileSync(new URL("../docs/review/upstream-candidate.json", import.meta.url))); +const historicalBytes = readFileSync(new URL("../docs/review/upstream-candidate.json", import.meta.url)); +const official = JSON.parse(readFileSync(new URL("../docs/review/upstream-official.json", import.meta.url))); +const baseline = JSON.parse(readFileSync(new URL("../docs/spec-baseline/spec-baseline-3.json", import.meta.url))); test("candidate is pinned, separate from official acceptance", () => validateManifest(manifest)); +test("merged official evidence is bound to baseline 3 and the immutable candidate", () => { + validateOfficialBaseline(official, baseline, historicalBytes); +}); +for (const [name, mutate] of [ + ["unknown schema", (m) => { m.schema = "unknown"; }], + ["premature status", (m) => { m.status = "Review"; }], + ["fork as authority", (m) => { m.repository = "XiaoHai67890/ERCs"; }], + ["wrong merge", (m) => { m.commit = "0".repeat(40); }], + ["changed historical identity", (m) => { m.historicalCandidate.sha256 = "0".repeat(64); }], + ["changed spec", (m) => { m.specSha256 = "0".repeat(64); }], + ["changed asset", (m) => { m.assets["ECDSA.sol"] = "0".repeat(64); }], +]) { + test(`official evidence rejects ${name}`, () => { + const changed = structuredClone(official); + mutate(changed); + assert.throws(() => validateOfficialBaseline(changed, baseline, historicalBytes)); + }); +} +test("official evidence rejects rewritten history and mismatched baseline pins", () => { + assert.throws(() => validateOfficialBaseline(official, baseline, Buffer.from("{}")), /Historical candidate/); + for (const mutate of [ + (m) => { m.id = "spec-baseline-2"; }, + (m) => { m.upstream.commit = manifest.commit; }, + (m) => { m.upstream.sha256 = "0".repeat(64); }, + (m) => { m.upstream.status = "Review"; }, + (m) => { m.clarifications = "approved"; }, + (m) => { m.protectedArtifacts = []; }, + ]) { + const changed = structuredClone(baseline); + mutate(changed); + assert.throws(() => validateOfficialBaseline(official, changed, historicalBytes)); + } +}); for (const [name, mutate] of [ ["moving commit", (m) => { m.commit = "main"; }], ["false approval", (m) => { m.status = "approved"; }], diff --git a/scripts/spec-baseline.mjs b/scripts/spec-baseline.mjs index ccf4e3b..a8e75d5 100644 --- a/scripts/spec-baseline.mjs +++ b/scripts/spec-baseline.mjs @@ -7,7 +7,19 @@ import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); -export const manifestPath = "docs/spec-baseline/spec-baseline-2.json"; +export const manifestPath = "docs/spec-baseline/spec-baseline-3.json"; +export const historicalBaselines = [ + { + id: "spec-baseline-1", + path: "docs/spec-baseline/spec-baseline-1.json", + sha256: "0713af8cbbc7ba4a4697d74689cc526c1a3ae86f9041d48b84da5fe6710b6cf5", + }, + { + id: "spec-baseline-2", + path: "docs/spec-baseline/spec-baseline-2.json", + sha256: "c4fe28f81708a6aa4f9e91dcef54b2d0d7a13067fd24f6286d6d25d210cc3255", + }, +]; const documents = ["README.md", "DIFFERENCES.md", "AUTHORIZATION.md", "OPEN_QUESTIONS.md"] .map((name) => `docs/spec-baseline/${name}`); const protectedPaths = [ @@ -32,20 +44,20 @@ export function gitBlob(bytes) { function validateMetadata(manifest) { assert.equal(manifest.schema, "erc-8350/spec-baseline/v1", "Unsupported baseline schema"); - assert.equal(manifest.id, "spec-baseline-2", "Unexpected baseline ID"); - assert.deepEqual(manifest.predecessor, { - id: "spec-baseline-1", - path: "docs/spec-baseline/spec-baseline-1.json", - sha256: "0713af8cbbc7ba4a4697d74689cc526c1a3ae86f9041d48b84da5fe6710b6cf5", - }, "Historical baseline identity must not change"); + assert.equal(manifest.id, "spec-baseline-3", "Unexpected baseline ID"); + assert.deepEqual(manifest.predecessor, historicalBaselines[1], + "Historical baseline identity must not change"); assert.equal(manifest.status, "recorded", "This baseline is a record, not an approval"); - assert.equal(manifest.clarifications, "proposed-not-upstream-approved", - "Do not promote proposed clarification to upstream approval"); + assert.equal(manifest.clarifications, "merged-upstream-draft", + "Do not promote merged Draft wording to Review, Final or release approval"); assert.match(manifest.recordedAt ?? "", /^\d{4}-\d{2}-\d{2}$/u); assert.equal(manifest.maintainer, "XiaoHai67890"); const upstream = manifest.upstream; assert.equal(upstream?.repository, "ethereum/ERCs", "Wrong normative repository"); assert.match(upstream.commit ?? "", commitPattern, "Pin a full upstream commit"); + assert.equal(upstream.commit, "8b11215cf267f710b49ea003384e678bcf953eae", + "Baseline 3 identifies the observed official merge"); + assert.equal(upstream.pullRequest, "https://github.com/ethereum/ERCs/pull/2147"); assert.equal(upstream.path, "ERCS/erc-8350.md"); assert.equal(upstream.mirror, "erc/erc-8350.md"); assert.equal(upstream.status, "Draft"); @@ -72,8 +84,10 @@ function validateMetadata(manifest) { export function validateBaseline(manifest, readArtifact = (path) => readFileSync(join(root, path))) { validateMetadata(manifest); - assert.equal(sha256(readArtifact(manifest.predecessor.path)), manifest.predecessor.sha256, - "Historical baseline bytes changed"); + for (const historical of historicalBaselines) { + assert.equal(sha256(readArtifact(historical.path)), historical.sha256, + `Historical baseline bytes changed: ${historical.id}`); + } for (const entry of manifest.protectedArtifacts) { assert.equal(sha256(readArtifact(entry.path)), entry.sha256, `Pinned artifact changed: ${entry.path}; assess the change and record a new baseline`); @@ -118,7 +132,7 @@ async function main() { else { console.log(`Specification baseline ${result.baseline}: PASS (${result.protectedArtifacts} pinned artifacts)`); console.log(`Upstream: ${result.upstreamVerification}`); - console.log("Clarifications remain proposed; this is not G4 or release acceptance."); + console.log("Clarifications are merged into Draft; this is not Review, G4 or release acceptance."); } } diff --git a/scripts/spec-baseline.test.mjs b/scripts/spec-baseline.test.mjs index f3f7212..761e098 100644 --- a/scripts/spec-baseline.test.mjs +++ b/scripts/spec-baseline.test.mjs @@ -4,7 +4,7 @@ import { readFileSync } from "node:fs"; import { dirname, join, resolve } from "node:path"; import test from "node:test"; import { fileURLToPath } from "node:url"; -import { manifestPath, validateBaseline, verifyUpstream } from "./spec-baseline.mjs"; +import { historicalBaselines, manifestPath, validateBaseline, verifyUpstream } from "./spec-baseline.mjs"; const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const baseline = JSON.parse(readFileSync(join(root, manifestPath), "utf8")); @@ -15,7 +15,7 @@ test("current baseline validates without network or git history", () => { assert.equal(result.status, "PASS"); assert.equal(result.protectedArtifacts, 10); assert.equal(result.upstreamVerification, "not-requested"); - assert.equal(result.clarifications, "proposed-not-upstream-approved"); + assert.equal(result.clarifications, "merged-upstream-draft"); }); for (const path of baseline.protectedArtifacts.map((entry) => entry.path)) { @@ -30,8 +30,10 @@ test("rejects a missing artifact rather than silently skipping it", () => { }); test("preserves the historical baseline bytes", () => { - assert.throws(() => validateBaseline(baseline, (path) => path === baseline.predecessor.path - ? Buffer.from("rewritten") : read(path)), /Historical baseline bytes changed/); + for (const historical of historicalBaselines) { + assert.throws(() => validateBaseline(baseline, (path) => path === historical.path + ? Buffer.from("rewritten") : read(path)), /Historical baseline bytes changed/); + } const changed = structuredClone(baseline); changed.predecessor.sha256 = "0".repeat(64); assert.throws(() => validateBaseline(changed, read), /Historical baseline identity/); @@ -39,6 +41,8 @@ test("preserves the historical baseline bytes", () => { for (const [name, mutate, message] of [ ["moving upstream ref", (m) => { m.upstream.commit = "master"; }, /full upstream commit/u], + ["substituted official commit", (m) => { m.upstream.commit = "0".repeat(40); }, /observed official merge/u], + ["premature Review status", (m) => { m.upstream.status = "Review"; }, /Expected values/u], ["moving normative URL", (m) => { m.upstream.url = m.upstream.url.replace(m.upstream.commit, "master"); }, /pinned file/u], ["missing runtime pin", (m) => { m.protectedArtifacts.pop(); }, /inventory/u], ["duplicate pin", (m) => { m.protectedArtifacts.push(m.protectedArtifacts[0]); }, /inventory/u],