@metaobjectsdev/sdk 0.24.5 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +1 -7
  2. package/agent-context/servers/csharp.meta.json +3 -1
  3. package/agent-context/servers/java.meta.json +3 -1
  4. package/agent-context/servers/kotlin.meta.json +3 -1
  5. package/agent-context/servers/python.meta.json +3 -1
  6. package/agent-context/servers/typescript.meta.json +3 -1
  7. package/agent-context/skills/metaobjects-audit/SKILL.md +15 -8
  8. package/agent-context/skills/metaobjects-audit/references/capability-checklist.md +6 -1
  9. package/agent-context/skills/metaobjects-audit/references/csharp.md +10 -1
  10. package/agent-context/skills/metaobjects-audit/references/java.md +10 -0
  11. package/agent-context/skills/metaobjects-audit/references/kotlin.md +11 -0
  12. package/agent-context/skills/metaobjects-audit/references/python.md +12 -2
  13. package/agent-context/skills/metaobjects-audit/references/typescript.md +7 -2
  14. package/agent-context/skills/metaobjects-authoring/SKILL.md +35 -6
  15. package/agent-context/skills/metaobjects-codegen/SKILL.md +210 -15
  16. package/agent-context/skills/metaobjects-codegen/references/csharp.md +1 -0
  17. package/agent-context/skills/metaobjects-codegen/references/java.md +1 -0
  18. package/agent-context/skills/metaobjects-codegen/references/kotlin.md +1 -0
  19. package/agent-context/skills/metaobjects-codegen/references/python.md +1 -0
  20. package/agent-context/skills/metaobjects-codegen/references/typescript.md +85 -6
  21. package/agent-context/skills/metaobjects-runtime-ui/SKILL.md +22 -0
  22. package/agent-context/skills/metaobjects-runtime-ui/references/csharp.md +15 -0
  23. package/agent-context/skills/metaobjects-runtime-ui/references/java.md +33 -0
  24. package/agent-context/skills/metaobjects-runtime-ui/references/kotlin.md +23 -0
  25. package/agent-context/skills/metaobjects-runtime-ui/references/python.md +25 -0
  26. package/agent-context/skills/metaobjects-runtime-ui/references/tanstack.md +28 -6
  27. package/agent-context/skills/metaobjects-runtime-ui/references/typescript.md +20 -0
  28. package/agent-context/skills/metaobjects-verify/SKILL.md +28 -0
  29. package/agent-context/templates/always-on.md.mustache +45 -4
  30. package/dist/agent-context/assemble.d.ts.map +1 -1
  31. package/dist/agent-context/assemble.js +52 -4
  32. package/dist/agent-context/assemble.js.map +1 -1
  33. package/dist/agent-context/scaffold.d.ts +16 -1
  34. package/dist/agent-context/scaffold.d.ts.map +1 -1
  35. package/dist/agent-context/scaffold.js +21 -1
  36. package/dist/agent-context/scaffold.js.map +1 -1
  37. package/dist/forge-types.d.ts.map +1 -1
  38. package/dist/forge-types.js +19 -1
  39. package/dist/forge-types.js.map +1 -1
  40. package/package.json +4 -9
  41. package/src/agent-context/assemble.ts +86 -6
  42. package/src/agent-context/scaffold.ts +33 -2
  43. package/src/forge-types.ts +21 -0
  44. package/dist/agent-docs/body.d.ts +0 -6
  45. package/dist/agent-docs/body.d.ts.map +0 -1
  46. package/dist/agent-docs/body.js +0 -593
  47. package/dist/agent-docs/body.js.map +0 -1
  48. package/dist/agent-docs/content-hash.d.ts +0 -8
  49. package/dist/agent-docs/content-hash.d.ts.map +0 -1
  50. package/dist/agent-docs/content-hash.js +0 -23
  51. package/dist/agent-docs/content-hash.js.map +0 -1
  52. package/dist/agent-docs/index.d.ts +0 -3
  53. package/dist/agent-docs/index.d.ts.map +0 -1
  54. package/dist/agent-docs/index.js +0 -4
  55. package/dist/agent-docs/index.js.map +0 -1
  56. package/src/agent-docs/body.ts +0 -592
  57. package/src/agent-docs/content-hash.ts +0 -25
  58. package/src/agent-docs/index.ts +0 -8
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @metaobjectsdev/sdk
2
2
 
3
- Programmatic SDK for MetaObjects: workspace memory records, path resolution, project config loading, and the agent-docs reference content. Consumed by the `meta` CLI and by AI-collaboration tooling (MCP exposers, codegen prompts).
3
+ Programmatic SDK for MetaObjects: workspace memory records, path resolution, and project config loading. Consumed by the `meta` CLI and by AI-collaboration tooling (MCP exposers, codegen prompts).
4
4
 
5
5
  ## Install
6
6
 
@@ -17,12 +17,6 @@ const metaRoot = await resolveMetaRoot(process.cwd());
17
17
  const config = await loadConfig(metaRoot);
