chore(metaobjects): upgrade to MetaObjects 1.0.0 and adopt generated names constants - #36
Merged
Conversation
Package pins → 1.0.0-rc.1 (metamodelVersion 1.0). Agent context + skills refreshed.
The owned generators were the real work. codegen/generators/{entity,queries,barrel}.ts
dated from around 0.23.2 while package.json pinned ^0.24.5 — five minor lines stale — and
carried three patterns MetaObjects has since retired:
- a bare `import { code, joinCode } from "ts-poet"`, which is the 0.21.6 split-tree
defect: with a globally-installed or linked CLI the project and the CLI resolve
different physical ts-poet copies, `instanceof Code` fails across them, and every
section stringifies with its own import header (duplicate imports, TS2300).
- `skipNonQueryable` gating persistability on `e.subType !== OBJECT_SUBTYPE_VALUE` —
the subtype check #248 replaced with "derives from a declared source.rdb".
- the delegation branch missing `isWriteThrough` (#214).
Nothing had ever reported any of this. They were brought forward by three-way merge
against 0.23.2 as the base, NOT by `meta eject --force`, which replaces the file
wholesale — this copy of queries.ts is deliberately trimmed to READ-ONLY finders (this
app's writes are bespoke) and a force-eject silently destroys that. The trim is preserved;
the #203 insertPreserving block that came with the merge is dropped along with the rest of
the write surface it belongs to.
Generated output changes are the expected 1.0 ones: $apiPrefix leaves the entity
descriptor (a deployment fact, now supplied by the runtime provider — nothing here read
it), enum fields report view "dropdown" rather than "text", and the Drizzle Db type alias
gains its third type parameter.
Gates: meta verify --templates --codegen --prompts data/templates clean · D1 schema in
sync · tsc -b clean · 204/204 vitest.
.metaobjects/adoption-audit.json records the audit. Two findings are upstream product
defects, not this repo's: an inline field.enum's members are inlined four times per
generated file and exported nowhere (a shared package-level enum does get a named const),
and the descriptor now says view "dropdown" while carrying no options.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3v6M6uqECZ2Sb5eUv6YY
… by hand
rc.2 carries three fixes that came out of upgrading this repo to rc.1, and two of them
change what is generated here:
- a `field.enum`'s form descriptor now carries its `options`. 0.25.0 moved an enum field's
view from "text" to "dropdown" and never gave the descriptor a member list, so it asked
for a <select> and supplied nothing to fill one.
- an inline `field.enum` now emits a runtime constant (`CouncilStatusEnum`) beside its
type alias. The alias is erased at runtime, so code needing a MEMBER had nothing to
import — which is why "complete" was a literal in five files here.
`src/db/queries.ts`, `src/render/share-html.ts` and `src/render/share-page.tsx` now read
`CouncilStatusEnum.enum.<member>`. The `"complete"` in `useCouncilStream.ts` and
`HomePage.tsx` is deliberately LEFT: that is `StreamStatus`, a client-only state machine
whose members merely overlap with Council.status. Binding it to the entity's enum would
couple two unrelated vocabularies because a grep matched.
The owned generators are re-synced against the rc.2 reference. entity.ts and barrel.ts are
now byte-identical to it — the previous pass had left entity.ts without the engine `ts-poet`
import block and barrel.ts without the ADR-0044 collision-safe emitted-name call, and
`meta eject`'s new staleness report is what caught both. queries.ts is the one that stays
different, by 9 lines: the READ-ONLY trim, re-applied on top of the current reference so it
now carries the #248 source-derived persistability check and the #214 write-through branch
it had been missing.
Gates: verify --templates --codegen clean · D1 schema in sync · tsc -b · 204/204 vitest ·
production build.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3v6M6uqECZ2Sb5eUv6YY
…once
The names artifact is the 1.0 headline feature and this project emitted none of
it, because `namesFile()` was never in `generators: [...]`. That is not an
oversight anyone here made: three of the agent-context skills stated "on
TypeScript (`meta init`), C# and Python the names generator is in the default
suite", and there is no default suite on TypeScript — `generators: [...]` IS the
complete list, exactly like the JVM's `<generators>`. `meta init` SCAFFOLDS it,
into a project initialized at 1.0; this one predates that, and upgrading a
package never edits a config. Upstream corrected the skills; the refreshed copies
are in this commit.
Wiring it changes what the entity generator emits: `councils` becomes
`CouncilNames.sources.primary.table` and every `text("visitor_id")` becomes
`text(CouncilNames.fields.visitorId.column)`. That diff is the point — the
physical name is now spelled once per run, in the artifact resolved from the same
`source.rdb` declaration the DDL comes from, and everything else references it.
Nothing hand-written had to change. A sweep of all 28 declared physical names
across src/, test/, scripts/ and codegen/ (generated dirs excluded) found zero
literal spellings: the only hits are the Drizzle table objects, which are the
typed handle the doctrine prefers. The .sql files under migrations/ are
`meta migrate` output, and test/setup.ts replays those files rather than
restating a single name.
Also corrects two stale claims in the config's own comments, both now wrong in a
way that would mislead someone reading it as the reference it is meant to be:
- entity/queries/routes/barrel are not "deprecated on
@metaobjectsdev/codegen-ts/generators" any more — 1.0 REMOVED them, and
there is no export left to fall back to.
- "only the entity/queries/routes/barrel generators are ownable" conflated two
different axes. `meta eject --list` names nine; `names` and `routes-hono`
have ownable copies AND are still exported. Removed-from-the-package and
has-an-ownable-copy are independent facts, so the comment now states both.
`namesFile` is imported from the package rather than ejected: there is no reason
to fork an artifact whose shape we do not intend to change, and the config
already says not to cargo-cult owning everything.
Gates, all at rc.4: `meta verify --templates --codegen --prompts` clean (22
templates), `meta verify --dialect d1 --d1 DB` in sync, `tsc -b` clean, 204 tests
green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3v6M6uqECZ2Sb5eUv6YY
rc.4 was 29 commits stale. All three of its breaking changes are inert here: this
project registers no `useEntityFetcher`, calls `attr("isArray")` nowhere, and runs
no `verify --docs` gate. `meta gen` reports 31 unchanged — the generated output is
byte-identical to rc.4's.
Only the agent-context refresh moved: two skill reference pages gained the
`excluded.<column>` guidance and the per-driver identifier section, both of which
came out of this estate's own pass.
Gates at rc.5, every one CI runs: `npm ci` from a cold `node_modules` · `npm run
demo:drift` 3 gates clean · `npm run verify:db` (migrations applied to a fresh
local D1, then the schema drift gate) 2 gates clean · `npx tsc -b` · `npm run
build:web` · `npm test` 204 passed across 33 files. Identical counts to the rc.4
baseline taken before the bump.
NOT part of this change, but observed while taking that baseline: the committed
`public/assets/index-*.js` bundle is stale — a fresh `npm run build:web` emits a
different content hash, at rc.4 and at rc.5 alike, so it is unrelated to
MetaObjects. CI builds the bundle but never diffs it, which is why nothing has
said so.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTcEKXTQMYt84fAjuw5A2M
Both of rc.6's breaking changes are inert here — no forge vocabulary, and `meta docs` is not part of this project's surface — so this is the estate that answers "what does the candidate cost a project it does not affect?" The answer is nothing: `npm run check` (gen → verify → verify:db → tsc -b → vitest) exits 0 unchanged, with `meta gen` reporting **31 unchanged**, `meta verify --dialect d1 --d1 DB` schema in sync against the local D1, and 204 tests passing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
rc.6 -> rc.7, carrying the four CLI fixes the estates found while running rc.6. `npm run check` (gen -> verify -> verify:db on local D1 -> tsc -b -> vitest) exit 0: 31 unchanged, 204 tests, both drift gates clean. Identical to the rc.6 result, which is what a candidate that only changes CLI diagnostics should look like from here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NTcEKXTQMYt84fAjuw5A2M
Off the release candidates and onto 1.0.0 itself. This branch IS the 1.0 upgrade, so it should name the release rather than a candidate. Agent context refreshed in the same commit, which clears the staleness nudge the version move would otherwise leave behind. Gates were re-run at the released version rather than assumed from the candidate round. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NTcEKXTQMYt84fAjuw5A2M
…unbook, refresh routes-hono comment
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Take this project — the public reference consumer for MetaObjects — off the MetaObjects 0.x/release-candidate line and onto the released MetaObjects 1.0.0, and land that as the project's 1.0 upgrade PR.
Context a reviewer reading only the diff would not have: this branch is one of nine maintainer-owned adopter estates that were deliberately run against every 1.0 release candidate in turn (rc.1, rc.2, rc.4, rc.5, rc.6, rc.7, then 1.0.0 itself) to satisfy the MetaObjects 1.0 release gate that an adopter estate must exercise the candidate with the drift gates ENFORCED before promote. That is why the history is a sequence of version-move commits rather than one bump: each was a real gate run at that candidate, and each commit message records the counts it produced. Keep that history; do not squash the story out of it.
Deliberate decisions embedded in the diff:
Acceptance: every gate this project itself declares must pass at 1.0.0 — npm run check (meta gen, meta verify, meta verify:db against a local D1, tsc -b, vitest) — and CI must be green before merge.
What Changed
@metaobjectsdev/*packages from^0.24.xto pinned1.0.0and regeneratesrc/db/generated/with the 1.0 toolchain; the commit history deliberately walks each 1.0 release candidate (rc.1 → rc.7 → 1.0.0), one gate-run commit per candidate.codegen/generators/to the 1.0 engine (ts-poet combinators imported from@metaobjectsdev/codegen-tsto share one instance, TPH-subtype and write-through entity routing) and wire the newnamesFile()generator inmetaobjects.config.ts, emittingCouncil.names.ts/CouncilTurn.names.tsso physical table and column names are declared once as importable constants.src/db/queries.ts,src/render/share-html.ts, andsrc/render/share-page.tsxwith the generatedCouncilStatusEnum.enum.*members (no behavior change), and refresh the generated agent context (.metaobjects/,.claude/skills/metaobjects-*) plus MetaObjects version refs inCLAUDE.mdanddocs/runbook.md.Risk Assessment
✅ Low: A well-bounded, behavior-equivalent version move: I verified statically that the names constants resolve to the exact pre-change physical table/column/check-constraint names matching migrations DDL, enum-constant adoption is value-identical, drift-gate and agent-context hash state match every committed file, and no consumer reads any removed descriptor field — with build/test/CI enforcement owned by later pipeline steps.
Testing
Drove the full
npm run checkgate plus the dedicateddemo:driftdrift gate live and both came back completely green with zero drift against the committed 1.0.0 generated output; added an adversarial hand-edit to a generated names artifact and confirmed the drift gate actually fails on it, then confirmed it goes green again once reverted. Beyond static gates, stood up the real Worker withwrangler devand drove the actual product: a real POST /api/council SSE convene persisted through the regenerated Council/CouncilTurn Drizzle schema into a local D1 database, and the resulting council rendered correctly on both the HTML share route and its JSON sibling; a second, manually-seeded council was screenshotted end-to-end in a real browser for visual proof. The only gap is that real Anthropic-authored wizard content wasn't observed, since this sandbox's ANTHROPIC_API_KEY is empty — that's outside this diff's changed surface (no LLM-calling code changed) and the orchestrator's graceful-degradation path that fired instead is itself legitimate, exercised code, not a stub.meta gen(31/31 files unchanged — zero drift vs. committed generated output),verify:portraits,verify:db(wrangler d1 migrations apply --local +meta verify --dialect d1 --d1 DB→ "schema…npm run demo:drift→ "22 template(s) clean" and "every file's generated contribution is current (no codegen drift)", exit 0. 03-demo-drift.log, reproduced fresh.wrangler d1 execute --local): CREATE TABLE councils/council_turns exactly matches the column names declared in Council.names.ts/CouncilTurn.names.ts (e.g. verd…wrangler dev, POSTed a real question to /api/council, captured the live SSE stream (event: council/error.../verdict/done), then confirmed the council + 5 turns were persisted in local D1 wit…Evidence: npm run check (meta gen zero-drift, verify:portraits, verify:db vs local D1, tsc -b, vitest) — full green run
Evidence: demo:drift baseline pass (templates+codegen+prompts clean)
Evidence: Adversarial: hand-edited Council.names.ts → demo:drift correctly fails with exit 1 (codegen drift detected)
Evidence: demo:drift green again after reverting the hand-edit
Evidence: Real SSE stream from POST /api/council against running wrangler dev
Evidence: D1 state after live convene: council + 5 turns persisted through the regenerated Council/CouncilTurn Drizzle tables
Evidence: SSR share page (GET /c/:slug) for the live-persisted council, rendered by share-html.ts/share-page.tsx
Evidence: Share JSON endpoint (GET /c/:slug.json) for the live-persisted council
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
src/db/queries.ts:129- setCouncilError still spells the Council.status member by hand (status: "error"), while the change's stated purpose (commit 3610293: "stop spelling enum members by hand"; "src/db/queries.ts … now read CouncilStatusEnum.enum.<member>") converted the three sibling sites in this same file (lines 34, 62, 121). Unlike the documented StreamStatus exceptions in useCouncilStream/HomePage (a deliberately separate client vocabulary), this is the same vocabulary, same column, same file — a missed site, not a documented decision. No functional impact: the Drizzle column's enum typing still type-checks the literal, so this is mechanical incompleteness of the conversion itself. The same-filekind: "error"at line 92 is the same pattern now that the engine emits CouncilTurnKindEnum.CLAUDE.md:28- The project CLAUDE.md tech-stack table still pins@metaobjectsdev/*@0.15.18, and docs/runbook.md:283 still claims "the project now tracks the current published line (@metaobjectsdev/*@0.15.x)" — both already stale at base (0.24.5) and now two version lines wrong after this 1.0.0 move. This is the exact class of stale version claim commit a0cda82 deliberately corrected in metaobjects.config.ts's comments ("two stale claims … now wrong in a way that would mislead someone reading it as the reference it is meant to be"); these two were missed. Mechanical doc correction inside the version-move PR's own subject.✅ **Test** - passed
✅ No issues found.
meta gen(31/31 files unchanged — zero drift vs. committed generated output),verify:portraits,verify:db(wrangler d1 migrations apply --local +meta verify --dialect d1 --d1 DB→ "schema…npm run demo:drift→ "22 template(s) clean" and "every file's generated contribution is current (no codegen drift)", exit 0. 03-demo-drift.log, reproduced fresh.wrangler d1 execute --local): CREATE TABLE councils/council_turns exactly matches the column names declared in Council.names.ts/CouncilTurn.names.ts (e.g. verd…wrangler dev, POSTed a real question to /api/council, captured the live SSE stream (event: council/error.../verdict/done), then confirmed the council + 5 turns were persisted in local D1 wit…npm run gen:db (meta gen) — 31/31 unchanged, zero driftnpm run verify (verify:portraits)npm run verify:db (wrangler d1 migrations apply --local+meta verify --dialect d1 --d1 DB)npx tsc -bnpx vitest run (33 files / 204 tests)npm run demo:drift (meta verify --templates --codegen --prompts data/templates)Adversarial hand-edit of src/db/generated/Council.names.ts to confirm demo:drift fails, then reverted and re-verified greenwrangler d1 execute --localagainst sqlite_master to confirm physical schema matches the generated <Entity>Names constantsLivewrangler devrun: POST /api/council (SSE), GET /c/:slug, GET /c/:slug.json, GET /api/healthManual D1 seed of a valid-slug council + turn, then GET /c/:slug screenshotted in headless ChromeGET /c/<slug-with-disallowed-char> and /c/bad!! to confirm the metadata-sourced slug validator 404s✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.