@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.d.cts CHANGED
@@ -1,6 +1,15 @@
1
1
  /** Set by the seq-studio delegate; direct users export them. */
2
2
  declare const BASE_URL_ENV = "SEQUENCE_ORM_BASE_URL";
3
3
  declare const TOKEN_ENV = "SEQUENCE_ORM_TOKEN";
4
+ /** JSON `{repository_id, commit, path, dirty?}` naming the Git Service source of this apply; set by the seq-studio delegate. */
5
+ declare const SOURCE_ENV = "SEQUENCE_ORM_SOURCE";
6
+ /** Namespace-admin override reason for an apply whose source does not verify; set by `seq-studio orm apply --unverified-source`. */
7
+ declare const UNVERIFIED_SOURCE_REASON_ENV = "SEQUENCE_ORM_UNVERIFIED_SOURCE_REASON";
8
+ /** The apply body's `source` claim, when the delegate supplied one. Atlas verifies it; it never authorizes anything. */
9
+ declare function applySourceFromEnv(env?: NodeJS.ProcessEnv): {
10
+ source?: Record<string, unknown>;
11
+ unverified_source_reason?: string;
12
+ };
4
13
  /**
5
14
  * Test/dev hook mirroring `LoadNamespaceOptions.ormModulePath`: when set, bare
6
15
  * `'@sequenceholdings/orm'` imports in consumer files resolve to this path
@@ -12,6 +21,8 @@ type TokenProvider = () => Promise<string | null>;
12
21
  /** Hand the CLI a refreshing token source (used by the seq-studio delegate). */
13
22
  declare function setTokenProvider(provider: TokenProvider): void;
14
23
  declare function runCli(argv: readonly string[]): Promise<number>;