18
18
  ```
19
19
 
20
- The canonical agent reference docs (scaffolded by `meta init`) are available via a sub-path:
21
-
22
- ```ts
23
- import { AGENT_DOCS_BODY, withContentHash } from "@metaobjectsdev/sdk/agent-docs";
24
- ```
25
-
26
20
  ## agent-context
27
21
 
28
22
  `@metaobjectsdev/sdk/agent-context` assembles the downstream AI-assistant context
@@ -1,5 +1,7 @@
1
1
  {
2
2
  "displayName": "C#",
3
3
  "install": "install the MetaObjects .NET tool (dotnet tool install) + add the MetaObjects.Codegen package",
4
- "codegenCommand": "dotnet meta gen"
4
+ "codegenCommand": "dotnet meta gen",
5
+ "docsCommand": "dotnet meta docs",
6
+ "apiSubDir": "api/csharp"
5
7
  }
@@ -1,5 +1,7 @@
1
1
  {
2
2
  "displayName": "Java",
3
3
  "install": "add com.metaobjects:metaobjects-metadata + metaobjects-codegen-spring + metaobjects-maven-plugin to your pom.xml",
4
- "codegenCommand": "mvn metaobjects:generate"
4
+ "codegenCommand": "mvn metaobjects:generate",
5
+ "docsCommand": "mvn metaobjects:docs",
6
+ "apiSubDir": "api/java"
5
7
  }
@@ -1,5 +1,7 @@
1
1
  {
2
2
  "displayName": "Kotlin",
3
3
  "install": "add com.metaobjects:metaobjects-codegen-kotlin + metaobjects-metadata-ktx + metaobjects-maven-plugin to your pom.xml",
4
- "codegenCommand": "mvn metaobjects:generate"
4
+ "codegenCommand": "mvn metaobjects:generate",
5
+ "docsCommand": "mvn metaobjects:docs",
6
+ "apiSubDir": "api/kotlin"
5
7
  }
@@ -1,5 +1,7 @@
1
1
  {
2
2
  "displayName": "Python",
3
3
  "install": "pip install metaobjects",
4
- "codegenCommand": "metaobjects gen"
4
+ "codegenCommand": "metaobjects gen",
5
+ "docsCommand": "metaobjects docs",
6
+ "apiSubDir": "api/python"
5
7
  }
@@ -1,5 +1,7 @@
1
1
  {
2
2
  "displayName": "TypeScript",
3
3
  "install": "npm install -D @metaobjectsdev/cli @metaobjectsdev/codegen-ts",
4
- "codegenCommand": "npx meta gen"
4
+ "codegenCommand": "npx meta gen",
5
+ "docsCommand": "npx meta docs",
6
+ "apiSubDir": "api"
5
7
  }
@@ -118,19 +118,25 @@ code behind a grep hit; a "duplicate" validator's *divergence* is the finding.
118
118
  references repo-wide (grep the emitted symbol / module path for importers):
119
119
  delete it and stop generating it.
120
120
  - **Artifacts an entity doesn't need** — REST routes / TanStack grids / forms /
121
- hooks emitted for an entity that has no such surface. The per-entity opt-outs
122
- exist for exactly this (`@emitRoutes: false`, `@emitTanstack: false`, a
123
- `layout.dataGrid`-gated grid): flag the opt-out NOT used where the artifact is
124
- unused.
121
+ hooks emitted for an entity that has no such surface. The remedy is the
122
+ generator's own `filter` option (`routesFile({ filter: (e) => e.name !== "Audit" })`,
123
+ `tanstackQuery({ filter })`), which is ANDed with the generator's built-in gates
124
+ and so can only NARROW what emits; a grid additionally needs a `layout.dataGrid`
125
+ on the entity at all. Flag the unused artifact and name the filter that would
126
+ suppress it. **Never recommend an `@emit*` attribute** — `@emitRoutes`,
127
+ `@emitTanstack`, `@emitForm`, `@emitGrid` and `@emitAngular` were never
128
+ registered vocabulary, so they passed `meta gen` (open load) and FAILED
129
+ `meta verify` (strict). A project carrying one is a **finding**, not an opt-out:
130
+ report it and route to `meta upgrade --apply` plus the generator config above.
125
131
  - **Generators wired but unconsumed** — a generator in `metaobjects.config.ts`
126
132
  `generators: [...]` (or the per-port equivalent) whose whole output class no
127
133
  code imports: drop the generator rather than generate into the void.
128
134
  - **Wrong target / duplicate output** — the same logical artifact emitted to two
129
135
  places (a mis-set per-target `outDir`), one of which is orphaned.
130
- Recommend generating ONLY what is consumed — narrow the generator set + use the
131
- per-entity opt-outs. A smaller, fully-consumed generated surface beats a large one
132
- with dead files (which also make the leverage ratio lie; discount them from the
133
- census).
136
+ Recommend generating ONLY what is consumed — decide per generator: drop the ones
137
+ whose whole output class nothing imports, and narrow the ones that over-emit with
138
+ their `filter`. A smaller, fully-consumed generated surface beats a large one with
139
+ dead files (which also make the leverage ratio lie; discount them from the census).
134
140
  - [ ] **G. Runtime-contract anti-patterns.** Module-global `db` vs context-as-parameter
135
141
  (ADR-0008); wire-canonicalization in the query path vs native in-process return types
136
142
  (ADR-0019); runtime reflection to resolve a type from FQN vs generated static imports /
@@ -331,6 +337,7 @@ Per finding: `file:line` → what → generated-equivalent exists? → recommend
331
337
  - **Not expressible → carry it in `@sql` or `@unmanaged`, never a hand-edited migration (#208, ADR-0043).** When a NAMED irreducible construct blocks origin authoring — recursive CTE, window function / `OVER`, `UNION` / `INTERSECT` / `EXCEPT`, lateral join — the body still belongs in the metadata: carry the hand-written SQL in the `source.rdb` **`@sql`** escape — a read-only-`@kind` body the tool REGISTERS, fingerprints, and drift-checks (adopt a pre-existing view with `meta migrate --allow adopt-view`); `@sql` forbids `origin.*` children (two sources of truth). A DB object whose DDL is owned **entirely elsewhere** (Flyway / a hand-migration) → mark its source **`@unmanaged: true`** (legal on any `@kind` incl. `table`); `meta migrate` never creates/drops/drift-checks it and `verify --db` reports it as external. `@sql` and `@unmanaged` are mutually exclusive. **Only a view left *undeclared*** — neither modeled, nor `@sql`, nor `@unmanaged` — is truly *unmanaged*, invisible to `meta verify --db`, so this audit is the only gate that sees it. "It's an aggregation" is NOT an irreducibility justification (plain count/sum/avg/min/max rollups are `origin.aggregate`); nor is a `DISTINCT ON` pick-one-row (`origin.first`) or a non-aggregate expression column (`origin.computed`).
332
338
  9. **A closed variant-set hand-modeled per instance** — N sibling modules / classes / config blocks, one per channel / provider / target, sharing a payload + config shape and diverging only by transport. Grep for sibling-file families and switch-on-a-string dispatch; verify the set is closed and recurring (never a one-off). → axis I "New-vocabulary OPPORTUNITY" (VOCAB CANDIDATE, advisory).
333
339
  10. **N declarations of one FIELD across objects (same-name-field census)** — the field-level sibling of signature 6. Census field names across `object.*` nodes (`grep -rn 'name: <field>'` the metadata dir); a name recurring in ≥2 objects where a canonical owner exists — one whose name the field embeds (`<owner><Field>`: `wizardId` → `Wizard.id`, `orderTotal` → `Order.total`) or whose type+constraints it matches — is provenance loss → `extends: Owner.field` (dotted child targets, ADR-0029). **VERIFY by diffing the copies' attrs: a `@maxLength` / `@required` / validator divergence across them is drift already shipping — cite it.** Evidence multiplier: `extends` already used elsewhere in the repo raises confidence. Do NOT flag: generic names on unrelated concepts (`id` / `name` / `status` with no owner-embedding name and no matching constraints); required per-node attrs a loader forces (e.g. `payloadRef` / `format` on sibling `template.prompt` nodes — a product constraint, not a copy).
340
+ 11. **A physical table / column / schema name spelled as a literal outside its metadata declaration** — in a hand-written repository, raw SQL, a migration script, a log line, or a hand-maintained body-to-column map. Every port emits a per-object names artifact (`<Entity>Names` / `<entity_snake>_names.py`) from the declaration, so a literal is a second spelling of the same fact; and because the physical column is free-form, deriving it from the field name is a guess that fails silently. **No verify subverb sees this** — `--codegen` diffs generated files, `--db` compares schema to metadata — so this audit is the only gate. Remedy: reference the constant; on the JVM the names generator is opt-in, so an un-wired pom is the first finding and wiring `SpringNamesGenerator` / `KotlinNamesGenerator` the first remedy. **Do NOT flag:** a typed ORM handle in its place (a Drizzle column object, an Exposed `Column`, an EF property — replacing one with a string is a regression); a physical name inside a `source.rdb` hand-written SQL body (a metadata document cannot import a constant, which is why that body is fingerprinted); a flattened value-object composite column (it belongs to no single field of either object); a write-through entity's replica view name (the artifact holds the PRIMARY source's); a relationship-synthesized foreign-key column (derived, never declared). A hand-written repository that imports `<Entity>Names` is the gold standard — call it out as good.
334
341
 
335
342
  ---
336
343
 
@@ -66,7 +66,12 @@ classify it (using the classification scheme in `SKILL.md`) and route the cutove
66
66
  ## Source — `source.rdb`
67
67
 
68
68
  - **`source.rdb`** (`@table`, `@schema`) — hunt hard-coded physical table/schema names that
69
- diverge from the default naming the source models.
69
+ diverge from the default naming the source models, AND any physical table/column/schema
70
+ name spelled as a literal outside its declaration — raw SQL, a hand-written repository, a
71
+ migration script, a body-to-column map (drift signature 11). Every port emits a per-object
72
+ names artifact from the declaration, so a literal is a second source of truth even when it
73
+ agrees with the naming strategy today. A typed ORM handle in its place is correct; on the
74
+ JVM an un-wired names generator is the finding first.
70
75
  - **`@kind` = `view` / `materializedView`** — hunt hand-written SQL views where an authored
71
76
  read-only source belongs. Apply the **view-necessity test** (SKILL.md, drift signature 8): a
72
77
  hand-written `CREATE VIEW` (or read-only SQL mirroring a read model) is a CODEGEN CANDIDATE when
@@ -51,6 +51,7 @@ rejected (exit 2).
51
51
  | `FromSqlInterpolated(` outside `.g.cs` | stored-proc call — candidate for the `callable` generator |
52
52
  | `// keep in sync with` / `// mirrors the` | second-source-of-truth comment — always a finding |
53
53
  | `HasPrecision(` hand-coded | `field.decimal` with `@precision`/`@scale` drives this from the `entity` generator |
54
+ | a table/column string in raw-SQL EF calls, or `nameof(Entity.Prop)` standing in for a column | second spelling of a declared physical name — reference `<Entity>Names.g.cs` (`AuthorNames.SourcePrimaryTable` / `<Field>Column`, default suite — `Names.Name` is the OBJECT's name, not the table); an EF property inside LINQ is the typed handle — correct |
54
55
 
55
56
  ---
56
57
 
@@ -59,7 +60,15 @@ rejected (exit 2).
59
60
  C# does not scaffold-and-own generators — the generators are provided by
60
61
  `MetaObjects.Codegen` and are not meant to be copied into the consumer project.
61
62
  There is no analog to the TS `codegen/generators/*.ts` pattern here. Generator
62
- selection uses stable names via `dotnet meta gen --generators <names>`.
63
+ selection uses stable names via `dotnet meta gen --generators <names>`, over a
64
+ **closed built-in registry** — there is no seam to register a generator of your own.
65
+
66
+ **So do not score a C# project down for "not owning its generators", and do not
67
+ recommend writing one.** The customization path here is the **declarative template**:
68
+ `dotnet meta gen --template-spec <json> --template-root <dir>`, whose entries append to
69
+ the default suite. A finding of the form "the built-ins do not emit the shape this
70
+ project needs" resolves to a template-spec, not to generator code. Worked example with
71
+ the full JSON: `docs/ports/csharp.md`.
63
72
 
64
73
  To re-scaffold the agent-context into a C# project, use the Node `meta` CLI (the
65
74
  single agent-docs assembler per ADR-0033):
@@ -51,6 +51,7 @@ plugin `<configuration>` — that is the source of truth for which generators ru
51
51
  | `// keep in sync with` / `// mirrors the` | second-source-of-truth comment — always a finding |
52
52
  | `ObjectMapper.readValue(` outside a `*Parser.java` file | check if a responding `template.prompt` (`@responseRef`) exists — output-parser codegen ships in Java |
53
53
  | `LIMIT ?` / `OFFSET ?` assembled by hand | generated CRUD routes handle pagination; OMDB `getObjects` accepts `QueryOptions` |
54
+ | a table/column string in a repository implementation — JDBC SQL, jOOQ `table("authors")`, a Spring Data query | second spelling of a declared physical name — reference `<Entity>Names` (`AuthorNames.SOURCE_PRIMARY_TABLE` / `_COLUMN`; opt-in `SpringNamesGenerator`): an un-wired generator is the finding first |
54
55
 
55
56
  ---
56
57
 
@@ -60,6 +61,15 @@ Java does not scaffold-and-own generators — they are provided by
60
61
  `metaobjects-codegen-spring` and wired by FQ class name in `pom.xml`. There is no
61
62
  analog to the TS `codegen/generators/*.ts` pattern here.
62
63
 
64
+ Java does, however, have **both** authoring paths, so a project needing a shape the
65
+ built-ins do not emit has two legitimate answers and neither is a finding on its own:
66
+ a **programmatic** generator (implement `com.metaobjects.generator.Generator`, name the
67
+ class in a `<generator>` element — the plugin loads it from the project classpath), or a
68
+ **declarative template** (`TemplateScopeGenerator`, wired with `<template>` / `<scope>` /
69
+ `<outputPattern>` / `<format>` / `<templatesDir>`). Prefer the template when the output
70
+ SHAPE is the thing being iterated on or the same output is wanted in another language;
71
+ prefer code when the logic is gnarly. See `docs/features/codegen-concepts.md` §3.
72
+
63
73
  To re-scaffold the agent-context into a Java project, use the Node `meta` CLI (the
64
74
  single agent-docs assembler per ADR-0033):
65
75
 
@@ -56,6 +56,7 @@ The active generator list is declared in `pom.xml` under the plugin `<configurat
56
56
  | `@Serializable` on a payload class not in generated sources | `KotlinPayloadGenerator` should own this; check `pom.xml` generator list |
57
57
  | `// keep in sync with` / `// mirrors the` | second-source-of-truth comment — always a finding |
58
58
  | `transaction(db) {` bodies that duplicate every CRUD operation | hand-written Exposed transactions are expected (see Calibration), but if they duplicate generated-CRUD logic exactly, audit further |
59
+ | a table/column string in `exec("…")` raw SQL or a Flyway script | reference `<Entity>Names` (`KotlinNamesGenerator`, opt-in); `AuthorTable.name` inside the Exposed DSL is the typed handle — correct, not a finding |
59
60
 
60
61
  ---
61
62
 
@@ -65,6 +66,16 @@ Kotlin does not scaffold-and-own generators — they are provided by
65
66
  `metaobjects-codegen-kotlin` and wired by FQ class name in `pom.xml`. There is no
66
67
  analog to the TS `codegen/generators/*.ts` pattern here.
67
68
 
69
+ Kotlin does, however, have **both** authoring paths — it shares the JVM engine — so a
70
+ project needing a shape the built-ins do not emit has two legitimate answers and neither
71
+ is a finding on its own: a **programmatic** generator (implement
72
+ `com.metaobjects.generator.Generator`, name the class in a `<generator>` element), or a
73
+ **declarative template** (`TemplateScopeGenerator`, wired with `<template>` / `<scope>` /
74
+ `<outputPattern>` / `<format>` / `<templatesDir>` — no KotlinPoet involved). Prefer the
75
+ template when the output SHAPE is the thing being iterated on or the same output is
76
+ wanted in another language; prefer code when the logic is gnarly. See
77
+ `docs/features/codegen-concepts.md` §3.
78
+
68
79
  To re-scaffold the agent-context into a Kotlin project, use the Node `meta` CLI (the
69
80
  single agent-docs assembler per ADR-0033):
70
81
 
@@ -52,14 +52,24 @@ regardless of server language — see the migration reference.
52
52
  | `def get_all_` / `def create_` / `def update_` / `def delete_` in non-generated files | hand-rolled CRUD — compare to the generated router |
53
53
  | `# keep in sync with` / `# mirrors the` | second-source-of-truth comment — always a finding |
54
54
  | `try: ... except KeyError` / `?? ''` around format strings in prompt code | silent-degradation hack around a prompt payload — flag it |
55
+ | a table/column string in the repository implementation (SQLAlchemy Core, asyncpg/psycopg SQL) | second spelling of a declared physical name — reference `<entity>_names.py` (`AUTHOR_SOURCE_PRIMARY_TABLE` / `AUTHOR_<FIELD>_COLUMN`, default suite); no typed handle exists on this port, so that is never the reason to waive it |
55
56
 
56
57
  ---
57
58
 
58
59
  ## Owned generators
59
60
 
60
61
  Python does not scaffold-and-own generators — they are provided by the `metaobjects`
61
- package and selected by stable name via `--generators`. There is no analog to the
62
- TS `codegen/generators/*.ts` pattern here.
62
+ package and selected by stable name via `--generators`, over a **closed built-in
63
+ registry**. There is no analog to the TS `codegen/generators/*.ts` pattern here, and no
64
+ seam to register a generator of your own. (`--provider module:symbol` registers
65
+ **metamodel vocabulary**, not a generator — do not read it as one.)
66
+
67
+ **So do not score a Python project down for "not owning its generators", and do not
68
+ recommend writing one.** The customization path here is the **declarative template**:
69
+ `metaobjects gen --template-spec <json> --templates <dir>`, whose entries append to the
70
+ default suite. A finding of the form "the built-ins do not emit the shape this project
71
+ needs" resolves to a template-spec, not to generator code. Worked example with the full
72
+ JSON: `docs/ports/python.md`.
63
73
 
64
74
  To re-scaffold the agent-context into a Python project, use the Node `meta` CLI (the
65
75
  single agent-docs assembler per ADR-0033):
@@ -14,8 +14,12 @@ Generated files carry the header:
14
14
  // @generated by @metaobjectsdev/codegen-ts
15
15
  ```
16
16
 
17
- The runner **only overwrites files bearing this header** — hand-edited sibling
18
- files (`<Entity>.extra.ts`) are never touched. Locate generated output at the
17
+ The header is **informational on this port** — the write decision never reads it. What
18
+ the runner overwrites is decided by `.metaobjects/.gen-state/`: the snapshot body if the
19
+ machine has one (three-way merge), otherwise the committed `.hashes.json` (byte-for-byte
20
+ what it wrote ⇒ overwrite; anything else ⇒ refused, exit 1). A sibling file you created
21
+ (`<Entity>.extra.ts` by convention) is never touched because it is not a path the runner
22
+ records — not because of its name, and not because of the header. Locate generated output at the
19
23
  `outDir` (and per-target `outDir` values) declared in `metaobjects.config.ts`. With
20
24
  the default single-target config, generated files land at the one configured
21
25
  `outDir` (e.g. `src/generated/`).
@@ -50,6 +54,7 @@ value-sets against the constructs that replace them.
50
54
  | `// keep in sync with` / `// mirrors the` | second-source-of-truth comment — always a finding |
51
55
  | camelCase↔snake_case mapping tables | generated views handle this; diff field by field |
52
56
  | `@generated` file with hand edits | three-way merge preserves them; review at codegen; never delete |
57
+ | a table/column string in a `sql` fragment, a Kysely identifier, or a migration/seed script outside the generated dir | second spelling of a declared physical name — reference `<Entity>.names.ts` (`ProgramNames.fields.<f>.column`); a Drizzle column object in its place is correct, not a finding |
53
58
 
54
59
  ---
55
60
 
@@ -49,10 +49,19 @@ aggregate — that is almost always **metadata you have not declared yet.** In o
49
49
  1. **Search the vocabulary** — `meta types <term>`, or `meta types --all
50
50
  <what-it-does>` to search by behavior. There are field subtypes, relationships,
51
51
  projections, origins, identities, sources, and attributes you may not know exist.
52
- Find the construct that models it.
52
+ Find the construct that models it. Add `--detail` for one construct's valid
53
+ `@attrs`, or `--format json` for the same answer as one machine-readable document
54
+ — that form carries every match (`--limit` never truncates it) with each attr's
55
+ `allowedValues`, so you read the accepted values rather than guessing them.
53
56
  2. **Declare it and generate** — then *consume* the generated query/type/route;
54
57
  never reimplement it alongside.
55
- 3. **Only if no construct can express it** and you have actually looked
58
+ 3. **If the model is right but the generated OUTPUT is wrong, change your generator.**
59
+ Naming, file layout, imports, framework, signatures are generator concerns, not
60
+ reasons to hand-write. The generators are in *your* repo and are yours to edit — a
61
+ standing rule not to change the MetaObjects repo does not reach them; they are a
62
+ different repository. Editing one is ordinary work, not an escalation. (See
63
+ `metaobjects-codegen` → "Your generators are yours".)
64
+ 4. **Only if no construct can express it** — and you have actually looked —
56
65
  hand-write it, wired to generated types. Business algorithms, external
57
66
  integrations, and bespoke interactions are legitimately hand-written; CRUD,
58
67
  validation, finders, relationships, and derived/aggregate data are not.
@@ -86,10 +95,20 @@ Read the existing code and schema *first*, then model to reproduce them:
86
95
 
87
96
  **Customize the CODEGEN to match the existing code before you change the existing code.**
88
97
  If generated output doesn't match the code's shape (naming, file layout, imports,
89
- signatures), **tune the generator/template/config to reproduce it** — that is the
90
- intended adoption path (owned generators, `outputPattern`, naming strategy see the
91
- `metaobjects-codegen` skill), **not a hack**. Reshaping working call sites to satisfy
92
- the generator's defaults is the *last* resort, not the first.
98
+ signatures), **tune the generator/template/config to reproduce it** — naming strategy
99
+ first, then the generator or the template. **Which lever you have depends on the port,
100
+ so establish that before planning:** on TypeScript and the JVM you can own a generator
101
+ outright (TS scaffolds copies into your repo; the JVM loads your class from the project
102
+ classpath), while on C# and Python the generator registry is closed and a **Mustache
103
+ template** is the customization path — a real one, with `scope` and `outputPattern`
104
+ doing the walk and the file naming. The `metaobjects-codegen` skill has the per-port
105
+ matrix and defines `outputPattern`; read it before concluding a shape is unreachable.
106
+ **Whichever lever you get, it is yours to edit.** A standing instruction not to
107
+ change the MetaObjects repo says nothing about your own generators or templates —
108
+ reading it as though it did is how an adoption ends up hand-written, and it is the most
109
+ common way this step is skipped. Editing a generator or a template here is ordinary
110
+ adoption work: not an escalation, not a hack, and nothing to ask permission for. Reshaping working call sites to satisfy the
111
+ generator's defaults is the *last* resort, not the first.
93
112
 
