@sequenceholdings/orm 2.8.4 → 2.9.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 (60) hide show
  1. package/dist/authoring.cjs +142 -56
  2. package/dist/authoring.cjs.map +1 -1
  3. package/dist/authoring.d.cts +7 -3
  4. package/dist/authoring.d.ts +7 -3
  5. package/dist/authoring.js +142 -56
  6. package/dist/authoring.js.map +1 -1
  7. package/dist/canonical.cjs +63 -13
  8. package/dist/canonical.cjs.map +1 -1
  9. package/dist/canonical.d.cts +77 -72
  10. package/dist/canonical.d.ts +77 -72
  11. package/dist/canonical.js +63 -13
  12. package/dist/canonical.js.map +1 -1
  13. package/dist/cli.cjs +170 -72
  14. package/dist/cli.cjs.map +1 -1
  15. package/dist/cli.d.cts +23 -1
  16. package/dist/cli.d.ts +23 -1
  17. package/dist/cli.js +167 -73
  18. package/dist/cli.js.map +1 -1
  19. package/dist/client.cjs.map +1 -1
  20. package/dist/client.d.cts +1 -1
  21. package/dist/client.d.ts +1 -1
  22. package/dist/client.js.map +1 -1
  23. package/dist/compiler.cjs +77 -20
  24. package/dist/compiler.cjs.map +1 -1
  25. package/dist/compiler.d.cts +2 -2
  26. package/dist/compiler.d.ts +2 -2
  27. package/dist/compiler.js +77 -20
  28. package/dist/compiler.js.map +1 -1
  29. package/dist/{constants-Dou1RkTo.d.cts → constants-D2o0vK6U.d.cts} +8 -1
  30. package/dist/{constants-Dou1RkTo.d.ts → constants-D2o0vK6U.d.ts} +8 -1
  31. package/dist/dynamic-input.d.cts +1 -1
  32. package/dist/dynamic-input.d.ts +1 -1
  33. package/dist/index.cjs +193 -82
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +62 -59
  36. package/dist/index.d.ts +62 -59
  37. package/dist/index.js +192 -83
  38. package/dist/index.js.map +1 -1
  39. package/dist/ir.cjs +70 -15
  40. package/dist/ir.cjs.map +1 -1
  41. package/dist/ir.d.cts +76 -76
  42. package/dist/ir.d.ts +76 -76
  43. package/dist/ir.js +70 -15
  44. package/dist/ir.js.map +1 -1
  45. package/dist/predicate-v2.cjs +70 -15
  46. package/dist/predicate-v2.cjs.map +1 -1
  47. package/dist/predicate-v2.d.cts +1 -1
  48. package/dist/predicate-v2.d.ts +1 -1
  49. package/dist/predicate-v2.js +70 -15
  50. package/dist/predicate-v2.js.map +1 -1
  51. package/dist/sdl.cjs +70 -15
  52. package/dist/sdl.cjs.map +1 -1
  53. package/dist/sdl.d.cts +1 -1
  54. package/dist/sdl.d.ts +1 -1
  55. package/dist/sdl.js +70 -15
  56. package/dist/sdl.js.map +1 -1
  57. package/package.json +3 -3
  58. package/templates/orm-namespace/package.json +2 -1
  59. package/dist/{definition-DjfC7Lll.d.cts → definition-B99BsOAO.d.cts} +4 -4
  60. package/dist/{definition-DjfC7Lll.d.ts → definition-B99BsOAO.d.ts} +4 -4
package/dist/cli.cjs CHANGED
@@ -105,6 +105,8 @@ function materializedRefreshFunctionName(table) {
105
105
  function materializeLockKey(schema, table) {
106
106
  return `orm:materialize:${schema}.${table}`;
107
107
  }
108
+ var OPERATION_DOCUMENTS_FILE = "operations.documents.json";
109
+ var OPERATION_DOCUMENTS_VERSION = 1;
108
110
 
109
111
  // src/hash.ts
110
112
  function stableJson(value) {
@@ -415,6 +417,48 @@ var policyExpressionSchema = zod.z.union([
415
417
  variants: zod.z.array(policyVariantSchema).max(10).default([])
416
418
  })
417
419
  ]);
