@sixb/sqlite 0.1.9 → 0.1.10

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.
package/dist/index.js CHANGED
@@ -3932,6 +3932,53 @@ DROP TABLE executions;
3932
3932
  ALTER TABLE executions_v2 RENAME TO executions;
3933
3933
  `;
3934
3934
 
3935
+ // src/migrations/038-projection-source-roots.sql
3936
+ var _038_projection_source_roots_default = `ALTER TABLE ontology_sources ADD COLUMN base_materialization_id TEXT;
3937
+ ALTER TABLE ontology_sources ADD COLUMN base_commit_id TEXT
3938
+ CHECK ((base_materialization_id IS NULL) = (base_commit_id IS NULL));
3939
+
3940
+ CREATE TABLE ontology_source_roots (
3941
+ project_id TEXT NOT NULL,
3942
+ source_id TEXT NOT NULL,
3943
+ materialization_id TEXT NOT NULL,
3944
+ root_sort_key TEXT NOT NULL,
3945
+ root_kind TEXT NOT NULL CHECK (root_kind IN ('object', 'link')),
3946
+ root_key TEXT NOT NULL CHECK (json_valid(root_key)),
3947
+ root TEXT NOT NULL CHECK (json_valid(root)),
3948
+ staging_ordinal INTEGER NOT NULL CHECK (staging_ordinal >= 0),
3949
+ deleted INTEGER NOT NULL DEFAULT 0 CHECK (deleted IN (0, 1)),
3950
+ active INTEGER NOT NULL DEFAULT 0 CHECK (active IN (0, 1)),
3951
+ retired_at TEXT,
3952
+ PRIMARY KEY (project_id, source_id, materialization_id, root_sort_key),
3953
+ UNIQUE (project_id, source_id, materialization_id, staging_ordinal),
3954
+ FOREIGN KEY (project_id, source_id, materialization_id)
3955
+ REFERENCES ontology_sources(project_id, source_id, materialization_id) ON DELETE RESTRICT,
3956
+ CHECK (active = 0 OR (deleted = 0 AND retired_at IS NULL))
3957
+ );
3958
+ CREATE UNIQUE INDEX idx_ontology_source_roots_active
3959
+ ON ontology_source_roots (project_id, source_id, root_sort_key) WHERE active = 1;
3960
+ CREATE INDEX idx_ontology_source_roots_lookup
3961
+ ON ontology_source_roots (project_id, root_sort_key, source_id) WHERE active = 1;
3962
+ CREATE INDEX idx_ontology_source_roots_cleanup
3963
+ ON ontology_source_roots (project_id, retired_at, source_id, materialization_id, root_sort_key)
3964
+ WHERE active = 0 AND retired_at IS NOT NULL;
3965
+
3966
+ INSERT INTO ontology_source_roots (
3967
+ project_id, source_id, materialization_id, root_sort_key, root_kind, root_key, root,
3968
+ staging_ordinal, active, retired_at
3969
+ )
3970
+ SELECT DISTINCT rows.project_id, rows.source_id, rows.materialization_id,
3971
+ rows.root_sort_key, rows.root_kind, rows.root_key, rows.root, rows.staging_ordinal,
3972
+ sources.status = 'active', sources.terminal_at
3973
+ FROM ontology_source_rows AS rows
3974
+ JOIN ontology_sources AS sources USING (project_id, source_id, materialization_id);
3975
+
3976
+ ANALYZE ontology_source_roots;
3977
+
3978
+ ALTER TABLE projection_runs ADD COLUMN source_changes_read INTEGER
3979
+ CHECK (source_changes_read IS NULL OR source_changes_read >= 0);
3980
+ `;
3981
+
3935
3982
  // src/migrations/037-execution-requester-groups.sql
3936
3983
  var _037_execution_requester_groups_default = `ALTER TABLE executions ADD COLUMN requester_group_ids TEXT NOT NULL DEFAULT '[]';
3937
3984
 
@@ -3959,6 +4006,61 @@ ALTER TABLE workflow_runs DROP COLUMN requester_group_ids;
3959
4006
  ALTER TABLE agent_runs DROP COLUMN requester_group_ids;
3960
4007
  `;
3961
4008
 
4009
+ // src/migrations/039-connector-optional-pkce.sql
4010
+ var _039_connector_optional_pkce_default = `-- Rebuild to remove NOT NULL while preserving existing attempts and their constraints.
4011
+ CREATE TABLE connector_authorization_attempts_optional_pkce (
4012
+ project_id TEXT NOT NULL,
4013
+ connector_id TEXT NOT NULL,
4014
+ id TEXT NOT NULL,
4015
+ slot TEXT NOT NULL,
4016
+ initiated_by_execution_id TEXT NOT NULL,
4017
+ state_hash TEXT NOT NULL,
4018
+ code_verifier TEXT CHECK (
4019
+ code_verifier IS NULL
4020
+ OR (json_valid(code_verifier) AND json_type(code_verifier) = 'object')
4021
+ ),
4022
+ redirect_uri TEXT NOT NULL,
4023
+ connection_run_id TEXT,
4024
+ return_to TEXT,
4025
+ callback_binding_hash TEXT,
4026
+ reauthorization_id TEXT,
4027
+ reauthorization_revision INTEGER CHECK (
4028
+ reauthorization_revision IS NULL OR reauthorization_revision >= 0
4029
+ ),
4030
+ reauthorization_connection_ids TEXT CHECK (
4031
+ reauthorization_connection_ids IS NULL
4032
+ OR (json_valid(reauthorization_connection_ids) AND json_type(reauthorization_connection_ids) = 'array')
4033
+ ),
4034
+ created_at TEXT NOT NULL,
4035
+ expires_at TEXT NOT NULL,
4036
+ PRIMARY KEY (project_id, connector_id, id),
4037
+ UNIQUE (project_id, id),
4038
+ UNIQUE (project_id, connector_id, connection_run_id),
4039
+ FOREIGN KEY (project_id, connector_id, connection_run_id)
4040
+ REFERENCES connector_connection_runs (project_id, connector_id, id)
4041
+ ON DELETE RESTRICT,
4042
+ CHECK (
4043
+ (connection_run_id IS NULL AND return_to IS NULL AND callback_binding_hash IS NULL)
4044
+ OR
4045
+ (connection_run_id IS NOT NULL AND return_to IS NOT NULL AND callback_binding_hash IS NOT NULL)
4046
+ ),
4047
+ CHECK (
4048
+ (reauthorization_id IS NULL AND reauthorization_revision IS NULL AND reauthorization_connection_ids IS NULL)
4049
+ OR
4050
+ (reauthorization_id IS NOT NULL AND reauthorization_revision IS NOT NULL AND reauthorization_connection_ids IS NOT NULL)
4051
+ )
4052
+ );
4053
+
4054
+ INSERT INTO connector_authorization_attempts_optional_pkce
4055
+ SELECT * FROM connector_authorization_attempts;
4056
+
4057
+ DROP TABLE connector_authorization_attempts;
4058
+ ALTER TABLE connector_authorization_attempts_optional_pkce RENAME TO connector_authorization_attempts;
4059
+
4060
+ CREATE INDEX idx_connector_attempts_expiry
4061
+ ON connector_authorization_attempts (project_id, connector_id, expires_at);
4062
+ `;
4063
+
3962
4064
  // src/migrations.ts
3963
4065
  var MIGRATIONS_TABLE_SQL = `
3964
4066
  CREATE TABLE IF NOT EXISTS sixb_migrations (
@@ -4030,7 +4132,9 @@ var sqliteStorageMigrations = defineMigrations({
4030
4132
  sqliteSql("034-device-authorizations", _034_device_authorizations_default),
4031
4133
  sqliteSql("035-share-grants", _035_share_grants_default),
4032
4134
  sqliteSql("036-share-sessions", _036_share_sessions_default),
4033
- sqliteSql("037-execution-requester-groups", _037_execution_requester_groups_default)
4135
+ sqliteSql("037-execution-requester-groups", _037_execution_requester_groups_default),
4136
+ sqliteSql("038-projection-source-roots", _038_projection_source_roots_default),
4137
+ sqliteSql("039-connector-optional-pkce", _039_connector_optional_pkce_default)
4034
4138
  ]
4035
4139
  });
