Skip to content

docs: plan issue #2830 — G02 wire/core boundary contract - #2931

Merged
ahouseholder merged 7 commits into
mainfrom
plan/2830-g02-wire-core-boundary-contract
Sep 1, 2026
Merged

docs: plan issue #2830 — G02 wire/core boundary contract#2931
ahouseholder merged 7 commits into
mainfrom
plan/2830-g02-wire-core-boundary-contract

Conversation

@sei-ahouseholder

@sei-ahouseholder sei-ahouseholder commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

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 the
structural guarantee. Partially supersedes ADR-0062 and revises the mechanism (not the
decision) of ADR-0063.

Renumbered 0081 → 0082. PR #2916 landed its own ADR-0081 (peer knowledge in
the hosted actor's store) on main first, and that number already has inbound
references across vultron/ and test/, so this branch's unlanded ADR moved.

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_by frontmatter + forward-link
    note; 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 a
    branch-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 not
    the AS2 type value.
  • notes/wire-core-boundary.md (new): design rationale, the four
    duplications, and the measurements.
  • AGENTS.md: four new pitfalls; marks the wire-spelling-shim pitfall as
    superseded in direction.
  • notes/vocabulary-registry.md, notes/core-wire-rendering-port.md,
    notes/README.md, docs/adr/index.md, mkdocs.yml: cross-links
    and nav.

Key findings

Three of the four member issues rested on a premise that turned out to be wrong:

  1. Every core type except CaseParticipant still silently drops wire-spelled (camelCase) keys #2262's stated blocker does not exist. It claims a CoreObject-level guard
    must be sequenced against Core ParticipantStatus carries wire concerns (ARCH-12-003 violation) — needs migration plan #2260. Measured: any class carrying
    alias_generator=to_camel yields an empty forbidden-key set, so the guard is
    structurally 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.
  2. Structural fix: disjoint type_ namespaces to eliminate wire/core VOCABULARY collision #2403's collision is load-bearing. As2WireRenderAdapter.render() resolves
    a 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.
  3. ARCH-22-003's "zero wire→core imports" goal was unreachable, and Resolve all ARCH-22 wire→core model import violations (goal state) #2670's
    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):

Configuration Failing tests
Targeted camelCase reject-guard 25
extra="forbid", nothing else 570 (+330 errors)
extra="forbid" + strip computed fields 180
extra="forbid" + strip computed + wire-name keys 179

The 570 figure nearly decided this the wrong way. Zero of those failures
involve a camelCase key — they are embargo_adherence (1096), a
@computed_field that appears in model_dump() output but is not settable, and
id_ (110). That diagnosis is what made extra="forbid" viable and stronger
than a camelCase-specific guard.

Incidental finding: persisted rows are keyed id_/type_ rather than
id/type, invisible only because unknown keys are currently ignored on
read-back.

Verification

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 two
dedicated 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 a
member'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_map import that ARCH-12-010 mandates, plus any
import still mandated by an unamended MUST when #2944 runs.

# Task Size Blocked by
#2932 Move branch-neutral shared primitives out of core/models/ M
#2933 Move the shared base hierarchy to the neutral layer M #2932
#2934 Delete the five wire re-export shim modules S
#2935 Remove | CoreObject from as_ObjectRef S
#2936 Derive AS2 ref-field sets from annotations M
#2937 Pairing registry + generic to_core() L #2936
#2938 WireParsePort + adapter M #2937
#2939 Projection → adapter-side translator modules L #2937
#2940 extra="forbid" + delete the enforcement lists L #2935
#2941 Disjoint vocabulary registry keys S #2937
#2942 Relocate the semantic extractor L #2939
#2943 Generate context.jsonld + CI drift check S #2500
#2944 Retarget the ARCH-22 ratchet; retire the xfail S #2934, #2935, #2939, #2942

Seven 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:

Entry points: #2932, #2934, #2935 and #2936 are unblocked today. #2935
(| CoreObject removal) is the one that unblocks the extra="forbid" work, so it
is the highest-leverage starting point.

- 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>
@sei-ahouseholder sei-ahouseholder added the specs-notes Docs-only PR label Aug 31, 2026
This was referenced Aug 31, 2026
ahouseholder and others added 3 commits August 31, 2026 21:10
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
markdownlint --fix rewrote line-initial issue references (#2670, #2942, #2947)
as ATX headings. Reflowed so no issue reference starts a line.

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 sei-ahouseholder left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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: ⚠️ MERGEABLE / BLOCKED — required contexts never report on a docs-only PR (see 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 exactembargo_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 internalsphase6-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 qualityphase5-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 / docsphase9-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.mdphase7-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 nitsphase8-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 / protocolphase2-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.

ahouseholder and others added 3 commits September 1, 2026 14:44
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>

@sei-ahouseholder sei-ahouseholder left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Execute: #2931 — 46/46 findings resolved

Fixed inline: 43 · Filed as issues: 3 · Skipped: 0
CI: ✅ 15/15 passing · Merge state: ⚠️ MERGEABLE / BLOCKED (see below)
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

  1. 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.)
  2. 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.
  3. The id_ 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_. Nine peer sites listed. Re-keying rows would have masked the defect — a stale stored id would validate silently to a different value — and does nothing for the 1096 embargo_adherence failures. #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

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.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. Left unmodified rather than violating the MUST_NOT; recorded as a second instance on #2952 with a suggested acceptance criterion.
  • Closes #1895 needs 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 drop Closes #1895 and leave it open blocked-by #2945 — is flagged for merge time. The pipeline did not make it.

Merge state

MERGEABLE / BLOCKED6 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-lint 0 errors (6 MS-05-003 warnings eliminated) · adr-index --check in sync
  • mkdocs build --strict: 101 warnings on this branch, 101 on origin/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

@sei-ahouseholder sei-ahouseholder left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Verify: #2931 — ✅ READY-TO-MERGE

Merge state: ✅ MERGEABLEBLOCKED (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 #2830residual 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's ADR-0082 sits 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-lint 0 errors (six MS-05-003 warnings eliminated) · adr-index --check in sync
  • mkdocs build --strict: 101 on this branch, 101 on origin/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.

@ahouseholder
ahouseholder merged commit acac522 into main Sep 1, 2026
15 checks passed
@ahouseholder
ahouseholder deleted the plan/2830-g02-wire-core-boundary-contract branch September 1, 2026 15:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment