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
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,29 @@ here.**
declared rule is now rejected at the model where it used to reach the database. Gated by
`fixtures/validation-conformance/cross-field-cases.json` in all five ports.

- **Two providers can no longer claim one attribute name on a node type.** The registry now
records which provider registered each attribute and refuses a second provider's claim at
composition, in all five ports (TypeScript, Python, C#, Java, Kotlin). A repeat on the same
`(type, subType)` keeps `ERR_PROVIDER_ATTR_CONFLICT` (its message now names the owner); a name
another provider already holds on the shared root or a descendant subtype is the new
`ERR_PROVIDER_ATTR_SHADOW`. The refusal names both providers and the attribute, whichever
registers first, and leaves nothing in the registry. The same provider repeating its own name,
sibling subtypes sharing a name, and registration outside a provider all stay legal. The shipped
core providers have no such collision, so existing projects see no change. `meta types --detail`
prints the registering provider beside each attribute, and its JSON carries a `provider` field.
A common attribute is accepted on every node type, so it counts as a claim on every slot: a
second provider registering a common attribute name another provider holds, as a common
attribute or on any type, and a provider declaring a per-type attribute whose name another
provider registered as common, are both `ERR_PROVIDER_ATTR_CONFLICT`, in either order (two
common registrations used to be first-wins). The registry records each common attribute's
provider. Java's core providers no longer declare a per-type `@description` beside the
documentation provider's common one (same type, same meaning); `@description` is still accepted
on every node. This is how per-paradigm physical-name words (`@column`, `@field`) stay safe as
providers multiply; no wire-name attribute is added. See ADR-0062 and
`docs/features/extending-with-providers.md`. New conformance fixtures: `attr-shadow-inherited`,
`attr-shadow-descendant`, `attr-shadow-extend`, `common-attr-conflict`,
`common-attr-per-type-after`, `common-attr-per-type-before` and the `attr-ownership/` sub-corpus. Fixes #527.

### Fixed

- **TypeScript: every `meta` command now loads the providers in `metaobjects.config.ts`.**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,14 @@ and address-free — the subtype names the *concept*; transport/protocol is a cl
Declare config as the *names* of required keys (values stay in env/config) and
generate a fail-closed check. Reference typed payloads instead of inlining shapes.

**Name your attributes so they cannot collide.** Two providers may not claim one attribute
name on a node type (ADR-0062): the registry refuses the second at composition with
`ERR_PROVIDER_ATTR_CONFLICT` (same `(type, subType)`) or `ERR_PROVIDER_ATTR_SHADOW` (the shared
`<type>.base` root, or a subtype of it), naming both providers. A physical address takes your
store's own idiomatic word (`@column` relational, `@field` document), unique on that node type;
`meta types --detail` shows which provider holds each name. A store with no provider of its own
uses the registered `attr.properties` bag rather than an invented attribute.

**Lifecycle.** Register with explicit provider wiring — never loosen `strict` to
free-ride ad-hoc attrs. When core later ships the concept your provider shrinks from
`register` to `extend` (the `template.toolcall` precedent). A second independent
Expand Down
2 changes: 1 addition & 1 deletion docs/CONFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ regenerate with `ls -d fixtures/<corpus>/*/ | wc -l` for directory-shaped corpor
| [`fixtures/template-codegen-conformance/`](../fixtures/template-codegen-conformance/) | 3 | ✓ | ✓ | ✓ | ✓ | ✓ |
| [`fixtures/template-output-render-conformance/`](../fixtures/template-output-render-conformance/) | 5 | ✓ | ✓ | ✓ | ✓ | ✓ |
| [`fixtures/generator-registry-conformance/`](../fixtures/generator-registry-conformance/) | 1 canonical manifest | ✓ | ✓ | ✓ | ✓ | ✓ |
| [`fixtures/provider-composition-conformance/`](../fixtures/provider-composition-conformance/) | 9 (5 error-shape + 4 compose-load) | ✓ | ✓ | — (JVM registry via Java) | ✓ | ✓ |
| [`fixtures/provider-composition-conformance/`](../fixtures/provider-composition-conformance/) | 18 (11 error-shape + 4 compose-load + 3 attr-ownership) | ✓ | ✓ | — (JVM registry via Java) | ✓ | ✓ |
| [`fixtures/source-resolution-conformance/`](../fixtures/source-resolution-conformance/) | 25 cases | ✓ (reference implementation) | ✓ | inherits via Java | ✓ | ✓ |
| [`fixtures/scope-conformance/`](../fixtures/scope-conformance/) | 10 cases | ✓ (reference implementation) | ✓ | inherits via Java | ✓ | ✓ |
| [`fixtures/dependency-conformance/`](../fixtures/dependency-conformance/) | 37 cases (26 resolution + 11 check) + 14 transport cases | ✓ (reference implementation; all three arms) | ✓ (resolution arm, all 26; no check, so the 11 check cases are not run) | inherits via Java | ✓ (resolution arm, all 26; no check, so the 11 check cases are not run) | ✓ (resolution arm, all 26 identically; no check yet, so the 11 check cases are not run; the transport arm is Node-only) |
Expand Down
29 changes: 26 additions & 3 deletions docs/features/extending-with-providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ unregistered id throws `ERR_PROVIDER_MISSING_DEPENDENCY`; a cycle throws
`ERR_PROVIDER_DUPLICATE_ID`. All three codes are stable across ports — see
[`../../fixtures/conformance/ERROR-CODES.json`](../../fixtures/conformance/ERROR-CODES.json).

## The two rules that govern every provider
## The three rules that govern every provider

Before the mechanics, the two rules a provider must obey. Both are machine-enforced —
Before the mechanics, the three rules a provider must obey. All three are machine-enforced —
you will hit an error, not a review comment.

### 1. Own vs projected attributes (ADR-0050)
Expand Down Expand Up @@ -80,6 +80,28 @@ So vendor or domain vocabulary enters by **registration**:
"Declared" includes "declared by your provider" — that is what keeps strict provenance
meaningful while still letting you extend the metamodel.

### 3. One attribute name, one provider, per node type (ADR-0062)

**Two providers may not claim the same attribute name on a node type.** The registry records
which provider registered each attribute, and refuses a second provider's claim at composition:

- on the same `(type, subType)` — `ERR_PROVIDER_ATTR_CONFLICT`;
- on the shared root `(type, base)` that the subtype inherits from, or on a subtype of it —
`ERR_PROVIDER_ATTR_SHADOW`, whether the second provider registers the type or `extend`s it;
- as a common attribute (accepted on every node type), or on any type when another provider
already registered it as a common attribute — `ERR_PROVIDER_ATTR_CONFLICT`. A common attribute
is on every slot, so it collides with the same name on any type, in either order.

The message names both providers and the attribute. The check runs both ways, so it does not
matter which provider registers first. The same provider repeating its own name, sibling
subtypes using one name, and registration outside a provider's turn are all legal.

This is what keeps per-paradigm physical names safe (ADR-0007, ADR-0018). Name your store's
address in your store's own word (`@column` for a relational field, `@field` for a document
field) and pick a word no other provider on that node type already holds. A store you have no
provider for goes in the `attr.properties` bag, not an invented attribute. `meta types
--detail` prints the registering provider next to each attribute, so you can see what is taken.