4036
4140
  function sqliteStoragePath(basePath) {
@@ -9014,7 +9118,7 @@ class SqliteConnectorConnectionPersistence {
9014
9118
  callback_binding_hash, reauthorization_id, reauthorization_revision,
9015
9119
  reauthorization_connection_ids, created_at, expires_at
9016
9120
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
9017
- `).run(record.projectId, record.connectorId, record.id, record.slot, record.initiatedByExecutionId, record.stateHash, json(record.codeVerifier), record.redirectUri, record.connectionRunId ?? null, record.returnTo ?? null, record.callbackBindingHash ?? null, record.reauthorizationId ?? null, record.reauthorizationRevision ?? null, record.reauthorizationConnectionIds ? json(record.reauthorizationConnectionIds) : null, iso(record.createdAt), iso(record.expiresAt));
9121
+ `).run(record.projectId, record.connectorId, record.id, record.slot, record.initiatedByExecutionId, record.stateHash, record.codeVerifier === undefined ? null : json(record.codeVerifier), record.redirectUri, record.connectionRunId ?? null, record.returnTo ?? null, record.callbackBindingHash ?? null, record.reauthorizationId ?? null, record.reauthorizationRevision ?? null, record.reauthorizationConnectionIds ? json(record.reauthorizationConnectionIds) : null, iso(record.createdAt), iso(record.expiresAt));
9018
9122
  return true;
9019
9123
  } catch (error) {
9020
9124
  if (isUniqueConstraintError2(error))
@@ -9195,7 +9299,7 @@ function authorizationAttemptFromRow(row) {
9195
9299
  slot: row.slot,
9196
9300
  initiatedByExecutionId: row.initiated_by_execution_id,
9197
9301
  stateHash: row.state_hash,
9198
- codeVerifier: parseJson(row.code_verifier),
9302
+ ...row.code_verifier === null ? {} : { codeVerifier: parseJson(row.code_verifier) },
9199
9303
  redirectUri: row.redirect_uri,
9200
9304
  ...row.connection_run_id === null ? {} : { connectionRunId: row.connection_run_id },
9201
9305
  ...row.return_to === null ? {} : { returnTo: row.return_to },
@@ -11387,6 +11491,12 @@ function sourceRecord(row) {
11387
11491
  projectionRevision: row.projection_revision,
11388
11492
  ownershipHash: row.ownership_hash,
11389
11493
  ontologyRevision: row.ontology_revision,
11494
+ ...row.base_materialization_id === null ? {} : {
11495
+ base: {
11496
+ materializationId: row.base_materialization_id,
11497
+ lastCommitId: row.base_commit_id
11498
+ }
11499
+ },
11390
11500
  rootCount: row.root_count,
11391
11501
  assertionCount: row.assertion_count,
11392
11502
  createdAt: row.created_at,
@@ -11558,7 +11668,7 @@ import {
11558
11668
  linkRefKey as linkRefKey3,
11559
11669
  linkRefSortKey,
11560
11670
  linkScopeSortKey as linkScopeSortKey2,
11561
- MaterializationConflictError as MaterializationConflictError5,
11671
+ MaterializationConflictError as MaterializationConflictError6,
11562
11672
  objectRefKey as objectRefKey3,
11563
11673
  objectRefSortKey,
11564
11674
  telemetryPointKey as telemetryPointKey2,
@@ -11578,7 +11688,7 @@ import {
11578
11688
 
11579
11689
  // src/ontology-storage/materialization-session.ts
11580
11690
  import {
11581
- MaterializationConflictError as MaterializationConflictError3
11691
+ MaterializationConflictError as MaterializationConflictError4
11582
11692
  } from "@sixb/core/internal/materialization";
11583
11693
  import {
11584
11694
  correlateMaterializationChunk,
@@ -11593,9 +11703,9 @@ import {
11593
11703
  import {
11594
11704
  linkRefKey,
11595
11705
  linkScopeSortKey,
11596
- MaterializationConflictError as MaterializationConflictError2,
11706
+ MaterializationConflictError as MaterializationConflictError3,
11597
11707
  objectRefKey,
11598
- projectionEntityKey,
11708
+ projectionEntityKey as projectionEntityKey2,
11599
11709
  telemetryPointSortKey
11600
11710
  } from "@sixb/core/internal/materialization";
11601
11711
  import {
@@ -11604,6 +11714,132 @@ import {
11604
11714
  linkSlotOverrideValue,
11605
11715
  startScopeAccumulator
11606
11716
  } from "@sixb/core/internal/ontology-storage-provider";
11717
+
11718
+ // src/ontology-storage/source-roots.ts
11719
+ import {
11720
+ MaterializationConflictError as MaterializationConflictError2,
11721
+ MaterializationValidationError,
11722
+ projectionEntityKey
11723
+ } from "@sixb/core/internal/materialization";
11724
+ import { sourceStageRoots, utf8SortKey } from "@sixb/core/internal/ontology-storage-provider";
11725
+ function replacementSourceRows(input) {
11726
+ const roots = input.incremental ? `ontology_source_roots AS changed
11727
+ CROSS JOIN ontology_source_roots AS roots ON roots.project_id = changed.project_id
11728
+ AND roots.source_id = changed.source_id AND roots.root_sort_key = changed.root_sort_key AND roots.active = 1` : "ontology_source_roots AS roots";
11729
+ const selected = input.incremental ? "changed" : "roots";
11730
+ return {
11731
+ sql: `SELECT * FROM ontology_source_rows WHERE project_id = ? AND source_id = ? AND materialization_id = ?
11732
+ UNION ALL
11733
+ SELECT rows.* FROM ${roots}
11734
+ CROSS JOIN ontology_source_rows AS rows ON rows.project_id = roots.project_id
11735
+ AND rows.source_id = roots.source_id AND rows.materialization_id = roots.materialization_id
11736
+ AND rows.root_sort_key = roots.root_sort_key
11737
+ WHERE ${selected}.project_id = ? AND ${selected}.source_id = ?
11738
+ AND ${input.incremental ? "changed.materialization_id = ?" : "roots.active = 1"}`,
11739
+ values: [
11740
+ input.projectId,
11741
+ input.sourceId,
11742
+ input.materializationId,
11743
+ input.projectId,
11744
+ input.sourceId,
11745
+ ...input.incremental ? [input.materializationId] : []
11746
+ ]
11747
+ };
11748
+ }
11749
+ function stageSourceRoots(db, manifest, input) {
11750
+ const roots = sourceStageRoots(manifest.projection_kind, input);
11751
+ if (!manifest.base_materialization_id && roots.some((root) => root.deleted)) {
11752
+ throw new MaterializationValidationError("Root deletions require a source delta base.");
11753
+ }
11754
+ const existing = db.query(`SELECT staging_ordinal, deleted FROM ontology_source_roots
11755
+ WHERE project_id = ? AND source_id = ? AND materialization_id = ? AND root_sort_key = ?`);
11756
+ const insert = db.query(`INSERT INTO ontology_source_roots (
11757
+ project_id, source_id, materialization_id, root_sort_key, root_kind, root_key, root, staging_ordinal, deleted
11758
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT DO NOTHING RETURNING root_sort_key`);
11759
+ for (const root of roots) {
11760
+ const inserted = insert.get(input.projectId, input.source.projectionId, input.materializationId, root.sortKey, root.root.kind, root.rootKey, canonicalJson(root.root), root.stagingOrdinal, Number(root.deleted));
11761
+ if (inserted)
11762
+ continue;
11763
+ const previous = existing.get(input.projectId, input.source.projectionId, input.materializationId, root.sortKey);
11764
+ if (!previous || previous.staging_ordinal !== root.stagingOrdinal || Boolean(previous.deleted) !== root.deleted) {
11765
+ throw new MaterializationValidationError("Source materialization repeats root or stream ordinal with different content.");
11766
+ }
11767
+ }
11768
+ }
11769
+ function activateSourceRoots(db, projectId, candidate, activation) {
11770
+ if (candidate.base_materialization_id !== null && (candidate.base_materialization_id !== activation.expected.activeMaterializationId || candidate.base_commit_id !== activation.expected.lastCommitId))
11771
+ throw new MaterializationConflictError2("projection-fence", "Source delta base changed before activation.");
11772
+ if (candidate.base_materialization_id !== null && candidate.root_count === 0)
11773
+ return;
11774
+ const scope = [projectId, activation.source.projectionId, activation.materializationId];
11775
+ db.query(`UPDATE ontology_source_roots AS roots SET active = 0, retired_at = ?
11776
+ WHERE project_id = ? AND source_id = ? AND active = 1
11777
+ ${candidate.base_materialization_id === null ? "" : `AND root_sort_key IN (
11778
+ SELECT root_sort_key FROM ontology_source_roots
11779
+ WHERE project_id = ? AND source_id = ? AND materialization_id = ?)`}`).run(activation.updatedAt, projectId, activation.source.projectionId, ...candidate.base_materialization_id === null ? [] : [...scope]);
11780
+ db.query(`UPDATE ontology_source_roots SET active = CASE WHEN deleted = 1 THEN 0 ELSE 1 END,
11781
+ retired_at = CASE WHEN deleted = 1 THEN ? ELSE NULL END
11782
+ WHERE project_id = ? AND source_id = ? AND materialization_id = ?`).run(activation.updatedAt, ...scope);
11783
+ }
11784
+ function assertSourceRootCoverage(db, manifest) {
11785
+ const invalid = db.query(`SELECT 1 FROM ontology_source_roots AS roots
11786
+ WHERE project_id = ? AND source_id = ? AND materialization_id = ?
11787
+ AND roots.deleted = EXISTS (
11788
+ SELECT 1 FROM ontology_source_rows AS rows
11789
+ WHERE rows.project_id = roots.project_id AND rows.source_id = roots.source_id
11790
+ AND rows.materialization_id = roots.materialization_id AND rows.root_sort_key = roots.root_sort_key
11791
+ ) LIMIT 1`).get(manifest.project_id, manifest.source_id, manifest.materialization_id);
11792
+ if (invalid)
11793
+ throw new MaterializationValidationError("Source roots must have complete assertions or an explicit deletion, never both.");
11794
+ }
11795
+ function activeSourceRows(db, projectId, refs) {
11796
+ const keys = refs.flatMap((entity) => {
11797
+ const entitySortKey = utf8SortKey(projectionEntityKey(entity));
11798
+ const roots = entity.kind === "object" ? [entitySortKey] : [
11799
+ entitySortKey,
11800
+ utf8SortKey(projectionEntityKey({ kind: "object", ref: entity.ref.source }))
11801
+ ];
11802
+ return roots.map((rootSortKey) => ({ rootSortKey, entitySortKey }));
11803
+ });
11804
+ return db.query(`WITH requested AS (
11805
+ SELECT DISTINCT json_extract(value, '$.rootSortKey') AS root_sort_key,
11806
+ json_extract(value, '$.entitySortKey') AS entity_sort_key FROM json_each(?)
11807
+ ) SELECT rows.* FROM requested
11808
+ CROSS JOIN ontology_source_roots AS roots ON roots.project_id = ?
11809
+ AND roots.active = 1 AND roots.root_sort_key = requested.root_sort_key
11810
+ CROSS JOIN ontology_source_rows AS rows ON rows.project_id = roots.project_id AND rows.source_id = roots.source_id
11811
+ AND rows.materialization_id = roots.materialization_id AND rows.entity_sort_key = requested.entity_sort_key
11812
+ AND rows.root_sort_key = roots.root_sort_key`).all(canonicalJson(keys), projectId);
11813
+ }
11814
+ function replacementAssertionRows(db, input) {
11815
+ const keys = input.refs.map((ref) => ({
11816
+ entityKind: ref.kind,
11817
+ entityKey: projectionEntityKey(ref),
11818
+ rootKeys: ref.kind === "object" ? [utf8SortKey(projectionEntityKey(ref))] : [
11819
+ utf8SortKey(projectionEntityKey(ref)),
11820
+ utf8SortKey(projectionEntityKey({ kind: "object", ref: ref.ref.source }))
11821
+ ]
11822
+ }));
11823
+ const previous = !input.includePrevious ? "" : `UNION ALL
11824
+ SELECT rows.* FROM requested_entities AS requested
11825
+ CROSS JOIN ontology_source_roots AS roots ON roots.project_id = ? AND roots.source_id = ?
11826
+ AND roots.active = 1 AND roots.root_sort_key IN (SELECT value FROM json_each(requested.root_keys))
11827
+ CROSS JOIN ontology_source_rows AS rows ON rows.project_id = roots.project_id AND rows.source_id = roots.source_id
11828
+ AND rows.materialization_id = roots.materialization_id AND rows.root_sort_key = roots.root_sort_key
11829
+ AND rows.entity_kind = requested.entity_kind AND rows.entity_key = requested.entity_key
11830
+ ${!input.incremental ? "" : `WHERE EXISTS (SELECT 1 FROM ontology_source_roots AS changed
11831
+ WHERE changed.project_id = roots.project_id AND changed.source_id = roots.source_id
11832
+ AND changed.materialization_id = ? AND changed.root_sort_key = roots.root_sort_key)`}`;
11833
+ return db.query(`WITH requested_entities AS (
11834
+ SELECT DISTINCT json_extract(value, '$.entityKind') AS entity_kind,
11835
+ json_extract(value, '$.entityKey') AS entity_key, json_extract(value, '$.rootKeys') AS root_keys FROM json_each(?)
11836
+ ) SELECT rows.* FROM requested_entities AS requested
11837
+ CROSS JOIN ontology_source_rows AS rows ON rows.project_id = ? AND rows.source_id = ?
11838
+ AND rows.materialization_id = ? AND rows.entity_kind = requested.entity_kind AND rows.entity_key = requested.entity_key
11839
+ ${previous}`).all(canonicalJson(keys), input.projectId, input.sourceId, input.materializationId, ...input.includePrevious ? [input.projectId, input.sourceId, ...input.incremental ? [input.materializationId] : []] : []);
11840
+ }
11841
+
11842
+ // src/ontology-storage/materialization-state.ts
11607
11843
  var SQLITE_MATERIALIZATION_WORK_TABLE = "ontology_materialization_work";
11608
11844
  var SQLITE_REPLACEMENT_WORK_TABLE = "ontology_replacement_work";
11609
11845
  function linkScopeRequestJson(scopes) {
@@ -11640,18 +11876,7 @@ class SqliteMaterializationStateReader {
11640
11876
  ON objects.project_id = ?
11641
11877
  AND objects.object_type_id = requested.object_type_id
11642
11878
  AND objects.primary_id = requested.primary_id`).all(requested, this.projectId);
11643
- const sourceRows = this.db.query(`WITH requested AS (${requestedObjects})
11644
- SELECT rows.* FROM requested
11645
- CROSS JOIN ontology_source_rows AS rows
11646
- ON rows.project_id = ?
11647
- AND rows.entity_kind = 'object'
11648
- AND rows.object_type_id = requested.object_type_id
11649
- AND rows.primary_id = requested.primary_id
11650
- JOIN ontology_sources AS sources
11651
- ON sources.project_id = rows.project_id
11652
- AND sources.source_id = rows.source_id
11653
- AND sources.materialization_id = rows.materialization_id
11654
- WHERE sources.status = 'active'`).all(requested, this.projectId);
11879
+ const sourceRows = activeSourceRows(this.db, this.projectId, refs.map((ref) => ({ kind: "object", ref })));
11655
11880
  const overrideRows = this.db.query(`WITH requested AS (${requestedObjects})
11656
11881
  SELECT overrides.* FROM requested
11657
11882
  CROSS JOIN ontology_object_overrides AS overrides
@@ -11690,7 +11915,7 @@ class SqliteMaterializationStateReader {
11690
11915
  const effectiveRow = effective.get(key);
11691
11916
  return {
11692
11917
  ref: structuredClone(ref),
11693
- source: sourceObject(sources.get(projectionEntityKey({ kind: "object", ref }))),
11918
+ source: sourceObject(sources.get(projectionEntityKey2({ kind: "object", ref }))),
11694
11919
  override: overrides.get(key) ?? null,
11695
11920
  effective: effectiveRow ? effectiveObjectSnapshot(effectiveRow) : null,
11696
11921
  latestTelemetry: telemetry.get(key) ?? []
@@ -11735,21 +11960,7 @@ class SqliteMaterializationStateReader {
11735
11960
  AND links.link_id = requested.link_id
11736
11961
  AND links.target_type_id = requested.target_type_id
11737
11962
  AND links.target_id = requested.target_id`).all(requested, this.projectId);
11738
- const sourceRows = this.db.query(`WITH requested AS (${sourceRequest})
11739
- SELECT rows.* FROM requested
11740
- CROSS JOIN ontology_source_rows AS rows
11741
- ON rows.project_id = ?
11742
- AND rows.entity_kind = 'link'
11743
- AND rows.source_type_id = requested.source_type_id
11744
- AND rows.source_primary_id = requested.source_primary_id
11745
- AND rows.link_id = requested.link_id
11746
- AND rows.target_type_id = requested.target_type_id
11747
- AND rows.target_primary_id = requested.target_primary_id
11748
- JOIN ontology_sources AS sources
11749
- ON sources.project_id = rows.project_id
11750
- AND sources.source_id = rows.source_id
11751
- AND sources.materialization_id = rows.materialization_id
11752
- WHERE sources.status = 'active'`).all(requested, this.projectId);
11963
+ const sourceRows = activeSourceRows(this.db, this.projectId, refs.map((ref) => ({ kind: "link", ref })));
11753
11964
  const overrideRows = this.db.query(`WITH requested AS (${sourceRequest})
11754
11965
  SELECT overrides.* FROM requested
11755
11966
  CROSS JOIN ontology_link_overrides AS overrides
@@ -11780,7 +11991,7 @@ class SqliteMaterializationStateReader {
11780
11991
  const effectiveRow = effective.get(key);
11781
11992
  return {
11782
11993
  ref: structuredClone(ref),
11783
- source: sourceLink(sources.get(projectionEntityKey({ kind: "link", ref }))),
11994
+ source: sourceLink(sources.get(projectionEntityKey2({ kind: "link", ref }))),
11784
11995
  override: overrides.get(key) ?? null,
11785
11996
  slotOverride: slotOverrides.get(linkScopeSortKey(ref.source, ref.linkId)) ?? null,
11786
11997
  effective: effectiveRow ? effectiveLinkSnapshot(effectiveRow) : null
@@ -11845,11 +12056,12 @@ class SqliteMaterializationStateReader {
11845
12056
  AND rows.source_type_id = requested.source_type_id
11846
12057
  AND rows.source_primary_id = requested.source_primary_id
11847
12058
  AND rows.link_id = requested.link_id
11848
- JOIN ontology_sources AS sources
12059
+ JOIN ontology_source_roots AS sources
11849
12060
  ON sources.project_id = rows.project_id
11850
12061
  AND sources.source_id = rows.source_id
11851
12062
  AND sources.materialization_id = rows.materialization_id
11852
- WHERE sources.status = 'active'`).all(requestedJson, this.projectId);
12063
+ AND sources.root_sort_key = rows.root_sort_key
12064
+ WHERE sources.active = 1`).all(requestedJson, this.projectId);
11853
12065
  const slotOverrideRows = this.db.query(`WITH requested AS (
11854
12066
  SELECT DISTINCT
11855
12067
  json_extract(value, '$.sourceTypeId') AS source_type_id,
@@ -11907,7 +12119,7 @@ class SqliteMaterializationStateReader {
11907
12119
  for (const row of rows) {
11908
12120
  const accumulator = accumulators.get(row.scope_sort_key);
11909
12121
  if (!accumulator) {
11910
- throw new MaterializationConflictError2("effective-state", `Unexpected link scope '${row.scope_sort_key}' returned by storage.`);
12122
+ throw new MaterializationConflictError3("effective-state", `Unexpected link scope '${row.scope_sort_key}' returned by storage.`);
11911
12123
  }
11912
12124
  const snapshot = effectiveLinkSnapshot(row);
11913
12125
  appendScopeSnapshot(accumulator, snapshot);
@@ -11938,11 +12150,12 @@ class SqliteMaterializationStateReader {
11938
12150
  SELECT rows.source_type_id, rows.source_primary_id, rows.link_id,
11939
12151
  rows.target_type_id, rows.target_primary_id
11940
12152
  FROM ontology_source_rows AS rows
11941
- JOIN ontology_sources AS sources
12153
+ JOIN ontology_source_roots AS sources
11942
12154
  ON sources.project_id = rows.project_id
11943
12155
  AND sources.source_id = rows.source_id
11944
12156
  AND sources.materialization_id = rows.materialization_id
11945
- WHERE rows.project_id = ? AND rows.entity_kind = 'link' AND sources.status = 'active'
12157
+ AND sources.root_sort_key = rows.root_sort_key
12158
+ WHERE rows.project_id = ? AND rows.entity_kind = 'link' AND sources.active = 1
11946
12159
  ), selected AS (
11947
12160
  SELECT DISTINCT links.*,
11948
12161
  ${linkSortExpression("links")} AS sort_key
@@ -11985,19 +12198,19 @@ class SqliteMaterializationStateReader {
11985
12198
  const candidates = this.replacementSources(sourceId, [candidateMaterializationId], refs.map((ref) => ({ kind: "object", ref })));
11986
12199
  return base.map((state) => ({
11987
12200
  ref: state.ref,
11988
- candidateSource: sourceObject(candidates.byMaterialization.get(candidateMaterializationId)?.get(projectionEntityKey({ kind: "object", ref: state.ref }))),
12201
+ candidateSource: sourceObject(candidates.byMaterialization.get(candidateMaterializationId)?.get(projectionEntityKey2({ kind: "object", ref: state.ref }))),
11989
12202
  override: state.override,
11990
12203
  effective: state.effective,
11991
12204
  latestTelemetry: state.latestTelemetry
11992
12205
  }));
11993
12206
  }
11994
- replacementLinkStates(sourceId, candidateMaterializationId, materializationIds, identities) {
12207
+ replacementLinkStates(sourceId, candidateMaterializationId, materializationIds, identities, incremental = false) {
11995
12208
  const refs = identities.map((identity) => identity.ref);
11996
12209
  const base = this.linkStates(refs);
11997
- const replacements = this.replacementSources(sourceId, materializationIds, refs.map((ref) => ({ kind: "link", ref })));
12210
+ const replacements = this.replacementSources(sourceId, materializationIds, refs.map((ref) => ({ kind: "link", ref })), incremental);
11998
12211
  const candidate = replacements.byMaterialization.get(candidateMaterializationId);
11999
12212
  return base.map((state, index) => {
12000
- const key = projectionEntityKey({ kind: "link", ref: state.ref });
12213
+ const key = projectionEntityKey2({ kind: "link", ref: state.ref });
12001
12214
  return {
12002
12215
  ref: state.ref,
12003
12216
  candidateSource: replacements.owned.has(key) ? sourceLink(candidate?.get(key)) : state.source,
@@ -12021,32 +12234,23 @@ class SqliteMaterializationStateReader {
12021
12234
  `).get(this.projectId, ref.source.objectTypeId, ref.source.primaryId, ref.linkId, ref.target.objectTypeId, ref.target.primaryId);
12022
12235
  return row ? row.last_commit_id : undefined;
12023
12236
  }
12024
- replacementSources(sourceId, materializationIds, refs) {
12237
+ replacementSources(sourceId, materializationIds, refs, incremental = false) {
12025
12238
  if (materializationIds.length === 0 || refs.length === 0) {
12026
12239
  return { owned: new Set, byMaterialization: new Map };
12027
12240
  }
12028
- const requestedEntities = canonicalJson(refs.map((ref) => ({ entityKind: ref.kind, entityKey: projectionEntityKey(ref) })));
12029
- const requestedMaterializations = canonicalJson([...new Set(materializationIds)]);
12030
- const rows = this.db.query(`WITH requested_entities AS (
12031
- SELECT DISTINCT json_extract(value, '$.entityKind') AS entity_kind,
12032
- json_extract(value, '$.entityKey') AS entity_key
12033
- FROM json_each(?)
12034
- ), requested_materializations AS (
12035
- SELECT value AS materialization_id FROM json_each(?)
12036
- )
12037
- SELECT rows.* FROM requested_materializations
12038
- CROSS JOIN requested_entities
12039
- CROSS JOIN ontology_source_rows AS rows
12040
- ON rows.project_id = ?
12041
- AND rows.source_id = ?
12042
- AND rows.materialization_id = requested_materializations.materialization_id
12043
- AND rows.entity_kind = requested_entities.entity_kind
12044
- AND rows.entity_key = requested_entities.entity_key`).all(requestedEntities, requestedMaterializations, this.projectId, sourceId);
12241
+ const rows = replacementAssertionRows(this.db, {
12242
+ projectId: this.projectId,
12243
+ sourceId,
12244
+ materializationId: materializationIds[0],
12245
+ incremental,
12246
+ includePrevious: materializationIds.length > 1,
12247
+ refs
12248
+ });
12045
12249
  const owned = new Set;
12046
12250
  const byMaterialization = new Map;
12047
12251
  for (const row of rows) {
12048
12252
  const source = storedSource(row);
12049
- const key = projectionEntityKey(source.assertion);
12253
+ const key = projectionEntityKey2(source.assertion);
12050
12254
  owned.add(key);
12051
12255
  const materialization = byMaterialization.get(row.materialization_id) ?? new Map;
12052
12256
  materialization.set(key, source);
@@ -12062,19 +12266,30 @@ class SqliteMaterializationStateReader {
12062
12266
  this.prepareReplacementLinks(input);
12063
12267
  }
12064
12268
  prepareReplacementObjects(input) {
12269
+ const rows = replacementSourceRows({
12270
+ projectId: this.projectId,
12271
+ sourceId: input.sourceId,
12272
+ materializationId: input.candidateMaterializationId,
12273
+ incremental: input.incremental ?? false
12274
+ });
12065
12275
  this.db.query(`
12066
12276
  INSERT INTO ${SQLITE_REPLACEMENT_WORK_TABLE} (
12067
12277
  session_id, entity_kind, identity_key, sort_key, diff_required
12068
12278
  )
12069
12279
  SELECT ?, 'object', json_array(object_type_id, primary_id),
12070
12280
  MIN(entity_sort_key), 1
12071
- FROM ontology_source_rows
12072
- WHERE project_id = ? AND source_id = ? AND entity_kind = 'object'
12073
- AND materialization_id IN (?, COALESCE(?, ''))
12281
+ FROM (${rows.sql}) AS rows
12282
+ WHERE entity_kind = 'object'
12074
12283
  GROUP BY object_type_id, primary_id
12075
- `).run(input.sessionId, this.projectId, input.sourceId, input.candidateMaterializationId, input.previousMaterializationId);
12284
+ `).run(input.sessionId, ...rows.values);
12076
12285
  }
12077
12286
  prepareReplacementLinks(input) {
12287
+ const rows = replacementSourceRows({
12288
+ projectId: this.projectId,
12289
+ sourceId: input.sourceId,
12290
+ materializationId: input.candidateMaterializationId,
12291
+ incremental: input.incremental ?? false
12292
+ });
12078
12293
  this.db.query(`
12079
12294
  WITH incident_objects AS (
12080
12295
  SELECT DISTINCT
@@ -12085,9 +12300,8 @@ class SqliteMaterializationStateReader {
12085
12300
  ), replacement_links AS (
12086
12301
  SELECT source_type_id, source_primary_id AS source_id, link_id,
12087
12302
  target_type_id, target_primary_id AS target_id
12088
- FROM ontology_source_rows
12089
- WHERE project_id = ? AND source_id = ? AND entity_kind = 'link'
12090
- AND materialization_id IN (?, COALESCE(?, ''))
12303
+ FROM (${rows.sql}) AS rows
12304
+ WHERE entity_kind = 'link'
12091
12305
  ), incident_links AS (
12092
12306
  SELECT links.source_type_id, links.source_id, links.link_id,
12093
12307
  links.target_type_id, links.target_id
@@ -12128,28 +12342,30 @@ class SqliteMaterializationStateReader {
12128
12342
  SELECT rows.source_type_id, rows.source_primary_id AS source_id,
12129
12343
  rows.link_id, rows.target_type_id, rows.target_primary_id AS target_id
12130
12344
  FROM ontology_source_rows AS rows
12131
- JOIN ontology_sources AS sources
12345
+ JOIN ontology_source_roots AS sources
12132
12346
  ON sources.project_id = rows.project_id
12133
12347
  AND sources.source_id = rows.source_id
12134
12348
  AND sources.materialization_id = rows.materialization_id
12349
+ AND sources.root_sort_key = rows.root_sort_key
12135
12350
  JOIN incident_objects
12136
12351
  ON incident_objects.object_type_id = rows.source_type_id
12137
12352
  AND incident_objects.primary_id = rows.source_primary_id
12138
12353
  WHERE rows.project_id = ? AND rows.entity_kind = 'link'
12139
- AND sources.status = 'active'
12354
+ AND sources.active = 1
12140
12355
  UNION
12141
12356
  SELECT rows.source_type_id, rows.source_primary_id AS source_id,
12142
12357
  rows.link_id, rows.target_type_id, rows.target_primary_id AS target_id
12143
12358
  FROM ontology_source_rows AS rows
12144
- JOIN ontology_sources AS sources
12359
+ JOIN ontology_source_roots AS sources
12145
12360
  ON sources.project_id = rows.project_id
12146
12361
  AND sources.source_id = rows.source_id
12147
12362
  AND sources.materialization_id = rows.materialization_id
12363
+ AND sources.root_sort_key = rows.root_sort_key
12148
12364
  JOIN incident_objects
12149
12365
  ON incident_objects.object_type_id = rows.target_type_id
12150
12366
  AND incident_objects.primary_id = rows.target_primary_id
12151
12367
  WHERE rows.project_id = ? AND rows.entity_kind = 'link'
12152
- AND sources.status = 'active'
12368
+ AND sources.active = 1
12153
12369
  ), diff_links AS (
12154
12370
  SELECT * FROM replacement_links
12155
12371
  UNION SELECT * FROM incident_links
@@ -12187,7 +12403,7 @@ class SqliteMaterializationStateReader {
12187
12403
  json_array(source_type_id, source_id, link_id, target_type_id, target_id),
12188
12404
  sort_key, diff_required
12189
12405
  FROM selected
12190
- `).run(input.sessionId, this.projectId, input.sourceId, input.candidateMaterializationId, input.previousMaterializationId, this.projectId, this.projectId, this.projectId, this.projectId, this.projectId, this.projectId, this.projectId, this.projectId, input.sessionId);
12406
+ `).run(input.sessionId, ...rows.values, this.projectId, this.projectId, this.projectId, this.projectId, this.projectId, this.projectId, this.projectId, this.projectId, input.sessionId);
12191
12407
  }
12192
12408
  replacementRows(input, cursor) {
12193
12409
  return this.db.query(`
@@ -12225,9 +12441,9 @@ function activeSourceMap(rows) {
12225
12441
  const result = new Map;
12226
12442
  for (const row of rows) {
12227
12443
  const source = storedSource(row);
12228
- const key = projectionEntityKey(source.assertion);
12444
+ const key = projectionEntityKey2(source.assertion);
12229
12445
  if (result.has(key)) {
12230
- throw new MaterializationConflictError2("source-materialization", `Multiple active sources assert ${key}.`);
12446
+ throw new MaterializationConflictError3("source-materialization", `Multiple active sources assert ${key}.`);
12231
12447
  }
12232
12448
  result.set(key, source);
12233
12449
  }
@@ -12242,7 +12458,7 @@ function activeScopeSourceMap(rows) {
12242
12458
  const ref = source.assertion.ref;
12243
12459
  const key = linkScopeSortKey(ref.source, ref.linkId);
12244
12460
  if (result.has(key)) {
12245
- throw new MaterializationConflictError2("source-materialization", `Multiple active sources assert cardinality-one link slot ${key}.`);
12461
+ throw new MaterializationConflictError3("source-materialization", `Multiple active sources assert cardinality-one link slot ${key}.`);
12246
12462
  }
12247
12463
  result.set(key, source);
12248
12464
  }
@@ -12306,7 +12522,7 @@ function storedSource(row) {
12306
12522
  }
12307
12523
  function effectiveObjectSnapshot(row) {
12308
12524
  if (!row.last_commit_id) {
12309
- throw new MaterializationConflictError2("effective-state", `Effective object ${row.object_type_id}:${row.primary_id} lacks materializer provenance.`);
12525
+ throw new MaterializationConflictError3("effective-state", `Effective object ${row.object_type_id}:${row.primary_id} lacks materializer provenance.`);
12310
12526
  }
12311
12527
  return {
12312
12528
  ref: objectRefFromColumns(row),
@@ -12319,7 +12535,7 @@ function effectiveObjectSnapshot(row) {
12319
12535
  }
12320
12536
  function effectiveLinkSnapshot(row) {
12321
12537
  if (!row.last_commit_id) {
12322
- throw new MaterializationConflictError2("effective-state", `Effective link ${linkRefKey(linkRefFromColumns(row))} lacks materializer provenance.`);
12538
+ throw new MaterializationConflictError3("effective-state", `Effective link ${linkRefKey(linkRefFromColumns(row))} lacks materializer provenance.`);
12323
12539
  }
12324
12540
  return {
12325
12541
  ref: linkRefFromColumns(row),
@@ -12333,7 +12549,7 @@ function effectiveLinkSnapshot(row) {
12333
12549
  }
12334
12550
  function storedPoint(row) {
12335
12551
  if (!row.last_commit_id) {
12336
- throw new MaterializationConflictError2("timeseries-point", `Telemetry point ${telemetryPointSortKey({
12552
+ throw new MaterializationConflictError3("timeseries-point", `Telemetry point ${telemetryPointSortKey({
12337
12553
  object: { objectTypeId: row.object_type_id, primaryId: row.object_id },
12338
12554
  propertyId: row.property_id
12339
12555
  }, row.at)} lacks materializer provenance.`);
@@ -12372,7 +12588,7 @@ class SqliteMaterializationSessions {
12372
12588
  }
12373
12589
  create(header) {
12374
12590
  if (!this.context?.active) {
12375
- throw new MaterializationConflictError3("effective-state", "Materialization sessions require an active storage transaction.");
12591
+ throw new MaterializationConflictError4("effective-state", "Materialization sessions require an active storage transaction.");
12376
12592
  }
12377
12593
  this.ensureTables();
12378
12594
  const session = new SqliteMaterializationSessionState(structuredClone(header), this.context.id);
@@ -12384,7 +12600,7 @@ class SqliteMaterializationSessions {
12384
12600
  require(session) {
12385
12601
  const value = this.sessions.get(session.providerToken);
12386
12602
  if (!value || !value.active || !this.context?.active || value.transactionId !== this.context.id) {
12387
- throw new MaterializationConflictError3("effective-state", "Materialization session is inactive.");
12603
+ throw new MaterializationConflictError4("effective-state", "Materialization session is inactive.");
12388
12604
  }
12389
12605
  return value;
12390
12606
  }
@@ -12444,7 +12660,7 @@ class SqliteMaterializationSessions {
12444
12660
  const session = this.require(input.session);
12445
12661
  const stream = session.workStreams[input.order];
12446
12662
  if (stream.started) {
12447
- throw new MaterializationConflictError3("effective-state", `Materialization ${input.order} work may only be streamed once per session.`);
12663
+ throw new MaterializationConflictError4("effective-state", `Materialization ${input.order} work may only be streamed once per session.`);
12448
12664
  }
12449
12665
  stream.started = true;
12450
12666
  session.workSealed = true;
@@ -12721,7 +12937,7 @@ function recordSummary(summary, record, columns) {
12721
12937
  // src/ontology-storage/materialization-writer.ts
12722
12938
  import {
12723
12939
  linkRefKey as linkRefKey2,
12724
- MaterializationConflictError as MaterializationConflictError4,
12940
+ MaterializationConflictError as MaterializationConflictError5,
12725
12941
  objectRefKey as objectRefKey2,
12726
12942
  telemetryPointKey
12727
12943
  } from "@sixb/core/internal/materialization";
@@ -12893,7 +13109,7 @@ class SqliteMaterializationWriter {
12893
13109
  `).run(projectId, point.series.object.objectTypeId, point.series.object.primaryId, point.series.propertyId, canonicalJson(point.value), point.unit ?? null, point.at, point.lastCommitId);
12894
13110
  } catch (error) {
12895
13111
  if (isSqliteConstraintError(error)) {
12896
- throw new MaterializationConflictError4("timeseries-point", `Telemetry point ${telemetryPointKey(point.series, point.at)} changed.`);
13112
+ throw new MaterializationConflictError5("timeseries-point", `Telemetry point ${telemetryPointKey(point.series, point.at)} changed.`);
12897
13113
  }
12898
13114
  throw error;
12899
13115
  }
@@ -12955,13 +13171,13 @@ class SqliteOntologyMaterializationStorage {
12955
13171
  const linkScopeRevisions = reader.linkScopeRevisions(input.expected.linkScopes);
12956
13172
  for (const [index, expected] of input.expected.linkScopes.entries()) {
12957
13173
  if (linkScopeRevisions[index]?.fingerprint !== expected.fingerprint) {
12958
- throw new MaterializationConflictError5("effective-state", `Expected link scope changed for ${expected.source.objectTypeId}:${expected.source.primaryId}.${expected.linkId}.`);
13174
+ throw new MaterializationConflictError6("effective-state", `Expected link scope changed for ${expected.source.objectTypeId}:${expected.source.primaryId}.${expected.linkId}.`);
12959
13175
  }
12960
13176
  }
12961
13177
  for (const expected of input.expected.points) {
12962
13178
  const point = reader.exactPoint(expected.series, expected.at);
12963
13179
  if ((point?.lastCommitId ?? null) !== expected.lastCommitId) {
12964
- throw new MaterializationConflictError5("timeseries-point", `Telemetry point ${telemetryPointKey2(expected.series, expected.at)} changed.`);
13180
+ throw new MaterializationConflictError6("timeseries-point", `Telemetry point ${telemetryPointKey2(expected.series, expected.at)} changed.`);
12965
13181
  }
12966
13182
  }
12967
13183
  return session.publicSession();
@@ -13019,10 +13235,10 @@ class SqliteOntologyMaterializationStorage {
13019
13235
  const reader = new SqliteMaterializationStateReader(this.db, session.header.commit.projectId);
13020
13236
  if (input.entityKind === "object") {
13021
13237
  if (replacement.projectionKind !== "object") {
13022
- throw new MaterializationConflictError5("source-materialization", "Link projection replacement cannot stream object state.");
13238
+ throw new MaterializationConflictError6("source-materialization", "Link projection replacement cannot stream object state.");
13023
13239
  }
13024
13240
  if (replacement.objectStreamStarted) {
13025
- throw new MaterializationConflictError5("effective-state", "Replacement object state may only be streamed once per session.");
13241
+ throw new MaterializationConflictError6("effective-state", "Replacement object state may only be streamed once per session.");
13026
13242
  }
13027
13243
  replacement.objectStreamStarted = true;
13028
13244
  for (const identities of reader.replacementIdentities({
@@ -13030,6 +13246,7 @@ class SqliteOntologyMaterializationStorage {
13030
13246
  sourceId: replacement.sourceId,
13031
13247
  candidateMaterializationId: replacement.candidateMaterializationId,
13032
13248
  previousMaterializationId: replacement.previousMaterializationId,
13249
+ incremental: replacement.incremental,
13033
13250
  kind: "object",
13034
13251
  pageRows: input.pageRows
13035
13252
  })) {
@@ -13048,10 +13265,10 @@ class SqliteOntologyMaterializationStorage {
13048
13265
  return;
13049
13266
  }
13050
13267
  if (replacement.projectionKind === "object" && !replacement.objectStreamCompleted) {
13051
- throw new MaterializationConflictError5("effective-state", "Object projection replacement must fully stream object state before link state.");
13268
+ throw new MaterializationConflictError6("effective-state", "Object projection replacement must fully stream object state before link state.");
13052
13269
  }
13053
13270
  if (replacement.linkStreamStarted) {
13054
- throw new MaterializationConflictError5("effective-state", "Replacement link state may only be streamed once per session.");
13271
+ throw new MaterializationConflictError6("effective-state", "Replacement link state may only be streamed once per session.");
13055
13272
  }
13056
13273
  replacement.linkStreamStarted = true;
13057
13274
  const materializationIds = [
@@ -13063,6 +13280,7 @@ class SqliteOntologyMaterializationStorage {
13063
13280
  sourceId: replacement.sourceId,
13064
13281
  candidateMaterializationId: replacement.candidateMaterializationId,
13065
13282
  previousMaterializationId: replacement.previousMaterializationId,
13283
+ incremental: replacement.incremental,
13066
13284
  kind: "link",
13067
13285
  pageRows: input.pageRows
13068
13286
  })) {
@@ -13073,7 +13291,7 @@ class SqliteOntologyMaterializationStorage {
13073
13291
  }
13074
13292
  return identity;
13075
13293
  });
13076
- const links = reader.replacementLinkStates(replacement.sourceId, replacement.candidateMaterializationId, materializationIds, linkIdentities);
13294
+ const links = reader.replacementLinkStates(replacement.sourceId, replacement.candidateMaterializationId, materializationIds, linkIdentities, replacement.incremental);
13077
13295
  yield { objects: [], links };
13078
13296
  }
13079
13297
  this.sessions.require(input.session);
@@ -13125,14 +13343,14 @@ class SqliteOntologyMaterializationStorage {
13125
13343
  requireReplacement(session, input) {
13126
13344
  if (session.replacement) {
13127
13345
  if (session.replacement.sourceId !== input.source.projectionId || session.replacement.candidateMaterializationId !== input.candidateMaterializationId) {
13128
- throw new MaterializationConflictError5("source-materialization", "Materialization session already owns another replacement union.");
13346
+ throw new MaterializationConflictError6("source-materialization", "Materialization session already owns another replacement union.");
13129
13347
  }
13130
13348
  return session.replacement;
13131
13349
  }
13132
13350
  const projectId = session.header.commit.projectId;
13133
13351
  const candidate = this.getSource(projectId, input.source.projectionId, input.candidateMaterializationId);
13134
13352
  if (!candidate || candidate.status !== "ready" || candidate.execution_token === null) {
13135
- throw new MaterializationConflictError5("source-materialization", `Candidate source materialization '${input.candidateMaterializationId}' is missing or is not ready.`);
13353
+ throw new MaterializationConflictError6("source-materialization", `Candidate source materialization '${input.candidateMaterializationId}' is missing or is not ready.`);
13136
13354
  }
13137
13355
  assertProjectionExecution(this.db, {
13138
13356
  projectId,
@@ -13142,12 +13360,13 @@ class SqliteOntologyMaterializationStorage {
13142
13360
  });
13143
13361
  const previous = this.getActiveSource(projectId, input.source.projectionId);
13144
13362
  if (candidate.protocol !== "replacement" || previous && (previous.protocol !== candidate.protocol || previous.projection_kind !== candidate.projection_kind)) {
13145
- throw new MaterializationConflictError5("source-materialization", "Source replacement kind or protocol does not match its active materialization.");
13363
+ throw new MaterializationConflictError6("source-materialization", "Source replacement kind or protocol does not match its active materialization.");
13146
13364
  }
13147
13365
  session.replacement = {
13148
13366
  sourceId: input.source.projectionId,
13149
13367
  candidateMaterializationId: input.candidateMaterializationId,
13150
13368
  previousMaterializationId: previous?.materialization_id ?? null,
13369
+ incremental: candidate.base_materialization_id !== null,
13151
13370
  projectionKind: candidate.projection_kind,
13152
13371
  objectStreamStarted: false,
13153
13372
  objectStreamCompleted: false,
@@ -13176,38 +13395,38 @@ class SqliteOntologyMaterializationStorage {
13176
13395
  if (!duplicate)
13177
13396
  return;
13178
13397
  if (duplicate.duplicate === "origin") {
13179
- throw new MaterializationConflictError5("run-correlation", "Ontology commit origin already has an authoritative commit.");
13398
+ throw new MaterializationConflictError6("run-correlation", "Ontology commit origin already has an authoritative commit.");
13180
13399
  }
13181
- throw new MaterializationConflictError5("idempotency", duplicate.duplicate === "id" ? `Ontology commit '${header.commit.id}' already exists.` : "Ontology idempotency key already exists.");
13400
+ throw new MaterializationConflictError6("idempotency", duplicate.duplicate === "id" ? `Ontology commit '${header.commit.id}' already exists.` : "Ontology idempotency key already exists.");
13182
13401
  }
13183
13402
  assertSource(expected, projectId) {
13184
13403
  const active = this.getActiveSource(projectId, expected.source.projectionId);
13185
13404
  if ((active?.materialization_id ?? null) !== expected.activeMaterializationId || (active?.last_commit_id ?? null) !== expected.lastCommitId) {
13186
- throw new MaterializationConflictError5("projection-fence", `Source '${expected.source.projectionId}' changed.`);
13405
+ throw new MaterializationConflictError6("projection-fence", `Source '${expected.source.projectionId}' changed.`);
13187
13406
  }
13188
13407
  }
13189
13408
  assertObject(reader, expected) {
13190
13409
  const row = reader.effectiveObjectRevision(expected.ref);
13191
13410
  if (!expected.exists) {
13192
13411
  if (row) {
13193
- throw new MaterializationConflictError5("effective-state", `Expected object ${objectRefKey3(expected.ref)} to be absent.`);
13412
+ throw new MaterializationConflictError6("effective-state", `Expected object ${objectRefKey3(expected.ref)} to be absent.`);
13194
13413
  }
13195
13414
  return;
13196
13415
  }
13197
13416
  if (!row || row.version !== expected.version || row.lastCommitId !== expected.lastCommitId) {
13198
- throw new MaterializationConflictError5("effective-state", `Expected object ${objectRefKey3(expected.ref)} changed.`);
13417
+ throw new MaterializationConflictError6("effective-state", `Expected object ${objectRefKey3(expected.ref)} changed.`);
13199
13418
  }
13200
13419
  }
13201
13420
  assertLink(reader, expected) {
13202
13421
  const lastCommitId = reader.effectiveLinkLastCommit(expected.ref);
13203
13422
  if (!expected.exists) {
13204
13423
  if (lastCommitId !== undefined) {
13205
- throw new MaterializationConflictError5("effective-state", `Expected link ${linkRefKey3(expected.ref)} to be absent.`);
13424
+ throw new MaterializationConflictError6("effective-state", `Expected link ${linkRefKey3(expected.ref)} to be absent.`);
13206
13425
  }
13207
13426
  return;
13208
13427
  }
13209
13428
  if (lastCommitId === undefined || lastCommitId !== expected.lastCommitId) {
13210
- throw new MaterializationConflictError5("effective-state", `Expected link ${linkRefKey3(expected.ref)} changed.`);
13429
+ throw new MaterializationConflictError6("effective-state", `Expected link ${linkRefKey3(expected.ref)} changed.`);
13211
13430
  }
13212
13431
  }
13213
13432
  async assertFinalization(session, input) {
@@ -13301,7 +13520,7 @@ class SqliteOntologyMaterializationStorage {
13301
13520
  assertSourceActivationCorrelation(session, activation);
13302
13521
  const candidate = this.getSource(commit.projectId, activation.source.projectionId, activation.materializationId);
13303
13522
  if (!candidate || candidate.status !== "ready" || candidate.execution_token === null) {
13304
- throw new MaterializationConflictError5("source-materialization", "Source activation candidate is missing or is not ready.");
13523
+ throw new MaterializationConflictError6("source-materialization", "Source activation candidate is missing or is not ready.");
13305
13524
  }
13306
13525
  assertProjectionExecution(this.db, {
13307
13526
  projectId: commit.projectId,
@@ -13313,6 +13532,7 @@ class SqliteOntologyMaterializationStorage {
13313
13532
  invalidCorrelation2("Source activation does not match its ready candidate identity.");
13314
13533
  }
13315
13534
  this.assertSource(activation.expected, commit.projectId);
13535
+ activateSourceRoots(this.db, commit.projectId, candidate, activation);
13316
13536
  const previous = this.getActiveSource(commit.projectId, activation.source.projectionId);
13317
13537
  if (previous) {
13318
13538
  assertPinnedDatasetWatermark({
@@ -13352,7 +13572,7 @@ class SqliteOntologyMaterializationStorage {
13352
13572
  `).run(commit.projectId, commit.id, commit.idempotencyKey, commit.requestHash, commit.executionId, origin.kind, origin.runId, origin.batchOrdinal, canonicalJson(commit.origin), commit.actor === undefined ? null : canonicalJson(commit.actor), commit.ontologyRevision, commit.projectionRevision ?? null, commit.ownershipHash ?? null, canonicalJson(commit.intent), canonicalJson(input.finalization.result), commit.committedAt);
13353
13573
  } catch (error) {
13354
13574
  if (isSqliteConstraintError(error)) {
13355
- throw new MaterializationConflictError5("idempotency", "Ontology commit identity already exists.");
13575
+ throw new MaterializationConflictError6("idempotency", "Ontology commit identity already exists.");
13356
13576
  }
13357
13577
  throw error;
13358
13578
  }
@@ -13372,8 +13592,8 @@ class SqliteOntologyMaterializationStorage {
13372
13592
  // src/ontology-storage/outbox.ts
13373
13593
  import { serializeSixbFailure as serializeSixbFailure2 } from "@sixb/core/internal/errors";
13374
13594
  import {
13375
- MaterializationConflictError as MaterializationConflictError6,
13376
- MaterializationValidationError
13595
+ MaterializationConflictError as MaterializationConflictError7,
13596
+ MaterializationValidationError as MaterializationValidationError2
13377
13597
  } from "@sixb/core/internal/materialization";
13378
13598
  import { ONTOLOGY_OUTBOX_FAILURE_CODES as ONTOLOGY_OUTBOX_FAILURE_CODES2 } from "@sixb/core/storage";
13379
13599
  class SqliteOntologyOutboxStorage {
@@ -13391,7 +13611,7 @@ class SqliteOntologyOutboxStorage {
13391
13611
  const leaseExpiry = assertTimestamp(input.leaseExpiresAt, "Ontology outbox lease expiry");
13392
13612
  assertNonblank(input.leaseId, "Ontology outbox lease id");
13393
13613
  if (leaseExpiry <= now) {
13394
- throw new MaterializationValidationError("Ontology outbox lease expiry must be later than the claim time.");
13614
+ throw new MaterializationValidationError2("Ontology outbox lease expiry must be later than the claim time.");
13395
13615
  }
13396
13616
  const rows = this.db.query(`
13397
13617
  UPDATE ontology_outbox
@@ -13409,7 +13629,7 @@ class SqliteOntologyOutboxStorage {
13409
13629
  `).all(input.leaseId, input.leaseExpiresAt, input.projectId, input.now, input.now, input.limit);
13410
13630
  return rows.map((row) => outboxRecord(row)).sort((left, right) => left.createdAt.localeCompare(right.createdAt) || left.envelope.id.localeCompare(right.envelope.id)).map((record) => {
13411
13631
  if (record.leaseId === null || record.leaseExpiresAt === null) {
13412
- throw new MaterializationConflictError6("outbox-lease", "Ontology outbox claim returned an unpaired lease.");
13632
+ throw new MaterializationConflictError7("outbox-lease", "Ontology outbox claim returned an unpaired lease.");
13413
13633
  }
13414
13634
  return { ...record, leaseId: record.leaseId, leaseExpiresAt: record.leaseExpiresAt };
13415
13635
  });
@@ -13508,7 +13728,7 @@ function validateLeaseIds(ids) {
13508
13728
  for (const id of ids) {
13509
13729
  assertNonblank(id, "Ontology outbox event id");
13510
13730
  if (seen.has(id)) {
13511
- throw new MaterializationValidationError(`Ontology outbox lease batch repeats event '${id}'.`);
13731
+ throw new MaterializationValidationError2(`Ontology outbox lease batch repeats event '${id}'.`);
13512
13732
  }
13513
13733
  seen.add(id);
13514
13734
  }
@@ -13519,11 +13739,11 @@ function assertLeaseInput(input) {
13519
13739
  assertNonblank(input.leaseId, "Ontology outbox lease id");
13520
13740
  }
13521
13741
  function leaseConflict() {
13522
- return new MaterializationConflictError6("outbox-lease", "Ontology outbox lease does not match.");
13742
+ return new MaterializationConflictError7("outbox-lease", "Ontology outbox lease does not match.");
13523
13743
  }
13524
13744
 
13525
13745
  // src/ontology-storage/sources.ts
13526
- import { MaterializationValidationError as MaterializationValidationError2 } from "@sixb/core/internal/materialization";
13746
+ import { MaterializationValidationError as MaterializationValidationError3 } from "@sixb/core/internal/materialization";
13527
13747
  import {
13528
13748
  assertSourceBeginInput as assertBeginInput,
13529
13749
  assertSourceExecutionIdentity as assertExecutionIdentity,
@@ -13537,8 +13757,53 @@ import {
13537
13757
  sourceMaterializationIdentity,
13538
13758
  sourceStageRow,
13539
13759
  sourceStageRows,
13540
- utf8SortKey
13760
+ utf8SortKey as utf8SortKey2
13541
13761
  } from "@sixb/core/internal/ontology-storage-provider";
13762
+
13763
+ // src/ontology-storage/source-cleanup.ts
13764
+ function cleanupSourceVersions(db, input) {
13765
+ let remaining = input.limit;
13766
+ let rowsDeleted = db.query(`DELETE FROM ontology_source_rows WHERE rowid IN (
13767
+ SELECT rows.rowid FROM ontology_source_roots AS roots
13768
+ JOIN ontology_sources AS sources USING (project_id, source_id, materialization_id)
13769
+ CROSS JOIN ontology_source_rows AS rows
13770
+ ON rows.project_id = roots.project_id AND rows.source_id = roots.source_id
13771
+ AND rows.materialization_id = roots.materialization_id AND rows.root_sort_key = roots.root_sort_key
13772
+ WHERE roots.project_id = ? AND roots.active = 0 AND roots.retired_at < ?
13773
+ AND sources.status IN ('superseded', 'abandoned')
13774
+ ORDER BY roots.retired_at, roots.source_id, roots.materialization_id, roots.root_sort_key, rows.entity_sort_key
13775
+ LIMIT ?
13776
+ )`).run(input.projectId, input.terminalBefore, remaining).changes;
13777
+ remaining -= rowsDeleted;
13778
+ if (remaining > 0) {
13779
+ const removed = db.query(`DELETE FROM ontology_source_roots WHERE rowid IN (
13780
+ SELECT roots.rowid FROM ontology_source_roots AS roots
13781
+ JOIN ontology_sources AS sources USING (project_id, source_id, materialization_id)
13782
+ WHERE roots.project_id = ? AND roots.active = 0 AND roots.retired_at < ?
13783
+ AND sources.status IN ('superseded', 'abandoned')
13784
+ AND NOT EXISTS (SELECT 1 FROM ontology_source_rows AS rows
13785
+ WHERE rows.project_id = roots.project_id AND rows.source_id = roots.source_id
13786
+ AND rows.materialization_id = roots.materialization_id AND rows.root_sort_key = roots.root_sort_key)
13787
+ ORDER BY roots.retired_at, roots.source_id, roots.materialization_id, roots.root_sort_key LIMIT ?
13788
+ )`).run(input.projectId, input.terminalBefore, remaining).changes;
13789
+ rowsDeleted += removed;
13790
+ remaining -= removed;
13791
+ }
13792
+ const materializationsDeleted = remaining === 0 ? 0 : db.query(`DELETE FROM ontology_sources WHERE rowid IN (
13793
+ SELECT sources.rowid FROM ontology_sources AS sources
13794
+ WHERE project_id = ? AND status IN ('superseded', 'abandoned') AND terminal_at < ?
13795
+ AND NOT EXISTS (SELECT 1 FROM ontology_source_roots AS roots
13796
+ WHERE roots.project_id = sources.project_id AND roots.source_id = sources.source_id
13797
+ AND roots.materialization_id = sources.materialization_id)
13798
+ AND NOT EXISTS (SELECT 1 FROM ontology_source_rows AS rows
13799
+ WHERE rows.project_id = sources.project_id AND rows.source_id = sources.source_id
13800
+ AND rows.materialization_id = sources.materialization_id)
13801
+ ORDER BY terminal_at, source_id, materialization_id LIMIT ?
13802
+ )`).run(input.projectId, input.terminalBefore, remaining).changes;
13803
+ return { rowsDeleted, materializationsDeleted };
13804
+ }
13805
+
13806
+ // src/ontology-storage/sources.ts
13542
13807
  class SqliteOntologySourceStorage {
13543
13808
  db;
13544
13809
  runRootOperation;
@@ -13573,10 +13838,10 @@ class SqliteOntologySourceStorage {
13573
13838
  dataset_id, dataset_version_id, dataset_version_created_at,
13574
13839
  projection_revision, ownership_hash, ontology_revision,
13575
13840
  root_count, assertion_count, created_at, ready_at, activated_at,
13576
- terminal_at, last_commit_id, updated_at
13841
+ terminal_at, last_commit_id, updated_at, base_materialization_id, base_commit_id
13577
13842
  ) VALUES (?, ?, ?, ?, ?, 'replacement', 'staging', ?, ?, ?, ?, ?, ?, ?,
13578
- NULL, NULL, ?, NULL, NULL, NULL, NULL, ?)
13579
- `).run(input.projectId, input.source.projectionId, input.materializationId, input.execution.projectionRunId, input.projectionKind, input.execution.executionToken, input.datasetVersion.datasetId, input.datasetVersion.versionId, input.datasetVersion.createdAt, input.projectionRevision, input.ownershipHash, input.ontologyRevision, input.createdAt, input.createdAt);
13843
+ NULL, NULL, ?, NULL, NULL, NULL, NULL, ?, ?, ?)
13844
+ `).run(input.projectId, input.source.projectionId, input.materializationId, input.execution.projectionRunId, input.projectionKind, input.execution.executionToken, input.datasetVersion.datasetId, input.datasetVersion.versionId, input.datasetVersion.createdAt, input.projectionRevision, input.ownershipHash, input.ontologyRevision, input.createdAt, input.createdAt, input.base?.materializationId ?? null, input.base?.lastCommitId ?? null);
13580
13845
  return sourceRecord(this.requireManifest(input.projectId, input.source.projectionId, input.materializationId));
13581
13846
  });
13582
13847
  }
@@ -13592,6 +13857,7 @@ class SqliteOntologySourceStorage {
13592
13857
  const rows = sourceStageRows(manifest.projection_kind, input.rows);
13593
13858
  const existing = this.findStageRows(input, rows);
13594
13859
  const { pending, unchanged } = reconcileSourceStageRows(rows, existing);
13860
+ stageSourceRoots(this.db, manifest, input);
13595
13861
  this.insertStageRows(input, pending);
13596
13862
  return { inserted: pending.length, unchanged };
13597
13863
  });
@@ -13617,7 +13883,7 @@ class SqliteOntologySourceStorage {
13617
13883
  throw sourceConflict(`Source materialization '${input.materializationId}' cannot transition from '${manifest.status}' to 'ready'.`);
13618
13884
  }
13619
13885
  if (input.readyAt < manifest.created_at) {
13620
- throw new MaterializationValidationError2("Source readyAt cannot precede source createdAt.");
13886
+ throw new MaterializationValidationError3("Source readyAt cannot precede source createdAt.");
13621
13887
  }
13622
13888
  this.assertReadyState(manifest, input.rootCount, input.assertionCount);
13623
13889
  const result = this.db.query(`
@@ -13656,55 +13922,7 @@ class SqliteOntologySourceStorage {
13656
13922
  assertNonblank(input.projectId, "Terminal source cleanup project id");
13657
13923
  assertTimestamp(input.terminalBefore, "Terminal source cleanup cutoff", true);
13658
13924
  assertPositiveInteger(input.limit, "Terminal source cleanup limit");
13659
- const manifests = this.db.query(`
13660
- SELECT * FROM ontology_sources
13661
- WHERE project_id = ? AND status IN ('superseded', 'abandoned')
13662
- AND terminal_at < ?
13663
- ORDER BY terminal_at, source_id, materialization_id
13664
- LIMIT ?
13665
- `).all(input.projectId, input.terminalBefore, input.limit);
13666
- let remaining = input.limit;
13667
- let rowsDeleted = 0;
13668
- let materializationsDeleted = 0;
13669
- for (const manifest of manifests) {
13670
- if (remaining === 0)
13671
- break;
13672
- const deleted = this.db.query(`
13673
- DELETE FROM ontology_source_rows
13674
- WHERE rowid IN (
13675
- SELECT rows.rowid
13676
- FROM ontology_source_rows AS rows
13677
- JOIN ontology_sources AS sources
13678
- ON sources.project_id = rows.project_id
13679
- AND sources.source_id = rows.source_id
13680
- AND sources.materialization_id = rows.materialization_id
13681
- WHERE rows.project_id = ? AND rows.source_id = ? AND rows.materialization_id = ?
13682
- AND sources.status IN ('superseded', 'abandoned')
13683
- AND sources.terminal_at = ? AND sources.terminal_at < ?
13684
- ORDER BY rows.entity_sort_key
13685
- LIMIT ?
13686
- )
13687
- `).run(manifest.project_id, manifest.source_id, manifest.materialization_id, manifest.terminal_at, input.terminalBefore, remaining).changes;
13688
- rowsDeleted += deleted;
13689
- remaining -= deleted;
13690
- if (remaining === 0)
13691
- break;
13692
- const removed = this.db.query(`
13693
- DELETE FROM ontology_sources
13694
- WHERE project_id = ? AND source_id = ? AND materialization_id = ?
13695
- AND status IN ('superseded', 'abandoned')
13696
- AND terminal_at = ? AND terminal_at < ?
13697
- AND NOT EXISTS (
13698
- SELECT 1 FROM ontology_source_rows AS rows
13699
- WHERE rows.project_id = ontology_sources.project_id
13700
- AND rows.source_id = ontology_sources.source_id
13701
- AND rows.materialization_id = ontology_sources.materialization_id
13702
- )
13703
- `).run(manifest.project_id, manifest.source_id, manifest.materialization_id, manifest.terminal_at, input.terminalBefore).changes;
13704
- materializationsDeleted += removed;
13705
- remaining -= removed;
13706
- }
13707
- return { rowsDeleted, materializationsDeleted };
13925
+ return cleanupSourceVersions(this.db, input);
13708
13926
  });
13709
13927
  }
13710
13928
  async summarizeTerminal(input) {
@@ -13748,7 +13966,7 @@ class SqliteOntologySourceStorage {
13748
13966
  }
13749
13967
  transitionToAbandoned(manifest, abandonedAt) {
13750
13968
  if (abandonedAt < manifest.created_at || manifest.ready_at !== null && abandonedAt < manifest.ready_at) {
13751
- throw new MaterializationValidationError2("Source abandonedAt cannot precede source creation or readiness.");
13969
+ throw new MaterializationValidationError3("Source abandonedAt cannot precede source creation or readiness.");
13752
13970
  }
13753
13971
  const changed = this.db.query(`
13754
13972
  UPDATE ontology_sources
@@ -13759,19 +13977,23 @@ class SqliteOntologySourceStorage {
13759
13977
  if (changed !== 1) {
13760
13978
  throw sourceConflict(`Source materialization '${manifest.materialization_id}' cannot transition to 'abandoned'.`);
13761
13979
  }
13980
+ this.db.query(`UPDATE ontology_source_roots SET retired_at = ?
13981
+ WHERE project_id = ? AND source_id = ? AND materialization_id = ? AND active = 0`).run(abandonedAt, manifest.project_id, manifest.source_id, manifest.materialization_id);
13762
13982
  return sourceRecord(this.requireManifest(manifest.project_id, manifest.source_id, manifest.materialization_id));
13763
13983
  }
13764
13984
  assertReadyState(manifest, rootCount, assertionCount) {
13765
13985
  const counts = this.db.query(`
13766
- SELECT COUNT(*) AS assertions, COUNT(DISTINCT root_key) AS roots,
13986
+ SELECT (SELECT COUNT(*) FROM ontology_source_rows
13987
+ WHERE project_id = ? AND source_id = ? AND materialization_id = ?) AS assertions, COUNT(*) AS roots,
13767
13988
  COUNT(DISTINCT staging_ordinal) AS ordinals,
13768
13989
  MIN(staging_ordinal) AS min_ordinal, MAX(staging_ordinal) AS max_ordinal
13769
- FROM ontology_source_rows
13990
+ FROM ontology_source_roots
13770
13991
  WHERE project_id = ? AND source_id = ? AND materialization_id = ?
13771
- `).get(manifest.project_id, manifest.source_id, manifest.materialization_id);
13992
+ `).get(manifest.project_id, manifest.source_id, manifest.materialization_id, manifest.project_id, manifest.source_id, manifest.materialization_id);
13772
13993
  if (counts.assertions !== assertionCount || counts.roots !== rootCount || counts.ordinals !== rootCount || rootCount > 0 && (counts.min_ordinal !== 0 || counts.max_ordinal !== rootCount - 1)) {
13773
- throw new MaterializationValidationError2("Source ready counts do not match the staged roots and assertions.");
13994
+ throw new MaterializationValidationError3("Source ready counts do not match the staged roots and assertions.");
13774
13995
  }
13996
+ assertSourceRootCoverage(this.db, manifest);
13775
13997
  const invalid = this.db.query(manifest.projection_kind === "link" ? `
13776
13998
  SELECT root_key
13777
13999
  FROM ontology_source_rows
@@ -13797,15 +14019,15 @@ class SqliteOntologySourceStorage {
13797
14019
  LIMIT 1
13798
14020
  `).get(manifest.project_id, manifest.source_id, manifest.materialization_id);
13799
14021
  if (invalid) {
13800
- throw new MaterializationValidationError2(manifest.projection_kind === "link" ? "Link projection roots must contain exactly their matching link assertion." : "Object projection roots must contain exactly their matching object assertion plus links sourced from that root.");
14022
+ throw new MaterializationValidationError3(manifest.projection_kind === "link" ? "Link projection roots must contain exactly their matching link assertion." : "Object projection roots must contain exactly their matching object assertion plus links sourced from that root.");
13801
14023
  }
13802
14024
  }
13803
14025
  findStageRows(input, rows) {
13804
14026
  if (rows.length === 0)
13805
14027
  return [];
13806
- const rootSortKeys = [...new Set(rows.map((row) => utf8SortKey(row.rootKey)))];
14028
+ const rootSortKeys = [...new Set(rows.map((row) => utf8SortKey2(row.rootKey)))];
13807
14029
  const ordinals = [...new Set(rows.map((row) => row.row.stagingOrdinal))];
13808
- const entitySortKeys = [...new Set(rows.map((row) => utf8SortKey(row.entityKey)))];
14030
+ const entitySortKeys = [...new Set(rows.map((row) => utf8SortKey2(row.entityKey)))];
13809
14031
  const existing = this.db.query(`
13810
14032
  SELECT * FROM ontology_source_rows
13811
14033
  WHERE project_id = ? AND source_id = ? AND materialization_id = ?
@@ -13826,10 +14048,10 @@ class SqliteOntologySourceStorage {
13826
14048
  return {
13827
14049
  entityKind: row.assertion.kind,
13828
14050
  entityKey,
13829
- entitySortKey: utf8SortKey(entityKey),
14051
+ entitySortKey: utf8SortKey2(entityKey),
13830
14052
  rootKind: row.root.kind,
13831
14053
  rootKey,
13832
- rootSortKey: utf8SortKey(rootKey),
14054
+ rootSortKey: utf8SortKey2(rootKey),
13833
14055
  stagingOrdinal: row.stagingOrdinal,
13834
14056
  root: row.root,
13835
14057
  assertion: row.assertion,
@@ -14341,7 +14563,7 @@ class SqliteProjectionRunStorage {
14341
14563
  input_exhausted = ?, missing_target_object_type_id = NULL,
14342
14564
  missing_target_object_id = NULL, missing_target_batch_ordinal = NULL,
14343
14565
  missing_target_first_seen_at = NULL, source_rows_read = 0,
14344
- source_rows_skipped = 0, error = NULL
14566
+ source_rows_skipped = 0, source_changes_read = NULL, error = NULL
14345
14567
  WHERE project_id = ? AND id = ? AND status = 'failed'
14346
14568
  AND json_extract(error, '$.code') = 'queue.enqueue_failed'
14347
14569
  `).run(queuedAt.toISOString(), input.fixedBatchSize === undefined ? null : 0, input.fixedBatchSize === undefined ? null : 0, input.fixedBatchSize === undefined ? null : 0, input.projectId, input.id);
@@ -14436,10 +14658,11 @@ class SqliteProjectionRunStorage {
14436
14658
  execution_token = NULL,
14437
14659
  source_rows_read = ?,
14438
14660
  source_rows_skipped = ?,
14661
+ source_changes_read = ?,
14439
14662
  input_exhausted = ?,
14440
14663
  error = ?
14441
14664
  WHERE project_id = ? AND id = ? AND status = 'running' AND execution_token = ?
14442
- `).run(input.status, plan.finishedAt.toISOString(), plan.progress.sourceRowsRead, plan.progress.sourceRowsSkipped, plan.inputExhausted ? 1 : existingRow.input_exhausted, plan.error === undefined ? null : serializeSixbFailure4(plan.error, PROJECTION_RUN_FAILURE_CODES), input.projectId, input.id, input.executionToken);
14665
+ `).run(input.status, plan.finishedAt.toISOString(), plan.progress.sourceRowsRead, plan.progress.sourceRowsSkipped, plan.progress.sourceChangesRead ?? null, plan.inputExhausted ? 1 : existingRow.input_exhausted, plan.error === undefined ? null : serializeSixbFailure4(plan.error, PROJECTION_RUN_FAILURE_CODES), input.projectId, input.id, input.executionToken);
14443
14666
  if (result.changes !== 1)
14444
14667
  throw staleProjectionRunExecution(input.id);
14445
14668
  return rowToProjectionRunRecord(this.requireRow(input.projectId, input.id));
@@ -14605,6 +14828,7 @@ class SqliteProjectionRunStorage {
14605
14828
  const args = [
14606
14829
  progress.sourceRowsRead,
14607
14830
  progress.sourceRowsSkipped,
14831
+ progress.sourceChangesRead ?? null,
14608
14832
  existing.project_id,
14609
14833
  existing.id,
14610
14834
  executionToken
@@ -14613,7 +14837,8 @@ class SqliteProjectionRunStorage {
14613
14837
  UPDATE projection_runs
14614
14838
  SET
14615
14839
  source_rows_read = ?,
14616
- source_rows_skipped = ?
14840
+ source_rows_skipped = ?,
14841
+ source_changes_read = ?
14617
14842
  WHERE project_id = ? AND id = ? AND status = 'running'
14618
14843
  AND execution_token = ?
14619
14844
  `).run(...args);
@@ -14683,7 +14908,8 @@ function restoreProjectionRunRow(row) {
14683
14908
  missingTargetFirstSeenAt: row.missing_target_first_seen_at ? new Date(row.missing_target_first_seen_at) : undefined,
14684
14909
  progress: {
14685
14910
  sourceRowsRead: databaseSafeInteger(row.source_rows_read, "sourceRowsRead"),
14686
- sourceRowsSkipped: databaseSafeInteger(row.source_rows_skipped, "sourceRowsSkipped")
14911
+ sourceRowsSkipped: databaseSafeInteger(row.source_rows_skipped, "sourceRowsSkipped"),
14912
+ ...row.source_changes_read == null ? {} : { sourceChangesRead: databaseSafeInteger(row.source_changes_read, "sourceChangesRead") }
14687
14913
  },
14688
14914
  error: row.error ?? undefined
14689
14915
  };
@@ -16758,4 +16984,4 @@ export {
16758
16984
  migrateSqliteStorage
16759
16985
  };
16760
16986
 
16761
- //# debugId=8848EEC97F2007FB64756E2164756E21
16987
+ //# debugId=B98ED3E1F74F05CB64756E2164756E21