@metaobjectsdev/cli 0.23.0 → 0.23.1-rc.1

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.
@@ -21,25 +21,94 @@
21
21
  // says NOTHING rather than reporting every name missing. Absence of evidence is
22
22
  // not evidence of absence, and a monorepo whose tests live outside `--cwd` must
23
23
  // not be told its requirements are unverified.
24
+ //
25
+ // WHAT COUNTS AS A TEST FILE IS THE PROJECT'S CALL, NOT OURS. The built-in patterns
26
+ // below are a convenience for the ecosystems this repo ports to, and they are a GUESS
27
+ // about someone else's repository. They were wrong on a mainstream case from the day
28
+ // they shipped: Maven Failsafe names integration tests `FooIT.java`, which matched
29
+ // nothing, so a JVM project naming a real integration test got a confident
30
+ // "the claim was never true".
31
+ //
32
+ // Two consequences, both deliberate:
33
+ // - `testFiles` (config: `verify.testFiles`) lets a project declare its own
34
+ // conventions, unioned with the built-ins. Nothing here can be authoritative
35
+ // about a convention we have never seen.
36
+ // - the fail-open above is extended from "no test files at all" to the case that
37
+ // actually bites: a name we cannot find in the corpus, which IS present in a file
38
+ // the corpus definition did not classify. That is our ignorance, not a broken
39
+ // claim, and it is reported as such (WARN_REQUIREMENT_TEST_UNCLASSIFIED) rather
40
+ // than as an error. The error is reserved for a name that appears NOWHERE.
24
41
  import { readdirSync, readFileSync, statSync } from "node:fs";
25
42
  import { join, relative, sep } from "node:path";
26
43
  import { TYPE_REQUIREMENT, } from "@metaobjectsdev/metadata";
27
44
  export const ERR_REQUIREMENT_TEST_MISSING = "ERR_REQUIREMENT_TEST_MISSING";
28
45
  export const WARN_REQUIREMENT_TEST_SKIPPED = "WARN_REQUIREMENT_TEST_SKIPPED";
46
+ export const WARN_REQUIREMENT_TEST_COMMENT_ONLY = "WARN_REQUIREMENT_TEST_COMMENT_ONLY";
47
+ export const WARN_REQUIREMENT_TEST_UNCLASSIFIED = "WARN_REQUIREMENT_TEST_UNCLASSIFIED";
29
48
  const IGNORE_SEGMENTS = new Set([
30
49
  "node_modules", ".git", "dist", "build", "out", ".next", "coverage",
31
50
  ".metaobjects", "generated", "target", "bin", "obj", "__pycache__", ".venv", "venv",
32
51
  ]);
33
- /** Test files across the five ecosystems this project ports to. */
52
+ /**
53
+ * Test files across the five ecosystems this project ports to — a CONVENIENCE DEFAULT,
54
+ * never an authority. A project whose conventions differ declares them via
55
+ * `verify.testFiles`; see the module header.
56
+ *
57
+ * The `IT` entries are Maven Failsafe's own defaults (`IT*`, `*IT`, `*ITCase`), which
58
+ * is how every JVM project in the wild names an integration test. Their absence is the
59
+ * bug that motivated making this list extensible in the first place.
60
+ */
34
61
  const TEST_FILE = new RegExp([
35
62
  "\\.(?:test|spec)\\.[cm]?[jt]sx?$", // bun / jest / vitest / mocha
36
63
  "(?:^|[./_-])[Tt]est[^/]*\\.java$", // JUnit — TestFoo.java
37
64
  "[A-Za-z0-9]Test(?:s)?\\.java$", // JUnit — FooTest.java / FooTests.java
65
+ "[A-Za-z0-9]IT(?:Case)?\\.java$", // Failsafe — FooIT.java / FooITCase.java
66
+ "^IT[A-Za-z0-9][^/]*\\.java$", // Failsafe — ITFoo.java
38
67
  "[A-Za-z0-9]Tests?\\.cs$", // xUnit / NUnit
39
68
  "^test_[^/]*\\.py$", // pytest
40
69
  "[^/]*_test\\.py$", // pytest, trailing convention
41
70
  "[A-Za-z0-9]Test(?:s)?\\.kt$", // Kotlin
71
+ "[A-Za-z0-9]IT(?:Case)?\\.kt$", // Failsafe under Kotlin — FooIT.kt
42
72
  ].join("|"));
