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
31 changes: 30 additions & 1 deletion agent-context/skills/metaobjects-audit/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,35 @@ code behind a grep hit; a "duplicate" validator's *divergence* is the finding.
(ADR-0019); runtime reflection to resolve a type from FQN vs generated static imports /
FQN registry (ADR-0001 / 0017); process-global registry vs per-loader (ADR-0014); code
that **mutates the loaded metadata tree** (read-only after load); JVM/Kotlin missing
startup validator; writes not routed to the `@role: primary` source.
startup validator; writes not routed to the `@role: primary` source; **`own*()`
accessor reads of effective properties / own-only member iteration (ADR-0039 — see
the active check below).**

- [ ] **G2. `own*()` accessor discipline (ADR-0039 — CORRECTNESS DEFECT, not advisory).**
In any custom generator, metamodel provider, or runtime path (NOT the sanctioned cases
below), **flag every read of a field/node's effective property, or own-only member
iteration, done through an own-only accessor** — it silently drops everything inherited
via `extends` (a super-reference, not a flatten), corrupting codegen and runtime. This
is exactly the class of bug that broke Kotlin's array-type derivation (a concrete field
inheriting an array flag from an abstract parent generated a scalar) and, per the audit,
is latent cross-port. Grep for the own-only accessors and verify each hit:
- **TS:** `ownAttr(`, `ownChildren(`, `ownFields(`, a raw `isArray` field flag read →
should be `attr(` / `children()` / `fields()` unless emitting a subclass's own members.
- **Python:** `own_children(`, `own_fields(`, and the **inverted** bare `attr(` (Python
`attr()` is OWN; the resolving form is `attrs().get(`) → flag `attr(` used to read an
effective value.
- **Java / Kotlin:** `getMetaAttr(name, false)` (the `,false` own overload), own-only
child walks (e.g. an own-only `filterIsInstance<…>()` source lookup that emits nothing
for an entity inheriting its source).
- **C#:** a native `IsArray` flag read, `OwnChildren()`, own attr reads.