94
113
  **Minimize churn to code the generator is not replacing.** The ONLY existing code that
95
114
  should change is the hand-written layer codegen now **owns** (the hand-rolled
@@ -743,6 +762,16 @@ Multi-source: multiple `source.rdb` children, each with a `@role`, exactly one
743
762
  { "source.rdb": { "@kind": "view", "@view": "v_author", "@schema": "blog" } }
744
763
  ```
745
764
 
765
+ **This declaration is the only place a physical name is ever spelled.** Codegen emits a
766
+ per-object names artifact from it (`<Entity>Names` — `ProgramNames.fields.createdAt.column`,
767
+ `ProgramNames.CreatedAtColumn`, `PROGRAM_CREATED_AT_COLUMN`, per port), the generated table
768
+ binding reads that artifact, and hand-written SQL, repositories and migration scripts
769
+ reference it — so a consumer never restates `v_author` or `created_at`, and a rename here
770
+ propagates. Declare `@column` explicitly whenever the physical name is not the naming
771
+ strategy's answer (`callPurpose` → `purpose_code`): nothing downstream can recover that
772
+ mapping by derivation, and the constant is what carries it. (See `metaobjects-codegen` →
773
+ "Never hand-write a physical name".)
774
+
746
775
  **An entity's PRIMARY source must be writable** (`table`) — read-only kinds are
747
776
  legal only in non-primary roles.
748
777
 
@@ -24,8 +24,28 @@ You run a `gen` step. The runner:
24
24
  2. Resolves output targets and precomputes shared render state.
25
25
  3. Runs each configured **generator** — most emit one file per entity; some emit a
26
26
  single shared file (a barrel, a DB-context, an app-config).
27
- 4. Refuses to overwrite any file that does NOT carry the `@generated` header;
28
- overwrites the ones that do.
27
+ 4. Decides whether it may overwrite a file and **the rule differs by port**:
28
+ - **TypeScript, C#, Python** — by a committed **hash manifest**
29
+ (`.metaobjects/.gen-state/.hashes.json`). If a file still hashes to what the
30
+ generator recorded writing, it is safe to overwrite; if it was edited, or there is
31
+ no record of it, the write is **refused by name**. TypeScript additionally
32
+ three-way-merges against a snapshot when one is present locally.
33
+ - **Java, Kotlin** — by a bare **`GENERATED`** token in the file's header comment
34
+ (`GeneratedFileWriter.GENERATED_MARKER`). The token is `GENERATED`, **not**
35
+ `@generated`: the matcher allows only whitespace between the comment punctuation
36
+ and the token, so an `@`-prefixed tag does not match it. Remove that token and
37
+ regeneration never touches the file again.
38
+
39
+ **The two rules protect a hand edit in opposite ways, so do not carry a habit across
40
+ ports.** On TypeScript, C# and Python, *editing the content* is what takes ownership —
41
+ that is what breaks the hash — and deleting the header changes nothing except your
42
+ ability to tell what generated the file. On Java and Kotlin the reverse holds: editing
43
+ a file protects it not at all, because the `GENERATED` token is still there and the
44
+ next run overwrites the edit; only removing that token does.
45
+
46
+ (The header text differs per port and none of it is the write decision on the
47
+ hash-manifest ports: TypeScript and Python emit `@generated by …`, C# emits
48
+ `<auto-generated/>`, Java and Kotlin emit the `GENERATED` token above.)
29
49
 
30
50
  The output is normal idiomatic code in your language — you import it and use it
31
51
  like any hand-written module.
@@ -35,8 +55,11 @@ like any hand-written module.
35
55
  Every emitted file carries a `@generated` header. This is load-bearing:
36
56
 
37
57
  - **Never hand-edit a file with a `@generated` header for a change you want to
38
- keep.** The next `gen` run overwrites it. If you need different output, change
39
- the metadata (or the template), not the generated file.
58
+ keep.** The next `gen` run overwrites it. If you need different output, change the
59
+ metadata, or change the generator that emits it.
60
+ - **This rule is about emitted output — it is not a rule about your generators.** A
61
+ generator you own carries no `@generated` header and is edited like any other source
62
+ file in your repo. See the next section before you conclude a shape is unreachable.
40
63
  - **Hand-written regions are preserved by three-way merge.** Where the codegen
41
64
  supports designated hand-editable regions, regeneration runs a three-way merge
42
65
  (base → yours → newly-generated) so your edits survive a regen. Code review is
@@ -54,6 +77,43 @@ for a shape origins can express is drift the drift gate can't even see. A genuin
54
77
  loose either — it goes in the `source.rdb` **`@sql`** escape (#208, ADR-0043) so the
55
78
  tool still registers, fingerprints, and drift-checks it (see the projection bullet).
56
79
 
80
+ ## Your generators are yours — editing one needs no permission
81
+
82
+ A generator in your repo is your code, not a vendor artifact. ADR-0034 is
83
+ **scaffold-and-own**: the generators the scaffolded config wires are copied into your
84
+ repo at init, and every reference template's header says so in its own first line
85
+ ("copy this into your repo … and own it", "now YOURS to change"). None carries a
86
+ `@generated` header. Editing one is ordinary work.
87
+
88
+ **A standing rule not to change the MetaObjects repo is not a rule about your
89
+ generators.** They are different repositories, and you own yours outright. That
90
+ generalisation is the observed failure mode, not a hypothetical: an agent told not to
91
+ touch upstream quietly drops "edit the generator" from the moves available to it and
92
+ hand-writes the layer instead — the single outcome the rest of this skill exists to
93
+ prevent. If you are about to hand-write something data-shaped because the generated
94
+ shape is wrong, the generator is the file to open, and you do not need to ask first.
95
+
96
+ **The order when generated output does not fit:**
97
+
98
+ 1. **The metadata**, if the model is wrong — wrong column type, missing relationship,
99
+ a join that should be a projection. Fix the spine first; it fixes every port at once.
100
+ 2. **Your own generator**, if the model is right and the *emit* is wrong — naming, file
101
+ layout, imports, framework, signatures.
102
+ 3. **Hand-write**, only for what metadata genuinely cannot express — and wire it to the
103
+ generated types.
104
+
105
+ Hand-writing something the metadata already describes is step 3 used as step 1.
106
+
107
+ **The converse, so ownership does not become sprawl:** wire a generator only for output
108
+ you will actually consume. Decide per generator, narrow one with its own `filter`, and
109
+ own the ones you keep — an emitted file nobody imports still reads as an invitation to
110
+ adopt the surface you decided against.
111
+
112
+ How you get a generator's source differs per port — a copy command on TypeScript,
113
+ implementing the port's generator interface elsewhere. Your language reference has the
114
+ mechanism; see also "The commands and config keys that implement the steps above differ
115
+ per port" below.
116
+
57
117
  ## Selecting generators by stable name
58
118
 
59
119
  Codegen is a set of named generators you opt into. Each generator has a **stable
@@ -271,17 +331,152 @@ output/template · doesn't fit → write a generator that emits your shape *from
271
331
  metadata* · only the genuinely un-modelable (business algorithms, external calls) is
272
332
  hand-written outside codegen — and it still imports the generated types.
273
333
 
274
- **The commands and config keys that implement the steps above differ per port, and the
275
- ports differ in how much is written down.** TypeScript has the whole procedure as a
276
- documented one `meta eject`, the `metaobjects.config.ts` keys, the exported `render*`
277
- functions in this skill's `references/typescript.md`. **The other ports have no eject
278
- command.** There, owning a generator means implementing that port's generator interface
279
- — `com.metaobjects.generator.Generator` (Java / Kotlin),
280
- `metaobjects.codegen.generator.Generator` (Python), `MetaObjects.Codegen.IGenerator`
281
- (C#) — and registering it with the build tool that runs codegen for your port. Their
282
- `references/` fragments document what each built-in emits, which is what you compare
283
- your own emit against; they do not carry a step-by-step retargeting procedure, so do
284
- not go looking for one.
334
+ ## Two ways to author a generator pick deliberately
335
+
336
+ A generator can be **programmatic** (code that builds the output) or **declarative** (a
337
+ Mustache template plus a scope). Both are first-class, both ship in every port, and they
338
+ are good at different things.
339
+
340
+ | | Programmatic | Declarative template |
341
+ |---|---|---|
342
+ | What you write | a `Generator` in the port's language, using its AST builder (ts-poet, KotlinPoet, …) | a `.mustache` file + `{ template, scope, outputPattern, format? }` |
343
+ | Output shape | expressed in code | **is the file you are editing** |
344
+ | Cross-language | per-port by construction | one template emits for any language — it renders against the neutral, byte-gated data dict |
345
+ | Logic | any | what a template can express: sections, iteration, presence flags |
346
+
347
+ **The rule:** reach for **programmatic** when the logic is gnarly or the run is hot; reach
348
+ for a **template** when the *shape* is what you are iterating on, or when you want the same
349
+ output across languages. `scope` is `perEntity` / `perPackage` / `perModel` — the walk you
350
+ would otherwise hand-write — and `outputPattern` is the output path per item, with
351
+ `{name}` / `{Name}` / `{package}` placeholders (e.g. `"{package}/{Name}Service.java"`).
352
+ Full tradeoff table and the data dict: `docs/features/codegen-concepts.md` §3 and §10.
353
+ **Asking whether a base/extension split or a write-if-absent file exists? That's §5-§7, not
354
+ here.** §5 (*Preserving hand edits*) states MetaObjects ships exactly one hand-edit strategy —
355
+ no shipped generator on any port emits a generated-base + hand-owned-concrete pair, or a
356
+ write-if-absent file; §6 names the `skip-existing` merge strategy `runGen` accepts for
357
+ building that pair yourself, reachable only from a programmatic caller (no CLI flag selects
358
+ it); §7 (*Safety*) is the per-port write-decision mechanism behind "What codegen does" step 4
359
+ above.
360
+
361
+ **A template is not limited to documents.** It emits source as readily as docs — that is
362
+ what the neutral data dict is for.
363
+
364
+ ### Which is available to you depends on the port — check before you plan
365
+
366
+ **TypeScript** has both, and the whole programmatic procedure is documented: `meta eject`,
367
+ the `metaobjects.config.ts` keys, the exported `render*` functions — see this skill's
368
+ `references/typescript.md`. The declarative path is declared in the SAME config: call
369
+ `templateGenerator()` in `generators`, or spread a parsed JSON spec with
370
+ `templateSpecToGenerators(parseTemplateSpec(...))` to reuse one written for C#/Python.
371
+ **There is no `--template-spec` flag on `meta gen` and its absence is not a gap** — the
372
+ config takes generator values, and keeping the declaration there is what keeps
373
+ `meta verify --codegen` regenerating with it.
374
+
375
+ **Java / Kotlin** have both. **No eject command** — a programmatic generator means
376
+ implementing `com.metaobjects.generator.Generator` and naming your class in the Maven
377
+ `<generator>` element, which the plugin loads from the project classpath. The declarative
378
+ path is `TemplateScopeGenerator`, wired the same way with `<template>` / `<scope>` /
379
+ `<outputPattern>` / `<format>` / `<templatesDir>` (plus the standard `<outputDir>`), and
380
+ covers Java and Kotlin alike. No `--template-spec` flag here either, for the same reason:
381
+ `<generator>` already loads a consumer class from the project classpath.
382
+
383
+ **C# and Python: the declarative path is your only option, and it is a real one.** Their
384
+ generator sets are **closed built-in registries** — `--generators` *selects* from what
385
+ ships, and there is no seam to register a `Generator` of your own. (Python's
386
+ `--provider module:symbol` registers **metamodel vocabulary**, not a generator; do not
387
+ reach for it here.) Use `--template-spec <json>` — plus `--templates <dir>` on Python or
388
+ `--template-root <dir>` on C# — and your entries are appended to the default suite. Worked
389
+ examples with the full JSON: `docs/ports/python.md` and `docs/ports/csharp.md`.
390
+
391
+ **The spec is auto-discovered, and that is load-bearing.** With no `--template-spec`, both
392
+ ports read `<projectRoot>/template-spec.json` — projectRoot being the metadata dir's parent.
393
+ Keep it there: `verify --codegen` accepts no `--template-spec` flag, so the conventional path
394
+ is how the drift gate learns your template generators exist. Put the spec somewhere else and
395
+ reach it only by flag, and `verify` regenerates without it and reports its output as stale.
396
+
397
+ So on C#/Python, "I need a shape the built-ins do not emit" is answered by a template, not
398
+ by writing generator code. Do not conclude the port cannot be customized.
399
+
400
+ Each port's `references/` fragment documents what its built-ins emit, which is what you
401
+ compare your own emit against; they do not carry a step-by-step retargeting procedure.
402
+
403
+ ## Never hand-write a physical name — the generator emits them
404
+
405
+ A table name, a column name, a schema — these are declared in metadata and derived by the
406
+ same resolver the migration and the runtime use. A string literal for one is a magic string
407
+ that no compiler checks and no gate catches, and it goes wrong silently: `@column` is
408
+ free-form, so a field named `callPurpose` may map to a column named `purpose_code`, which is
409
+ neither the field name nor any transformation of it. A consumer deriving the column as
410
+ `to_snake_case(<the field's name>)` gets that case wrong and never finds out.
411
+
412
+ Every port emits a per-object names artifact. Reference it:
413
+
414
+ ```ts
415
+ import { ProgramNames } from "./generated/Program.names.js";
416
+
417
+ ProgramNames.name // "Program" — the OBJECT's name
418
+ ProgramNames.sources.primary.table // "programs" — physical table
419
+ ProgramNames.sources.primary.kind // "table" — table | view | proc | …
420
+ ProgramNames.fields.createdAt.name // "createdAt" — logical / wire name
421
+ ProgramNames.fields.createdAt.column // "created_at" — physical column
422
+ ProgramNames.indexes.ix_prog_owner.index // "ix_prog_owner" — database index name
423
+ ```
424
+
425
+ The artifact mirrors the metadata tree: every node carries its own `type`, `subType` and
426
+ `name`, and a physical name sits under the key that says **what kind of database object it
427
+ is**. A view is `sources.primary.view`, a stored proc `sources.primary.proc`, and a
428
+ write-through entity's read view `sources.replica.view` — so `sources.replica.table` is a
429
+ compile error rather than a wrong answer. There is no `readOnly`: it was derived from
430
+ `kind`, never declared, so ask `kind`.
431
+
432
+ The artifact is per-object and the shape is per-language; the guarantee is the same
433
+ everywhere — **each physical name is spelled once, and generated code references it**:
434
+
435
+ | Port | Artifact | Reads as |
436
+ |---|---|---|
437
+ | TypeScript | `<Entity>.names.ts` | `ProgramNames.fields.createdAt.column` |
438
+ | C# | `<Entity>Names.g.cs` | `ProgramNames.CreatedAtColumn` |
439
+ | Java | `<Entity>Names.java` | `ProgramNames.CREATED_AT_COLUMN` |
440
+ | Kotlin | `<Entity>Names.kt` | `ProgramNames.CREATED_AT_COLUMN` |
441
+ | Python | `<entity_snake>_names.py` | `PROGRAM_CREATED_AT_COLUMN` |
442
+
443
+ On TypeScript (`meta init`), C# and Python the names generator is in the default suite; on
444
+ the JVM it is opt-in — add `SpringNamesGenerator` / `KotlinNamesGenerator` to the pom's
445
+ `<generators>`, and the Exposed table binding switches to the constants once it is present.
446
+
447
+ **It follows `extends`.** An object that extends another does not restate what it
448
+ inherits: C# and Java use real class inheritance (`class CopayAuthNames extends
449
+ AuthNames`), TypeScript spreads (`...AuthNames.fields`), and Kotlin and Python re-export
450
+ the parent's constants by reference. An abstract base a persisted object extends gets an
451
+ artifact of its own — columns only, no table name, because it has none. So if you are
452
+ reading a subtype's artifact and its table name is not there, it is on the base, which is
453
+ where it belongs.
454
+
455
+ **Where generated code consumes it, and where it does not.** TypeScript, C# and Kotlin
456
+ bind an ORM (Drizzle, EF Core, Exposed) and so must spell physical names — their generated
457
+ code references these constants, and a cross-port gate proves no generated file spells one
458
+ literally. **Java and Python generate no SQL at all**: their DTOs and models carry logical
459
+ names, and persistence is the repository interface/`Protocol` you implement. There the
460
+ artifact exists *for your code*, which is the only place a physical name appears.
461
+
462
+ Two categories stay literal, deliberately, and the gate pins them as such rather than
463
+ exempting them:
464
+
465
+ - a **flattened value-object column** (`@storage: flattened`) is a composite —
466
+ `<owner field column>_<member column>` — belonging to no single field of either object,
467
+ so there is no one constant to reference;
468
+ - a **write-through entity's replica view name**: the artifact holds the object's PRIMARY
469
+ source's name (its table), and a write-through entity has two physical names.
470
+
471
+ **But prefer a typed handle where one exists — this rule has a real limit.** If the ORM
472
+ gives you a type-checked object for the same thing, use that. Replacing a Drizzle column
473
+ object (`programs.createdAt`, checked against the schema at compile time) with a string
474
+ constant makes the code **worse**: it trades an error the compiler catches for one the
475
+ database raises at runtime. The constants are for the places with no typed handle — raw
476
+ SQL, migration scripts, log lines, an external system's column mapping, a port whose
477
+ generated model carries no persistence binding at all.
478
+
479
+ The rule is *don't invent the string*, not *replace every name with a constant*.
285
480
 
286
481
  ## Dialects
287
482
 
@@ -47,6 +47,7 @@ or run the default set. Output lands under `--namespace` in `--output-dir`.
47
47
  | `callable` | `<Entity>.callable.g.cs` — an FR-015 calling method for a `source.rdb @kind="storedProc"|"tableFunction"`, via EF `FromSqlInterpolated` (args from the `@parameterRef` value object in declaration order). |
48
48
  | `payload` | `<Payload>.payload.cs` — the strict typed payload `record` (+ any nested element records) per `template.prompt` `@payloadRef` / `@responseRef` (each an `object.value`): `@payloadRef` types the REQUEST, `@responseRef` the REPLY the parser/extractor bind to. |
49
49
  | `output-parser` / `extractor` / `output-prompt` / `render-helper` | the prompt-pillar artifacts for a **responding `template.prompt`** — one carrying `@responseRef` (ADR-0052: these tiers are INBOUND; `template.output` is outbound only and emits no parser). The strict parser, the tolerant `extract`, the **output-format prompt fragment** (`output-prompt`; presentation via `@promptStyle: guide`/`inline`/`exampleOnly`), and the typed render helper. See the **prompts** reference. |
50
+ | `names` | `<Entity>Names.g.cs` — `public abstract class <Entity>Names` of `const` physical database names, MIRRORING THE METADATA TREE: the object's own `Type`/`SubType`/`Name` (`Name` is the OBJECT's name, never a physical one), then per `source.rdb` keyed by `@role` — `SourcePrimaryType`/`SubType`/`Kind`, `SourcePrimarySchema` when declared, and the physical name under the alias for its `@kind` (`SourcePrimaryTable` / `SourceReplicaView` / `SourcePrimaryProc` / `…Function` / `…MaterializedView`) — then a `<Field>Field`/`<Field>Column` pair per field, then each identity and index (`IdentityPkName`, and `IdentityUqCustEmailIndex` / `IndexIxCustStatusIndex` — the DATABASE name, carried for `identity.secondary` and `index.lookup` only), then a complete `ColumnsByField`. There is no `ReadOnly`: it is a derivation over `@kind`, so ask `SourcePrimaryKind`. Emitted for every object with a declared or inherited primary source, PLUS a fragment for any abstract base such an object extends (its own identity + columns, and NO source members — it has no table). A class whose object extends another one **inherits** it (`class CopayAuthNames : AuthNames`) rather than restating its constants — a C# `const` is inherited, so `CopayAuthNames.IdColumn` and `CopayAuthNames.SourcePrimaryTable` resolve through the base (`Type`/`SubType`/`Name` are redeclared with `new`, since every artifact carries its own). `abstract`, not `static`, precisely so it can be inherited; it still cannot be instantiated. In the run, `entity` and `db-context` reference the constants (`[Table(OrderNames.SourcePrimaryTable)]`, `[Column(OrderNames.StatusColumn)]`, `.ToView(OrderNames.SourceReplicaView)`); a `--generators` selection omitting `names` falls back to literals. |
50
51
  | `template` | the generic Mustache `templateGenerator()` primitive. |
51
52
 
52
53
  Metadata lives under `metaobjects/` (or wherever you point `--metadata-dir`) in the
@@ -106,6 +106,7 @@ separate `metaobjects-codegen-base` module instead.)
106
106
  | `SpringRenderHelperGenerator` | the typed render helper for a `template.prompt` payload |
107
107
  | `LlmTraceHelperGenerator` | `<Entity>TraceHelper.java` per concrete entity — the LLM-trace helper |
108
108
  | `SpringFilterAllowlistGenerator` | per-entity filter allowlist |
109
+ | `SpringNamesGenerator` | `<Entity>Names.java` — `public abstract class` of `static final String` names that MIRRORS THE METADATA TREE. Every node carries its own `TYPE`/`SUB_TYPE`/`NAME`, so `AuthorNames.NAME` is the OBJECT's name (`"Author"`), **not** a table name, and a physical name sits under the member that says what it is: `SOURCE_<ROLE>_TABLE` / `_VIEW` / `_MATERIALIZED_VIEW` / `_PROC` / `_FUNCTION`, from the metamodel's own `@kind`-to-alias map, with `<ROLE>` being `PRIMARY` or `REPLICA` (a write-through entity has a member for each). Plus a `<FIELD>_FIELD`/`<FIELD>_COLUMN` pair per field and a complete `COLUMNS_BY_FIELD`; an `identity.secondary` or `index.lookup` also carries `IDENTITY_<NAME>_INDEX` / `INDEX_<NAME>_INDEX`, while `identity.primary` deliberately carries no index member (migrate names a PK by a dialect-conditional formula this artifact must not restate). **There is no `READ_ONLY`** — it was a derivation over `@kind`, never metadata, so ask `SOURCE_<ROLE>_KIND`. Emitted for every object with a declared or inherited primary source, PLUS a fragment for any abstract base such an object extends (columns only, no source). A class whose object extends another one **extends** it (`class CopayAuthNames extends AuthNames`) rather than restating its constants — Java inherits static members, so `CopayAuthNames.SOURCE_PRIMARY_TABLE` and `CopayAuthNames.ID_COLUMN` both resolve through the base. **This port generates no SQL** (the repository is an interface you implement), so nothing generated consumes these — they exist for your implementation. |
109
110
  | `JavaObjectCodeGenerator` | module `metaobjects-codegen-base` (`com.metaobjects.generator.direct.object.javacode`), a separate module from the Spring generators above. Flavor-selected via the `flavor` generator arg. `flavor=pojoAware` → `class <Name> extends PojoObject` (a concrete `MetaObjectAware` class with a `(MetaObject)` constructor) — its inherited `getMetaData()` back-reference is what breaks a default Jackson/Gson mapper, see "Serializing generated objects" below. `flavor=valueObject` → `class <Name> extends ValueObject` (map-backed; less hostile to a default mapper, but still not the sanctioned serialization path). Either concrete flavor also emits a `<Name>Extractor` plus a self-registering `ObjectClassBindingProvider`. For a plain default-Jackson-friendly type, use the `codegen-spring` record surface instead — never `pojoAware`. |
110
111
 
111
112
  **Projections (read-only views).** An `object.projection` (read-only `source.rdb`