docs: plan issue #2830 — G02 wire/core boundary contract - #2931
Conversation
- Add ADR-0081: one declarative core<->wire pairing registry, one generic bidirectional translator on the adapter side, and extra="forbid" on the core branch as the structural guarantee. Supersedes ADR-0062; revises the mechanism (not the decision) of ADR-0063. - Record that ARCH-22-003's "zero wire->core imports" goal was unreachable: ARCH-12-001, ARCH-12-005 and ARCH-12-010 each mandate an import it forbade. Retarget it at an enumerated one-member exemption set. - Amend ARCH-12-001/002 (shared base moves to a branch-neutral layer), ARCH-12-003 (extra="forbid"), ARCH-12-005 (projection on the adapter side), ARCH-22-003, and VM-01-004 (registry key is not the AS2 type value). - Add ARCH-23-001..006 for the pairing registry, disjoint keys, unchanged serialized output, annotation-derived ref fields, round-trip fidelity, and the prohibition on core types in wire annotations. - Add notes/wire-core-boundary.md with the four duplications, the measured blast radii (25 vs 570 failures), and the embargo_adherence computed-field and id_ round-trip findings. - Add four AGENTS.md pitfalls: union-exposed validators must raise a ValueError subclass; extra="forbid" needs round-trip cleanups first; ARCH-01-001 and ARCH-22-001 are different rules; check a ratchet's goal state against the spec corpus before adding the xfail. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- glossary: from_core()/to_core() entry notes the ADR-0081 relocation to adapter-side translators; add Pairing Registry and WireParsePort entries. Corrects the ARCH-03-001 citation to ARCH-01-001. - reference/codebase/ARCHITECTURE.md: drop the restated violation count (MS-16-001 — it said 32, the test set holds 31, #2670 lists a third number) and record that migrating to the from_core() seam is no longer the remedy, since those methods are themselves the core imports. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
sei-ahouseholder
left a comment
There was a problem hiding this comment.
PR Triage: #2931 — docs: plan issue #2830 — G02 wire/core boundary contract
Linked issues: #2830, #2262, #2403, #1895, #891
Changed files: 14 files — specs, docs/adr, notes, plan/history (docs-only; 0 .py)
CI status: ✅ passing (7/7 reporting checks)
Merge state: phase12-docs-only-prs-blocked-45)
Base branch: main (0 commits behind)
Needs integration tests: no
What was verified as sound
The ADR's measured evidence holds up under independent reproduction — this matters, because the PR itself says "the 570 figure nearly decided this the wrong way":
| Claim | Result |
|---|---|
extra="forbid" alone → 570 failed / 330 errors |
✅ character-exact on the unit suite (-m 'not integration') |
| Zero of those failures involve a camelCase key | ✅ exact — embargo_adherence 1096, id_ 110, camelCase 0 |
alias_generator=to_camel ⇒ empty forbidden-key set |
✅ verified empirically — #2262's stated sequencing blocker really is absent |
VOCABULARY.get(type(obj).__name__) is the pairing index |
✅ adapters/driven/wire_render/as2.py:81-82, 15 colliding keys |
| Five wire re-export shim modules | ✅ exactly five, confirmed by AST sweep |
mkdocs --strict adds no warnings |
✅ 101 on branch, 101 on origin/main, identical warning sets |
ADR template conformance, adr-index --check, spec-lint, markdownlint |
✅ all clean |
Findings
❌ FAIL — the ARCH-12-005 amendment strands nine MUSTs elsewhere in the corpus
This is the dominant finding. Amended ARCH-12-005 makes from_core()/to_core() on wire vocabulary classes forbidden, but nine unamended MUST/MUST_NOT entries across five spec files still mandate that exact mechanism. ADR-0081 cites none of them. Its own recorded Lesson is "check that the target does not contradict a MUST elsewhere in the corpus."
| # | Severity | Description | Outcome |
|---|---|---|---|
phase5-arch20-002-mandates-from-core-0 |
❌ FAIL | architecture.yaml:777 ARCH-20-002 MUST: adapter must invoke wire_cls.from_core(). ADR-0081 §4 says it no longer does — and never amends it. Task #2939 cannot land without violating a MUST |
fix-now |
phase5-arch20-008-009-mandate-to-core-1 |
❌ FAIL | architecture.yaml:863,878 ARCH-20-008/009 MUST wire→core conversion use to_core() |
fix-now |
phase5-arch22-001-forbidden-seam-example-2 |
❌ FAIL | architecture.yaml:990 ARCH-22-001's worked example is as_Foo.from_core(); the PR added ARCH-23-006 refines: ARCH-22-001 without fixing it |
fix-now |
phase5-dl-05-normalize-ratchet-stranded-3 |
❌ FAIL | datalayer.yaml DL-05-005/008/009 mandate the _NORMALIZE_WIRE_TO_CORE ratchet; ARCH-23-002 makes their subject set permanently empty |
fix-now-expand-scope |
phase5-cm-sdo-clp-mandate-projections-4 |
❌ FAIL | CM-08-002, CM-18-015, SDO-04-001, CLP-07-010 all mandate the forbidden projections | fix-now-expand-scope |
phase5-af-01-005-factory-projection-5 |
❌ FAIL | AF-01-005 MUST puts core→wire projection inside wire factories, making ARCH-22-003's one-member exemption set unachievable | fix-now-expand-scope |
❌ FAIL — wrong facts that 13 downstream Tasks would inherit
| # | Severity | Description | Outcome |
|---|---|---|---|
phase8-arch22-violation-count-wrong-7 |
❌ FAIL | The ARCH-22 violation count is 31, not 29 — verified three ways (len(KNOWN_VIOLATIONS), live _VIOLATIONS scan with the ratchet symmetric-difference empty, literal count). The PR strips a drifting count from ARCHITECTURE.md while importing a wrong one into four new places. Should read "30 of 31" |
fix-now |
phase6-arch12-005-miscited-as-mandate-6 |
❌ FAIL | The "zero wire→core imports was unreachable" argument cites ARCH-12-005 as mandating the import. Pre-PR ARCH-12-005 says such methods are "not required" — it permits, it does not mandate. The entry that actually mandates it is ARCH-20-002 | fix-now |
phase8-id-underscore-root-cause-wrong-8 |
❌ FAIL | The id_/type_ finding is real but its cause is wrong. id_ is not an unknown key — validate_by_name=True makes it sanctioned, and round-trip holds. All 110 failures are CaseLedgerEntry, whose mode="before" validator injects data["id"] beside an existing id_. Tasks #2933/#2940 are scoped on a false premise |
fix-now |
❌ FAIL — active notes silently contradicted
| # | Severity | Description | Outcome |
|---|---|---|---|
phase9-datalayer-design-note-false-fact-9 |
❌ FAIL | notes/datalayer-design.md:245 — "remaining 5 shadowing types… none has a to_core() yet" is false; all 15 are in _NORMALIZE_WIRE_TO_CORE. This PR states the correction in ADR-0062 and not here, and the file is in wire-core-boundary.md's own related_notes |
fix-now |
phase9-activity-factories-note-contradicted-10 |
❌ FAIL | notes/activity-factories.md:258 — "in adapter code, always use wire_cls.from_core(core_obj)… the canonical path", for a mechanism this PR forbids |
fix-now |
phase9-status-dimension-note-contradicted-11 |
❌ FAIL | notes/status-dimension-objects.md:159,170 directs implementers to write the very methods #2939 deletes |
fix-now |
❌ FAIL — issue linkage
| # | Severity | Description | Outcome |
|---|---|---|---|
phase2-2943-blocker-misstated-12 |
❌ FAIL | #2943 is listed with no blocker and named an entry point, but GitHub has #2943 blockedBy #2500 (OPEN), and #2500's body — edited this session — says "Blocks #2943" |
fix-now |
phase2-2944-closes-2670-incomplete-13 |
❌ FAIL | #2944 AC-5 closes #2670 but is not blocked by #2932/#2933, which are also #2670 children — #2670 can close with two open children | fix-now |
phase2-1895-closes-rule4-violation-14 |
❌ FAIL | #2828 rule 4 allows Closes only for fully-resolved members; #1895's remainder went to an Idea (#2945, whose options include retiring the corpus) with no recorded maintainer waiver — unlike #891, which has one |
fix-now |
⚠️ IMPROVE (28)
ADR internals — phase6-arch12-002-claimed-amended-15 (ADR claims ARCH-12-002 was amended; it is unchanged in the diff) · phase6-supersession-story-inconsistent-16 (three-way split: partially_superseded_by frontmatter vs ADR-0081:366 "Supersedes ADR-0062" vs ADR-0062:157 "flip status to superseded and archive") · phase6-adr0081-missing-supersedes-field-17 · phase6-adr0062-self-contradicting-body-18 (leaves body vs addendum to reconcile — the failure mode adr/index.md:58-62 forbids) · phase6-adr0063-no-forward-link-19 (ADR-0063 untouched though its mechanism is revised, and it is the ADR behind ARCH-20-002)
Spec quality — phase5-arch23-002-verification-vacuous-20 (the assertion passes on an empty dict before walk_packages, so it would pass before any work is done) · phase5-missing-verification-fields-21 (new MUST_NOT on ARCH-12-005 and new normative VM-01-004 content, no verification:; spec-lint warns) · phase5-sdo-03-005-error-type-stale-22 · phase5-arch12-group-rationale-stale-23 · phase5-arch22-003-count-in-statement-24 (MS-16-001 count-in-statement) · phase5-exemption-set-arithmetic-underived-25 (enums.py + 4 extractor modules import core symbols absent from ADR §8's relocation list)
Notes / docs — phase9-core-wire-port-frontmatter-26 · phase9-vocabulary-registry-frontmatter-27 (both NF-06-001: body cites ADR-0081, frontmatter doesn't) · phase9-notes-readme-entries-stale-28 · phase9-glossary-present-tense-aspirational-32 (Pairing Registry / WireParsePort in present indicative for artifacts that don't exist; sibling entry hedges correctly) · phase9-glossary-wireparseport-no-spec-id-33 · phase9-vocabulary-registry-count-in-prose-34 · phase9-domain-validation-note-stale-pointer-35
AGENTS.md — phase7-agents-md-pairing-registry-unhedged-29 ("use the pairing registry" — it doesn't exist yet, and unlike its sibling has no "until then") · phase7-agents-md-pitfalls-too-long-30 · phase7-agents-md-duplicates-counts-31 · phase9-agents-md-missing-nf-06-002-42
ADR measurement nits — phase8-354-lines-misattributed-36 (354 is the both-directions total; to_core() alone is 195) · phase8-sixteen-subclasses-count-wrong-37 (matches neither 17 nor 29) · phase8-collision-is-registry-keys-38
Scheduling / protocol — phase2-issues-all-someday-tier-39 (all 16 at Schedule=Someday, though #2830 says #2262 must not be cut and the PR names #2935 highest-leverage) · phase2-2830-ac5-also-unmet-40 (AC-5 also unmet, undisclosed) · phase2-tasks-parent-to-bug-not-epic-41 (7 Tasks parent to #2670, issueType Bug, vs AC-4's domain epic)
🎫 NEW-ISSUE (3)
| # | Description | Outcome |
|---|---|---|
phase9-mdlint-fix-rewrites-history-43 |
mdlint.sh runs markdownlint-cli2 --fix "**/*.md" with no plan/** exclusion, so --fix rewrote a write-once history entry (commit 7de4a6c0), violating HM-01-005 MUST_NOT. Recurs for any entry with a line-initial #NNNN |
new-issue-no-ask |
phase12-docs-only-prs-blocked-45 |
Ruleset main-2 requires contexts that python-app.yml/demo-integration.yml path-filter away, so every docs-only PR sits BLOCKED and needs an admin bypass (how #2880 merged) |
new-issue-no-ask |
phase7-agents-md-exceeds-400-lines-44 |
AGENTS.md is 1152 lines vs the ≤400 target in notes/agents-md-structure.md:61. Pre-existing (1105 before), not newly crossed |
new-issue-no-ask |
Total: 15 FAIL · 28 IMPROVE · 3 NEW-ISSUE
Outcomes: 40 fix-now · 3 fix-now-expand-scope · 3 new-issue-no-ask
The FAIL set is concentrated, not scattered: one root cause — amending ARCH-12-005 to forbid
from_core()/to_core()without sweeping the corpus for entries that mandate them — accounts for 6 of the 15, and three more are counts or causal claims that the 13 new Tasks would inherit. The ADR's measurements are sound; its citations and counts are not.
Triage artifact: .claude/pr-2931-triage.json
Next step: /pr-execute (or the rest of /pr-ship) to apply fixes.
spec-conformance — the ARCH-12-005 amendment forbade from_core()/to_core() on wire vocabulary classes but left nine MUST/MUST_NOT entries elsewhere in the corpus still mandating them. Amended all nine: - ARCH-20-002/008/009: retargeted at the pairing registry and WireParsePort rather than per-class projections; ARCH-20-008 gained a verification. - ARCH-22-001: worked example replaced with the adapter-side translator seam. - DL-05-005/008/009: normalisation reassigned to the adapter-side translator; _NORMALIZE_WIRE_TO_CORE and _NOT_YET_NORMALIZED must be deleted, not emptied. - CM-08-002, CM-18-015, SDO-04-001, CLP-07-010: renamed the mechanism; CM-18-015's verification rewritten off as_ParticipantStatus.from_core(). - AF-01-005: inverted — factories now receive a complete translator-produced wire object and MUST NOT import a core type, preserving the #1300 guard against partial hand-built wire objects. - SDO-03-005: rewritten in terms of extra="forbid"; expects pydantic ValidationError, not VultronValidationError. - ARCH-12-005, VM-01-004: added the missing verification fields (MS-05-003). - ARCH-23-002: verification no longer passes vacuously — it must force full submodule registration and assert both registries non-empty first. - ARCH-22-003: count removed from the statement; exemption set encoded as an enumeration plus post-Task residue, never an arithmetic. - ARCH-12 group rationale, ARCH-12-007, ARCH-20-001: stale references to the old meaning of ARCH-12-005 corrected. adr-check — corrected three facts the 13 downstream Tasks would have inherited: - The ARCH-22 violation count is 31, not 29 (len(KNOWN_VIOLATIONS) == len(_VIOLATIONS) == 31, symmetric difference empty). #2670's prose miscounts itself; its own enumeration lists 31. - ARCH-12-005 never mandated a wire->core import — it said such methods were "not required". ARCH-20-002 is the entry that mandated it. Corrected in the ADR, the note, and ARCH-22-003's rationale. - The id_/type_ persistence rows are an alias-injection bug, not an unknown-key bug: validate_by_name=True makes id_ a sanctioned input and the round-trip holds. All 110 failures are CaseLedgerEntry._set_id_from_case writing data["id"] beside an already-present id_. Re-keying rows would mask the defect rather than fix it, and does nothing for the 1096 embargo_adherence failures — so #2933/#2940 were scoped on a false premise. - ADR-0062: "7 of the 15" consequence revised in place per the index's revise-don't-append rule; supersession stated consistently as partial. - ADR-0081: gained supersedes:; dropped the false claim that ARCH-12-002 was amended; 354-line and "sixteen subclasses" figures corrected; clarified that the pairing collision is between registry keys, not class names. - ADR-0063: gained partially_superseded_by and a forward note; index regenerated with adr-index --write. notes-currency — three active notes contradicted the amended contract: - datalayer-design.md claimed 5 shadowing types lack to_core(); all 15 are normalised (#2268, #2402 completed the set). - activity-factories.md prescribed "always use wire_cls.from_core()". - status-dimension-objects.md directed implementers to write the very methods #2939 deletes. Plus frontmatter (NF-06-001) on core-wire-rendering-port.md and vocabulary-registry.md, two stale notes/README.md entries, and MS-16-001 count-in-prose removals. agents-md — pairing-registry pitfall now hedged (#2937 has not landed, so there is no registry to use); two over-long pitfalls trimmed to pointers with the detail moved into notes/wire-core-boundary.md; duplicated measurement counts removed; the NF-06-001 note-maintenance rule documented. code-review — the ARCH-22 ratchet test's own docstring, KNOWN_VIOLATIONS comment, failure message and xfail reason all still instructed implementers to use the now-forbidden as_Foo.from_core() seam and to target frozenset(). Prose only; the assertion is deliberately left targeting frozenset() because retargeting it at the exemption set is #2944's job. Verification: spec-lint 0 errors (six MS-05-003 missing-verification warnings eliminated); test/architecture 146 passed / 25 xfailed; test/metadata 497 passed; adr-index --check in sync; markdownlint clean; notes loader 93 notes / 0 errors. Refs #2830, #2262, #2403 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Renumbers this branch's ADR 0081 -> 0082. PR #2916 landed its own ADR-0081 ("Peer Knowledge Lives in the Hosted Actor's Own Store, Not as a Hosted Actor") on main first, and that number already has inbound references across vultron/ and test/, so the unlanded ADR is the one that moves. Conflicts, both in adr-index generator output, resolved by taking main's side and regenerating: - docs/adr/index.md — regenerated with `adr-index --write`; now lists both ADR-0081 (peer knowledge) and ADR-0082 (wire/core boundary). - mkdocs.yml — nav labels are hand-crafted and only checked for completeness, so the ADR-0082 entry was added by hand in number order. All 51 references to the wire/core-boundary ADR were rewritten across 22 files (specs, notes, docs, AGENTS.md, and the ARCH-22 ratchet test docstring). Every occurrence was inspected individually first: references to main's ADR-0081 in vultron/ and test/ are deliberately untouched. Known exception: plan/history/2608/learning/CONCERN-2830.md still cites docs/adr/0081-wire-core-boundary-pairing-registry.md. HM-01-005 makes history entries write-once (MUST NOT be modified), and that spec has no sanctioned amendment path for an external renumbering, so the stale path is left in place rather than violating the rule. Recorded on #2952. Verification after the merge: - Full unit suite: no failures, no errors (pre-existing xfails only). - mypy 1315 files clean; pyright 0 errors; black clean; flake8 vultron/ test/ clean (CI scope). - adr-index --check: index and nav in sync. - mkdocs build --strict: 101 warnings on this branch and 101 on origin/main — identical, pre-existing, tracked as #2904. This branch adds none. - test/architecture/test_codebase_docs_paths.py (new ratchet from main) passes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…re-boundary-contract
sei-ahouseholder
left a comment
There was a problem hiding this comment.
PR Execute: #2931 — 46/46 findings resolved
Fixed inline: 43 · Filed as issues: 3 · Skipped: 0
CI: ✅ 15/15 passing · Merge state:
Commits: 8a216c9f (fixes) · 40940e95 + 73424d3f (merge + ADR renumber)
⚠️ ADR renumbered 0081 → 0082
main landed its own ADR-0081 mid-run ("Peer Knowledge Lives in the Hosted Actor's Own Store", #2916). That number already had inbound references across vultron/ and test/, so this branch's unlanded ADR is the one that moved. 51 references rewritten across 22 files — every occurrence was inspected individually first, so main's ADR-0081 references are deliberately untouched.
The two merge conflicts (docs/adr/index.md, mkdocs.yml) were both adr-index generator output: resolved by taking main's side, regenerating with adr-index --write, and hand-adding the nav entry in number order (nav labels are hand-crafted and only checked for completeness).
The dominant fix: nine stranded MUSTs
Amending ARCH-12-005 to forbid from_core()/to_core() had left nine MUST/MUST_NOT entries across five spec files still mandating them. All nine amended:
| Spec | Change |
|---|---|
| ARCH-20-002 | Adapter resolves via the pairing registry and delegates to the adapter-side translator; invoking a projection method on the wire class is now explicitly forbidden |
| ARCH-20-008 / -009 | Retargeted at WireParsePort; -008 gained a verification and now prohibits getattr(obj, "to_core", None) duck-typing; -009's trigger moved from "no to_core() override" to "no counterpart in the pairing registry" |
| ARCH-22-001 | Worked example replaced with the translator seam; rationale now explains why the per-class seam was abandoned |
| DL-05-005 / -008 / -009 | Normalisation reassigned to the translator; _NORMALIZE_WIRE_TO_CORE and _NOT_YET_NORMALIZED must be deleted, not emptied — an empty list in source is an invitation to add to it |
| CM-08-002, CM-18-015 | Mechanism renamed; CM-18-015's verification rewritten off as_ParticipantStatus.from_core() |
| SDO-04-001, CLP-07-010 | Translators named as a third artifact that must move with both branches |
| AF-01-005 | Inverted — factories now receive a complete translator-produced wire object and MUST NOT import a core type. The #1300 guard survives by requiring completeness; the prohibition shifts onto partial hand-built wire objects |
| SDO-03-005 | Rewritten in terms of extra="forbid"; expects pydantic ValidationError, not VultronValidationError |
Also: ARCH-12-005 and VM-01-004 gained the missing verification: fields, and ARCH-23-002's verification no longer passes vacuously — it must force full submodule registration and assert both registries non-empty before checking disjointness. Six MS-05-003 warnings eliminated; spec-lint reports 0 errors.
ARCH-22-003 now encodes the exemption set as an enumeration plus post-Task residue, never an arithmetic. Only vocab/base/registry.py is pinned by a MUST that survives ADR-0082 (ARCH-12-010); everything else is a relocation target, and KNOWN_VIOLATIONS stays a superset until those land.
Three wrong facts the 13 Tasks would have inherited
- The count is 31, not 29. Confirmed four independent ways. Corrected in ADR-0082,
notes/wire-core-boundary.md, the PR body, and #2670's annotating comment. (#2670's prose miscounts against its own 31-path enumeration.) - ARCH-12-005 never mandated the import — it said such methods were "not required". ARCH-20-002 is the entry that did. Corrected in the ADR, the note, ARCH-22-003's rationale, #2670's comment, and the ratchet test's docstring.
- The
id_rows are an alias-injection bug, not an unknown-key bug.validate_by_name=Truemakesid_a sanctioned input and the round-trip holds; all 110 failures areCaseLedgerEntry._set_id_from_casewritingdata["id"]beside an already-presentid_. Nine peer sites listed. Re-keying rows would have masked the defect — a stale storedidwould validate silently to a different value — and does nothing for the 1096embargo_adherencefailures. #2933/#2940 were scoped on a false premise.
Three active notes that contradicted the amended contract were also fixed: datalayer-design.md (claimed 5 types lack to_core(); all 15 are normalised), activity-factories.md ("always use wire_cls.from_core()"), status-dimension-objects.md (directed implementers to write the methods #2939 deletes).
GitHub-side
- #2944 now
blockedByall six sibling children of #2670, soCloses #2670can no longer fire with open children. - #2943's real blocker (#2500) recorded; removed from the "unblocked today" list.
- #2830 carries a recorded AC-3/AC-4/AC-5 amendment instead of two silently-failed criteria.
- Schedule promoted off Someday: #2935 → Now, #2937/#2940 → Next (#2830 named #2262 as must-not-cut; the PR named #2935 highest-leverage).
Filed (3)
| Issue | Type | What |
|---|---|---|
| #2952 | Bug | mdlint.sh --fix rewrites write-once plan/history/ entries, forcing HM-01-005 violations |
| #2953 | Bug | Docs-only PRs are permanently BLOCKED — required checks are path-filtered away |
| #2954 | Task | Root AGENTS.md is 1152 lines against a documented 400-line target |
Deliberately not done (2)
plan/history/.../CONCERN-2830.mdstill cites the pre-rename ADR path. HM-01-005 makes history entries write-once and defines no amendment path for an external renumbering. Left unmodified rather than violating the MUST_NOT; recorded as a second instance on #2952 with a suggested acceptance criterion.Closes #1895needs a maintainer call. The rule-4 deviation is now explicitly disclosed in the PR body and #2830 carries an AC-5 amendment admitting a tracked-Idea sink. The actual decision — accept that, or dropCloses #1895and leave it open blocked-by #2945 — is flagged for merge time. The pipeline did not make it.
Merge state
MERGEABLE / BLOCKED — 6 of 14 required contexts satisfied. Touching test/architecture/ made Tests (pytest) and all four Lint (*) contexts run for the first time on this PR; the 8 demo-integration contexts still cannot report because demo-integration.yml is path-filtered to vultron/**, docker/**, integration_tests/**, test/ci/**. That is the standing repo condition filed as #2953, not a defect here — the Admin role holds bypass_mode: always on that ruleset, which is how recent docs PRs merged.
Verification
- Full unit suite: 0 failures, 0 errors (pre-existing xfails only)
- mypy 1315 files clean · pyright 0 errors · black clean ·
flake8 vultron/ test/clean spec-lint0 errors (6 MS-05-003 warnings eliminated) ·adr-index --checkin syncmkdocs build --strict: 101 warnings on this branch, 101 onorigin/main— identical sets, zero added (pre-existing, #2904). Re-verified against the new main after both merges.- New-from-main ratchets pass:
test_codebase_docs_paths.py,test_adr_decision_citations.py
Execute artifact: .claude/pr-2931-execute.json · 46/46 findings have an outcome
Next step: /pr-verify
There was a problem hiding this comment.
PR Verify: #2931 — ✅ READY-TO-MERGE
Merge state: ✅ MERGEABLE — BLOCKED (repo policy, not a defect — see below) · base main
CI: ✅ 15/15 passing
Findings: 46/46 accounted for — 43 CONFIRMED · 3 NOTED · 0 UNRESOLVED
Integrity: len(execute.results) == len(triage.findings) = 46 ✓ · merge_state.synced = true ✓
Merge-state gate
| Check | Result |
|---|---|
Live merge-state.sh |
MERGEABLE / BLOCKED, not a draft |
| Conflict markers at HEAD | clean (<<<<<<< / >>>>>>> grep) |
merge-tree vs current origin/main |
no conflicts |
| Behind base | 3 commits — but strict_required_status_checks_policy: false, so not BEHIND and not blocking |
Why BLOCKED: 6 of 14 required contexts report. Touching test/architecture/ made Tests (pytest) and all four Lint (*) contexts run for the first time on this PR; the 8 demo-integration contexts still cannot report because demo-integration.yml is path-filtered to vultron/**, docker/**, integration_tests/**, test/ci/**. That is the standing repo condition filed as #2953, not a defect in this PR, and the Admin role holds bypass_mode: always on the ruleset. Per the merge-state gate, BLOCKED is a policy state a human resolves and does not block the verdict.
FAIL findings — 15/15 CONFIRMED at HEAD
Verified against the working tree, not just the commit diff:
| Finding | Evidence at HEAD |
|---|---|
phase5-arch20-002-mandates-from-core-0 |
ARCH-20-002 now resolves via the pairing registry and MUST NOT invoke a projection method on the wire class; adr: [ADR-0063, ADR-0082]; depends_on ARCH-23-001 |
phase5-arch20-008-009-mandate-to-core-1 |
ARCH-20-008 retargeted at WireParsePort, gained a verification |
phase5-arch22-001-forbidden-seam-example-2 |
as_Foo.from_core example gone from the entry |
phase5-dl-05-normalize-ratchet-stranded-3 |
DL-05-005/008/009 carry ADR-0082 |
phase5-cm-sdo-clp-mandate-projections-4 |
CM-08-002, CM-18-015, SDO-04-001, CLP-07-010 all carry ADR-0082 |
phase5-af-01-005-factory-projection-5 |
AF-01-005 inverted; carries ADR-0082 |
phase6-arch12-005-miscited-as-mandate-6 |
ARCH-20-002 cited in the ADR, the note, ARCH-22-003's rationale, #2670's comment, and the ratchet test |
phase8-arch22-violation-count-wrong-7 |
28 of 29 absent from both ADR and note; 31-based figures present |
phase8-id-underscore-root-cause-wrong-8 |
validate_by_name mechanism documented in both ADR and note |
phase9-datalayer-design-note-false-fact-9 |
the "none has a to_core() yet" sentence is gone |
phase9-activity-factories-note-contradicted-10 |
the "always use wire_cls.from_core" prescription is gone |
phase9-status-dimension-note-contradicted-11 |
redirected to ARCH-23-001 |
phase2-2943-blocker-misstated-12 |
PR body records #2500; #2943 dropped from the unblocked list |
phase2-2944-closes-2670-incomplete-13 |
#2944 blockedBy = [2932, 2933, 2934, 2935, 2939, 2942] — all six siblings |
phase2-1895-closes-rule4-violation-14 |
deviation disclosed in the PR body; AC-5 amendment on #2830 — residual human decision, see below |
Two spot-checks initially flagged and were false positives of the grep window, resolved by reading the entries: ARCH-20-002's remaining
from_core()mention is its rationale explaining why the mechanism moved (correct and desirable), and CLP-07-010'sADR-0082sits at line 497, just outside a 20-line window.
IMPROVE findings — 28/28 CONFIRMED
ADR renumber 0081 → 0082 complete (old file gone, supersedes: present, ADR-0063 carries partially_superseded_by, ADR-0062's self-contradiction removed, ARCH-12-002 claim dropped) · ARCH-23-002's verification no longer vacuous · ARCH-22-003 carries no count · notes frontmatter and notes/README.md current · AGENTS.md pitfall hedged on #2937 and the NF-06-001 maintenance rule documented · glossary hedged and WireParsePort cited · index lists both ADR-0081 (peer knowledge) and ADR-0082 (wire/core) · nav in sync.
NOTED — 3 filed issues, all real and open
| Issue | Type | State |
|---|---|---|
| #2952 | Bug | OPEN — mdlint.sh --fix rewrites write-once history entries |
| #2953 | Bug | OPEN — docs-only PRs permanently BLOCKED |
| #2954 | Task | OPEN — AGENTS.md 1152 lines vs 400 target |
Schedule set by inheritance from the parent epic (calve-epics step 4, "Inherit the
horizon"), not by hand-picking: all 13 Tasks = Now (epic #2692 = Now); the 3 Ideas =
Later (epic #890); #2952/#2953 = Next (epic #1190); #2954 = Focus (epic #607).
This also corrected a pre-existing inconsistency — #2670 was Someday under a Now epic,
so the 7 Tasks parented to it would have inherited the wrong horizon; #2670 is now Now.
Verified live on Project #24: 19/19 items match their epic's tier.
Resolved at review — Closes #1895 stays
Verify originally flagged this as a rule-4 deviation needing a human call, on the grounds
that #2945's option set "includes retiring the corpus — the opposite of what #1895 asked
for." That characterisation was overstated and is retracted. #2945 carries all five of
#1895's open questions forward in an explicit carried-over section, does not advocate
retirement (option 3 of 3, and the only one annotated with what it costs), and supersedes
#1895 with evidence — its "add the missing terms" premise assumed a per-semantic-message
paradigm the implementation abandoned for AS2 activity patterns, cited a pre-ADR-0069
namespace, and called the corpus normative when the generated pages say otherwise.
What remained was a formality — rule 4 names a Task, #2945 is an Idea — and an Idea is the
right container for an unmade generate / curate / retire decision. Maintainer decision:
keep Closes #1895. Correction recorded on
#2830.
Deliberately not fixed
plan/history/2608/learning/CONCERN-2830.md still cites the pre-rename ADR path. HM-01-005 makes history entries write-once and defines no amendment path for an external renumbering, so the dangling path was left rather than violating the MUST_NOT. Recorded as a second instance on #2952 with a suggested acceptance criterion.
Verification re-run at HEAD
- Full unit suite: 0 failures, 0 errors · mypy 1315 files clean · pyright 0 errors · black clean ·
flake8 vultron/ test/clean spec-lint0 errors (six MS-05-003 warnings eliminated) ·adr-index --checkin syncmkdocs build --strict: 101 on this branch, 101 onorigin/main— identical sets, zero added (pre-existing, #2904), re-verified against the post-merge main- New-from-main ratchets pass:
test_codebase_docs_paths.py,test_adr_decision_citations.py
Verdict: READY-TO-MERGE. Session artifacts cleaned up. No open decisions.
Summary
Planning session for G02 (#2830) — the wire/core boundary contract. Records
ADR-0082: one declarative core↔wire pairing registry, one generic bidirectional
translator on the adapter side, and
extra="forbid"on the core branch as thestructural guarantee. Partially supersedes ADR-0062 and revises the mechanism (not the
decision) of ADR-0063.
The four members turned out to be one problem stated four ways: the boundary's
knowledge is duplicated, and every mechanism that needs the core↔wire pairing
either re-derives it from a bare-name collision or hand-maintains a private copy.
One declarative pairing registry collapses three of those duplications rather than
requiring three fixes — which also converts #2403 from a risky rename into a
consequence.
Changes
docs/adr/0082-wire-core-boundary-pairing-registry.md(new): the decision,with the measured evidence behind it.
docs/adr/0062-…:partially_superseded_byfrontmatter + forward-linknote; corrects its stale "covers 7 of the 15" consequence (13 wire vocabulary classes still shadow a CORE_VOCABULARY type on the write path #2268/Write to_core() for 5 actor wire types and migrate into _NORMALIZE_WIRE_TO_CORE #2402
completed the set).
specs/architecture.yaml: amends ARCH-12-001/002 (shared base moves to abranch-neutral layer), ARCH-12-003 (adds the
extra="forbid"contract),ARCH-12-005 (projection on the adapter side, not on wire classes), ARCH-22-003
(retargeted at an enumerated exemption set); adds group ARCH-23
(ARCH-23-001…006).
specs/vocabulary-model.yaml: amends VM-01-004 — the registry key is notthe AS2
typevalue.notes/wire-core-boundary.md(new): design rationale, the fourduplications, and the measurements.
AGENTS.md: four new pitfalls; marks the wire-spelling-shim pitfall assuperseded in direction.
notes/vocabulary-registry.md,notes/core-wire-rendering-port.md,notes/README.md,docs/adr/index.md,mkdocs.yml: cross-linksand nav.
Key findings
Three of the four member issues rested on a premise that turned out to be wrong:
CoreObject-level guardmust be sequenced against Core ParticipantStatus carries wire concerns (ARCH-12-003 violation) — needs migration plan #2260. Measured: any class carrying
alias_generator=to_camelyields an empty forbidden-key set, so the guard isstructurally inert there and arms itself when Remove alias_generator from the six core actor classes; render actor AS2 responses from as_* wire types #2288/Remove alias_generator from ParticipantStatus/CaseStatus, delete the flat-field shim behind reject-guards, retire the #1991 ratchet #2289 remove the alias.
As2WireRenderAdapter.render()resolvesa core class to its wire counterpart via
VOCABULARY.get(type(obj).__name__)—the name collision is the pairing index. Removing it required making the
pairing explicit first.
acceptance criterion with it. ARCH-12-001, ARCH-12-005 and ARCH-12-010 each
mandate an import it forbade. An unreachable goal test invites an implementer
to violate a MUST to make it pass.
Two spikes measured the boundary-guard options (reverted; tree clean):
extra="forbid", nothing elseextra="forbid"+ strip computed fieldsextra="forbid"+ strip computed + wire-name keysThe 570 figure nearly decided this the wrong way. Zero of those failures
involve a camelCase key — they are
embargo_adherence(1096), a@computed_fieldthat appears inmodel_dump()output but is not settable, andid_(110). That diagnosis is what madeextra="forbid"viable and strongerthan a camelCase-specific guard.
Incidental finding: persisted rows are keyed
id_/type_rather thanid/type, invisible only because unknown keys are currently ignored onread-back.
Verification
markdownlint-cli2clean;adr-index --checkreports index and nav in sync.mkdocs build --strictfails identically on this branch and onorigin/main(101 warnings both) — pre-existing, tracked as All 101 markdown_exec vocabulary-example blocks fail: frozen-model assignment in _strip_published_udpated #2904. This PR adds zero
warnings.
Scope note
AC-3 is not met as written. It asks for a recorded adopt-or-decline on PyLD
and
activitypubdantic. Per maintainer direction, #891 is instead split into twodedicated Ideas carrying the evidence, and the verdict is deliberately deferred to
them. #1895 is likewise superseded by an Idea covering the ontology direction.
Closes #1895— rule-4 note, resolved. Planning-protocol rule 4 (#2828) sends amember's remainder to a Task; #1895's went to an Idea (#2945). That is the right
container here: #2945's question is generate, curate, or retire, and no Task can be
written until one is chosen. #2945 carries all five of #1895's open questions forward
explicitly and supersedes it with evidence — #1895's "add the missing terms" framing
assumed a per-semantic-message paradigm the implementation abandoned for AS2 activity
patterns, cited the wrong namespace (pre-ADR-0069), and called the corpus normative when
the generated pages say it is not. Maintainer decision at review: keep
Closes #1895.AC-3, AC-4 and AC-5 amendments are recorded at
#2830 (comment).
Implementation Issues
Tasks (13) — the ADR-0082 ladder. All 31 ARCH-22 violations clear except the
enumerated exemption set; the
residue is the
find_in_core_type_mapimport that ARCH-12-010 mandates, plus anyimport still mandated by an unamended MUST when #2944 runs.
core/models/| CoreObjectfromas_ObjectRefto_core()WireParsePort+ adapterextra="forbid"+ delete the enforcement listscontext.jsonld+ CI drift checkSeven are wired under #2670 (which keeps its body and its 29-file enumeration
intact — it becomes the tracker for its own decomposition); six under #2692.
Ideas (3), under epic #890 — the deferred outward-facing half:
activitypubdanticadoption (split from Investigate PyLD normalization and activitypubdantic for improved AS2/JSON-LD wire parsing #891)Entry points: #2932, #2934, #2935 and #2936 are unblocked today. #2935
(
| CoreObjectremoval) is the one that unblocks theextra="forbid"work, so itis the highest-leverage starting point.