73
+ /** Files worth searching when a name is missing from the corpus, to tell "nowhere" from
74
+ * "somewhere I did not classify". Source-ish only; a match in a lockfile proves nothing. */
75
+ const SOURCE_FILE = /\.(?:[cm]?[jt]sx?|java|kt|kts|cs|py|rb|go|rs|scala|groovy|feature)$/;
76
+ /**
77
+ * A glob as permissive as the ones adopters actually write (`**​/*IT.kt`, `*.feature`),
78
+ * anchored at the project root and matched against forward-slash relative paths.
79
+ *
80
+ * Deliberately small: `**` spans separators, `*` does not, `?` is one non-separator
81
+ * character. Anything richer belongs to a glob library, and pulling one in for a config
82
+ * knob this narrow is not worth the dependency.
83
+ */
84
+ function globToRegExp(glob) {
85
+ let out = "";
86
+ for (let i = 0; i < glob.length; i++) {
87
+ const c = glob[i];
88
+ if (c === "*") {
89
+ if (glob[i + 1] === "*") {
90
+ // `**/` may match zero segments, so `**/*.feature` matches a root-level file.
91
+ if (glob[i + 2] === "/") {
92
+ out += "(?:.*/)?";
93
+ i += 2;
94
+ }
95
+ else {
96
+ out += ".*";
97
+ i += 1;
98
+ }
99
+ }
100
+ else
101
+ out += "[^/]*";
102
+ continue;
103
+ }
104
+ if (c === "?") {
105
+ out += "[^/]";
106
+ continue;
107
+ }
108
+ out += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
109
+ }
110
+ return new RegExp(`^${out}$`);
111
+ }
43
112
  /** Markers that a test exists but is disabled, across the same ecosystems. */
