fix(cli): scaffold outDir defaults to src/generated, not ./src/db#1
Closed
dmealing wants to merge 1 commit into
Closed
fix(cli): scaffold outDir defaults to src/generated, not ./src/db#1dmealing wants to merge 1 commit into
dmealing wants to merge 1 commit into
Conversation
`meta init` scaffolded `outDir: "./src/db"` alongside `dbImport: "../db"`,
which puts generated files in src/db/ right next to the src/db.ts the user
must create for their DB instance — same path stem, two different things —
and the generated routes' `import { db } from "../db"` can resolve to the
generated barrel (src/db/index.ts) instead of the user's src/db.ts. Default
to `src/generated` (matches docs/ports/typescript.md) so generated code and
the user's db module stay clearly separate. Found while building the
five-minute quickstart demo against published 0.9.0.
Adds an init test asserting the scaffolded outDir.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
dmealing
added a commit
that referenced
this pull request
Jun 25, 2026
…om the migration binding (#1) The meta init scaffold conflated a generic invariant with a project-specific binding in the always-injected AGENTS.md/CLAUDE.md, and guessed the binding: 1. The "never hand-write" principle listed only code artifacts (FKs, CRUD, validators, finders) and never named the DB schema/migrations as metadata-derived — so an agent would hand-write a raw ALTER TABLE against a generated schema, silently reintroducing the exact drift meta verify exists to catch. 2. The stack line asserted "migrations are TS" — a guessed binding that's wrong for adopters who consume meta migrate output as Flyway/Liquibase/etc. Fix (tool states invariants; project states bindings; tool never guesses): - Extend the principle to name the database schema and migrations as metadata-derived, tool-agnostically: "The schema is a disposable, generated artifact: change the metadata and regenerate, never hand-write SQL." - Drop the "; migrations are TS" clause from the assembled stack line. Deliberately NOT adding a persistence/migrations config field (issue's optional "last point"): the generic invariant stops the agent hand-writing migrations, and a project that needs a specific line states it in a hand-written region of AGENTS.md (three-way merge already preserves those) — no new config surface. The always-installed metaobjects-verify/migration reference fragment already carries the project-agnostic how. Regenerated the 4 cross-stack conformance fixtures; agent-context conformance + root/sdk byte-identity gates green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LuZWKnWzYGVnESijL7uuky
dmealing
added a commit
that referenced
this pull request
Jun 25, 2026
…idator removal (#73) * fix(agent-context): hedge the JVM startup-validator claim in verify skill (invariant vs binding) Audit of the agent-context corpus (5 skill bodies + 16 reference fragments + README) for the issue #1 defect class — conflating a generic invariant with a project-specific binding, or guessing a binding — found it broadly clean: per-stack reference fragments correctly carry their own stack's binding without guessing a second one, LLM callers are explicitly bring-your-own, and the "schema/migrations are metadata-derived" framing is intact across all ports. One consistency fix: verify/SKILL.md asserted that on the JVM ports "a runtime startup validator catches generated-table drift at app boot" as a flat fact — but whether an adopter wires that validator is an opt-in project binding. migration.md already hedges the same capability ("can catch"); this aligns verify/SKILL.md to it: "can catch ... an optional complementary check (if your project wires one)". Regenerated the 4 cross-stack conformance fixtures (the skill body is installed for every stack). Conformance + byte-identity gates green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LuZWKnWzYGVnESijL7uuky * no-mistakes(document): sync Java migration/drift docs to ADR-0015 validator removal --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
dmealing
added a commit
that referenced
this pull request
Jun 25, 2026
Lockstep npm release of the @metaobjectsdev/* packages (13 publish candidates). Ships the agent-friendly meta CLI (--format/TOON output, deploy-all agent-context references, #71) plus the agent-context scaffold fixes (#1, #73): the injected AGENTS.md/CLAUDE.md now name the database schema + migrations as metadata-derived and no longer guess the migration binding. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LuZWKnWzYGVnESijL7uuky
dmealing
added a commit
that referenced
this pull request
Jun 30, 2026
…ime (all 5 ports) (#126) * feat: ADR-0036 Wave 2 (TS) — field.timestamp instant-by-default + @localTime Flip field.timestamp to instant / timezone-aware BY DEFAULT (Postgres timestamptz) and add a boolean @localTime attr on field.timestamp as the rare naive / wall-clock opt-out (timestamp without time zone). Retire the @dbColumnType: timestamp_with_tz escape hatch entirely — the @dbColumnType legal set shrinks to { uuid, jsonb }. ADR-0036 Wave 2, TS reference port. This establishes the canonical golden + canonical Postgres schema + shared fixtures the other four ports byte-match. Metamodel (metadata): - db-constants.ts: add FIELD_ATTR_LOCAL_TIME; drop timestamp_with_tz from DB_COLUMN_TYPE_VALUES + DB_COLUMN_TYPE_LEGAL_SUBTYPES (now [uuid, jsonb]). - spec/metamodel/db.json: new boolean @localTime on field.timestamp; updated @dbColumnType description/allowedValues. Regenerated db-definition.embedded.ts. Schema / codegen: - migrate-ts expected-schema.ts: field.timestamp → timestamptz by default, @localTime:true → TIMESTAMP; removed the timestamp_with_tz override branch. - codegen-ts column-mapper.ts: field.timestamp → timestamp({ withTimezone: true }) by default, @localTime:true → withTimezone:false; removed timestamp_with_tz. Shared cross-port fixtures (other ports byte-match these): - persistence-conformance meta.fitness.json: dropped @dbColumnType on the two tz fields (recordedAt, tsTzVal — now bare = tz); marked the wire-naive fields (createdAt, observedAt, tsVal) @localTime:true to preserve their naive wire form. Canonical schema.postgres.sql + every roundtrip/query expected VALUE are byte-identical (regenerated, no diff). - flattened-kitchen-sink conformance: dropped timestamp_with_tz on updatedAt (now bare tz) across input + expected + expected-effective. - Regenerated registry-conformance golden (expected-registry.json + coverage-report.json) and metamodel-docs. ADR-0019: temporal clause updated — field.timestamp instant/tz-aware by default, @localTime the naive opt-out. Tests: field-definition/db-definition-completeness, attr-schema-validate, expected-schema, column-mapper, golden, sqlite-roundtrip (now builds expected with dialect:"sqlite" so SQLite's tz-blind introspection matches). All affected TS suites green; bun run --filter '*' build green. Live-PG integration tests are CI/Testcontainers-gated (not run locally). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GF9xLEQZaPus5Y6opk398n * docs(adr): ADR-0036 decision #1 — @localTime attribute (not field.localDateTime subtype) Corrected per ADR-0037: timezone-awareness is a same-kind orthogonal modifier of field.timestamp, so it's a boolean @localTime attribute (default false = instant), not a sibling subtype. Matches the Wave-2 implementation. * feat: ADR-0036 Wave 2 (Python) — field.timestamp instant-by-default + @localTime Port the TS reference (fa40542) to Python. field.timestamp is now an absolute instant / timezone-aware BY DEFAULT; the rare naive / wall-clock case opts out with the boolean @localTime attr. The @dbColumnType: timestamp_with_tz escape hatch is retired — the legal @dbColumnType set shrinks to { uuid, jsonb }. The canonical golden + shared persistence fixtures are TS-owned and unchanged; this makes the Python port byte-match them. Metamodel: - db_constants.py: add FIELD_ATTR_LOCAL_TIME; drop DB_COLUMN_TYPE_TIMESTAMP_TZ from VALID_DB_COLUMN_TYPES (now [uuid, jsonb]); tombstone the retired constant. - db_provider.py: register @localTime (boolean, NO allowed_values) on field.timestamp only; description sourced from the embedded spec_metamodel/db.json by apply_spec_descriptions (single-source). @dbColumnType allowed_values shrinks via VALID_DB_COLUMN_TYPES. - spec_metamodel/db.json: re-copied from the TS-updated repo-root spec/metamodel/db.json (byte-identity gate) — new boolean @localTime on field.timestamp + slimmed @dbColumnType allowedValues/description. Loader: - validation_passes.py: drop timestamp_with_tz from the @dbColumnType legal pairing map (now uuid/jsonb→field.string only). A timestamp_with_tz value now fails Rule 1 (unrecognized value) → still ERR_BAD_ATTR_VALUE, so the shared error-dbcolumntype-illegal-pairing fixture stays green (trigger shifts from illegal-pairing to unknown-value). Runtime (the core native-type flip): - object_manager.py write codec: default field.timestamp → tz-aware datetime (UTC tzinfo → driver binds timestamptz); @localTime:true → naive datetime (no tzinfo → driver binds plain timestamp). Inverts the prior default. The normalization boundary (tests/integration/normalization.py) already keys tz-aware↔Z / naive↔no-Z off tzinfo, so it is unchanged. Codegen/schema: Python is pure data-access (ADR-0015) — no DDL/schema emitter and the Pydantic type map is datetime.datetime for both modes, so no codegen column-type branch existed to flip. Tests: registry-conformance byte-match green; spec_metamodel embed byte-match green; full conformance (301) green incl. the shared illegal-pairing fixture; test_field_uuid_dbcolumntype updated for the retirement + a new @localTime-ok case. Native-type rule unit-verified (default aware / @localTime naive). The persistence op:roundtrip (tsVal naive / tsTzVal tz-aware) needs pg8000 + Testcontainers Postgres — CI-gated, not run locally. The 2 failing test_cli_staleness_nudge tests are pre-existing (unrelated agent-docs message wording), failing identically on the base branch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GF9xLEQZaPus5Y6opk398n * feat: ADR-0036 Wave 2 (JVM: Java + Kotlin) — field.timestamp instant-by-default + @localTime Flip field.timestamp to instant / timezone-aware BY DEFAULT (Postgres timestamptz, java.time.Instant) and add a boolean @localTime attr on field.timestamp as the rare naive / wall-clock opt-out (timestamp without time zone, java.time.LocalDateTime). Retire the @dbColumnType: timestamp_with_tz escape hatch entirely — the @dbColumnType legal set shrinks to { uuid, jsonb }. Java + Kotlin share metadata, so both ports are flipped in lockstep. Byte-matches the TS-established golden + shared fixtures (NOT modified here): expected-registry.json now carries @localTime (boolean, no allowedValues) on field.timestamp and @dbColumnType allowedValues=[uuid, jsonb]. Metadata (shared Java metadata module, used by both ports): - CoreDBMetaDataProvider: add LOCAL_TIME constant; drop DB_COLUMN_TYPE_TIMESTAMP_TZ from VALID_DB_COLUMN_TYPES (now [uuid, jsonb]); refresh Javadoc. - TimestampField: register @localTime (boolean) on field.timestamp (manifest description/allowedValues sourced from the shared spec/metamodel/db.json the TS commit already updated — read off the classpath by SpecMetamodelReader). - ValidationPhase.validateDbColumnType: drop the timestamp_with_tz legal pairing; the retired value now trips Rule 1 (unrecognized value) → still ERR_BAD_ATTR_VALUE. Native type binding flip (INVERTS the pre-Wave-2 timestampWithTzOptIn logic): - Java SpringTypeMapper: default field.timestamp → java.time.Instant; @localTime:true → java.time.LocalDateTime. Helper timestampWithTzOptIn → localTimeOptIn (reads @localTime). - Java OMDB JdbcCodecs.TimestampCodec: default binds/reads as a UTC instant (setObject TIMESTAMP_WITH_TIMEZONE); @localTime binds/reads naive (setTimestamp(UTC Calendar)). Helper isTimestampTz → isLocalTime. SimpleMappingHandlerDB / ColumnDef: drop the dead COLTYPE_TIMESTAMP_TZ hint (timestamp tz is no longer a @dbColumnType hint — the codec reads @localTime off the field directly). - Kotlin KotlinTypeMapper: default → Instant + Exposed instantWithTimeZone(...) / TIMESTAMP WITH TIME ZONE; @localTime:true → LocalDateTime + Exposed datetime(...). Helper timestampWithTzOptIn → localTimeOptIn; usesInstantWithTimeZone now true for the DEFAULT (drives the per-package instantWithTimeZone support file). Tests (JVM): - metadata: registry-conformance byte-matches the golden (Java + Kotlin both green); DbColumnTypeValidationTest rewired (retired-tz-value-rejected, @localTime-legal, illegal-pairing message on uuid-on-timestamp). - codegen-spring/codegen-kotlin: SpringTypeMapperTest / KotlinTypeMapperTest flipped; SpringDtoGeneratorTest + the Kotlin Exposed/projection/snapshot tests updated (regenerated single-entity-primitives snapshot: createdAt → Instant / instantWithTimeZone + new MetaInstantWithTimeZoneColumnType.kt support file). - OMDB codec round-trip (Derby): meta.codec.json tsVal + the raw-codec probe marked @localTime so the naive (Derby-compatible) path is exercised; the instant/tz default is Postgres-only and gated by persistence-conformance. - Testcontainers AllTypes op:roundtrip GREEN both ports (Java 24/24, Kotlin 24/24): tsVal (@localTime naive LocalDateTime) + tsTzVal (default tz Instant) write+read. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GF9xLEQZaPus5Y6opk398n * feat(csharp): ADR-0036 Wave 2 — field.timestamp default flip to instant/tz-aware + @localTime Port the C# implementation of ADR-0036 Wave 2 to match the TS-established golden registry + shared fixtures: - Register @localTime (boolean, no allowedValues) on field.timestamp only, via the db provider (DbConstants.FIELD_ATTR_LOCAL_TIME + DbSchema.LocalTimeSchema + DbProvider field.timestamp Extend). Description byte-matches the golden. - Retire @dbColumnType: timestamp_with_tz — drop it from VALID_DB_COLUMN_TYPES (legal set is now [uuid, jsonb]) and from the ValidateDbColumnType pairing; update the @dbColumnType descriptions in DbSchema.cs + SpecMetamodel/db.json (copied from spec/metamodel/db.json) to byte-match the golden. Exempt @dbColumnType from the generic allowedValues check (Check 3) so a retired-value still emits a single ERR_BAD_ATTR_VALUE (mirrors the TS Check-3 exemption), not a duplicate. - CLR-type binding (the core change): default field.timestamp → DateTimeOffset (an absolute instant); field.timestamp @localTime:true → DateTime (naive). CSharpNaming gains IsLocalTime + the field-aware ScalarForField; the entity generators emit the conditional CLR type. - EF/DDL column mapping: default field.timestamp → `timestamp with time zone` (timestamptz); @localTime:true → `timestamp without time zone`. Removed the timestamp_with_tz override branch. - Regenerated the committed integration fixtures (AllTypes/Asset/AppDbContext): tsTzVal + Asset.RecordedAt now DateTimeOffset/timestamptz; tsVal + observedAt stay DateTime/timestamp via @localTime. - Integration test-harness: DateTimeOffset write coercion (UTC) + EntityRow pass-through + Normalization UTC formatter (fix the double-Z) for the persistence roundtrip; the api-contract generated server provisions createdAt as timestamptz + interprets an offset-less instant body as UTC so the EF write succeeds on any CI host timezone. The registry-conformance byte-match passes; @localTime + the slimmed @dbColumnType allowedValues byte-match the golden. All C# suites green (Conformance 668, Codegen 251, Render 290, CLI 42, IntegrationTests 82 incl. the AllTypes roundtrip + api-contract generated lane against Testcontainers PG). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GF9xLEQZaPus5Y6opk398n * fix(jvm): ADR-0036 Wave 2 — api-contract harness + Kotlin controller emit Instant for createdAt `Author.createdAt` is a bare `field.timestamp`, which under ADR-0036 Wave 2 generates as `java.time.Instant` (instant/tz-aware default). Align the JVM api-contract test harnesses (Java + Kotlin) — and close one genuine Wave-2 generator gap — so the generated-controller lane compiles and round-trips. createdAt wire form is now an absolute UTC instant (`yyyy-MM-ddTHH:mm:ssZ`). Offset-less corpus seed/scenario strings are interpreted as UTC (append `Z`), mirroring the C# lane. Java: - generated/GeneratedAuthorControllerHarness: DTO ctor reflection signature `LocalDateTime.class` → `Instant.class`; build createdAt via `Instant.parse` (UTC-interpreting); register a UTC Instant Jackson deserializer so offset-less request bodies deserialize into the generated DTO's `Instant` field. - generated/InMemoryAuthorRepositorySource: `AuthorDto.createdAt` is `Instant`; filter comparison parses operands as UTC instants; refreshed stale comment. - AuthorApiServer (reference lane): `createdAt` column → `timestamp with time zone`; read/write/filter via UTC `OffsetDateTime`/`Instant`; emit `...Z` wire. Kotlin: - generated/GeneratedAuthorControllerHarness: register a UTC Instant Jackson deserializer for the generated `Author.createdAt: Instant`. - AuthorApiServer (reference lane): `AuthorTable.createdAt` → Exposed `timestamp()` (Instant); parse/format/filter via UTC instants; emit `...Z`. - KotlinSpringControllerGenerator (generator gap): a default `field.timestamp` column is `Column<Instant>`, but the controller imported only `LocalDateTime` and the timestamp filter-coercer produced a `LocalDateTime` — a missing import + a ClassCast at the `p.value as Instant` dispatch cast. Now import `java.time.Instant` when an Instant-typed timestamp column is in the filter surface, and emit an Instant-producing (UTC-interpreting) coercer for it. Instant-free entities stay byte-identical (snapshot golden unchanged). Verified against Testcontainers Postgres (Docker available): all Java + Kotlin api-contract integration tests green (53/53 each, incl. generated/TPH/jsonb/m2m lanes); codegen-kotlin 255/255 (snapshot golden byte-identical). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GF9xLEQZaPus5Y6opk398n --------- Co-authored-by: Doug Mealing <noreply@anthropic.com>
dmealing
added a commit
that referenced
this pull request
Jul 14, 2026
…#3, #11 FR-036 made the router validate POST/PATCH against the read/entity model, which was wrong for a create/patch WIRE body. Introduce dedicated wire-shaped validation models (entity_model.py) and bind them in the router: - New <Entity>Create model for POST: excludes the auto-gen PK (identity.primary @generation increment|uuid) [#2] and @readonly fields; makes a @required field that is SERVER-FILLED — ANY @default (literal OR sql-expr now()/gen_random_uuid()) or @autoset — OPTIONAL [#1, the regression: a normal POST omitting createdAt @required @default:"now()" no longer 400s]; keys by the WIRE name field.name [#3] (was @column, so a column-renamed entity's constraints checked the wrong key — bypass or spurious 400); carries the field constraints. Mirrors the TS InsertSchema (fieldWillBeOptional / autoGenPkFieldNames). - <Entity>Patch now excludes the PK [#11] and keys by the wire name [#3]. - The read model <Entity> (@column-named) is unchanged — no consumer break. FR-035 tristate + the repo create/update dict seam are unchanged. Router: POST validates <Entity>Create(**dto) / per-subtype <Sub>Create(**dto); PATCH validates <Entity>Patch(**dto). Gated by 4 new unit tests (server-filled optional, auto-gen PK excluded, wire-name validation, patch excludes PK). Regenerated 3 goldens. Verified: entity-model + validation-conformance + generated api-contract lanes 54/0 (the only broader-suite failures are the 2 pre-existing, unrelated cli-staleness-nudge tests). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NGQ7oSuNcjhsMHWwZzhBwr
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.
What
meta initscaffoldsmetaobjects.config.tswithoutDir: "src/generated"instead of"./src/db".Why
The old default put generated files in
src/db/right next to thesrc/db.tsthe user must hand-create for their DB instance — same path stem, two different things. Worse, the generated routes'import { db } from "../db"(fromdbImport: "../db") can resolve to the generated barrelsrc/db/index.tsinstead of the user'ssrc/db.ts.docs/ports/typescript.mdalready usessrc/generated; this aligns the scaffold with the docs so generated code and the user's db module stay clearly separated.Found while building the five-minute quickstart demo end-to-end against published
0.9.0(the demo otherwise works great —meta init → gen → Fastify → curlreturns 201/200 JSON + 400 Zod validation).Changes
cli/src/commands/init.ts— scaffold templateoutDir→src/generated.cli/test/init.test.ts— new test asserting the scaffoldedoutDir(and that./src/dbis not used).Test
bun test packages/cli→ 276 pass, 0 fail.🤖 Generated with Claude Code