Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,73 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Unreleased]

## [0.21.0] — npm `0.21.0` · PyPI `0.21.0` · NuGet `0.21.0` · Maven `7.21.0`

> ### ⚠️ BREAKING FOR METADATA AUTHORS — three changes make previously-valid metadata fail to load
>
> This is the pre-1.0 breaking slot (MINOR), not a patch, **specifically so it is not
> auto-adopted**: on a caret range `^0.20.x` resolves `<0.21.0`, so you pick this up only by
> deliberately bumping your range. Read
> **[the migration guide](docs/features/migrations/value-assembly-origins-and-source-role-shrink.md)**
> before upgrading — it carries the exact loader errors and a rewrite rule for each change.
>
> 1. **Assembly origins are illegal on an `object.value`.** `origin.aggregate`, `origin.computed`,
> `origin.collection` and `origin.first` on a value-hosted field now fail with
> `ERR_SUBTYPE_RULE_VIOLATION`. **`origin.passthrough` is unaffected** and stays legal on a value.
> 2. **`source.rdb @role` accepts only `primary | replica`.** `index`, `cache`, `publish` and
> `mirror` are retired to reserved-not-registered; a legacy use fails with `ERR_BAD_ATTR_VALUE`.
> 3. **A payload's nested `field.object @objectRef` must target an `object.value`.** Previously
> TypeScript, C# and Python accepted a non-value target *and emitted code from it*; it now fails
> at load in all four loaders.

### Changed — assembly origins live on projections, not on values (#210) — BREAKING

**Generated-output change — regenerate to pick it up; three-way merge preserves hand edits.**

The durable rule this encodes: **"passthrough on a value is lineage; assembly origins live on
projections."** An `object.value` is pure shape — constructed by a caller or by embedding, never
populated from a store. Deriving a field by rolling up, computing over, or collecting from a backing
store is what an `object.projection` is *for*, and letting a value do it blurred the one distinction
the taxonomy exists to draw (ADR-0028).

`origin.passthrough` deliberately **stays legal** on a value. There it is FR-015 *parameter lineage*
— it is how a stored-proc argument's type is bound to its source column — and the loaders already
drew exactly that line via the FR-024 B5 value-host exemption. Retiring it would have silently
dropped the `ERR_PASSTHROUGH_TYPE_MISMATCH` check on proc arguments.

**The migration path is additive:** `@payloadRef` and `@responseRef` now accept a **sourceless
`object.projection`** as well as an `object.value`. A payload that was assembling values re-hosts as
a projection and keeps its origins. See the migration guide for the rewrite, including the caveat
that adding an `extends` anchor can flip a field's optionality (`@required` inherits through it).

Implemented as one named subtype set (`ASSEMBLY_ORIGIN_SUBTYPES`) hoisted above the origin dispatch
in every loader, so cross-port coverage is a property of the constant rather than of four separate
branches. No new vocabulary, no new error codes; `object.value`'s registry `rules` and `description`
strings change to drop the retired "by assembly" construction mode.

### Changed — `source.rdb @role` shrinks to `primary | replica` (#212) — BREAKING

`index`, `cache`, `publish` and `mirror` are **reserved-not-registered** — documented on the axis,
absent from the registry (the ADR-0040 treatment). The re-entry bar is recorded in ADR-0007
Amendment 2: *a role member enters the registry only when a shipping consumer dispatches on it.*

The justification is that **no port ever built the dispatch these members anticipated.** Across all
five, every read of `@role` is an equality test against `primary`; Java's OMDB has zero role usage,
and Kotlin's and Python's write-through read paths are explicitly role-agnostic, finding the replica
by read-only `@kind`. The consumed information content was one bit, which makes the four unused
members indistinguishable from `replica` to every consumer. 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,
whereas re-adding a reserved one is additive.

### Fixed — a raw `NUL` byte made a TypeScript source file invisible to search tooling