420
+
421
+ // src/canonical/resolved-rows.ts
422
+ function logicalKey(table) {
423
+ return (table.primaryKey ?? []).map((columnName) => table.columns.find((c) => c.columnName === columnName)?.name ?? columnName);
424
+ }
425
+ function sameKey(key, expected) {
426
+ return key.length === expected.length && key.every((name2) => expected.includes(name2));
427
+ }
428
+ function validateRowIdentity(input) {
429
+ validateExistsIn(input);
430
+ if (input.keys.length > 1) validateCompositeKey(input);
431
+ }
432
+ function validateExistsIn({ resolved, sourceByName, fail: fail4 }) {
433
+ const name2 = resolved.existsIn?.[0];
434
+ if (name2 === void 0) return;
435
+ if (sourceByName.get(name2)?.kind !== "etl") fail4(`existsIn '${name2}' must be an etl source`);
436
+ if (resolved.policies.insert !== void 0 || resolved.policies.write !== void 0) {
437
+ fail4("existsIn allows only an update policy: rows come from the etl source");
438
+ }
439
+ }
440
+ function validateCompositeKey({ resolved, keys, tableByName, fail: fail4 }) {
441
+ const keyList = `(${keys.join(", ")})`;
442
+ if (resolved.existsIn === void 0) {
443
+ fail4(`requires exactly one primary-key column: composite key ${keyList} requires existsIn`);
444
+ return;
445
+ }
446
+ const writes = ["insert", "update", "write"].filter((kind) => resolved.policies[kind] !== void 0);
447
+ if (writes.length > 0) fail4(`composite key ${keyList} requires a read-only table: remove the ${writes.join("/")} policy`);
448
+ if (resolved.materialized) fail4(`composite key ${keyList} cannot be materialized`);
449
+ for (const source of resolved.sources) {
450
+ if (source.kind === "prefetch") {
451
+ fail4(`composite key ${keyList} cannot have prefetch source '${source.name}'`);
452
+ continue;
453
+ }
454
+ const table = tableByName.get(source.table);
455
+ if (table && !sameKey(logicalKey(table), keys)) {
456
+ fail4(`composite key ${keyList} requires ${source.kind} source '${source.name}' to be keyed by it; table '${source.table}' is keyed by (${logicalKey(table).join(", ")})`);
457
+ }
458
+ }
459
+ }
460
+
461
+ // src/canonical/resolved.ts
418
462
  function parseDurationMs(input) {
419
463
  if (typeof input === "number") return Number.isInteger(input) && input > 0 ? input : void 0;
420
464
  const match = /^(\d+(?:\.\d+)?)(ms|s|m)$/.exec(input);
@@ -491,6 +535,13 @@ var canonicalResolvedSchema = zod.z.object({
491
535
  resolve: zod.z.array(canonicalResolveRuleSchema).max(500),
492
536
  /** Sources whose tombstone hides the resolved row (v1: the manual source only). */
493
537
  deletedBy: zod.z.array(identifier).max(1).default([]),
538
+ /**
539
+ * `rows.existsIn`: the one etl source whose rows are the table's rows (read
540
+ * directly as the row source). Only an update policy may write. It is also
541
+ * what admits a composite primary key: a read-only table keyed exactly like
542
+ * that source.
543
+ */
544
+ existsIn: zod.z.array(identifier).length(1).optional(),
494
545
  /**
495
546
  * Materialized: the view reads an indexed internal table (`table`) that the
496
547
  * data plane keeps in step with `compute`, the internal view holding the
@@ -509,8 +560,8 @@ function validateResolvedView({ view, tables, views = [] }) {
509
560
  if (!resolved) return [];
510
561
  const errors = [];
511
562
  const fail4 = (message) => errors.push(`resolved table '${view.view}': ${message}`);
512
- const pkColumn = view.primaryKey?.length === 1 ? view.columns.find((c) => c.columnName === view.primaryKey?.[0]) : void 0;
513
- if (!pkColumn) {
563
+ const keyColumns = (view.primaryKey ?? []).map((columnName) => view.columns.find((c) => c.columnName === columnName)).filter((column) => column !== void 0);
564
+ if (keyColumns.length === 0 || keyColumns.length !== view.primaryKey?.length) {
514
565
  fail4("requires exactly one primary-key column");
515
566
  return errors;
516
567
  }
@@ -518,7 +569,7 @@ function validateResolvedView({ view, tables, views = [] }) {
518
569
  const scope = {
519
570
  view,
520
571
  resolved,
521
- pkColumn,
572
+ keys: new Map(keyColumns.map((column) => [column.name, column])),
522
573
  dataColumns: new Map(view.columns.filter((c) => !systemNames.has(c.name) && c.cell === void 0).map((c) => [c.name, c])),
523
574
  sourceByName: /* @__PURE__ */ new Map(),
524
575
  fail: fail4
@@ -532,6 +583,7 @@ function validateResolvedView({ view, tables, views = [] }) {
532
583
  if (!table) fail4(`source '${source.name}' references unknown table '${source.table}'`);
533
584
  else validateSource({ scope, source, table });
534
585
  }
586
+ validateRowIdentity({ resolved, keys: [...scope.keys.keys()], sourceByName: scope.sourceByName, tableByName, fail: fail4 });
535
587
  validateRules(scope);
536
588
  validateViewCells(scope);
537
589
  if (resolved.materialized) validateMaterialized({ scope, tableByName, views });
@@ -561,7 +613,7 @@ function validateSource({ scope, source, table }) {
561
613
  validatePrefetchFields({ scope, source });
562
614
  }
563
615
  function validatePrefetchFields({ scope, source }) {
564
- const { fail: fail4, pkColumn } = scope;
616
+ const { fail: fail4, keys } = scope;
565
617
  if (source.kind !== "prefetch") {
566
618
  if (source.key || source.fetch || source.function || source.prefetchDefaults || source.writeback) {
567
619
  fail4(`only prefetch sources may declare key/fetch/function/prefetchDefaults/writeback ('${source.name}')`);
@@ -570,7 +622,7 @@ function validatePrefetchFields({ scope, source }) {
570
622
  }
571
623
  if (!source.key || !source.fetch || !source.function || !source.prefetchDefaults) {
572
624
  fail4(`prefetch source '${source.name}' requires key, fetch, function and prefetchDefaults`);
573
- } else if (source.key !== pkColumn.name && !source.columns.includes(source.key)) {
625
+ } else if (!keys.has(source.key) && !source.columns.includes(source.key)) {
574
626
  fail4(`prefetch source '${source.name}' key '${source.key}' must be the primary key or one of its columns`);
575
627
  }
576
628
  for (const column of source.writeback?.columns ?? []) {
@@ -578,19 +630,19 @@ function validatePrefetchFields({ scope, source }) {
578
630
  }
579
631
  }
580
632
  function validateSourceColumns({ scope, source, byName }) {
581
- const { fail: fail4, pkColumn, dataColumns } = scope;
582
- for (const column of [pkColumn.name, ...source.columns]) {
583
- const own = column === pkColumn.name ? pkColumn : dataColumns.get(column);
633
+ const { fail: fail4, keys, dataColumns } = scope;
634
+ for (const column of [...keys.keys(), ...source.columns]) {
635
+ const own = keys.get(column) ?? dataColumns.get(column);
584
636
  const theirs = byName.get(column);
585
637
  if (!own) fail4(`source '${source.name}' supplies unknown column '${column}'`);
586
638
  else if (!theirs) fail4(`source '${source.name}' table '${source.table}' has no column '${column}'`);
587
639
  else if (JSON.stringify(theirs.type) !== JSON.stringify(own.type)) fail4(`column '${column}' has a different type on source '${source.name}'`);
588
640
  }
589
641
  }
590
- function validateRules({ resolved, pkColumn, dataColumns, sourceByName, fail: fail4 }) {
642
+ function validateRules({ resolved, keys, dataColumns, sourceByName, fail: fail4 }) {
591
643
  const ruled = /* @__PURE__ */ new Set();
592
644
  for (const rule of resolved.resolve) {
593
- if (!dataColumns.has(rule.column) || rule.column === pkColumn.name) {
645
+ if (!dataColumns.has(rule.column) || keys.has(rule.column)) {
594
646
  fail4(`resolve rule targets unknown column '${rule.column}'`);
595
647
  continue;
596
648
  }
@@ -607,7 +659,7 @@ function validateRules({ resolved, pkColumn, dataColumns, sourceByName, fail: fa
607
659
  }
608
660
  }
609
661
  for (const column of dataColumns.keys()) {
610
- if (column !== pkColumn.name && !ruled.has(column)) fail4(`column '${column}' has no resolve rule`);
662
+ if (!keys.has(column) && !ruled.has(column)) fail4(`column '${column}' has no resolve rule`);
611
663
  }
612
664
  }
613
665
  var VIEW_CELL_TYPES = {
@@ -616,10 +668,10 @@ var VIEW_CELL_TYPES = {
616
668
  by: "text",
617
669
  conflicts: "jsonb"
618
670
  };
619
- function validateViewCells({ view, dataColumns, pkColumn, fail: fail4 }) {
671
+ function validateViewCells({ view, dataColumns, keys, fail: fail4 }) {
620
672
  const expected = /* @__PURE__ */ new Map();
621
673
  for (const column of dataColumns.values()) {
622
- if (column.name === pkColumn.name) continue;
674
+ if (keys.has(column.name)) continue;
623
675
  for (const part of RESOLVED_VIEW_CELL_PARTS) {
624
676
  expected.set(cellColumnName(column.name, part), { part, columnName: cellColumnName(column.columnName, part) });
625
677
  }
@@ -1845,8 +1897,13 @@ var resolvedSourceIrSchema = zod.z.object({
1845
1897
  }).optional()
1846
1898
  });
1847
1899
  var resolvedMetaIrSchema = zod.z.object({
1848
- /** Logical pk column of the resolved view (single-column). */
1849
- pk: zod.z.string(),
1900
+ /**
1901
+ * Logical pk column of the resolved view. Absent for a composite key, which
1902
+ * only a read-only `existsIn` table may declare: it has no writes, prefetch
1903
+ * or materialization (the paths that address one key column), and its reads
1904
+ * use the view's `primaryKey` like any keyed view.
1905
+ */
1906
+ pk: zod.z.string().optional(),
1850
1907
  /** Source name of the manual source. */
1851
1908
  manual: zod.z.string(),
1852
1909
  sources: zod.z.array(resolvedSourceIrSchema),
@@ -3271,7 +3328,7 @@ function buildCompileContext(rawCanonical) {
3271
3328
  appendable: readOnly ? [] : active.filter((c) => c.type === "jsonb").map((c) => c.name),
3272
3329
  ...touchOnUpdate.length > 0 ? { touchOnUpdate } : {},
3273
3330
  ...cells ? { cells } : {},
3274
- ...resolved ? { resolved: resolvedMeta({ resolved, pk: primaryKey2[0] }) } : {},
3331
+ ...resolved ? { resolved: resolvedMeta({ resolved, primaryKey: primaryKey2 }) } : {},
3275
3332
  constraints: isView ? [] : constraints,
3276
3333
  dynamicFilterMaxDepth: 6,
3277
3334
  dynamicFilterMaxNodes: 64
@@ -3553,10 +3610,10 @@ function resolvedWritable(entries) {
3553
3610
  const system = new Set(Object.values(RESOLVED_VIEW_SYSTEM_COLUMNS).map((column) => column.name));
3554
3611
  return entries.filter((entry) => !entry.deprecated && !system.has(entry.name)).map((entry) => entry.name);
3555
3612
  }
3556
- function resolvedMeta({ resolved, pk }) {
3613
+ function resolvedMeta({ resolved, primaryKey: primaryKey2 }) {
3557
3614
  const manual = resolved.sources.find((source) => source.kind === "manual");
3558
3615
  return {
3559
- pk,
3616
+ ...primaryKey2.length === 1 ? { pk: primaryKey2[0] } : {},
3560
3617
  manual: manual?.name ?? "",
3561
3618
  deletedBy: [...resolved.deletedBy],
3562
3619
  ...resolved.materialized ? { materialized: { ...resolved.materialized } } : {},
@@ -3682,7 +3739,9 @@ function applyPrefetchDirectives({ node, root, meta }) {
3682
3739
  if (directive.maxStaleness !== void 0 && maxStalenessMs === void 0) {
3683
3740
  fail(`@prefetch(maxStaleness: "${directive.maxStaleness}") must look like '500ms', '30s' or '5m'`, "invalid-source-directive");
3684
3741
  }
3685
- const key = root.kind === "byPk" ? { one: root.pk[0].value } : selectKey({ where: root.where, pk: meta.resolved.pk, source, alias: root.alias });
3742
+ const pk = meta.resolved?.pk;
3743
+ if (pk === void 0) return fail(`@prefetch on '${root.alias}' needs a single-column primary key`, "invalid-source-directive");
3744
+ const key = root.kind === "byPk" ? { one: root.pk[0].value } : selectKey({ where: root.where, pk, source, alias: root.alias });
3686
3745
  return { source: source.name, ...maxStalenessMs !== void 0 ? { maxStalenessMs } : {}, key };
3687
3746
  });
3688
3747
  return { ...root, prefetch: plans };
@@ -6578,6 +6637,7 @@ function buildCanonicalResolved(resolved) {
6578
6637
  })),
6579
6638
  resolve: resolved.rules.map((rule) => ({ ...rule, candidates: [...rule.candidates] })),
6580
6639
  deletedBy: [...resolved.deletedBy],
6640
+ ...resolved.existsIn !== void 0 ? { existsIn: [...resolved.existsIn] } : {},
6581
6641
  ...resolved.materialized ? { materialized: { ...resolved.materialized } } : {},
6582
6642
  policies
6583
6643
  };
@@ -10288,6 +10348,42 @@ async function readSnapshot(dir, ordinal) {
10288
10348
  const path$1 = path.join(dir, MIGRATIONS_DIRNAME, META_DIRNAME, `${pad(ordinal)}.snapshot.json`);
10289
10349
  return parseJson(path$1, await readSnapshotText(dir, ordinal));
10290
10350
  }
10351
+ async function verifyCommittedLedger(dir, entries) {
10352
+ const firstRequiredSnapshot = entries.length - 2;
10353
+ let previousId3;
10354
+ let previousSnapshotPresent = false;
10355
+ let previousHash = null;
10356
+ for (const [index3, entry] of entries.entries()) {
10357
+ const sql4 = await readMigrationSql(dir, entry);
10358
+ if (checksumOf(sql4) !== entry.checksum) {
10359
+ return `${MIGRATIONS_DIRNAME}/${entry.name}.sql does not match its journal checksum \u2014 committed migrations must not be edited`;
10360
+ }
10361
+ const snapshotText = index3 >= firstRequiredSnapshot ? await readSnapshotText(dir, entry.ordinal) : await readPrunableSnapshotText(dir, entry.ordinal);
10362
+ if (snapshotText !== void 0) {
10363
+ if (checksumOf(snapshotText) !== entry.snapshot_checksum) {
10364
+ return `the snapshot for ${entry.name} does not match its journal checksum \u2014 committed snapshots must not be edited (the next diff builds on it)`;
10365
+ }
10366
+ const snapshot = JSON.parse(snapshotText);
10367
+ if (previousSnapshotPresent && snapshot.prevId !== previousId3) {
10368
+ return `snapshot chain broken at ${entry.name} \u2014 regenerate the workspace with \`seq-studio orm diff\``;
10369
+ }
10370
+ previousId3 = snapshot.id;
10371
+ }
10372
+ previousSnapshotPresent = snapshotText !== void 0;
10373
+ if (entry.previous_content_hash !== previousHash) {
10374
+ return `content-hash chain broken at ${entry.name} \u2014 migrations must be appended in order by \`seq-studio orm diff\``;
10375
+ }
10376
+ previousHash = entry.content_hash;
10377
+ }
10378
+ return void 0;
10379
+ }
10380
+ async function readPrunableSnapshotText(dir, ordinal) {
10381
+ const path$1 = path.join(dir, MIGRATIONS_DIRNAME, META_DIRNAME, `${pad(ordinal)}.snapshot.json`);
10382
+ return promises.readFile(path$1, "utf8").catch((error) => {
10383
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return void 0;
10384
+ throw error;
10385
+ });
10386
+ }
10291
10387
  async function readMigrationSql(dir, entry) {
10292
10388
  const path$1 = path.join(dir, MIGRATIONS_DIRNAME, `${entry.name}.sql`);
10293
10389
  return promises.readFile(path$1, "utf8").catch(() => {
@@ -10448,30 +10544,10 @@ async function checkWorkspace({
10448
10544
  log(`no committed migrations for '${loaded.definition.namespace}' \u2014 run \`seq-studio orm diff\` and commit the result`);
10449
10545
  return 1;
10450
10546
  }
10451
- let previousId3;
10452
- let previousHash = null;
10453
- for (const entry of workspace.journal.entries) {
10454
- const sql4 = await readMigrationSql(dir, entry);
10455
- if (checksumOf(sql4) !== entry.checksum) {
10456
- log(`${MIGRATIONS_DIRNAME}/${entry.name}.sql does not match its journal checksum \u2014 committed migrations must not be edited`);
10457
- return 1;
10458
- }
10459
- const snapshotText = await readSnapshotText(dir, entry.ordinal);
10460
- if (checksumOf(snapshotText) !== entry.snapshot_checksum) {
10461
- log(`the snapshot for ${entry.name} does not match its journal checksum \u2014 committed snapshots must not be edited (the next diff builds on it)`);
10462
- return 1;
10463
- }
10464
- const snapshot = JSON.parse(snapshotText);
10465
- if (entry.ordinal > 1 && snapshot.prevId !== previousId3) {
10466
- log(`snapshot chain broken at ${entry.name} \u2014 regenerate the workspace with \`seq-studio orm diff\``);
10467
- return 1;
10468
- }
10469
- if (entry.previous_content_hash !== previousHash) {
10470
- log(`content-hash chain broken at ${entry.name} \u2014 migrations must be appended in order by \`seq-studio orm diff\``);
10471
- return 1;
10472
- }
10473
- previousId3 = snapshot.id;
10474
- previousHash = entry.content_hash;
10547
+ const broken = await verifyCommittedLedger(dir, workspace.journal.entries);
10548
+ if (broken) {
10549
+ log(broken);
10550
+ return 1;
10475
10551
  }
10476
10552
  if (tip.content_hash !== loaded.contentHash) {
10477
10553
  log(`definitions changed since ${tip.name} \u2014 run \`seq-studio orm diff\` and commit the new migration`);
@@ -11350,6 +11426,22 @@ function parseArgs(argv) {
11350
11426
  // src/cli.ts
11351
11427
  var BASE_URL_ENV = "SEQUENCE_ORM_BASE_URL";
11352
11428
  var TOKEN_ENV = "SEQUENCE_ORM_TOKEN";
11429
+ var SOURCE_ENV = "SEQUENCE_ORM_SOURCE";
11430
+ var UNVERIFIED_SOURCE_REASON_ENV = "SEQUENCE_ORM_UNVERIFIED_SOURCE_REASON";
11431
+ function applySourceFromEnv(env = process.env) {
11432
+ const reason = env[UNVERIFIED_SOURCE_REASON_ENV]?.trim();
11433
+ const override = reason ? { unverified_source_reason: reason } : {};
11434
+ const raw = env[SOURCE_ENV];
11435
+ if (!raw) return override;
11436
+ let parsed;
11437
+ try {
11438
+ parsed = JSON.parse(raw);
11439
+ } catch {
11440
+ throw new Error(`${SOURCE_ENV} is not valid JSON`);
11441
+ }
11442
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error(`${SOURCE_ENV} must be a JSON object`);
11443
+ return { source: Object.fromEntries(Object.entries(parsed)), ...override };
11444
+ }
11353
11445
  var ORM_MODULE_PATH_ENV = "SEQUENCE_ORM_MODULE_PATH";
11354
11446
  var USAGE = `usage:
11355
11447
  seq-studio orm init <dir> scaffold a namespace package (v2: TypeScript schema + persisted operations)
@@ -11667,15 +11759,21 @@ async function generateCommand(rest) {
11667
11759
  await writeGenerateOutputs(dir, compiled.project);
11668
11760
  const { loaded, manifest } = compiled.project;
11669
11761
  console.log(
11670
- `wrote ${SCHEMA_GRAPHQL_FILE}, ${OPERATIONS_MANIFEST_FILE}, ${TYPE_POLICIES_FILE}, sql-operations.gen.ts, namespace.contract.json (${loaded.canonical.tables.length} table(s), ${loaded.canonical.views?.length ?? 0} view(s), ${Object.keys(manifest.operations).length} operation(s))`
11762
+ `wrote ${SCHEMA_GRAPHQL_FILE}, ${OPERATIONS_MANIFEST_FILE}, ${OPERATION_DOCUMENTS_FILE}, ${TYPE_POLICIES_FILE}, sql-operations.gen.ts, namespace.contract.json (${loaded.canonical.tables.length} table(s), ${loaded.canonical.views?.length ?? 0} view(s), ${Object.keys(manifest.operations).length} operation(s))`
11671
11763
  );
11672
11764
  return runConsumerCodegen(dir);
11673
11765
  }
11674
11766
  async function writeGenerateOutputs(dir, project) {
11675
- const { loaded, sdl, manifest } = project;
11767
+ const { loaded, sdl, manifest, documents } = project;
11676
11768
  await promises.writeFile(path.join(dir, SCHEMA_GRAPHQL_FILE), sdl);
11677
11769
  await promises.writeFile(path.join(dir, "namespace.contract.json"), `${JSON.stringify(loaded.canonical, null, 2)}
11678
11770
  `);
11771
+ const sortedDocuments = [...documents].sort((a, b) => a.path.localeCompare(b.path)).map(({ path, contents }) => ({ path, contents }));
11772
+ await promises.writeFile(
11773
+ path.join(dir, OPERATION_DOCUMENTS_FILE),
11774
+ `${JSON.stringify({ documentsVersion: OPERATION_DOCUMENTS_VERSION, documents: sortedDocuments }, null, 2)}
11775
+ `
11776
+ );
11679
11777
  const operations = Object.entries(manifest.operations).map(([name2, op]) => ({
11680
11778
  name: name2,
11681
11779
  kind: op.kind,
@@ -11881,8 +11979,20 @@ function reportRegistryState(registry) {
11881
11979
  if (match && match.id === registry.activeVersionId) console.log("-- registry: this content is the ACTIVE version");
11882
11980
  else if (match) console.log(`-- registry: registered (status ${match.status}), not active`);
11883
11981
  else console.log(`-- registry: new content (active is ${active ? active.content_hash.slice(0, 12) : "none"})`);
11982
+ if (registry.activeActivation) console.log(`-- registry: ${describeActivation(registry.activeActivation)}`);
11884
11983
  return 0;
11885
11984
  }
11985
+ function describeActivation(activation) {
11986
+ const since = `live since ${activation.activated_at.slice(0, 16).replace("T", " ")}Z`;
11987
+ const by = `by ${activation.actor_sub.replace(/^google-apps\|/, "")}`;
11988
+ if (activation.source_commit && activation.source_branch) {
11989
+ const at = activation.source_path ? ` at ${activation.source_path}` : "";
11990
+ return `${since} from ${activation.source_commit.slice(0, 12)} on ${activation.source_branch}${at} (${activation.provenance_class}) ${by}`;
11991
+ }
11992
+ const failure = activation.reported_source?.verification_failure;
11993
+ const why = typeof failure === "string" ? `: ${failure}` : "";
11994
+ return `${since} from an untraced source (${activation.provenance_class}${why}) ${by}`;
11995
+ }
11886
11996
  var META_DIRNAME2 = "meta";
11887
11997
  var JOURNAL_FILENAME2 = "journal.json";
11888
11998
  var padOrdinal = (ordinal) => String(ordinal).padStart(4, "0");
@@ -11970,30 +12080,10 @@ async function checkWorkspaceV2({
11970
12080
  log(`no committed migrations for '${namespace}' \u2014 run \`seq-studio orm diff\` and commit the result`);
11971
12081
  return 1;
11972
12082
  }
11973
- let previousId3;
11974
- let previousHash = null;
11975
- for (const entry of workspace.journal.entries) {
11976
- const sql4 = await readMigrationSql(dir, entry);
11977
- if (checksumOf(sql4) !== entry.checksum) {
11978
- log(`${MIGRATIONS_DIRNAME}/${entry.name}.sql does not match its journal checksum \u2014 committed migrations must not be edited`);
11979
- return 1;
11980
- }
11981
- const snapshotText = await readSnapshotText(dir, entry.ordinal);
11982
- if (checksumOf(snapshotText) !== entry.snapshot_checksum) {
11983
- log(`the snapshot for ${entry.name} does not match its journal checksum \u2014 committed snapshots must not be edited (the next diff builds on it)`);
11984
- return 1;
11985
- }
11986
- const snapshot = JSON.parse(snapshotText);
11987
- if (entry.ordinal > 1 && snapshot.prevId !== previousId3) {
11988
- log(`snapshot chain broken at ${entry.name} \u2014 regenerate the workspace with \`seq-studio orm diff\``);
11989
- return 1;
11990
- }
11991
- if (entry.previous_content_hash !== previousHash) {
11992
- log(`content-hash chain broken at ${entry.name} \u2014 migrations must be appended in order by \`seq-studio orm diff\``);
11993
- return 1;
11994
- }
11995
- previousId3 = snapshot.id;
11996
- previousHash = entry.content_hash;
12083
+ const broken = await verifyCommittedLedger(dir, workspace.journal.entries);
12084
+ if (broken) {
12085
+ log(broken);
12086
+ return 1;
11997
12087
  }
11998
12088
  if (tip.content_hash !== loaded.hash) {
11999
12089
  log(`definitions changed since ${tip.name} \u2014 run \`seq-studio orm diff\` and commit the new migration`);
@@ -12107,7 +12197,8 @@ async function applyV2Command(rest, dir) {
12107
12197
  const applied = await apiRequestStrict("POST", `/api/orm/namespaces/${namespace}/apply`, {
12108
12198
  version_id: registered.version_id,
12109
12199
  ...allowDestructive ? { allow_destructive: true } : {},
12110
- ...schemaAlreadyActive && registered.operation_set_id ? { operation_set_id: registered.operation_set_id } : {}
12200
+ ...schemaAlreadyActive && registered.operation_set_id ? { operation_set_id: registered.operation_set_id } : {},
12201
+ ...applySourceFromEnv()
12111
12202
  });
12112
12203
  const live = { namespace, contentHash: registered.content_hash };
12113
12204
  if ("ops_only" in applied) {
@@ -12564,7 +12655,8 @@ async function applyCommand(rest) {
12564
12655
  if (dryRun) return dryRunRehearsal({ namespace, versionId: registered.version_id, contentHash: registered.content_hash });
12565
12656
  const applied = await apiRequestStrict("POST", `/api/orm/namespaces/${namespace}/apply`, {
12566
12657
  version_id: registered.version_id,
12567
- ...allowDestructive ? { allow_destructive: true } : {}
12658
+ ...allowDestructive ? { allow_destructive: true } : {},
12659
+ ...applySourceFromEnv()
12568
12660
  });
12569
12661
  if ("status" in applied) {
12570
12662
  const verdict = await awaitQueuedApply({ dir, namespace, contentHash: registered.content_hash, runId: applied.run_id, applyWait });
@@ -12744,11 +12836,13 @@ async function apiRequestStrict(method, path, body) {
12744
12836
  }
12745
12837
  async function fetchRegistryState(namespace, contentHash) {
12746
12838
  if (!process.env[BASE_URL_ENV]?.trim() || !await resolveToken()) return { kind: "offline" };
12747
- const body = await apiRequest("GET", `/api/orm/namespaces/${namespace}?hash=${encodeURIComponent(contentHash)}`, void 0, { allow404: true });
12839
+ const body = await apiRequest("GET", `/api/orm/namespaces/${namespace}?hash=${encodeURIComponent(contentHash)}&activations_limit=1`, void 0, { allow404: true });
12748
12840
  if (!body) return { kind: "missing" };
12841
+ const latest = body.activations?.[0];
12749
12842
  return {
12750
12843
  kind: "found",
12751
12844
  activeVersionId: body.namespace.active_version_id,
12845
+ activeActivation: latest && latest.id === body.namespace.active_activation_id ? latest : null,
12752
12846
  versions: body.versions,
12753
12847
  hashVersion: body.hash_version ?? null
12754
12848
  };
@@ -12765,7 +12859,11 @@ var firstLine2 = (sql4) => sql4.split("\n")[0] ?? sql4;
12765
12859
 
12766
12860
  exports.BASE_URL_ENV = BASE_URL_ENV;
12767
12861
  exports.ORM_MODULE_PATH_ENV = ORM_MODULE_PATH_ENV;
12862
+ exports.SOURCE_ENV = SOURCE_ENV;
12768
12863
  exports.TOKEN_ENV = TOKEN_ENV;
12864
+ exports.UNVERIFIED_SOURCE_REASON_ENV = UNVERIFIED_SOURCE_REASON_ENV;
12865
+ exports.applySourceFromEnv = applySourceFromEnv;
12866
+ exports.describeActivation = describeActivation;
12769
12867
  exports.errorDetail = errorDetail;
12770
12868
  exports.parseApplyWait = parseApplyWait;
12771
12869
  exports.runCli = runCli;