## What modularity does and does not mean

> **MetaObjects modularity is additive and behavioural, never subtractive: every port
Expand Down Expand Up @@ -499,7 +521,8 @@ Composition surfaces these codes consistently across all ports (see
| `ERR_PROVIDER_DUPLICATE_ID` | Two provider objects report the same `id` | `provider-extension-duplicate-id` |
| `ERR_PROVIDER_MISSING_DEPENDENCY` | Provider declares dep on an unregistered id | `provider-extension-missing-dependency` |
| `ERR_PROVIDER_DEPENDENCY_CYCLE` | Provider dependencies form a cycle | `provider-extension-dependency-cycle` |
| `ERR_PROVIDER_ATTR_CONFLICT` | Two providers declare the same attr on the same `(type, subType)` | (covered by negative tests in core) |
| `ERR_PROVIDER_ATTR_CONFLICT` | Two providers declare the same attr on the same `(type, subType)`, or one registers a common attr whose name the other holds as a common attr or on any type | `attr-conflict`, `common-attr-conflict`, `common-attr-per-type-after`, `common-attr-per-type-before` (provider-composition corpus) |
| `ERR_PROVIDER_ATTR_SHADOW` | A provider declares an attr another provider already holds on the shared root or a descendant subtype of the same type | `attr-shadow-inherited`, `attr-shadow-descendant`, `attr-shadow-extend` (provider-composition corpus) |

Each conformance fixture's `providers.json: string[]` lists the provider ids to
compose for that test — the same wire format consumers use at runtime. Each
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,14 @@ and address-free — the subtype names the *concept*; transport/protocol is a cl
Declare config as the *names* of required keys (values stay in env/config) and
generate a fail-closed check. Reference typed payloads instead of inlining shapes.

**Name your attributes so they cannot collide.** Two providers may not claim one attribute
name on a node type (ADR-0062): the registry refuses the second at composition with
`ERR_PROVIDER_ATTR_CONFLICT` (same `(type, subType)`) or `ERR_PROVIDER_ATTR_SHADOW` (the shared
`<type>.base` root, or a subtype of it), naming both providers. A physical address takes your
store's own idiomatic word (`@column` relational, `@field` document), unique on that node type;
`meta types --detail` shows which provider holds each name. A store with no provider of its own
uses the registered `attr.properties` bag rather than an invented attribute.

**Lifecycle.** Register with explicit provider wiring — never loosen `strict` to
free-ride ad-hoc attrs. When core later ships the concept your provider shrinks from
`register` to `extend` (the `template.toolcall` precedent). A second independent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,14 @@ and address-free — the subtype names the *concept*; transport/protocol is a cl
Declare config as the *names* of required keys (values stay in env/config) and
generate a fail-closed check. Reference typed payloads instead of inlining shapes.

**Name your attributes so they cannot collide.** Two providers may not claim one attribute
name on a node type (ADR-0062): the registry refuses the second at composition with
`ERR_PROVIDER_ATTR_CONFLICT` (same `(type, subType)`) or `ERR_PROVIDER_ATTR_SHADOW` (the shared
`<type>.base` root, or a subtype of it), naming both providers. A physical address takes your
store's own idiomatic word (`@column` relational, `@field` document), unique on that node type;
`meta types --detail` shows which provider holds each name. A store with no provider of its own
uses the registered `attr.properties` bag rather than an invented attribute.

**Lifecycle.** Register with explicit provider wiring — never loosen `strict` to
free-ride ad-hoc attrs. When core later ships the concept your provider shrinks from
`register` to `extend` (the `template.toolcall` precedent). A second independent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,14 @@ and address-free — the subtype names the *concept*; transport/protocol is a cl
Declare config as the *names* of required keys (values stay in env/config) and
generate a fail-closed check. Reference typed payloads instead of inlining shapes.

**Name your attributes so they cannot collide.** Two providers may not claim one attribute
name on a node type (ADR-0062): the registry refuses the second at composition with
`ERR_PROVIDER_ATTR_CONFLICT` (same `(type, subType)`) or `ERR_PROVIDER_ATTR_SHADOW` (the shared
`<type>.base` root, or a subtype of it), naming both providers. A physical address takes your
store's own idiomatic word (`@column` relational, `@field` document), unique on that node type;
`meta types --detail` shows which provider holds each name. A store with no provider of its own
uses the registered `attr.properties` bag rather than an invented attribute.

**Lifecycle.** Register with explicit provider wiring — never loosen `strict` to
free-ride ad-hoc attrs. When core later ships the concept your provider shrinks from
`register` to `extend` (the `template.toolcall` precedent). A second independent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,14 @@ and address-free — the subtype names the *concept*; transport/protocol is a cl
Declare config as the *names* of required keys (values stay in env/config) and
generate a fail-closed check. Reference typed payloads instead of inlining shapes.

**Name your attributes so they cannot collide.** Two providers may not claim one attribute
name on a node type (ADR-0062): the registry refuses the second at composition with
`ERR_PROVIDER_ATTR_CONFLICT` (same `(type, subType)`) or `ERR_PROVIDER_ATTR_SHADOW` (the shared
`<type>.base` root, or a subtype of it), naming both providers. A physical address takes your
store's own idiomatic word (`@column` relational, `@field` document), unique on that node type;
`meta types --detail` shows which provider holds each name. A store with no provider of its own
uses the registered `attr.properties` bag rather than an invented attribute.

**Lifecycle.** Register with explicit provider wiring — never loosen `strict` to
free-ride ad-hoc attrs. When core later ships the concept your provider shrinks from
`register` to `extend` (the `template.toolcall` precedent). A second independent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,14 @@ and address-free — the subtype names the *concept*; transport/protocol is a cl
Declare config as the *names* of required keys (values stay in env/config) and
generate a fail-closed check. Reference typed payloads instead of inlining shapes.

**Name your attributes so they cannot collide.** Two providers may not claim one attribute
name on a node type (ADR-0062): the registry refuses the second at composition with
`ERR_PROVIDER_ATTR_CONFLICT` (same `(type, subType)`) or `ERR_PROVIDER_ATTR_SHADOW` (the shared
`<type>.base` root, or a subtype of it), naming both providers. A physical address takes your
store's own idiomatic word (`@column` relational, `@field` document), unique on that node type;
`meta types --detail` shows which provider holds each name. A store with no provider of its own
uses the registered `attr.properties` bag rather than an invented attribute.

**Lifecycle.** Register with explicit provider wiring — never loosen `strict` to
free-ride ad-hoc attrs. When core later ships the concept your provider shrinks from
`register` to `extend` (the `template.toolcall` precedent). A second independent
Expand Down
3 changes: 2 additions & 1 deletion fixtures/conformance/ERROR-CODES.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"ERR_PROVIDER_DEPENDENCY_CYCLE": "Provider composition has a dependency cycle.",
"ERR_PROVIDER_DUPLICATE_ID": "Two providers in a composition share the same id.",
"ERR_PROVIDER_MISSING_DEPENDENCY": "A provider declares a dependency id with no matching provider.",
"ERR_PROVIDER_ATTR_CONFLICT": "A provider extend redefines an attr another provider already declared.",
"ERR_PROVIDER_ATTR_CONFLICT": "A provider extend redefines an attr another provider already declared, or a provider registers a common attr whose name another provider holds as a common attr or on any type, or the reverse (ADR-0062).",
"ERR_PROVIDER_ATTR_SHADOW": "A provider declares an attribute name on a type that another provider already declares on a related slot: the type it inherits from (<type>.base), or a subtype that inherits from it (ADR-0062). Two providers may not claim one attribute name on a node type; the second needs its own idiomatic name. The same provider re-declaring its own attribute stays legal.",
"ERR_EXTEND_REQUIRED_ATTR": "A provider tried to project a REQUIRED attribute onto a type it does not own (via extends). Projection is optional-only: a required attr registered this way disappears silently whenever that provider is composed out, taking its validation rule with it. Declare it with the type instead (ADR-0050).",
"ERR_SUBTYPE_RULE_VIOLATION": "A node violates a subtype composition rule (e.g. a value object with an identity or a source — FR-024 value purity; a projection extending a non-projection — FR-024/ADR-0028).",
"ERR_OVERLAY_NO_TARGET": "An overlay node has no existing target to merge into.",
Expand Down
Loading
Loading