`constraint-merge.ts` used a NUL as a composite-key join delimiter — sound technique, but written as
a literal `0x00` byte rather than an escape, which made the whole file test as *binary*. `file(1)`
reported it as `data` and binary-skipping search tools silently ignored it. Runtime-identical fix;
the companion instance in the Java port shipped in `0.20.16`. These were the last two in the repo.

## [0.20.16] — npm `0.20.16` · PyPI `0.20.16` · NuGet `0.20.16` · Maven `7.20.16`

**Coordinated across all four registries.** The fix below is Kotlin, Python and Java, so
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -438,9 +438,9 @@ Preserve the following contracts exactly across all language ports:

**Metamodel subtype vocabularies (must be identical across languages):** the `registry-conformance` gate (`fixtures/registry-conformance/`) is the structural enforcer of this rule — each port emits its registry as a canonical manifest byte-matched to `expected-registry.json`. **All five ports (TS / C# / Java / Kotlin / Python) are live + green** (SP-G Java/Kotlin reconciliation complete; the JVM runners compose from the defined metamodel provider set so codegen-base/om classpath SPI does not pollute the measured vocabulary). See `fixtures/registry-conformance/README.md`.
- Filter operators: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `like`, `isNull`
- Object subtypes: `entity` (owns data: own identity, writable sources, lifecycle), `value` (pure shape: NO identity, NO source, ever; constructed — by caller/assembly/embedding — never populated; may `extends` entity fields for shape), `projection` (derived read-only representation: fields `extends`-bound / origin-derived / self-declared-under-external-assembly, all read-only at subtype level; identity optional and MUST extend an entity identity; sources restricted to read-only `@kind`s; the declared field set IS the exposure — inclusive list, fail-closed). A field carrying `origin.*` is derived ⇒ read-only wherever it lives (incl. on entities). An entity's primary source must be a writable `@kind` (read-only kinds only in read role). See [ADR-0028](spec/decisions/ADR-0028-object-taxonomy-projection-value-purity.md). (FR-024 Phase E — `object.projection`/`value` are registered in `expected-registry.json` and the projection/value validation passes [identity pass-through, value-purity, projection-licensing, `@via` inference/cardinality, extends/origin agreement, derived-field providability] are enforced cross-port in all 5 ports. The **B4b** entity-primary-source-readonly cutover [the "writable `@kind`" clause above — `ERR_ENTITY_PRIMARY_SOURCE_READONLY`] + the projection codegen fan-out (read-only DTOs for view-kind projections; FR-015 proc-callables for proc-kind projections; api-docs label `object.projection` units as `projection` and document their generated `<Name>Dto`) are now shipped cross-port; the remaining FR-024 work is the declared-API surface — tracked in #10.)
- Object subtypes: `entity` (owns data: own identity, writable sources, lifecycle), `value` (pure shape: NO identity, NO source, ever; constructed — by caller/embedding — never populated; may `extends` entity fields for shape; a value-hosted field may carry `origin.passthrough` but never an assembly origin), `projection` (derived read-only representation: fields `extends`-bound / origin-derived / self-declared-under-external-assembly, all read-only at subtype level; identity optional and MUST extend an entity identity; sources restricted to read-only `@kind`s; the declared field set IS the exposure — inclusive list, fail-closed). A field carrying `origin.*` is derived ⇒ read-only wherever it lives (incl. on entities). An entity's primary source must be a writable `@kind` (read-only kinds only in read role). See [ADR-0028](spec/decisions/ADR-0028-object-taxonomy-projection-value-purity.md). (FR-024 Phase E — `object.projection`/`value` are registered in `expected-registry.json` and the projection/value validation passes [identity pass-through, value-purity, projection-licensing, `@via` inference/cardinality, extends/origin agreement, derived-field providability] are enforced cross-port in all 5 ports. The **B4b** entity-primary-source-readonly cutover [the "writable `@kind`" clause above — `ERR_ENTITY_PRIMARY_SOURCE_READONLY`] + the projection codegen fan-out (read-only DTOs for view-kind projections; FR-015 proc-callables for proc-kind projections; api-docs label `object.projection` units as `projection` and document their generated `<Name>Dto`) are now shipped cross-port; the remaining FR-024 work is the declared-API surface — tracked in #10.)
- Source subtypes: `rdb` (paradigm; ADR-0007). The pre-v2 `dbTable`/`dbView` subtypes are RETIRED — `source.rdb` + `@kind: table|view|materializedView|storedProc|tableFunction` is the form, with read-only-ness derived from `@kind`. Multi-source via `@role` (exactly one `primary` per object). Source physical name = `@table` (NOT `@name`); field physical name = `@column` (renamed from `@dbColumn`). Referential actions on relationships: `@onDelete` / `@onUpdate`.
- Origin subtypes: `passthrough`, `aggregate`
- Origin subtypes: `passthrough`, `aggregate`, `collection`, `computed`, `first` (concrete; `base` is the abstract root). `passthrough` is legal on an `object.value`-hosted field (FR-015 parameter lineage); the four assembly origins (`aggregate`/`computed`/`collection`/`first`) live on `object.projection` only — a value-hosted assembly origin is `ERR_SUBTYPE_RULE_VIOLATION` (#210).
- Relationship subtypes: `association`, `aggregation`, `composition`. Cardinality via `@cardinality: one|many`; target via `@objectRef`. **M:N (FR-018) slim vocabulary:** `@cardinality: "many"` + `@objectRef` (target) + `@through` (the junction/through entity — a third entity that MUST declare two `identity.reference` children, one per FK side). The relationship's FK fields are **derived** from those references (the `identity.reference` SSOT for FK direction), never restated. `@sourceRefField` (optional) disambiguates a *directed* self-join by naming the source-side FK field on the junction (the other reference is the target side). `@symmetric` (optional boolean) marks an *undirected* self-join (union-on-read) — valid only when `@objectRef` == the declaring entity, and mutually exclusive with `@sourceRefField`. The pre-FR-018 `@joinEntity`/`@joinFields` attrs are REMOVED. Validation errors: symmetric-on-hetero / symmetric+sourceRefField → `ERR_BAD_ATTR_VALUE`; junction-missing-two-references / sourceRefField-not-matching / M:N-attr-on-1:N → `ERR_INVALID_RELATIONSHIP`.
- Index subtypes: `index.lookup` (non-unique retrieval index; uniqueness is encoded in the **type**: `identity.secondary` = unique alternate key, `index.lookup` = non-unique; `@unique` is REMOVED from `identity.secondary` — `ERR_UNKNOWN_ATTR` on any legacy `@unique`). RDB-physical escapes `@using`/`@expr`/`@where`/`@orders` are registered by the db provider on **both** `identity.secondary` and `index.lookup`. `index.fulltext` / `index.vector` / `index.spatial` are reserved on the subtype axis — documented, NOT registered (YAGNI + 1.0 vocab freeze). See [ADR-0040](spec/decisions/ADR-0040-index-type-and-secondary-key-purity.md).
- Layout subtypes: `dataGrid`
Expand Down
8 changes: 6 additions & 2 deletions agent-context/skills/metaobjects-audit/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,8 +330,12 @@ Per finding: `file:line` → what → generated-equivalent exists? → recommend
## Prompt anti-patterns (hunt per site; classify: fully-modeled / partial / fully-inline)

- Inline prompt strings (triple-quoted / template-literal constants in service code).
- Untyped payloads (`str.format(**dict)` / f-strings / ad-hoc dicts) — payload should be an
`object.value` with `origin.*` (`passthrough` / `aggregate` / `collection`) fields.
- Untyped payloads (`str.format(**dict)` / f-strings / ad-hoc dicts) — payload should be a declared
shape: an `object.value` (caller-supplied fields; `origin.passthrough` only — FR-015 parameter
lineage) or, when fields derive by `aggregate` / `collection` / `computed` / `first`, a
**sourceless `object.projection`** carrying those origins (#210 — assembly origins on an
`object.value` fail load with `ERR_SUBTYPE_RULE_VIOLATION`; `@payloadRef` accepts the sourceless
projection).
- Silent-degradation hack (`try/except KeyError` or `?? ''` around formatting) — flag every instance.
- Hand-rolled output parsing (regex / XML / ad-hoc JSON) vs declared `template.output` +
generated `parse*` / `safeParse*` / `extract*` parser — **generated in all five ports**
Expand Down
28 changes: 17 additions & 11 deletions agent-context/skills/metaobjects-prompts/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Both carry the generic attrs:

| Attr | Required | Purpose |
|---|---|---|
| `@payloadRef` | yes | the `object.value` declaring the payload shape |
| `@payloadRef` | yes | the `object.value` — or sourceless `object.projection` (#210) — declaring the payload shape |
| `@textRef` | yes for `template.prompt` and a `template.output @kind: document` (the default) — a `template.output @kind: email` carries **no** `@textRef`; it uses `@subjectRef` + `@htmlBodyRef` (+ optional `@textBodyRef`) instead | the 2-layer logical text reference `group/source`, resolved by a provider |
| `@format` | no | `text` (default) / `html` / `xml` / `csv` / `json` / `markdown` / `spreadsheet` — drives the escaper |
| `@maxChars` | no | build-time size budget |
Expand All @@ -41,7 +41,8 @@ selects how the output-format prompt fragment presents the payload shape to an L
(see "the output-format prompt fragment" below); `@requiredTags` names output tags
the rendered text must contain (`verify` checks it) on both subtypes.
`template.prompt` additionally carries `@responseRef` — naming the response
value-object the prompt expects, for typed LLM-call trace derivation.
shape (an `object.value` or sourceless `object.projection`, #210) the prompt
expects, for typed LLM-call trace derivation.

A third, structurally different subtype is also registered core vocabulary:
**`template.toolcall`** (`@toolName` + `@payloadRef`, ADR-0011) — a vendor-agnostic
Expand All @@ -50,21 +51,26 @@ LLM tool-call envelope with no renderable text body (the body IS the
`@format` attrs above). The vocabulary exists today; MCP exposure of declared
prompts/tools is roadmap, not shipped — don't promise it.

## The payload is an `object.value` you declare
## The payload is a shape you declare — an `object.value`, or a sourceless `object.projection`

The payload is **not** an entity — it's an `object.value` whose DECLARED fields ARE
the prompt's typed shape. Every port's payload codegen is
The payload is **not** an entity — it's a declared shape whose fields ARE the
prompt's typed surface: an `object.value` (caller-supplied fields;
`origin.passthrough` only — FR-015 parameter lineage), or a **sourceless
`object.projection`** (#210 — no `source.*` child, own or inherited) when fields
derive by assembly. Every port's payload codegen is
**declared-type-authoritative (#270)**: a field's generated type comes only from its
declared `field.<subType>` + `isArray` + `@objectRef`, and a nested payload is a
declared `field.object @objectRef` to another `object.value` (`isArray: true` for a
list). The caller supplies the field values at render time. An `origin.*` child on a
payload field is IGNORED for typing — never author assembly origins (`aggregate` /
`collection` / `computed` / `first`) on a payload VO. Derivation belongs to
**projection** read models (`object.projection` over an entity), covered by the
`metaobjects-authoring` skill and `docs/features/source-kinds.md`, not here.
list — nested targets stay value-only, loader-enforced). The caller supplies the
field values at render time. An `origin.*` child on a payload field is IGNORED for
typing — and the assembly origins (`aggregate` / `collection` / `computed` /
`first`) are ILLEGAL on an `object.value` host (`ERR_SUBTYPE_RULE_VIOLATION`, #210):
an origin-derived payload lives on the sourceless projection, which `@payloadRef`
accepts. Projections generally are covered by the `metaobjects-authoring` skill and
`docs/features/source-kinds.md`, not here.

Declaring the payload shape is what makes payload bloat visible: adding a field to
the prompt is a diff on the `object.value`, and `verify` catches template/payload
the prompt is a diff on the declared shape, and `verify` catches template/payload
drift at build time instead of letting a prompt silently degrade.

```json
Expand Down
4 changes: 2 additions & 2 deletions docs/CONFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ regenerate with `ls -d fixtures/<corpus>/*/ | wc -l`.

| Corpus | Fixtures | TS | Java | Kotlin | C# | Python |
|---|---|---|---|---|---|---|
| [`fixtures/conformance/`](../fixtures/conformance/) (metamodel) | 262 | ✓ | ✓ | inherits via `metadata-ktx` | ✓ | ✓ |
| [`fixtures/conformance/`](../fixtures/conformance/) (metamodel) | 270 | ✓ | ✓ | inherits via `metadata-ktx` | ✓ | ✓ |
| [`fixtures/yaml-conformance/`](../fixtures/yaml-conformance/) | 15 | 15 / 15 | 14 / 15 (1 ledgered: `yaml-quoted-leading-zero` — Java pipeline strips quotes off `"007"`) | inherits via Java | 14 / 15 (1 ledgered: `error-yaml-coerced-hex-in-string` — YamlDotNet doesn't coerce `0xFF`) | 15 / 15 |
| [`fixtures/verify-conformance/`](../fixtures/verify-conformance/) | 31 | ✓ | ✓ | inherits via Java | ✓ | ✓ |
| [`fixtures/verify-strict-conformance/`](../fixtures/verify-strict-conformance/) | 1 | ✓ | — | — | — | ✓ |
Expand Down Expand Up @@ -69,7 +69,7 @@ unit-test runners (`bun test`, `dotnet test`, `pytest`, `mvn test`) pull Docker.

## Fixture-to-doc mapping

### `fixtures/conformance/` — metamodel loader + canonical serializer (262)
### `fixtures/conformance/` — metamodel loader + canonical serializer (270)

| Fixture prefix | Feature doc |
|---|---|
Expand Down
21 changes: 20 additions & 1 deletion docs/features/migrations/0.x-to-1.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,26 @@ covering projections, entities, values, and parameter refs alike.
`origin.computed`'s job (`@expr`, shipped in #195); #159 adds further node kinds to that
expression grammar.

## 7. Deprecated `codegen-ts/generators` export removed (at the 1.0 cut)
## 7. Assembly origins leave `object.value`; nested payload targets are value-only; `@role` shrinks (shipped 0.21.0 / Maven 7.21.0)

Three coordinated breaking changes in one cut (#210 / #212):

- A field hosted on an `object.value` may no longer carry an **assembly origin**
(`origin.aggregate` / `origin.computed` / `origin.collection` / `origin.first`)
— `ERR_SUBTYPE_RULE_VIOLATION`. Re-host the payload as a **sourceless
`object.projection`**; `@payloadRef`/`@responseRef` now accept one.
`origin.passthrough` on a value (FR-015 parameter lineage) stays legal.
- A payload field's nested `field.object @objectRef` must resolve to an
`object.value` (previously unconstrained by the loader; TS/C#/Python accepted
and emitted from a non-value target) — `ERR_SUBTYPE_RULE_VIOLATION`.
- `source.rdb @role` is exactly `primary | replica`; the former `index` /
`cache` / `publish` / `mirror` members are retired (reserved-not-registered)
— `ERR_BAD_ATTR_VALUE`.

- **Full mechanical rewrites for all three:**
[value-assembly-origins-and-source-role-shrink.md](value-assembly-origins-and-source-role-shrink.md).

## 8. Deprecated `codegen-ts/generators` export removed (at the 1.0 cut)

Importing the built-in generators from `@metaobjectsdev/codegen-ts/generators`
(`entityFile` / `queriesFile` / `routesFile` / `barrel`) is **removed** at 1.0.
Expand Down
Loading
Loading