**Sanctioned (do NOT flag):** (a) a generator emitting a generated **subclass** that
iterates `ownFields()` so inherited members aren't re-emitted (the generated base
declares them — the `class Sub extends Base` / TPH pattern); (b) the own-mode canonical
serializer + overlay-merge + super-resolution walks (library-internal); (c) the single
deliberately-own attribute `@dbColumnType` (a physical column-type override, never
inherited). Any own read that carries a comment naming one of these cases is fine; an
uncommented own read of an effective property is the defect.
- [ ] **H. Authoring-correctness / ADR-conformance (deep).** Invented/unregistered
`@`-attrs or post-bootstrap registration (ADR-0023 — custom attrs belong in a registered
provider or `attr.properties`); retired source-v2 forms (`source.dbTable` / `@name` /
Expand Down Expand Up @@ -172,6 +200,7 @@ Per finding: `file:line` → what → generated-equivalent exists? → recommend
4. **Drift-admitting comments** — grep: `"keep in sync with"` / `"mirrors the"` / `"matching the"`.
5. **Runtime schema patching** (`ALTER TABLE … ADD COLUMN IF NOT EXISTS`, `_ensure_schema()`) — N schema owners.
6. **N declarations of one shape** — same entity as Drizzle table + Zod schema + Pydantic model + hand dataclass; target is 1 + N generated.
7. **`own*()` accessor read of an effective property** (ADR-0039) — `ownAttr` / `ownFields` / `own_children` / bare Python `attr(` / `getMetaAttr(name, false)` / native `IsArray` used to read a value or iterate members outside the sanctioned subclass-emit / own-serializer / `@dbColumnType` cases → silently drops `extends`-inherited values. A **correctness defect** (axis G2), not advisory.

---

Expand Down
10 changes: 10 additions & 0 deletions agent-context/skills/metaobjects-authoring/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,16 @@ Resolution facts:

`abstract` and `extends` are **structural keys** (bare, no `@`).

**Extends-inherited properties are real — consume metadata through the resolving
accessors (ADR-0039).** If you write a custom generator or a metamodel provider that
reads this metadata, a concrete field/entity's inherited attributes and members live
on the parent it `extends`, not on the node itself (extends is a super-*reference*, not
a flatten). Always read a property or iterate a member set via the **resolving/effective**
accessor (TS `attr()`/`children()`/`fields()`, Python `attrs().get()`), **never an
`own*()` accessor** — an own-only read silently drops everything inherited via `extends`
and corrupts the generated code. See the `metaobjects-codegen` skill for the full
per-port mapping.

**`overlay` is a different concept.** `extends:` is an IS-A relationship between
two distinct nodes. `overlay: true` re-opens the *same* named node to amend it
across files (same `package` + same `name` → merged; last-writer-wins on attr
Expand Down
38 changes: 38 additions & 0 deletions agent-context/skills/metaobjects-codegen/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,44 @@ the `generators` array in `metaobjects.config.ts` next to the built-ins — it r
the same pass, writes under the same target rules, and carries the `@generated`
header so it round-trips like any other.

### Never read metadata through an `own*()` accessor (ADR-0039) — top bug source

When writing OR reviewing a generator, **read every field/node property and iterate
every member set through the resolving/effective accessor — never the `own*()` form.**
`extends` is a **super-reference, not a flatten**: a concrete field/entity that
`extends` an abstract parent keeps its inherited attributes and members physically on
the parent, reachable only through the *resolving* accessor. An `own*()` read of an
effective property (`isArray`, `subType`, `maxLength`, `precision`/`scale`, `default`,
the physical column name, `objectRef`, `storage`, `required`, …) or an own-only member
iteration **silently drops everything inherited via `extends`** — the classic symptom
was a concrete field that inherited `isArray: true` from an abstract parent generating
a *scalar* column. These reads compile and pass every fixture that never exercises
`extends`, so they are a latent, cross-port top bug source.

**The one legitimate `own*()` use:** a generator emitting a generated **subclass** that
`extends` a generated base iterates **own members** (`ownFields()`) so the inherited
members are **not re-emitted** — the generated base class already declares them (the
`class Sub extends Base` / TPH pattern). Everywhere else, resolve. (The own-mode
canonical serializer and overlay-merge are the only other sanctioned own reads, and
they are library-internal, not app-generator concerns.) The one deliberately-own
attribute is `@dbColumnType` — a physical column-type override that is never inherited.

**Per-port own↔resolving mapping** (reach for the resolving column; comment any
`own*()` call with the sanctioned case it is):

| Port | Resolving (default — use this) | Own-only (avoid unless emitting a subclass's own members) |
|---|---|---|
| TypeScript | `attr(name)`, `children()`, `fields()` | `ownAttr(name)`, `ownChildren()`, `ownFields()`, the raw `isArray` field flag |
| Python | `attrs().get(name)`, `children()`, `fields()` | `attr(name)` **(own!)**, `own_children()`, `own_fields()` |
| Java / Kotlin | `getMetaAttr(name)`, resolving `getChildren()` | `getMetaAttr(name, false)`, own-only child walks |
| C# | resolving attr/`Children`/`Fields` accessors | `IsArray` native flag, `OwnChildren()`, own attr reads |

**Naming inversion — the trap:** the *default-named* accessor is NOT consistently the
safe one. **TS `attr()` RESOLVES; Python `attr()` is OWN** (own-only). In Python you
must call `attrs().get(name)` to get the inherited value — a bare `attr(name)` is the
own read that drops inheritance. When you review or port a generator, check the port's
convention, not the method name.

**Close but not exact?** You don't always need a new generator — a generated file is
a normal source file. Copy it and customize the copy (three-way merge preserves your
edits on regen), or customize the template a built-in renders from. Reach for a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,35 @@ code behind a grep hit; a "duplicate" validator's *divergence* is the finding.
(ADR-0019); runtime reflection to resolve a type from FQN vs generated static imports /
FQN registry (ADR-0001 / 0017); process-global registry vs per-loader (ADR-0014); code
that **mutates the loaded metadata tree** (read-only after load); JVM/Kotlin missing
startup validator; writes not routed to the `@role: primary` source.
startup validator; writes not routed to the `@role: primary` source; **`own*()`
accessor reads of effective properties / own-only member iteration (ADR-0039 — see
the active check below).**

- [ ] **G2. `own*()` accessor discipline (ADR-0039 — CORRECTNESS DEFECT, not advisory).**
In any custom generator, metamodel provider, or runtime path (NOT the sanctioned cases
below), **flag every read of a field/node's effective property, or own-only member
iteration, done through an own-only accessor** — it silently drops everything inherited
via `extends` (a super-reference, not a flatten), corrupting codegen and runtime. This
is exactly the class of bug that broke Kotlin's array-type derivation (a concrete field
inheriting an array flag from an abstract parent generated a scalar) and, per the audit,
is latent cross-port. Grep for the own-only accessors and verify each hit:
- **TS:** `ownAttr(`, `ownChildren(`, `ownFields(`, a raw `isArray` field flag read →
should be `attr(` / `children()` / `fields()` unless emitting a subclass's own members.
- **Python:** `own_children(`, `own_fields(`, and the **inverted** bare `attr(` (Python
`attr()` is OWN; the resolving form is `attrs().get(`) → flag `attr(` used to read an
effective value.
- **Java / Kotlin:** `getMetaAttr(name, false)` (the `,false` own overload), own-only
child walks (e.g. an own-only `filterIsInstance<…>()` source lookup that emits nothing
for an entity inheriting its source).
- **C#:** a native `IsArray` flag read, `OwnChildren()`, own attr reads.

**Sanctioned (do NOT flag):** (a) a generator emitting a generated **subclass** that
iterates `ownFields()` so inherited members aren't re-emitted (the generated base
declares them — the `class Sub extends Base` / TPH pattern); (b) the own-mode canonical
serializer + overlay-merge + super-resolution walks (library-internal); (c) the single
deliberately-own attribute `@dbColumnType` (a physical column-type override, never
inherited). Any own read that carries a comment naming one of these cases is fine; an
uncommented own read of an effective property is the defect.
- [ ] **H. Authoring-correctness / ADR-conformance (deep).** Invented/unregistered
`@`-attrs or post-bootstrap registration (ADR-0023 — custom attrs belong in a registered
provider or `attr.properties`); retired source-v2 forms (`source.dbTable` / `@name` /
Expand Down Expand Up @@ -172,6 +200,7 @@ Per finding: `file:line` → what → generated-equivalent exists? → recommend
4. **Drift-admitting comments** — grep: `"keep in sync with"` / `"mirrors the"` / `"matching the"`.
5. **Runtime schema patching** (`ALTER TABLE … ADD COLUMN IF NOT EXISTS`, `_ensure_schema()`) — N schema owners.
6. **N declarations of one shape** — same entity as Drizzle table + Zod schema + Pydantic model + hand dataclass; target is 1 + N generated.
7. **`own*()` accessor read of an effective property** (ADR-0039) — `ownAttr` / `ownFields` / `own_children` / bare Python `attr(` / `getMetaAttr(name, false)` / native `IsArray` used to read a value or iterate members outside the sanctioned subclass-emit / own-serializer / `@dbColumnType` cases → silently drops `extends`-inherited values. A **correctness defect** (axis G2), not advisory.

---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,16 @@ Resolution facts:

`abstract` and `extends` are **structural keys** (bare, no `@`).

**Extends-inherited properties are real — consume metadata through the resolving
accessors (ADR-0039).** If you write a custom generator or a metamodel provider that
reads this metadata, a concrete field/entity's inherited attributes and members live
on the parent it `extends`, not on the node itself (extends is a super-*reference*, not
a flatten). Always read a property or iterate a member set via the **resolving/effective**
accessor (TS `attr()`/`children()`/`fields()`, Python `attrs().get()`), **never an
`own*()` accessor** — an own-only read silently drops everything inherited via `extends`
and corrupts the generated code. See the `metaobjects-codegen` skill for the full
per-port mapping.

**`overlay` is a different concept.** `extends:` is an IS-A relationship between
two distinct nodes. `overlay: true` re-opens the *same* named node to amend it
across files (same `package` + same `name` → merged; last-writer-wins on attr
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,44 @@ the `generators` array in `metaobjects.config.ts` next to the built-ins — it r
the same pass, writes under the same target rules, and carries the `@generated`
header so it round-trips like any other.

### Never read metadata through an `own*()` accessor (ADR-0039) — top bug source

When writing OR reviewing a generator, **read every field/node property and iterate
every member set through the resolving/effective accessor — never the `own*()` form.**
`extends` is a **super-reference, not a flatten**: a concrete field/entity that
`extends` an abstract parent keeps its inherited attributes and members physically on
the parent, reachable only through the *resolving* accessor. An `own*()` read of an
effective property (`isArray`, `subType`, `maxLength`, `precision`/`scale`, `default`,
the physical column name, `objectRef`, `storage`, `required`, …) or an own-only member
iteration **silently drops everything inherited via `extends`** — the classic symptom
was a concrete field that inherited `isArray: true` from an abstract parent generating
a *scalar* column. These reads compile and pass every fixture that never exercises
`extends`, so they are a latent, cross-port top bug source.

**The one legitimate `own*()` use:** a generator emitting a generated **subclass** that
`extends` a generated base iterates **own members** (`ownFields()`) so the inherited
members are **not re-emitted** — the generated base class already declares them (the
`class Sub extends Base` / TPH pattern). Everywhere else, resolve. (The own-mode
canonical serializer and overlay-merge are the only other sanctioned own reads, and
they are library-internal, not app-generator concerns.) The one deliberately-own
attribute is `@dbColumnType` — a physical column-type override that is never inherited.

**Per-port own↔resolving mapping** (reach for the resolving column; comment any
`own*()` call with the sanctioned case it is):

| Port | Resolving (default — use this) | Own-only (avoid unless emitting a subclass's own members) |
|---|---|---|
| TypeScript | `attr(name)`, `children()`, `fields()` | `ownAttr(name)`, `ownChildren()`, `ownFields()`, the raw `isArray` field flag |
| Python | `attrs().get(name)`, `children()`, `fields()` | `attr(name)` **(own!)**, `own_children()`, `own_fields()` |
| Java / Kotlin | `getMetaAttr(name)`, resolving `getChildren()` | `getMetaAttr(name, false)`, own-only child walks |
| C# | resolving attr/`Children`/`Fields` accessors | `IsArray` native flag, `OwnChildren()`, own attr reads |

**Naming inversion — the trap:** the *default-named* accessor is NOT consistently the
safe one. **TS `attr()` RESOLVES; Python `attr()` is OWN** (own-only). In Python you
must call `attrs().get(name)` to get the inherited value — a bare `attr(name)` is the
own read that drops inheritance. When you review or port a generator, check the port's
convention, not the method name.

**Close but not exact?** You don't always need a new generator — a generated file is
a normal source file. Copy it and customize the copy (three-way merge preserves your
edits on regen), or customize the template a built-in renders from. Reach for a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,35 @@ code behind a grep hit; a "duplicate" validator's *divergence* is the finding.
(ADR-0019); runtime reflection to resolve a type from FQN vs generated static imports /
FQN registry (ADR-0001 / 0017); process-global registry vs per-loader (ADR-0014); code
that **mutates the loaded metadata tree** (read-only after load); JVM/Kotlin missing
startup validator; writes not routed to the `@role: primary` source.
startup validator; writes not routed to the `@role: primary` source; **`own*()`
accessor reads of effective properties / own-only member iteration (ADR-0039 — see
the active check below).**

- [ ] **G2. `own*()` accessor discipline (ADR-0039 — CORRECTNESS DEFECT, not advisory).**
In any custom generator, metamodel provider, or runtime path (NOT the sanctioned cases
below), **flag every read of a field/node's effective property, or own-only member
iteration, done through an own-only accessor** — it silently drops everything inherited
via `extends` (a super-reference, not a flatten), corrupting codegen and runtime. This
is exactly the class of bug that broke Kotlin's array-type derivation (a concrete field
inheriting an array flag from an abstract parent generated a scalar) and, per the audit,
is latent cross-port. Grep for the own-only accessors and verify each hit:
- **TS:** `ownAttr(`, `ownChildren(`, `ownFields(`, a raw `isArray` field flag read →
should be `attr(` / `children()` / `fields()` unless emitting a subclass's own members.
- **Python:** `own_children(`, `own_fields(`, and the **inverted** bare `attr(` (Python
`attr()` is OWN; the resolving form is `attrs().get(`) → flag `attr(` used to read an
effective value.
- **Java / Kotlin:** `getMetaAttr(name, false)` (the `,false` own overload), own-only
child walks (e.g. an own-only `filterIsInstance<…>()` source lookup that emits nothing
for an entity inheriting its source).
- **C#:** a native `IsArray` flag read, `OwnChildren()`, own attr reads.

**Sanctioned (do NOT flag):** (a) a generator emitting a generated **subclass** that
iterates `ownFields()` so inherited members aren't re-emitted (the generated base
declares them — the `class Sub extends Base` / TPH pattern); (b) the own-mode canonical
serializer + overlay-merge + super-resolution walks (library-internal); (c) the single
deliberately-own attribute `@dbColumnType` (a physical column-type override, never
inherited). Any own read that carries a comment naming one of these cases is fine; an
uncommented own read of an effective property is the defect.
- [ ] **H. Authoring-correctness / ADR-conformance (deep).** Invented/unregistered
`@`-attrs or post-bootstrap registration (ADR-0023 — custom attrs belong in a registered
provider or `attr.properties`); retired source-v2 forms (`source.dbTable` / `@name` /
Expand Down Expand Up @@ -172,6 +200,7 @@ Per finding: `file:line` → what → generated-equivalent exists? → recommend
4. **Drift-admitting comments** — grep: `"keep in sync with"` / `"mirrors the"` / `"matching the"`.
5. **Runtime schema patching** (`ALTER TABLE … ADD COLUMN IF NOT EXISTS`, `_ensure_schema()`) — N schema owners.
6. **N declarations of one shape** — same entity as Drizzle table + Zod schema + Pydantic model + hand dataclass; target is 1 + N generated.
7. **`own*()` accessor read of an effective property** (ADR-0039) — `ownAttr` / `ownFields` / `own_children` / bare Python `attr(` / `getMetaAttr(name, false)` / native `IsArray` used to read a value or iterate members outside the sanctioned subclass-emit / own-serializer / `@dbColumnType` cases → silently drops `extends`-inherited values. A **correctness defect** (axis G2), not advisory.

---

Expand Down
Loading
Loading