feat(metamodel): consolidate projection/payload vocabulary (#210, #212) - #276
Merged
Conversation
…pe, not a raw byte childRuleId() joins its composite-key parts on NUL — sound as a delimiter (cannot collide with a real name) — but the delimiter was written as a raw 0x00 byte inside the string literal, which made the whole file test as binary: file(1) reported it as "data", and binary-skipping text-search tools ignored it silently, returning no output rather than an error. A sibling instance of this same defect in the Java port caused a cross-port code audit to mis-classify that file. This was the last NUL-bearing tracked source file in the repo. The literal is now the escape sequence backslash-u0000 (unambiguous in TS; avoids the deprecated-octal reading backslash-0 can take before a digit). The runtime string is byte-identical, confirmed by the constraint-merge tests, and the file now tests as UTF-8 text. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S3msoGxjRMwx94PhKSLDuE
Closes #212's actionable content. @ROLE registered six members (primary, replica, index, cache, publish, mirror), but cross-port verification established that every read of @ROLE in all five ports is an equality test against primary — no consumer ever dispatched on the other five, so the consumed information content is one bit and the four unused members were indistinguishable from replica. Per the 2026-08-05 ruling, allowedValues shrinks to ["primary", "replica"]; index/cache/publish/mirror become reserved-not-registered (the ADR-0040 treatment) with the re-entry bar recorded in the registered description: a role member enters the registry only when a shipping consumer dispatches on it. An adopter scan over this repo, the public reference app and downstream consumer models found zero uses of the four retired members. Mechanics, all five ports (Kotlin composes from the JVM registry): - spec/metamodel/db.json + the regenerated TS embedded definition + the committed Python/C# spec copies carry the two-member list. - Per-port constants for the four retired members are deleted outright (TS source-constants.ts, Java MetaSource/RdbSource, C# SourceConstants, Python source_constants); the roles arrays shrink to the two survivors. C# SourceSchema's in-code description byte-matches db.json. - expected-registry.json and the metamodel docs regenerate; both diffs are confined to the @ROLE attribute block/row. - New conformance fixture error-source-role-reserved: a retired member now fails load with ERR_BAD_ATTR_VALUE via the existing generic allowedValues check — no new error code, no new validation pass. - spec/roadmap.md marks #212 shipped (the ADR-0007/0028/FR-024 doc amendments landed with the ruling). No behavior dispatch changes anywhere (none existed); codegen golden output is byte-identical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S3msoGxjRMwx94PhKSLDuE
…end stale research-doc claim (#212) Review follow-ups on the #212 @ROLE shrink, both stale six-member enumerations the constant-name straggler grep could not see (they were free-text literals, not constant reads): - ValidationPhase.validateSourceNode emitted a self-contradictory error on the exact path the new error-source-role-reserved fixture exercises: "@ROLE 'publish' is not a valid value; allowed: primary, replica, index, cache, publish, mirror" — a hand-written allowed-list listing the rejected value among the allowed ones, and a cross-port diagnostic divergence (TS derives its list from the registry spec). The message now derives from MetaSource.VALID_ROLES via a TreeSet (sorted — Set.of iteration order is unspecified, and a non-deterministic error string would be its own defect), so it cannot drift from the registered set again. The stale block comment above it is corrected too. The sibling hand-written @kind list is pre-existing and deliberately left alone. - docs/superpowers/specs/2026-08-02-multi-persistence-architecture-research.md asserted present-tense that source-constants.ts "already ships" the six-member SOURCE_ROLES — now false, and this file names the very source file the shrink changed. Amended with a dated header note in the same style as the 2026-05-23 source-v2 design spec (specs asserting current behavior get amended; plans stay immutable records), without rewriting the research. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S3msoGxjRMwx94PhKSLDuE
…ad refs to sourceless projections (#210) Closes #210, per the 2026-08-05 ruling. The durable rule: passthrough on a value is lineage; assembly origins live on projections. Half 1 — a field hosted on an object.value may no longer carry origin.aggregate, origin.computed, origin.collection or origin.first: all four loaders (TS / Python / Java — Kotlin inherits it — / C#) now reject the shape with ERR_SUBTYPE_RULE_VIOLATION at the offending origin node, driven by a new ASSEMBLY_ORIGIN_SUBTYPES constant per port. origin.passthrough STAYS legal on a value — FR-015 parameter lineage, the FR-024 B5 exemption — and keeps its ERR_PASSTHROUGH_TYPE_MISMATCH check. The now-unreachable value-host branches inside the aggregate/first validation arms are removed. Half 2 — @payloadRef / @responseRef accept an object.value OR a SOURCELESS object.projection ("sourceless" per the #248 persistability contract: no declared/inherited source.* child; unambiguous for a concrete projection since ERR_PROJECTION_INHERITED_SOURCE). A sourced projection stays ERR_INVALID_TEMPLATE. Message widened identically in all four loaders: "does not resolve to an object.value or sourceless object.projection at root". Codegen resolvers widen in all five ports (TS payload-codegen was already subtype-blind — doc updated; TS prompt-render's batch filter, Python resolve_payload_vo, Java SpringNaming.resolveValueObjectRef, Kotlin KotlinGenUtil.resolveValueObjectRef, C# RenderHelperGenerator. ResolveValueObject). Payload emitters walk projection fields with resolving accessors (ADR-0039) — pinned by the extends-bound field in the new positive fixture across every port's runner. NESTED payload targets stay value-only — a deliberate decision, not an omission: a payload field's `field.object @objectRef` must resolve to an object.value, now enforced FAIL-CLOSED in all four loaders (ERR_SUBTYPE_RULE_VIOLATION on the field node; the #219/ADR-0044-adjudicated gap where TS/C#/Python codegen accepted an entity nested target while Kotlin/Java filtered). The per-port codegen filters stay as belt-and-braces. Registry: object.value's rules + description drop the "by assembly" construction mode and state the new rule; object.projection's rules gain the complement sentence. expected-registry.json, the embedded TS definition, the Python/C# spec copies and the metamodel docs regenerate — diffs confined to those strings. Seven new conformance fixtures gate all four loaders: error-value-origin-{aggregate,computed,collection,first}, template-payload-ref-sourceless-projection (positive, expected + expected-effective), error-template-payload-ref-sourced-projection, error-payload-nested-object-ref-entity. Plus per-port loader unit tests (TS + Python). Re-hosted as sourceless projections: the codegen-kotlin payload-with-origins fixture (its snapshot is byte-identical — payload emitters are declared-type-authoritative per #270), origin-collection-simple, the examples/advanced-modeling payload (generated output regenerated under its drift gate; its config filter now keys on persistability), and every port test that loaded an assembly origin on a value host (TS x5 files, Kotlin x7 models, Java Spring x11 hosts, C# x4 models). Python payload-emitter tests construct trees programmatically (no loader) and stay as #270's defense-in-depth pins. Docs: ADR-0028 amended (assembly origins leave object.value); roadmap #210 marked shipped; new migration guide docs/features/migrations/value-assembly-origins-and-source-role-shrink.md covering this change AND the #212 @ROLE shrink; templates-and-payloads.md target-set statements widened; CONFORMANCE.md fixture count 263 -> 270. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S3msoGxjRMwx94PhKSLDuE
…ession, doc-comment widen sweep, pass-level nested-visited Review round 1 on the #210 landing, all findings addressed: - ADR-0028's Consequences still asserted the retired rule ("value+origin.* payloads remain valid ... no migration is forced") 35 lines below the amendment that retired it — in the very ADR the loader's new error messages cite. Annotated in place (the Decision-3 supersession style): the clause now holds only for origin.passthrough; a migration IS forced for assembly origins, pointer to the migration guide. The hazard-2 lesson one layer up: sweep prose asserting the old rule, not just the subtype names. - Six stale "must be an object.value" doc comments widened: C# RenderHelperGenerator (header skip-contract, inline @payloadRef comment, and the AppliesTo XML doc, which misdescribed the widened resolver it delegates to), TS prompt-render-file header, and both payload emitters' GeneratePayloadRecords/generatePayloadInterfaces docs (now say subtype-blind; the loader owns the target-set constraint). - TS resolveEmittedName's doc claimed a subtype filter the code never implemented (findObject is subtype-blind); it now says exactly that. - TS template-payload-target-210 test gains the Python mirror's drift guard: the originNode table must set-equal ASSEMBLY_ORIGIN_SUBTYPES, so a fifth assembly origin cannot pass vacuously via children:[undefined]. - The nested value-only walk's visited set hoists to the pass level in TS/Python/C#: a bad payload shared by N templates now reports ONCE, converging on Java's throw-on-first single-error count. - examples/advanced-modeling config: dropped the unreachable value-subtype clause from the filter (a value can never declare a source, loader-enforced) and removed the filter from routesFile (its built-in #248 persistability gate covers it; regen verified byte-identical, 26 unchanged). formFile keeps the filter as the one generator that needs it. Reruns green: TS metadata 2312 / codegen-ts 1036 + golden 141 / drift gate; Python loader+conformance 391; C# Conformance 858 + Codegen 343; workspace typecheck clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S3msoGxjRMwx94PhKSLDuE
…hing the retired shape; migration guide covers all three breaking changes Final whole-branch review found no code defect but blocked the cut on the doctrine surface. Everything here is docs, skills, or comments; the one source file touched is a C# doc comment. CRITICAL — the shipped agent-authoring skills taught un-loadable metadata: - metaobjects-audit SKILL.md's prompt anti-pattern prescribed "an object.value with origin.* (passthrough / aggregate / collection) fields" — metadata the loader now rejects, in a skill that installs into consumer repos via @metaobjectsdev/sdk. Rewritten to the #210 rule (value = passthrough-only lineage; assembly origins on a sourceless projection, which @payloadRef accepts). Method lesson recorded: the unit-D sweep grepped the literal origin.<subtype> spellings, but this file writes the vocabulary as bare member names inside an origin.* summary — the sweep was re-run across agent-context/ with a spelling-agnostic pattern (bare aggregate|collection|computed|first near value/payload) and every hit read. - metaobjects-prompts SKILL.md never received the @payloadRef widen: it pointed at projections for derivation while requiring the payload be an object.value — closing this release's own migration path in the same file. The attr table, the section heading/body, and the @responseRef line now state the widened target set; the nested value-only rule is noted as loader-enforced. - The four fixtures/agent-context-conformance expected trees regenerated via the corpus regen script (byte-gate green); the sdk-bundled copy is build output (gitignored), re-bundled locally. The migration guide now frames THREE breaking changes, not two: the value-only nested payload-target rule gets its own section with the error and a rewrite (declare a curated object.value, optionally extends-bound, and repoint @objectref), since TS/C#/Python previously loaded AND emitted from a non-value nested target. Its "byte-identical across the re-host" promise is scoped to the host-subtype change alone, with an explicit warning that an extends anchor can flip a field's optionality (the canonical example's title?: -> title: diff is cited). The @ROLE error quote is marked wording-varies-by-port (Java differs), and the ERR_SOURCE_NO_PRIMARY note now says what is actually new (seeing both errors, not the second appearing). Reachability + staleness: - 0.x-to-1.0.md gains section 7 for the three changes, linking the guide (old section 7 renumbers to 8; no inbound anchors existed). - source-kinds.md's multi-source section rewritten off the six-member world and the never-built "route by @ROLE" dispatch (ADR-0007 Amendment 2 got the retraction; the user-facing doc now matches): @ROLE is primary|replica, a designation not a routing mechanism; links the guide. - templates-and-payloads.md: the surviving "the payload IS an object.value" aside widened; See-also links the guide. - spec/roadmap.md: #271 marked SHIPPED (efe12d4) instead of an unshipped blocking prelude; FR-021's "object.value projections with the same origin.* machinery" parenthetical corrected to the post-#210 split. - docs/CONFORMANCE.md L72 fixture count 263 -> 270 (L28 was already updated; the file contradicted itself). - C# MetaSource.cs doc comment: "@ROLE (primary/replica/...)" loses the open-enum ellipsis — the set is closed (#212). - advanced-modeling config comment: entityFile does NOT skip sourceless shapes — it emits shape-only output; queriesFile/routesFile skip. The rewrite had flattened the nuance the prior comment carried. Gates: sdk suite 150 pass (incl. the agent-context byte gate over all four stacks); C# build clean; advanced-modeling regen still "26 unchanged" + drift gate green. CHANGELOG.md deliberately untouched (owned by the release controller). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S3msoGxjRMwx94PhKSLDuE
This cut carries THREE breaking changes, not the two the plan anticipated. The third surfaced in final review: a payload's nested field.object @objectref must now target an object.value, and TypeScript, C# and Python previously accepted a non-value target AND emitted code from it -- so it is a real break for those ports, not a tightening of an existing rule. Cut as MINOR rather than PATCH on the mechanical argument, not the doctrinal one: pre-1.0, a caret range ^0.20.x resolves <0.21.0, so a patch would be auto-adopted on a routine update and adopters' metadata would stop loading with no deliberate action on their part. MINOR makes them opt in, which is what the pre-1.0 breaking slot exists for. RELEASING.md's own litmus test agrees -- a consumer on ^prev cannot run `npm update && meta gen` and still get working output. The changelog entry leads with a banner enumerating all three breaks and links the migration guide from the top rather than burying it, and states plainly that origin.passthrough is unaffected -- the most likely thing for an author to over-correct on. Also pins the migration hub's section 7 to the real version (it was written anticipatorily as "0.20.x") and fixes two cosmetic slips in the guide: a now-singular "errors you'll see" heading, and a stray double blank line left by moving the nested-target error into its own section. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S3msoGxjRMwx94PhKSLDuE
…ce.rdb, not object subtype
…phrasing in CLAUDE.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Units C+D of the projection/payload vocabulary batch — the pre-1.0 BREAKING consolidation, closing #212 and #210, shipping as a coordinated 0.21.0 / Maven 7.21.0 MINOR across all four registries.
THREE breaking changes for metadata authors:
(1) Assembly origins (origin.aggregate/computed/collection/first) are now ILLEGAL on an object.value-hosted field — ERR_SUBTYPE_RULE_VIOLATION. origin.passthrough deliberately STAYS legal on a value: there it is FR-015 parameter lineage, not assembly, and the loaders already drew exactly that line via the FR-024 B5 value-host exemption. Retiring it too would have silently dropped the ERR_PASSTHROUGH_TYPE_MISMATCH check on stored-proc arguments. The durable rule encoded is 'passthrough on a value is lineage; assembly origins live on projections' (ADR-0028).
(2) source.rdb @ROLE shrinks from six registered members to primary|replica; index/cache/publish/mirror become reserved-not-registered. Justification: no port ever built the role-routing dispatch these anticipated — every read of @ROLE in all five ports is an equality test against primary, so the consumed information was one bit. An adopter scan across this repo, the public reference app and downstream consumer models found zero uses. Pruning now is the reversible direction (removing a registered member post-1.0 would be a 2.0 event; re-adding a reserved one is additive).
(3) A payload's nested field.object @objectref must target an object.value. TypeScript, C# and Python previously accepted a non-value target AND emitted code from it, so this is a genuine third break, not a tightening. It is implemented as ONE fail-closed loader rule rather than five per-port codegen filters, deliberately — the ports disagreed, and an independent adjudication ruled that filtering one port's payload closure without its extract closure would emit mappers constructing classes that no longer exist.
The migration path is additive: @payloadRef/@responseRef now accept a sourceless object.projection, so a payload that was assembling values re-hosts as a projection and keeps its origins.
Deliberate design choices a reviewer could not infer: the assembly-origin ban is implemented as a named subtype SET hoisted above the origin dispatch in every loader, so cross-port coverage is a property of the constant rather than of four separate branches. Nested payload targets deliberately stay value-only — the widen covers template-level refs only, and that is recorded in the commit. No new vocabulary, attributes or error codes anywhere (ADR-0023); the registry diff is one shrunk allowedValues plus three object.value/object.projection doctrinal strings.
Also in this branch: a raw NUL byte in constraint-merge.ts became an escape. That is not cosmetic housekeeping — a companion instance in the Java port made that file test as BINARY, so this environment's binary-skipping grep silently returned nothing for it, which mis-scoped an earlier unit onto a false premise. These were the last two NUL-bearing files in the repo.
Cut as MINOR not PATCH because pre-1.0 a caret range ^0.20.x resolves <0.21.0 — a patch would be auto-adopted on a routine npm update and adopters' metadata would stop loading with no deliberate action.
Already cleared by: per-unit task reviews with fix rounds, a release-grade whole-branch review that initially said DON'T SHIP (it found a shipped agent-context skill instructing agents to author now-illegal metadata — that skill ships inside @metaobjectsdev/sdk and installs into consumer repos), a doctrine-surface fix wave, and a scoped re-review that independently re-ran the sweep with a spelling-agnostic pattern and char-checked the migration guide's quoted loader errors against source.
What Changed
object.value—origin.aggregate/computed/collection/firston a value-hosted field now fail load withERR_SUBTYPE_RULE_VIOLATIONin all five ports, enforced via a shared hoisted subtype-set constant placed above the origin dispatch (cross-port coverage is a property of the constant, not four separate branches);origin.passthroughstays legal (it is FR-015 parameter lineage there). As the additive migration path,@payloadRef/@responseRefnow accept a sourcelessobject.projection, so an assembly-origin payload re-hosts as a projection and keeps its origins.source.rdb @roleshrinks toprimary|replica—index/cache/publish/mirrorbecome reserved-not-registered and the registryallowedValuesmanifest drops to two members; every port's@roleread was already an equality test againstprimary, so the consumed information was one bit.@objectRefmust target anobject.value— enforced by a single fail-closed loader rule rather than five per-port codegen filters; TypeScript, C# and Python previously accepted a non-value target and emitted code from it. Also writes theNULjoin delimiter inconstraint-merge.tsas an escape, removing the last binary-detected file in the repo.Risk Assessment
Testing
Exercised the change entirely against the TypeScript reference port's source (the only port with a locally-runnable test suite here; the repo runs Java/C#/Python/Kotlin gates on hosted CI only). All targeted tests pass: the new #210 unit test (12 cases), the full TS conformance corpus (532, including all 8 new cross-port error/migration fixtures — each verified a real pass, not a ledger gap), registry-conformance (8, locking the shrunk @ROLE), the codegen payload/vo-only tests (20), the constraint-merge tests after the NUL→escape fix (12), and the modified CLI/codegen/errors tests (15). I additionally drove the loader + payload codegen directly from worktree src to produce reviewer-visible end-to-end evidence: the migration path (a value-hosted assembly origin re-hosted onto a sourceless object.projection) loads with zero errors and emits correct payload TS, while each of the three breaking changes fires its exact error code and each legal counterpart loads clean — including confirmation that origin.passthrough kept its ERR_PASSTHROUGH_TYPE_MISMATCH type-preservation check on values. No failures found. One environmental gotcha resolved mid-testing: scripts run from /tmp resolved @metaobjectsdev/metadata to a stale/non-workspace copy (pre-widen behavior); I re-ran everything against the absolute src paths the tests use. Cross-port loader enforcement is verified via the shared conformance contract here, with each port's own gate owned by remote CI.
Evidence: Migration path end-to-end (load + emitted payload TS)
LOAD errors: (none — migration-path model loads clean) === Emitted payload TS — AuthorReport (sourceless object.projection as @payloadRef) === export interface AuthorReport { name: string; postCount?: number | null; summary?: string | null; } === Emitted payload TS — ReviewRequest (object.value, nested value-only) === export interface ReviewRequest { instructions?: string | null; note?: AuthorNote | null; } export interface AuthorNote { text: string; }Evidence: The three breaking changes + legal carve-outs (against src)
(1) origin.aggregate on value -> [ "ERR_SUBTYPE_RULE_VIOLATION" ] (1) origin.computed on value -> [ "ERR_SUBTYPE_RULE_VIOLATION" ] (1) origin.collection on value -> [ "ERR_SUBTYPE_RULE_VIOLATION" ] (1) origin.first on value -> [ "ERR_SUBTYPE_RULE_VIOLATION" ] ( ) origin.passthrough on value -> [ "(clean — loads)" ] (2) @role: index -> [ "ERR_BAD_ATTR_VALUE" ] ( ) @role: replica (legal) -> [ "(clean — loads)" ] (3) nested @objectRef->entity -> [ "ERR_SUBTYPE_RULE_VIOLATION" ] ( ) nested @objectRef->value -> [ "(clean — loads)" ]Evidence: ERR_PASSTHROUGH_TYPE_MISMATCH preserved on values (passthrough carve-out rationale)
passthrough string->int on value (mismatch): [ "ERR_PASSTHROUGH_TYPE_MISMATCH" ] passthrough string->string on value (ok) : [ "(clean)" ]Evidence: Migration-path demo source (loads + generates payload codegen from src)
Evidence: Breaking-changes demo source (three breaks + carve-outs, from src)
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 1 issue found → auto-fixed ✅
server/typescript/packages/codegen-ts/src/runner.ts:160- The dbImport/dialect guard keys on!e.isAbstract && e.subType !== OBJECT_SUBTYPE_VALUE, but this change makes the sourcelessobject.projectiona first-class, migration-recommended payload shape (the migration guide tells adopters to re-host assembly-origin payloads as sourceless projections, and@payloadRef/@responseRefnow accept one). A sourceless projection emits NO database code —queriesFile/routesFile/apiModelall gate emission onhasAnyRdbSource(source-detect.ts, which returns false for a projection with nosource.rdb), and the migration guide itself states 'a sourceless projection has no DDL, someta migrateemits nothing for it.' Yet the guard counts a sourceless projection as 'db-emitting' and hard-throws requiringdialect+dbImport(it runs BEFORE normalizeConfig fills its inert placeholders). Concrete reachable path: a prompt-only model (no sourced entity) whose payload is a sourceless projection — e.g. anorigin.computed @expr-only or origin-free self-declared projection that references no entity — fails codegen with 'codegen config is missing dialect and dbImport' for code that is never emitted. The common assembly-origin migration is unaffected (aggregate/collection/first origins reference sourced entities, so the guard trips legitimately anyway); only the computed-only/origin-free-in-a-no-entity-project edge case bites. The principled fix is a one-line alignment with the Persistability is decided by a hardcodedsubType === "value"compare — any custom object subtype silently becomes a table #248 R2 invariant the generators already honor — filter onhasAnyRdbSource(e)rather thansubType !== OBJECT_SUBTYPE_VALUE— which is exactly the source-based predicate this same branch already applied to the example's own config filter (isPersistedin examples/advanced-modeling/metaobjects.config.ts:37). Flagging ask-user because it governs a user-facing config requirement / when codegen errors; recommend folding the guard alignment into this breaking release since the author already established the correct predicate.🔧 Fix: fix(runner): gate dbImport/dialect guard on source.rdb, not object subtype
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
cd server/typescript && bun test packages/metadata/test/template-payload-target-210.test.ts (12 pass — all three breaks + migration path + nested-value-only, against src)cd server/typescript && bun test packages/metadata/test/conformance.test.ts (532 pass — full TS conformance corpus incl. all 8 new fixtures: error-value-origin-{aggregate,computed,collection,first}, error-source-role-reserved, error-payload-nested-object-ref-entity, error-template-payload-ref-sourced-projection, template-payload-ref-sourceless-projection)Per-fixture classification (runFixture + classifyAgainstLedger): all 8 new fixtures classify as real 'pass', none in conformance-expected-failures.json ledgercd server/typescript && bun test packages/codegen-ts/test/payload-codegen.test.ts packages/codegen-ts/test/vo-only-config-optional.test.ts packages/metadata/test/origin-collection.test.ts (20 pass — codegen side: sourceless projection needs no DB config + payload emit)cd server/typescript && bun test packages/metadata/test/registry-conformance.test.ts (8 pass — shrunk @role allowedValues manifest byte-matches)cd server/typescript && bun test packages/metadata/test/constraint-merge.test.ts packages/metadata/test/constraint-validate.test.ts (12 pass — NUL→escape fix exercised)cd server/typescript && bun test packages/codegen-ts/test/fr004-verify-demo.test.ts packages/cli/test/integration/verify.test.ts packages/cli/test/unit/payload-field-tree.test.ts packages/metadata/test/errors.test.ts (15 pass — modified test files + error-code ledger)Manual end-to-end (worktree src): migration-path model (sourceless projection @payloadRef w/ origin.aggregate + nested-value @responseRef) loads clean and emits payload TS interfacesManual end-to-end (worktree src): each of the three breaks fires its exact code (ERR_SUBTYPE_RULE_VIOLATION / ERR_BAD_ATTR_VALUE) and each legal counterpart loads cleanManual check (worktree src): ERR_PASSTHROUGH_TYPE_MISMATCH still fires for a string→int passthrough on a value (carve-out rationale preserved)Byte check: tr -cd '\000' on constraint-merge.ts = 0 raw NUL at target (1 at base 726f9debe); delimiter now written as ""🔧 **Document** - 1 issue found → auto-fixed ✅
CLAUDE.md:443- The always-loaded taxonomy summary lists only 'Origin subtypes: passthrough, aggregate', omitting computed/collection/first, which have been registered subtypes since the 0.17.0 line (meta migrate: aggregate/monitoring read-model views can't be modeled as projections — 4 missing origin/field capabilities #195). This is PRE-EXISTING — not made stale by this change, which bans assembly origins on object.value without altering the registered origin-subtype set. Flagging as a follow-up because the line is directly adjacent to this change's subject (which origin subtypes are permitted where); leaving it risks a future session believing only two origin subtypes exist. Out of scope to fix here per scope-discipline (do not opportunistically rewrite unrelated documentation).🔧 Fix: docs: complete origin subtypes plus #210 host rule
✅ Re-checked - no issues remain.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.