44
113
  const SKIP_MARKER = new RegExp([
45
114
  "\\b(?:it|test|describe)\\.(?:skip|todo)\\b", // jest/vitest/bun
@@ -50,7 +119,7 @@ const SKIP_MARKER = new RegExp([
50
119
  "\\[Ignore[\\](]", // MSTest / NUnit
51
120
  "\\bSkip\\s*=", // xUnit [Fact(Skip = "...")]
52
121
  ].join("|"));
53
- function walk(dir, root, acc, depth = 0) {
122
+ function walk(dir, root, acc, isTestFile, depth = 0) {
54
123
  if (depth > 12)
55
124
  return; // pathological trees; the scan is advisory, not exhaustive
56
125
  let entries;
@@ -64,16 +133,22 @@ function walk(dir, root, acc, depth = 0) {
64
133
  if (e.isDirectory()) {
65
134
  if (IGNORE_SEGMENTS.has(e.name) || e.name.startsWith("."))
66
135
  continue;
67
- walk(join(dir, e.name), root, acc, depth + 1);
136
+ walk(join(dir, e.name), root, acc, isTestFile, depth + 1);
68
137
  continue;
69
138
  }
70
- if (!e.isFile() || !TEST_FILE.test(e.name))
139
+ if (!e.isFile())
71
140
  continue;
72
141
  const abs = join(dir, e.name);
142
+ const rel = relative(root, abs).split(sep).join("/");
143
+ if (!isTestFile(rel, e.name)) {
144
+ if (SOURCE_FILE.test(e.name) && acc.unclassified.length < 20_000)
145
+ acc.unclassified.push(rel);
146
+ continue;
147
+ }
73
148
  try {
74
149
  if (statSync(abs).size > 512 * 1024)
75
150
  continue;
76
- acc.byFile.set(relative(root, abs).split(sep).join("/"), readFileSync(abs, "utf8").split("\n"));
151
+ acc.byFile.set(rel, readFileSync(abs, "utf8").split("\n"));
77
152
  acc.files++;
78
153
  }
79
154
  catch {
@@ -81,6 +156,45 @@ function walk(dir, root, acc, depth = 0) {
81
156
  }
82
157
  }
83
158
  }
159
+ /** Does this path or body look like a test the corpus definition simply did not match?
160
+ * Deliberately narrow: living under a test directory, or containing an assertion/test
161
+ * declaration. Without this, a name occurring anywhere in PRODUCTION source downgrades a
162
+ * genuinely broken claim to a warning — the exact failure the comment-only check exists
163
+ * to catch. */
164
+ const TESTISH_PATH = /(^|\/)(tests?|spec|__tests__|src\/test)(\/|$)/i;
165
+ const TESTISH_BODY = /\b(assert\w*|expect|should|@Test|def test_|it\(|test\(|describe\()/;
166
+ /** Test by LOCATION or by CONTENT — either is enough. Production source with a matching
167
+ * name satisfies neither, which is the case that must stay a hard error. */
168
+ function looksLikeTest(rel, lines) {
169
+ return TESTISH_PATH.test(rel) || lines.some((l) => TESTISH_BODY.test(l));
170
+ }
171
+ /**
172
+ * Where does this name live, if not in the test corpus?
173
+ *
174
+ * Only ever called on the miss path, so the cost is paid per BROKEN claim rather than
175
+ * per run. Returns the first unclassified source file containing the name, which is
176
+ * enough to tell the author which pattern they are missing.
177
+ */
178
+ function findOutsideCorpus(name, root, files) {
179
+ const rx = wordRx(name);
180
+ for (const rel of files) {
181
+ try {
182
+ const abs = join(root, ...rel.split("/"));
183
+ if (statSync(abs).size > 512 * 1024)
184
+ continue;
185
+ const lines = readFileSync(abs, "utf8").split("\n");
186
+ for (let i = 0; i < lines.length; i++) {
187
+ const line = lines[i] ?? "";
188
+ if (rx.test(line) && !isCommentLine(line, rel) && looksLikeTest(rel, lines))
189
+ return rel;
190
+ }
191
+ }
192
+ catch {
193
+ /* unreadable file is not a finding */
194
+ }
195
+ }
196
+ return undefined;
197
+ }
84
198
  /** Every `requirement.*` node in the tree, at any nesting depth. */
85
199
  function collect(root) {
86
200
  const out = [];
@@ -102,6 +216,23 @@ function collect(root) {
102
216
  * emit the confident false error this scan is built to avoid. Camel-case boundaries
103
217
  * stay strict, which is what actually prevents a short name matching a longer one.
104
218
  */
219
+ /**
220
+ * Is this whole line a comment?
221
+ *
222
+ * WHOLE-LINE ONLY, deliberately. Stripping from the first `//` would truncate a code
223
+ * line containing one inside a string — a test titled with a URL is the obvious case —
224
+ * and turn a real match into a confident false error, which is the failure this scan is
225
+ * built to avoid. A trailing comment after code therefore still counts as code; that
226
+ * under-flags, which is the repo's standing bias for drift checks.
227
+ *
228
+ * `#` is Python-only: in TypeScript it opens a private field, not a comment.
229
+ */
230
+ function isCommentLine(line, file) {
231
+ const t = line.trimStart();
232
+ if (t.startsWith("//") || t.startsWith("/*") || t.startsWith("*"))
233
+ return true;
234
+ return file.endsWith(".py") && t.startsWith("#");
235
+ }
105
236
  function wordRx(name) {
106
237
  return new RegExp(`(?:^|[^A-Za-z0-9])${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![A-Za-z0-9])`);
107
238
  }
@@ -112,12 +243,17 @@ function wordRx(name) {
112
243
  * and silent on `abandoned`/`superseded`, because a retired requirement naming a
113
244
  * deleted test is the entry doing its job, not drift.
114
245
  */
115
- export function checkVerifiedBy(root, cwd) {
246
+ export function checkVerifiedBy(root, cwd, testFiles) {
116
247
  const reqs = collect(root).filter((r) => r.verifiedBy().length > 0);
117
248
  if (reqs.length === 0)
118
249
  return []; // opt-in by declaration
119
- const corpus = { files: 0, byFile: new Map() };
120
- walk(cwd, cwd, corpus);
250
+ // Project-declared conventions ADD to the built-ins: the failure being fixed is
251
+ // under-matching, and a project that names an extra convention is telling us
252
+ // something we did not know — not asking us to forget what we did.
253
+ const declared = (testFiles ?? []).map(globToRegExp);
254
+ const isTestFile = (rel, base) => TEST_FILE.test(base) || declared.some((rx) => rx.test(rel));
255
+ const corpus = { files: 0, byFile: new Map(), unclassified: [] };
256
+ walk(cwd, cwd, corpus, isTestFile);
121
257
  if (corpus.files === 0)
122
258
  return []; // fail open: nothing to judge against
123
259
  const out = [];
@@ -126,10 +262,20 @@ export function checkVerifiedBy(root, cwd) {
126
262
  const rx = wordRx(test);
127
263
  let foundIn;
128
264
  let skippedAt;
265
+ // #293-adjacent (the `verifiedBy` audit): a name that occurs ONLY in comments
266
+ // satisfied this scan, because the match is line-agnostic. That is how a claim
267
+ // came to name `mountCrudRoutes`, whose single occurrence in an entire corpus was
268
+ // inside a `// via mountCrudRoutes(...)` note. Tracked separately so the name can
269
+ // still be reported as found (it is) while saying what it was found in.
270
+ let commentOnlyAt;
129
271
  for (const [file, lines] of corpus.byFile) {
130
272
  for (let i = 0; i < lines.length; i++) {
131
273
  if (!rx.test(lines[i] ?? ""))
132
274
  continue;
275
+ if (isCommentLine(lines[i] ?? "", file)) {
276
+ commentOnlyAt ??= `${file}:${i + 1}`;
277
+ continue;
278
+ }
133
279
  foundIn ??= file;
134
280
  // a decorator/annotation sits above the declaration it disables
135
281
  const window = lines.slice(Math.max(0, i - 3), i + 1).join("\n");
@@ -139,16 +285,46 @@ export function checkVerifiedBy(root, cwd) {
139
285
  if (foundIn !== undefined && skippedAt !== undefined)
140
286
  break;
141
287
  }
288
+ // Found, but only ever in prose. Not an error — the scan's job is to catch a name
289
+ // that has gone missing, and this one has not — but a comment proves nothing, so
290
+ // the claim is reported rather than silently accepted.
291
+ if (foundIn === undefined && commentOnlyAt !== undefined) {
292
+ out.push({
293
+ severity: "warn",
294
+ code: WARN_REQUIREMENT_TEST_COMMENT_ONLY,
295
+ name: req.name,
296
+ message: `'verifiedBy' names '${test}', which appears only in a comment (${commentOnlyAt}) ` +
297
+ `and in no test declaration. A comment proves nothing — name the test that asserts it.`,
298
+ });
299
+ continue;
300
+ }
142
301
  if (foundIn === undefined) {
143
302
  if (req.requiresLiveNodes()) {
144
- out.push({
145
- severity: "error",
146
- code: ERR_REQUIREMENT_TEST_MISSING,
147
- name: req.name,
148
- message: `'verifiedBy' names '${test}', which appears in none of the ` +
149
- `${corpus.files} test file(s) found under this project. Either the test was ` +
150
- `renamed or removed, or the claim was never true.`,
151
- });
303
+ // Before calling a claim broken, rule out the likelier explanation: that this
304
+ // project names its tests in a way the corpus definition does not know. A name
305
+ // sitting in an unclassified source file is OUR ignorance, and saying "the claim
306
+ // was never true" about it is the tool being confidently wrong.
307
+ const elsewhere = findOutsideCorpus(test, cwd, corpus.unclassified);
308
+ out.push(elsewhere !== undefined
309
+ ? {
310
+ severity: "warn",
311
+ code: WARN_REQUIREMENT_TEST_UNCLASSIFIED,
312
+ name: req.name,
313
+ message: `'verifiedBy' names '${test}', which is not in any of the ${corpus.files} ` +
314
+ `file(s) recognised as tests, but DOES appear in ${elsewhere}. That file is ` +
315
+ `probably a test this scan does not know how to recognise — declare the ` +
316
+ `convention in metaobjects.config.ts (verify.testFiles, e.g. ` +
317
+ `["**/*IT.kt"]) and this becomes a real check instead of a guess.`,
318
+ }
319
+ : {
320
+ severity: "error",
321
+ code: ERR_REQUIREMENT_TEST_MISSING,
322
+ name: req.name,
323
+ message: `'verifiedBy' names '${test}', which appears in none of the ` +
324
+ `${corpus.files} test file(s) found under this project, and in no other ` +
325
+ `source file either. Either the test was renamed or removed, or the ` +
326
+ `claim was never true.`,
327
+ });
152
328
  }
153
329
  continue;
154
330
  }
@@ -1 +1 @@
1
- {"version":3,"file":"verified-by-scan.js","sourceRoot":"","sources":["../../../src/lib/verified-by-scan.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"verified-by-scan.js","sourceRoot":"","sources":["../../../src/lib/verified-by-scan.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metaobjectsdev/cli",
3
- "version": "0.23.0",
3
+ "version": "0.23.1-rc.1",
4
4
  "description": "CLI for MetaObjects: scaffold, codegen, migrate, and drift-detection commands.",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
@@ -49,15 +49,15 @@
49
49
  ],
50
50
  "dependencies": {
51
51
  "@libsql/kysely-libsql": "^0.4.0",
52
- "@metaobjectsdev/codegen-ts": "0.23.0",
53
- "@metaobjectsdev/codegen-ts-react": "0.23.0",
54
- "@metaobjectsdev/codegen-ts-tanstack": "0.23.0",
55
- "@metaobjectsdev/docs-site": "0.23.0",
56
- "@metaobjectsdev/metadata": "0.23.0",
57
- "@metaobjectsdev/migrate-ts": "0.23.0",
58
- "@metaobjectsdev/render": "0.23.0",
59
- "@metaobjectsdev/runtime-ts": "0.23.0",
60
- "@metaobjectsdev/sdk": "0.23.0",
52
+ "@metaobjectsdev/codegen-ts": "0.23.1-rc.1",
53
+ "@metaobjectsdev/codegen-ts-react": "0.23.1-rc.1",
54
+ "@metaobjectsdev/codegen-ts-tanstack": "0.23.1-rc.1",
55
+ "@metaobjectsdev/docs-site": "0.23.1-rc.1",
56
+ "@metaobjectsdev/metadata": "0.23.1-rc.1",
57
+ "@metaobjectsdev/migrate-ts": "0.23.1-rc.1",
58
+ "@metaobjectsdev/render": "0.23.1-rc.1",
59
+ "@metaobjectsdev/runtime-ts": "0.23.1-rc.1",
60
+ "@metaobjectsdev/sdk": "0.23.1-rc.1",
61
61
  "@toon-format/toon": "^2.3.0",
62
62
  "jiti": "^2.4.0"
63
63
  },
@@ -8,7 +8,7 @@
8
8
  // the text ships. Required-slot misses are warnings (don't fail the build).
9
9
 
10
10
  import { join, resolve as resolvePath } from "node:path";
11
- import { parseVerifyArgs } from "../lib/args.js";
11
+ import { parseVerifyArgs, type MigrateFlags } from "../lib/args.js";
12
12
  import { log } from "../lib/log.js";
13
13
  import { warnIfAgentContextStale } from "../lib/agent-context-staleness.js";
14
14
  import { scanSourceForAntiPatterns } from "../lib/anti-patterns.js";
@@ -18,7 +18,7 @@ import { loadMetaobjectsConfig } from "../lib/load-metaobjects-config.js";
18
18
  import { computeCodegenDrift } from "../lib/codegen-drift.js";
19
19
  import { checkRequirements, summariseRequirements } from "../lib/requirement-check.js";
20
20
  import { checkVerifiedBy } from "../lib/verified-by-scan.js";
21
- import { resolveD1Config } from "../lib/config.js";
21
+ import { resolveD1Config, resolveMigrateConfig } from "../lib/config.js";
22
22
  import {
23
23
  buildWranglerExecuteArgs,
24
24
  defaultWranglerRunner,
@@ -33,6 +33,11 @@ import {
33
33
  computeDrift,
34
34
  computeDriftFromActual,
35
35
  collectUnmanagedNames,
36
+ introspect,
37
+ diff,
38
+ readSnapshot,
39
+ snapshotPath,
40
+ type SchemaSnapshot,
36
41
  introspectD1,
37
42
  findWranglerConfig,
38
43
  parseWranglerConfig,
@@ -67,6 +72,19 @@ const DEFAULT_PROMPTS_DIR = "prompts";
67
72
  // exported by the package, so named here to avoid an inline literal at the use site.
68
73
  const ERR_UNKNOWN_ATTR = "ERR_UNKNOWN_ATTR";
69
74
 
75
+ /**
76
+ * A no-flags MigrateFlags, so `resolveMigrateConfig` yields exactly what `meta migrate`
77
+ * would use with nothing passed on the command line — config value, else default. verify
78
+ * consumes only `outDir` from the result (#292); the other fields exist to satisfy the
79
+ * shared shape, and reading any of them here would be reaching into migrate's decisions.
80
+ */
81
+ const EMPTY_MIGRATE_FLAGS = {
82
+ db: undefined, dialect: undefined, format: undefined, outDir: undefined, slug: undefined,
83
+ allow: [], onAmbiguous: undefined, dryRun: false, d1Binding: undefined, remote: false,
84
+ apply: false, rollback: undefined, yes: false, fromDb: false, baseline: false,
85
+ applyPending: false,
86
+ } as const satisfies MigrateFlags;
87
+
70
88
  /** Coerce a string-array attr (array, or a single string) into a string[]. */
71
89
  function attrAsStringArray(attr: unknown): string[] {
72
90
  if (Array.isArray(attr)) return attr.filter((s): s is string => typeof s === "string");
@@ -177,7 +195,13 @@ export async function verifyCommand(
177
195
  function runRequirementVerify(): number {
178
196
  // `@verifiedBy` resolution needs the project on disk, so it is a separate
179
197
  // scan; its diagnostics carry the same severities and share this reporter.
180
- const diags = [...checkRequirements(root), ...checkVerifiedBy(root, cwd)];
198
+ // `verify.testFiles` lets a project name its own test-file conventions. What counts
199
+ // as a test is project-specific, and the built-in patterns are a convenience, not an
200
+ // authority — see the verified-by-scan header.
201
+ const diags = [
202
+ ...checkRequirements(root),
203
+ ...checkVerifiedBy(root, cwd, forgeConfig?.verify?.testFiles),
204
+ ];
181
205
 
182
206
  // Printed on EVERY run, clean or not — a gate that says nothing when it
183
207
  // passes cannot be told apart from a gate that checked nothing, and the
@@ -398,14 +422,24 @@ export async function verifyCommand(
398
422
  const viewStrategy = forgeConfig?.columnNamingStrategy ?? "snake_case";
399
423
  const expectedViews = buildProjectionViews(root, { dialect: kysely.dialect, columnNamingStrategy: viewStrategy });
400
424
  let driftResult;
425
+ let actual: SchemaSnapshot;
401
426
  try {
402
- driftResult = await computeDrift(kysely.db, kysely.dialect, root, { allow, views: expectedViews });
427
+ // Introspect once and keep the result: #292's snapshot check needs the same
428
+ // `actual` this drift comparison uses, and re-introspecting for it would both
429
+ // cost a second round trip and open a window where the two could disagree.
430
+ actual = await introspect(kysely.db, kysely.dialect);
431
+ driftResult = await computeDriftFromActual(actual, kysely.dialect, root, { allow, views: expectedViews });
403
432
  } catch (err) {
404
433
  log.error(`verify: failed to introspect ${kysely.displayUrl}: ${(err as Error).message}`);
405
434
  return 1;
406
435
  }
407
436
 
408
- return reportSchemaDrift(driftResult, ledgerDrift, kysely.displayUrl);
437
+ const snapshotDrift =
438
+ driftResult.changes.length === 0
439
+ ? await checkCommittedSnapshot(actual, kysely.dialect, kysely.displayUrl)
440
+ : [];
441
+
442
+ return reportSchemaDrift(driftResult, [...ledgerDrift, ...snapshotDrift], kysely.displayUrl);
409
443
  } finally {
410
444
  try {
411
445
  await kysely.close();
@@ -489,6 +523,69 @@ export async function verifyCommand(
489
523
  // Shared drift-reporting + exit-code logic for BOTH schema-drift paths (sqlite/
490
524
  // postgres via computeDrift, D1 via computeDriftFromActual) — #225 requires the
491
525
  // D1 path to feed the SAME reporting, not a forked copy.
526
+ // #292 — the committed reference snapshot is itself checked, and this is the only
527
+ // place in the toolchain that can do it.
528
+ //
529
+ // `meta migrate` diffs metadata against `.metaobjects/migrations/.schema.<dialect>.json`
530
+ // by default (`--from-db` is the documented opt-out), so that file decides what DDL the
531
+ // next migration contains. Nothing verified it. A snapshot gone stale — an interrupted
532
+ // migrate, a rollback, a bad merge resolution — passed `verify` clean and then made the
533
+ // next `migrate --slug` emit DDL that fails at apply (`column ... already exists`), which
534
+ // surfaces as a migration that cannot be applied and a history that cannot be reproduced.
535
+ //
536
+ // THE GATE IS CONDITIONED ON metadata==DB, deliberately, and that is what makes it
537
+ // false-positive-free. The snapshot means "the schema the COMMITTED MIGRATIONS land you
538
+ // in" and it advances at GENERATION time, so between `migrate --slug` and applying that
539
+ // migration the snapshot legitimately leads the database. In exactly that window the
540
+ // metadata↔DB drift is non-empty and this check stays silent; when metadata and the DB
541
+ // agree there is no pending work left to explain a difference, so a snapshot that
542
+ // disagrees is stale, full stop.
543
+ //
544
+ // Keying on the drift result rather than on the migration ledger is the load-bearing
545
+ // choice. A ledger-based "are there unapplied migrations?" test looks equivalent and is
546
+ // not: a project that applies its migrations out of band — psql, a CI step, another
547
+ // tool — has no ledger rows at all, so every migration reads as pending and the gate
548
+ // would silently never fire. That is the same class of defect as the one being fixed.
549
+ //
550
+ // Fails OPEN when there is no snapshot on disk (a project that has never generated one
551
+ // offline is not in an error state) and when the file cannot be read or parsed (that is
552
+ // migrate's error to raise, with its own message, not a drift verdict).
553
+ async function checkCommittedSnapshot(
554
+ actual: SchemaSnapshot,
555
+ dialect: Dialect,
556
+ displayUrl: string,
557
+ ): Promise<string[]> {
558
+ if (dialect === "d1") return []; // d1 keeps migrations Wrangler-native; no offline snapshot
559
+ // Resolve the migrations dir through migrate's OWN precedence (flag > config >
560
+ // default) rather than re-deriving it, so verify can never look somewhere migrate
561
+ // does not write. Only `outDir` is consumed; the rest of the resolved config is
562
+ // migrate's business.
563
+ const migrateConfig = await resolveMigrateConfig(EMPTY_MIGRATE_FLAGS, cwd);
564
+ const dir = resolvePath(cwd, migrateConfig.outDir);
565
+ let snapshot: SchemaSnapshot | null;
566
+ try {
567
+ snapshot = await readSnapshot(snapshotPath(dir, dialect));
568
+ } catch {
569
+ return [];
570
+ }
571
+ if (snapshot === null) return [];
572
+
573
+ const result = await diff({
574
+ expected: snapshot,
575
+ actual,
576
+ allow: {},
577
+ unmanagedNames: collectUnmanagedNames(root),
578
+ });
579
+ if (result.changes.length === 0) return [];
580
+
581
+ return [
582
+ `the committed schema snapshot disagrees with ${displayUrl} ` +
583
+ `(${result.changes.length} difference(s)) — the next 'meta migrate' would emit DDL from it ` +
584
+ `and fail at apply. Re-derive it with 'meta migrate --from-db --db <url> --dialect ${dialect}'.`,
585
+ ...summarizeDrift(result.changes),
586
+ ];
587
+ }
588
+
492
589
  function reportSchemaDrift(driftResult: DiffResult, ledgerDrift: string[], displayUrl: string): number {
493
590
  // #208 §8 — make declared-external objects visible: they are excluded from the
494
591
  // drift comparison (computeDrift/computeDriftFromActual thread them out), so
@@ -506,8 +603,13 @@ export async function verifyCommand(
506
603
  return 0;
507
604
  }
508
605
 
509
- log.error(`meta verify schema drift vs ${displayUrl} (${changes.length} change(s)):`);
510
- for (const line of summarizeDrift(changes)) log.error(` ${line}`);
606
+ // The header is conditional: #292's snapshot findings arrive through `ledgerDrift`
607
+ // with the metadata↔DB comparison clean, and announcing "schema drift (0 change(s))"
608
+ // above them would contradict the very check that just passed.
609
+ if (changes.length > 0) {
610
+ log.error(`meta verify — schema drift vs ${displayUrl} (${changes.length} change(s)):`);
611
+ for (const line of summarizeDrift(changes)) log.error(` ${line}`);
612
+ }
511
613
  for (const line of ledgerDrift) log.error(` ${line}`);
512
614
  return 1;
513
615
  }
@@ -148,6 +148,47 @@ function subtreeClaimsAnything(req: MetaRequirement): boolean {
148
148
  return false;
149
149
  }
150
150
 
151
+ /**
152
+ * Resolve the owner segment of an `@implementedBy` reference to the node it names.
153
+ *
154
+ * OBJECTS FIRST, through the loader's own resolver, so package-local binding stays the
155
+ * ADR-0042 contract and never a parallel name scan (#228).
156
+ *
157
+ * Then ROOT-LEVEL NON-OBJECT nodes — `template.prompt` and its siblings today. The
158
+ * attribute is documented as naming "the model nodes realising this requirement", and a
159
+ * declared prompt is one: it is the durable artifact a capability like "the game master
160
+ * is told what the party can see" actually lives in. Resolving only objects meant the
161
+ * prompt estate — the thing whose retirement is hardest to see in a model, since a
162
+ * removed prompt leaves no table behind — was the one part of a model that could not
163
+ * carry a status. So L4 means "a declared top-level model node", not "an object".
164
+ *
165
+ * Requirements themselves are excluded: hierarchy is nesting, and a requirement claiming
166
+ * a requirement would be a second, contradictory parent mechanism.
167
+ */
168
+ function resolveClaimTarget(root: MetaData, owner: string, referrerPkg: string): MetaData | undefined {
169
+ const { node } = resolveObjectRef(root, owner, referrerPkg);
170
+ if (node !== undefined) return node;
171
+
172
+ const candidates = root
173
+ .children()
174
+ .filter((c) => c.type !== TYPE_OBJECT && c.type !== TYPE_REQUIREMENT);
175
+
176
+ // A fully-qualified reference binds exactly, like every other FQN in the model.
177
+ if (owner.includes(PACKAGE_SEPARATOR)) {
178
+ return candidates.find((c) => c.resolutionKey() === owner);
179
+ }
180
+ // A bare reference prefers the referrer's own package, then a root-level node of that
181
+ // bare name. An ambiguous bare name binds NOTHING — same fail-closed rule objects use,
182
+ // because silently picking one of two same-named nodes is how a claim ends up pointing
183
+ // at the wrong thing without anyone noticing.
184
+ const local = referrerPkg === "" ? [] : candidates.filter((c) => c.resolutionKey() === `${referrerPkg}${PACKAGE_SEPARATOR}${owner}`);
185
+ if (local.length === 1) return local[0];
186
+ // Root-level (unpackaged) only, matching resolveObjectRef's own bare fallback. A bare
187
+ // ref must not reach into an arbitrary package just because the name is unique there.
188
+ const bare = candidates.filter((c) => c.name === owner && c.resolutionKey() === owner);
189
+ return bare.length === 1 ? bare[0] : undefined;
190
+ }
191
+
151
192
  /** Walk dotted member segments by CHILD NAME from an object node. */
152
193
  function resolveMember(obj: MetaData, path: string[]): MetaData | undefined {
153
194
  let cur: MetaData | undefined = obj;
@@ -196,7 +237,7 @@ function claimedObjectKeys(root: MetaData, reqs: MetaRequirement[]): Set<string>
196
237
  const referrerPkg = req.package ?? req.fileDefaultPackage ?? "";
197
238
  for (const ref of req.implementedBy()) {
198
239
  const { owner, path } = splitMemberRef(ref);
199
- const { node } = resolveObjectRef(root, owner, referrerPkg);
240
+ const node = resolveClaimTarget(root, owner, referrerPkg);
200
241
  if (node === undefined) continue;
201
242
  if (path.length > 0 && resolveMember(node, path) === undefined) continue;
202
243
  claimed.add(node.resolutionKey());
@@ -302,7 +343,7 @@ export function checkRequirements(root: MetaData): Diagnostic[] {
302
343
  // binds package-locally under the ADR-0042 contract — the loader's own
303
344
  // resolver, never a parallel name scan (#228).
304
345
  const referrerPkg = req.package ?? req.fileDefaultPackage ?? "";
305
- const { node } = resolveObjectRef(root, owner, referrerPkg);
346
+ const node = resolveClaimTarget(root, owner, referrerPkg);
306
347
  const isObjectRef = path.length === 0;
307
348
 
308
349
  // GRAIN, and it stays functional-only DELIBERATELY. On a functional