24
+ /** Where the live version came from: `live since <time> from <commit> on <branch> at <path> by <actor>`. */
25
+ declare function describeActivation(activation: RegistryActivation): string;
15
26
  interface ApplyWait {
16
27
  wait: boolean;
17
28
  timeoutMs: number;
@@ -24,5 +35,16 @@ declare function parseApplyWait(rest: readonly string[]): ApplyWait;
24
35
  * details name the variable, column, constraint, or requirement at fault.
25
36
  */
26
37
  declare function errorDetail(payload: unknown): string | null;
38
+ /** One row of the namespace's activation history, as the detail route returns it (absent on older Atlas). */
39
+ interface RegistryActivation {
40
+ id: string;
41
+ provenance_class: string;
42
+ source_commit: string | null;
43
+ source_branch: string | null;
44
+ source_path: string | null;
45
+ reported_source: Record<string, unknown> | null;
46
+ actor_sub: string;
47
+ activated_at: string;
48
+ }
27
49
 
28
- export { BASE_URL_ENV, ORM_MODULE_PATH_ENV, TOKEN_ENV, errorDetail, parseApplyWait, runCli, setTokenProvider };
50
+ export { BASE_URL_ENV, ORM_MODULE_PATH_ENV, type RegistryActivation, SOURCE_ENV, TOKEN_ENV, UNVERIFIED_SOURCE_REASON_ENV, applySourceFromEnv, describeActivation, errorDetail, parseApplyWait, runCli, setTokenProvider };
package/dist/cli.d.ts CHANGED
@@ -1,6 +1,15 @@
1
1
  /** Set by the seq-studio delegate; direct users export them. */
2
2
  declare const BASE_URL_ENV = "SEQUENCE_ORM_BASE_URL";
3
3
  declare const TOKEN_ENV = "SEQUENCE_ORM_TOKEN";
4
+ /** JSON `{repository_id, commit, path, dirty?}` naming the Git Service source of this apply; set by the seq-studio delegate. */
5
+ declare const SOURCE_ENV = "SEQUENCE_ORM_SOURCE";
6
+ /** Namespace-admin override reason for an apply whose source does not verify; set by `seq-studio orm apply --unverified-source`. */
7
+ declare const UNVERIFIED_SOURCE_REASON_ENV = "SEQUENCE_ORM_UNVERIFIED_SOURCE_REASON";
8
+ /** The apply body's `source` claim, when the delegate supplied one. Atlas verifies it; it never authorizes anything. */
9
+ declare function applySourceFromEnv(env?: NodeJS.ProcessEnv): {
10
+ source?: Record<string, unknown>;
11
+ unverified_source_reason?: string;
12
+ };
4
13
  /**
5
14
  * Test/dev hook mirroring `LoadNamespaceOptions.ormModulePath`: when set, bare
6
15
  * `'@sequenceholdings/orm'` imports in consumer files resolve to this path
@@ -12,6 +21,8 @@ type TokenProvider = () => Promise<string | null>;
12
21
  /** Hand the CLI a refreshing token source (used by the seq-studio delegate). */
13
22
  declare function setTokenProvider(provider: TokenProvider): void;
14
23
  declare function runCli(argv: readonly string[]): Promise<number>;
24
+ /** Where the live version came from: `live since <time> from <commit> on <branch> at <path> by <actor>`. */
25
+ declare function describeActivation(activation: RegistryActivation): string;
15
26
  interface ApplyWait {
16
27
  wait: boolean;
17
28
  timeoutMs: number;
@@ -24,5 +35,16 @@ declare function parseApplyWait(rest: readonly string[]): ApplyWait;
24
35
  * details name the variable, column, constraint, or requirement at fault.
25
36
  */
26
37
  declare function errorDetail(payload: unknown): string | null;
38
+ /** One row of the namespace's activation history, as the detail route returns it (absent on older Atlas). */
39
+ interface RegistryActivation {
40
+ id: string;
41
+ provenance_class: string;
42
+ source_commit: string | null;
43
+ source_branch: string | null;
44
+ source_path: string | null;
45
+ reported_source: Record<string, unknown> | null;
46
+ actor_sub: string;
47
+ activated_at: string;
48
+ }
27
49
 
28
- export { BASE_URL_ENV, ORM_MODULE_PATH_ENV, TOKEN_ENV, errorDetail, parseApplyWait, runCli, setTokenProvider };
50
+ export { BASE_URL_ENV, ORM_MODULE_PATH_ENV, type RegistryActivation, SOURCE_ENV, TOKEN_ENV, UNVERIFIED_SOURCE_REASON_ENV, applySourceFromEnv, describeActivation, errorDetail, parseApplyWait, runCli, setTokenProvider };
package/dist/cli.js CHANGED
@@ -102,6 +102,8 @@ function materializedRefreshFunctionName(table) {
102
102
  function materializeLockKey(schema, table) {
103
103
  return `orm:materialize:${schema}.${table}`;
104
104
  }
105
+ var OPERATION_DOCUMENTS_FILE = "operations.documents.json";
106
+ var OPERATION_DOCUMENTS_VERSION = 1;
105
107
 
106
108
  // src/hash.ts
107
109
  function stableJson(value) {
@@ -412,6 +414,48 @@ var policyExpressionSchema = z.union([
412
414
  variants: z.array(policyVariantSchema).max(10).default([])
413
415
  })
414
416
  ]);
417
+
418
+ // src/canonical/resolved-rows.ts
419
+ function logicalKey(table) {
420
+ return (table.primaryKey ?? []).map((columnName) => table.columns.find((c) => c.columnName === columnName)?.name ?? columnName);
421
+ }
422
+ function sameKey(key, expected) {
423
+ return key.length === expected.length && key.every((name2) => expected.includes(name2));
424
+ }
425
+ function validateRowIdentity(input) {
426
+ validateExistsIn(input);
427
+ if (input.keys.length > 1) validateCompositeKey(input);
428
+ }
429
+ function validateExistsIn({ resolved, sourceByName, fail: fail4 }) {
430
+ const name2 = resolved.existsIn?.[0];
431
+ if (name2 === void 0) return;
432
+ if (sourceByName.get(name2)?.kind !== "etl") fail4(`existsIn '${name2}' must be an etl source`);
433
+ if (resolved.policies.insert !== void 0 || resolved.policies.write !== void 0) {
434
+ fail4("existsIn allows only an update policy: rows come from the etl source");
435
+ }
436
+ }
437
+ function validateCompositeKey({ resolved, keys, tableByName, fail: fail4 }) {
438
+ const keyList = `(${keys.join(", ")})`;
439
+ if (resolved.existsIn === void 0) {
440
+ fail4(`requires exactly one primary-key column: composite key ${keyList} requires existsIn`);
441
+ return;
442
+ }
443
+ const writes = ["insert", "update", "write"].filter((kind) => resolved.policies[kind] !== void 0);
444
+ if (writes.length > 0) fail4(`composite key ${keyList} requires a read-only table: remove the ${writes.join("/")} policy`);
445
+ if (resolved.materialized) fail4(`composite key ${keyList} cannot be materialized`);
446
+ for (const source of resolved.sources) {
447
+ if (source.kind === "prefetch") {
448
+ fail4(`composite key ${keyList} cannot have prefetch source '${source.name}'`);
449
+ continue;
450
+ }
451
+ const table = tableByName.get(source.table);
452
+ if (table && !sameKey(logicalKey(table), keys)) {
453
+ fail4(`composite key ${keyList} requires ${source.kind} source '${source.name}' to be keyed by it; table '${source.table}' is keyed by (${logicalKey(table).join(", ")})`);
454
+ }
455
+ }
456
+ }
457
+
458
+ // src/canonical/resolved.ts
415
459
  function parseDurationMs(input) {
416
460
  if (typeof input === "number") return Number.isInteger(input) && input > 0 ? input : void 0;
417
461
  const match = /^(\d+(?:\.\d+)?)(ms|s|m)$/.exec(input);
@@ -488,6 +532,13 @@ var canonicalResolvedSchema = z.object({
488
532
  resolve: z.array(canonicalResolveRuleSchema).max(500),
489
533
  /** Sources whose tombstone hides the resolved row (v1: the manual source only). */
490
534
  deletedBy: z.array(identifier).max(1).default([]),
535
+ /**
536
+ * `rows.existsIn`: the one etl source whose rows are the table's rows (read
537
+ * directly as the row source). Only an update policy may write. It is also
538
+ * what admits a composite primary key: a read-only table keyed exactly like
539
+ * that source.
540
+ */
541
+ existsIn: z.array(identifier).length(1).optional(),
491
542
  /**
492
543
  * Materialized: the view reads an indexed internal table (`table`) that the
493
544
  * data plane keeps in step with `compute`, the internal view holding the
@@ -506,8 +557,8 @@ function validateResolvedView({ view, tables, views = [] }) {
506
557
  if (!resolved) return [];
507
558
  const errors = [];
508
559
  const fail4 = (message) => errors.push(`resolved table '${view.view}': ${message}`);
509
- const pkColumn = view.primaryKey?.length === 1 ? view.columns.find((c) => c.columnName === view.primaryKey?.[0]) : void 0;
510
- if (!pkColumn) {
560
+ const keyColumns = (view.primaryKey ?? []).map((columnName) => view.columns.find((c) => c.columnName === columnName)).filter((column) => column !== void 0);
561
+ if (keyColumns.length === 0 || keyColumns.length !== view.primaryKey?.length) {
511
562
  fail4("requires exactly one primary-key column");
512
563
  return errors;
513
564
  }
@@ -515,7 +566,7 @@ function validateResolvedView({ view, tables, views = [] }) {
515
566
  const scope = {
516
567
  view,
517
568
  resolved,
518
- pkColumn,
569
+ keys: new Map(keyColumns.map((column) => [column.name, column])),
519
570
  dataColumns: new Map(view.columns.filter((c) => !systemNames.has(c.name) && c.cell === void 0).map((c) => [c.name, c])),
520
571
  sourceByName: /* @__PURE__ */ new Map(),
521
572
  fail: fail4
@@ -529,6 +580,7 @@ function validateResolvedView({ view, tables, views = [] }) {
529
580
  if (!table) fail4(`source '${source.name}' references unknown table '${source.table}'`);
530
581
  else validateSource({ scope, source, table });
531
582
  }
583
+ validateRowIdentity({ resolved, keys: [...scope.keys.keys()], sourceByName: scope.sourceByName, tableByName, fail: fail4 });
532
584
  validateRules(scope);
533
585
  validateViewCells(scope);
534
586
  if (resolved.materialized) validateMaterialized({ scope, tableByName, views });
@@ -558,7 +610,7 @@ function validateSource({ scope, source, table }) {
558
610
  validatePrefetchFields({ scope, source });
559
611
  }
560
612
  function validatePrefetchFields({ scope, source }) {
561
- const { fail: fail4, pkColumn } = scope;
613
+ const { fail: fail4, keys } = scope;
562
614
  if (source.kind !== "prefetch") {
563
615
  if (source.key || source.fetch || source.function || source.prefetchDefaults || source.writeback) {
564
616
  fail4(`only prefetch sources may declare key/fetch/function/prefetchDefaults/writeback ('${source.name}')`);
@@ -567,7 +619,7 @@ function validatePrefetchFields({ scope, source }) {
567
619
  }
568
620
  if (!source.key || !source.fetch || !source.function || !source.prefetchDefaults) {
569
621
  fail4(`prefetch source '${source.name}' requires key, fetch, function and prefetchDefaults`);
570
- } else if (source.key !== pkColumn.name && !source.columns.includes(source.key)) {
622
+ } else if (!keys.has(source.key) && !source.columns.includes(source.key)) {
571
623
  fail4(`prefetch source '${source.name}' key '${source.key}' must be the primary key or one of its columns`);
572
624
  }
573
625
  for (const column of source.writeback?.columns ?? []) {
@@ -575,19 +627,19 @@ function validatePrefetchFields({ scope, source }) {
575
627
  }
576
628
  }
577
629
  function validateSourceColumns({ scope, source, byName }) {
578
- const { fail: fail4, pkColumn, dataColumns } = scope;
579
- for (const column of [pkColumn.name, ...source.columns]) {
580
- const own = column === pkColumn.name ? pkColumn : dataColumns.get(column);
630
+ const { fail: fail4, keys, dataColumns } = scope;
631
+ for (const column of [...keys.keys(), ...source.columns]) {
632
+ const own = keys.get(column) ?? dataColumns.get(column);
581
633
  const theirs = byName.get(column);
582
634
  if (!own) fail4(`source '${source.name}' supplies unknown column '${column}'`);
583
635
  else if (!theirs) fail4(`source '${source.name}' table '${source.table}' has no column '${column}'`);
584
636
  else if (JSON.stringify(theirs.type) !== JSON.stringify(own.type)) fail4(`column '${column}' has a different type on source '${source.name}'`);
585
637
  }
586
638
  }
587
- function validateRules({ resolved, pkColumn, dataColumns, sourceByName, fail: fail4 }) {
639
+ function validateRules({ resolved, keys, dataColumns, sourceByName, fail: fail4 }) {
588
640
  const ruled = /* @__PURE__ */ new Set();
589
641
  for (const rule of resolved.resolve) {
590
- if (!dataColumns.has(rule.column) || rule.column === pkColumn.name) {
642
+ if (!dataColumns.has(rule.column) || keys.has(rule.column)) {
591
643
  fail4(`resolve rule targets unknown column '${rule.column}'`);
592
644
  continue;
593
645
  }
@@ -604,7 +656,7 @@ function validateRules({ resolved, pkColumn, dataColumns, sourceByName, fail: fa
604
656
  }
605
657
  }
606
658
  for (const column of dataColumns.keys()) {
607
- if (column !== pkColumn.name && !ruled.has(column)) fail4(`column '${column}' has no resolve rule`);
659
+ if (!keys.has(column) && !ruled.has(column)) fail4(`column '${column}' has no resolve rule`);
608
660
  }
609
661
  }
610
662
  var VIEW_CELL_TYPES = {
@@ -613,10 +665,10 @@ var VIEW_CELL_TYPES = {
613
665
  by: "text",
614
666
  conflicts: "jsonb"
615
667
  };
616
- function validateViewCells({ view, dataColumns, pkColumn, fail: fail4 }) {
668
+ function validateViewCells({ view, dataColumns, keys, fail: fail4 }) {
617
669
  const expected = /* @__PURE__ */ new Map();
618
670
  for (const column of dataColumns.values()) {
619
- if (column.name === pkColumn.name) continue;
671
+ if (keys.has(column.name)) continue;
620
672
  for (const part of RESOLVED_VIEW_CELL_PARTS) {
621
673
  expected.set(cellColumnName(column.name, part), { part, columnName: cellColumnName(column.columnName, part) });
622
674
  }
@@ -1842,8 +1894,13 @@ var resolvedSourceIrSchema = z.object({
1842
1894
  }).optional()
1843
1895
  });
1844
1896
  var resolvedMetaIrSchema = z.object({
1845
- /** Logical pk column of the resolved view (single-column). */
1846
- pk: z.string(),
1897
+ /**
1898
+ * Logical pk column of the resolved view. Absent for a composite key, which
1899
+ * only a read-only `existsIn` table may declare: it has no writes, prefetch
1900
+ * or materialization (the paths that address one key column), and its reads
1901
+ * use the view's `primaryKey` like any keyed view.
1902
+ */
1903
+ pk: z.string().optional(),
1847
1904
  /** Source name of the manual source. */
1848
1905
  manual: z.string(),
1849
1906
  sources: z.array(resolvedSourceIrSchema),
@@ -3268,7 +3325,7 @@ function buildCompileContext(rawCanonical) {
3268
3325
  appendable: readOnly ? [] : active.filter((c) => c.type === "jsonb").map((c) => c.name),
3269
3326
  ...touchOnUpdate.length > 0 ? { touchOnUpdate } : {},
3270
3327
  ...cells ? { cells } : {},
3271
- ...resolved ? { resolved: resolvedMeta({ resolved, pk: primaryKey2[0] }) } : {},
3328
+ ...resolved ? { resolved: resolvedMeta({ resolved, primaryKey: primaryKey2 }) } : {},
3272
3329
  constraints: isView ? [] : constraints,
3273
3330
  dynamicFilterMaxDepth: 6,
3274
3331
  dynamicFilterMaxNodes: 64
@@ -3550,10 +3607,10 @@ function resolvedWritable(entries) {
3550
3607
  const system = new Set(Object.values(RESOLVED_VIEW_SYSTEM_COLUMNS).map((column) => column.name));
3551
3608
  return entries.filter((entry) => !entry.deprecated && !system.has(entry.name)).map((entry) => entry.name);
3552
3609
  }
3553
- function resolvedMeta({ resolved, pk }) {
3610
+ function resolvedMeta({ resolved, primaryKey: primaryKey2 }) {
3554
3611
  const manual = resolved.sources.find((source) => source.kind === "manual");
3555
3612
  return {
3556
- pk,
3613
+ ...primaryKey2.length === 1 ? { pk: primaryKey2[0] } : {},
3557
3614
  manual: manual?.name ?? "",
3558
3615
  deletedBy: [...resolved.deletedBy],
3559
3616
  ...resolved.materialized ? { materialized: { ...resolved.materialized } } : {},
@@ -3679,7 +3736,9 @@ function applyPrefetchDirectives({ node, root, meta }) {
3679
3736
  if (directive.maxStaleness !== void 0 && maxStalenessMs === void 0) {
3680
3737
  fail(`@prefetch(maxStaleness: "${directive.maxStaleness}") must look like '500ms', '30s' or '5m'`, "invalid-source-directive");
3681
3738
  }
3682
- const key = root.kind === "byPk" ? { one: root.pk[0].value } : selectKey({ where: root.where, pk: meta.resolved.pk, source, alias: root.alias });
3739
+ const pk = meta.resolved?.pk;
3740
+ if (pk === void 0) return fail(`@prefetch on '${root.alias}' needs a single-column primary key`, "invalid-source-directive");
3741
+ const key = root.kind === "byPk" ? { one: root.pk[0].value } : selectKey({ where: root.where, pk, source, alias: root.alias });
3683
3742
  return { source: source.name, ...maxStalenessMs !== void 0 ? { maxStalenessMs } : {}, key };
3684
3743
  });
3685
3744
  return { ...root, prefetch: plans };
@@ -6575,6 +6634,7 @@ function buildCanonicalResolved(resolved) {
6575
6634
  })),
6576
6635
  resolve: resolved.rules.map((rule) => ({ ...rule, candidates: [...rule.candidates] })),
6577
6636
  deletedBy: [...resolved.deletedBy],
6637
+ ...resolved.existsIn !== void 0 ? { existsIn: [...resolved.existsIn] } : {},
6578
6638
  ...resolved.materialized ? { materialized: { ...resolved.materialized } } : {},
6579
6639
  policies
6580
6640
  };
@@ -10285,6 +10345,42 @@ async function readSnapshot(dir, ordinal) {
10285
10345
  const path = join(dir, MIGRATIONS_DIRNAME, META_DIRNAME, `${pad(ordinal)}.snapshot.json`);
10286
10346
  return parseJson(path, await readSnapshotText(dir, ordinal));
10287
10347
  }
10348
+ async function verifyCommittedLedger(dir, entries) {
10349
+ const firstRequiredSnapshot = entries.length - 2;
10350
+ let previousId3;
10351
+ let previousSnapshotPresent = false;
10352
+ let previousHash = null;
10353
+ for (const [index3, entry] of entries.entries()) {
10354
+ const sql4 = await readMigrationSql(dir, entry);
10355
+ if (checksumOf(sql4) !== entry.checksum) {
10356
+ return `${MIGRATIONS_DIRNAME}/${entry.name}.sql does not match its journal checksum \u2014 committed migrations must not be edited`;
10357
+ }
10358
+ const snapshotText = index3 >= firstRequiredSnapshot ? await readSnapshotText(dir, entry.ordinal) : await readPrunableSnapshotText(dir, entry.ordinal);
10359
+ if (snapshotText !== void 0) {
10360
+ if (checksumOf(snapshotText) !== entry.snapshot_checksum) {
10361
+ 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)`;
10362
+ }
10363
+ const snapshot = JSON.parse(snapshotText);
10364
+ if (previousSnapshotPresent && snapshot.prevId !== previousId3) {
10365
+ return `snapshot chain broken at ${entry.name} \u2014 regenerate the workspace with \`seq-studio orm diff\``;
10366
+ }
10367
+ previousId3 = snapshot.id;
10368
+ }
10369
+ previousSnapshotPresent = snapshotText !== void 0;
10370
+ if (entry.previous_content_hash !== previousHash) {
10371
+ return `content-hash chain broken at ${entry.name} \u2014 migrations must be appended in order by \`seq-studio orm diff\``;
10372
+ }
10373
+ previousHash = entry.content_hash;
10374
+ }
10375
+ return void 0;
10376
+ }
10377
+ async function readPrunableSnapshotText(dir, ordinal) {
10378
+ const path = join(dir, MIGRATIONS_DIRNAME, META_DIRNAME, `${pad(ordinal)}.snapshot.json`);
10379
+ return readFile(path, "utf8").catch((error) => {
10380
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return void 0;
10381
+ throw error;
10382
+ });
10383
+ }
10288
10384
  async function readMigrationSql(dir, entry) {
10289
10385
  const path = join(dir, MIGRATIONS_DIRNAME, `${entry.name}.sql`);
10290
10386
  return readFile(path, "utf8").catch(() => {
@@ -10445,30 +10541,10 @@ async function checkWorkspace({
10445
10541
  log(`no committed migrations for '${loaded.definition.namespace}' \u2014 run \`seq-studio orm diff\` and commit the result`);
10446
10542
  return 1;
10447
10543
  }
10448
- let previousId3;
10449
- let previousHash = null;
10450
- for (const entry of workspace.journal.entries) {
10451
- const sql4 = await readMigrationSql(dir, entry);
10452
- if (checksumOf(sql4) !== entry.checksum) {
10453
- log(`${MIGRATIONS_DIRNAME}/${entry.name}.sql does not match its journal checksum \u2014 committed migrations must not be edited`);
10454
- return 1;
10455
- }
10456
- const snapshotText = await readSnapshotText(dir, entry.ordinal);
10457
- if (checksumOf(snapshotText) !== entry.snapshot_checksum) {
10458
- 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)`);
10459
- return 1;
10460
- }
10461
- const snapshot = JSON.parse(snapshotText);
10462
- if (entry.ordinal > 1 && snapshot.prevId !== previousId3) {
10463
- log(`snapshot chain broken at ${entry.name} \u2014 regenerate the workspace with \`seq-studio orm diff\``);
10464
- return 1;
10465
- }
10466
- if (entry.previous_content_hash !== previousHash) {
10467
- log(`content-hash chain broken at ${entry.name} \u2014 migrations must be appended in order by \`seq-studio orm diff\``);
10468
- return 1;
10469
- }
10470
- previousId3 = snapshot.id;
10471
- previousHash = entry.content_hash;
10544
+ const broken = await verifyCommittedLedger(dir, workspace.journal.entries);
10545
+ if (broken) {
10546
+ log(broken);
10547
+ return 1;
10472
10548
  }
10473
10549
  if (tip.content_hash !== loaded.contentHash) {
10474
10550
  log(`definitions changed since ${tip.name} \u2014 run \`seq-studio orm diff\` and commit the new migration`);
@@ -11347,6 +11423,22 @@ function parseArgs(argv) {
11347
11423
  // src/cli.ts
11348
11424
  var BASE_URL_ENV = "SEQUENCE_ORM_BASE_URL";
11349
11425
  var TOKEN_ENV = "SEQUENCE_ORM_TOKEN";
11426
+ var SOURCE_ENV = "SEQUENCE_ORM_SOURCE";
11427
+ var UNVERIFIED_SOURCE_REASON_ENV = "SEQUENCE_ORM_UNVERIFIED_SOURCE_REASON";
11428
+ function applySourceFromEnv(env = process.env) {
11429
+ const reason = env[UNVERIFIED_SOURCE_REASON_ENV]?.trim();
11430
+ const override = reason ? { unverified_source_reason: reason } : {};
11431
+ const raw = env[SOURCE_ENV];
11432
+ if (!raw) return override;
11433
+ let parsed;
11434
+ try {
11435
+ parsed = JSON.parse(raw);
11436
+ } catch {
11437
+ throw new Error(`${SOURCE_ENV} is not valid JSON`);
11438
+ }
11439
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error(`${SOURCE_ENV} must be a JSON object`);
11440
+ return { source: Object.fromEntries(Object.entries(parsed)), ...override };
11441
+ }
11350
11442
  var ORM_MODULE_PATH_ENV = "SEQUENCE_ORM_MODULE_PATH";
11351
11443
  var USAGE = `usage:
11352
11444
  seq-studio orm init <dir> scaffold a namespace package (v2: TypeScript schema + persisted operations)
@@ -11664,15 +11756,21 @@ async function generateCommand(rest) {
11664
11756
  await writeGenerateOutputs(dir, compiled.project);
11665
11757
  const { loaded, manifest } = compiled.project;
11666
11758
  console.log(
11667
- `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))`
11759
+ `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))`
11668
11760
  );
11669
11761
  return runConsumerCodegen(dir);
11670
11762
  }
11671
11763
  async function writeGenerateOutputs(dir, project) {
11672
- const { loaded, sdl, manifest } = project;
11764
+ const { loaded, sdl, manifest, documents } = project;
11673
11765
  await writeFile(join(dir, SCHEMA_GRAPHQL_FILE), sdl);
11674
11766
  await writeFile(join(dir, "namespace.contract.json"), `${JSON.stringify(loaded.canonical, null, 2)}
11675
11767
  `);
11768
+ const sortedDocuments = [...documents].sort((a, b) => a.path.localeCompare(b.path)).map(({ path, contents }) => ({ path, contents }));
11769
+ await writeFile(
11770
+ join(dir, OPERATION_DOCUMENTS_FILE),
11771
+ `${JSON.stringify({ documentsVersion: OPERATION_DOCUMENTS_VERSION, documents: sortedDocuments }, null, 2)}
11772
+ `
11773
+ );
11676
11774
  const operations = Object.entries(manifest.operations).map(([name2, op]) => ({
11677
11775
  name: name2,
11678
11776
  kind: op.kind,
@@ -11878,8 +11976,20 @@ function reportRegistryState(registry) {
11878
11976
  if (match && match.id === registry.activeVersionId) console.log("-- registry: this content is the ACTIVE version");
11879
11977
  else if (match) console.log(`-- registry: registered (status ${match.status}), not active`);
11880
11978
  else console.log(`-- registry: new content (active is ${active ? active.content_hash.slice(0, 12) : "none"})`);
11979
+ if (registry.activeActivation) console.log(`-- registry: ${describeActivation(registry.activeActivation)}`);
11881
11980
  return 0;
11882
11981
  }
11982
+ function describeActivation(activation) {
11983
+ const since = `live since ${activation.activated_at.slice(0, 16).replace("T", " ")}Z`;
11984
+ const by = `by ${activation.actor_sub.replace(/^google-apps\|/, "")}`;
11985
+ if (activation.source_commit && activation.source_branch) {
11986
+ const at = activation.source_path ? ` at ${activation.source_path}` : "";
11987
+ return `${since} from ${activation.source_commit.slice(0, 12)} on ${activation.source_branch}${at} (${activation.provenance_class}) ${by}`;
11988
+ }
11989
+ const failure = activation.reported_source?.verification_failure;
11990
+ const why = typeof failure === "string" ? `: ${failure}` : "";
11991
+ return `${since} from an untraced source (${activation.provenance_class}${why}) ${by}`;
11992
+ }
11883
11993
  var META_DIRNAME2 = "meta";
11884
11994
  var JOURNAL_FILENAME2 = "journal.json";
11885
11995
  var padOrdinal = (ordinal) => String(ordinal).padStart(4, "0");
@@ -11967,30 +12077,10 @@ async function checkWorkspaceV2({
11967
12077
  log(`no committed migrations for '${namespace}' \u2014 run \`seq-studio orm diff\` and commit the result`);
11968
12078
  return 1;
11969
12079
  }
11970
- let previousId3;
11971
- let previousHash = null;
11972
- for (const entry of workspace.journal.entries) {
11973
- const sql4 = await readMigrationSql(dir, entry);
11974
- if (checksumOf(sql4) !== entry.checksum) {
11975
- log(`${MIGRATIONS_DIRNAME}/${entry.name}.sql does not match its journal checksum \u2014 committed migrations must not be edited`);
11976
- return 1;
11977
- }
11978
- const snapshotText = await readSnapshotText(dir, entry.ordinal);
11979
- if (checksumOf(snapshotText) !== entry.snapshot_checksum) {
11980
- 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)`);
11981
- return 1;
11982
- }
11983
- const snapshot = JSON.parse(snapshotText);
11984
- if (entry.ordinal > 1 && snapshot.prevId !== previousId3) {
11985
- log(`snapshot chain broken at ${entry.name} \u2014 regenerate the workspace with \`seq-studio orm diff\``);
11986
- return 1;
11987
- }
11988
- if (entry.previous_content_hash !== previousHash) {
11989
- log(`content-hash chain broken at ${entry.name} \u2014 migrations must be appended in order by \`seq-studio orm diff\``);
11990
- return 1;
11991
- }
11992
- previousId3 = snapshot.id;
11993
- previousHash = entry.content_hash;
12080
+ const broken = await verifyCommittedLedger(dir, workspace.journal.entries);
12081
+ if (broken) {
12082
+ log(broken);
12083
+ return 1;
11994
12084
  }
11995
12085
  if (tip.content_hash !== loaded.hash) {
11996
12086
  log(`definitions changed since ${tip.name} \u2014 run \`seq-studio orm diff\` and commit the new migration`);
@@ -12104,7 +12194,8 @@ async function applyV2Command(rest, dir) {
12104
12194
  const applied = await apiRequestStrict("POST", `/api/orm/namespaces/${namespace}/apply`, {
12105
12195
  version_id: registered.version_id,
12106
12196
  ...allowDestructive ? { allow_destructive: true } : {},
12107
- ...schemaAlreadyActive && registered.operation_set_id ? { operation_set_id: registered.operation_set_id } : {}
12197
+ ...schemaAlreadyActive && registered.operation_set_id ? { operation_set_id: registered.operation_set_id } : {},
12198
+ ...applySourceFromEnv()
12108
12199
  });
12109
12200
  const live = { namespace, contentHash: registered.content_hash };
12110
12201
  if ("ops_only" in applied) {
@@ -12561,7 +12652,8 @@ async function applyCommand(rest) {
12561
12652
  if (dryRun) return dryRunRehearsal({ namespace, versionId: registered.version_id, contentHash: registered.content_hash });
12562
12653
  const applied = await apiRequestStrict("POST", `/api/orm/namespaces/${namespace}/apply`, {
12563
12654
  version_id: registered.version_id,
12564
- ...allowDestructive ? { allow_destructive: true } : {}
12655
+ ...allowDestructive ? { allow_destructive: true } : {},
12656
+ ...applySourceFromEnv()
12565
12657
  });
12566
12658
  if ("status" in applied) {
12567
12659
  const verdict = await awaitQueuedApply({ dir, namespace, contentHash: registered.content_hash, runId: applied.run_id, applyWait });
@@ -12741,11 +12833,13 @@ async function apiRequestStrict(method, path, body) {
12741
12833
  }
12742
12834
  async function fetchRegistryState(namespace, contentHash) {
12743
12835
  if (!process.env[BASE_URL_ENV]?.trim() || !await resolveToken()) return { kind: "offline" };
12744
- const body = await apiRequest("GET", `/api/orm/namespaces/${namespace}?hash=${encodeURIComponent(contentHash)}`, void 0, { allow404: true });
12836
+ const body = await apiRequest("GET", `/api/orm/namespaces/${namespace}?hash=${encodeURIComponent(contentHash)}&activations_limit=1`, void 0, { allow404: true });
12745
12837
  if (!body) return { kind: "missing" };
12838
+ const latest = body.activations?.[0];
12746
12839
  return {
12747
12840
  kind: "found",
12748
12841
  activeVersionId: body.namespace.active_version_id,
12842
+ activeActivation: latest && latest.id === body.namespace.active_activation_id ? latest : null,
12749
12843
  versions: body.versions,
12750
12844
  hashVersion: body.hash_version ?? null
12751
12845
  };
@@ -12760,6 +12854,6 @@ function positional(rest) {
12760
12854
  }
12761
12855
  var firstLine2 = (sql4) => sql4.split("\n")[0] ?? sql4;
12762
12856
 
12763
- export { BASE_URL_ENV, ORM_MODULE_PATH_ENV, TOKEN_ENV, errorDetail, parseApplyWait, runCli, setTokenProvider };
12857
+ export { BASE_URL_ENV, ORM_MODULE_PATH_ENV, SOURCE_ENV, TOKEN_ENV, UNVERIFIED_SOURCE_REASON_ENV, applySourceFromEnv, describeActivation, errorDetail, parseApplyWait, runCli, setTokenProvider };
12764
12858
  //# sourceMappingURL=cli.js.map
12765
12859
  //# sourceMappingURL=cli.js.map