@sixb/sqlite 0.1.0 → 0.1.2
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/README.md +18 -13
- package/dist/action-run-storage.d.ts +5 -2
- package/dist/action-run-storage.d.ts.map +1 -1
- package/dist/agents/agent-storage.d.ts +4 -2
- package/dist/agents/agent-storage.d.ts.map +1 -1
- package/dist/agents/rows.d.ts +3 -10
- package/dist/agents/rows.d.ts.map +1 -1
- package/dist/agents/runs.d.ts +3 -2
- package/dist/agents/runs.d.ts.map +1 -1
- package/dist/ai-usage-storage.d.ts +22 -0
- package/dist/ai-usage-storage.d.ts.map +1 -0
- package/dist/auth-storage/service-accounts.d.ts.map +1 -1
- package/dist/auth-storage/shared.d.ts +1 -10
- package/dist/auth-storage/shared.d.ts.map +1 -1
- package/dist/connector-connection-storage.d.ts +9 -0
- package/dist/connector-connection-storage.d.ts.map +1 -0
- package/dist/execution-storage.d.ts +14 -0
- package/dist/execution-storage.d.ts.map +1 -0
- package/dist/index.d.ts +13 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4114 -1149
- package/dist/index.js.map +30 -27
- package/dist/latest-run-query.d.ts.map +1 -1
- package/dist/migrations.d.ts +0 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-session.d.ts +50 -8
- package/dist/ontology-storage/materialization-session.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-state.d.ts +4 -5
- package/dist/ontology-storage/materialization-state.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-writer.d.ts.map +1 -1
- package/dist/ontology-storage/materializations.d.ts +0 -2
- package/dist/ontology-storage/materializations.d.ts.map +1 -1
- package/dist/ontology-storage/outbox.d.ts.map +1 -1
- package/dist/ontology-storage/shared.d.ts +3 -3
- package/dist/ontology-storage/shared.d.ts.map +1 -1
- package/dist/ontology-storage/sources.d.ts.map +1 -1
- package/dist/pipeline-run-storage.d.ts +7 -2
- package/dist/pipeline-run-storage.d.ts.map +1 -1
- package/dist/projection-run-storage.d.ts +9 -2
- package/dist/projection-run-storage.d.ts.map +1 -1
- package/dist/run-list-query.d.ts +1 -1
- package/dist/run-list-query.d.ts.map +1 -1
- package/dist/sync-run-storage.d.ts +7 -2
- package/dist/sync-run-storage.d.ts.map +1 -1
- package/dist/testing.d.ts +6 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/transactions.d.ts +3 -0
- package/dist/transactions.d.ts.map +1 -1
- package/dist/webhook-run-storage.d.ts +9 -2
- package/dist/webhook-run-storage.d.ts.map +1 -1
- package/dist/workflow-run-storage.d.ts +7 -3
- package/dist/workflow-run-storage.d.ts.map +1 -1
- package/package.json +4 -3
- package/src/action-run-storage.ts +51 -74
- package/src/agents/agent-storage.ts +5 -3
- package/src/agents/rows.ts +8 -40
- package/src/agents/runs.ts +27 -21
- package/src/ai-usage-storage.ts +324 -0
- package/src/auth-storage/service-accounts.ts +14 -21
- package/src/auth-storage/shared.ts +0 -22
- package/src/connector-connection-storage.ts +784 -0
- package/src/execution-storage.ts +158 -0
- package/src/index.ts +63 -22
- package/src/latest-run-query.ts +7 -1
- package/src/migrations/002-workflow-run-output.sql +27 -0
- package/src/migrations/003-merge-sync-runs.sql +71 -0
- package/src/migrations/004-executions.sql +142 -0
- package/src/migrations/005-workflow-executions.sql +7 -0
- package/src/migrations/006-narrow-ontology-source-root-index.sql +4 -0
- package/src/migrations/007-split-overrides.sql +154 -0
- package/src/migrations/008-action-executions.sql +4 -0
- package/src/migrations/009-agent-executions.sql +115 -0
- package/src/migrations/010-ai-usage-accounting-foundation.sql +74 -0
- package/src/migrations/011-sync-failure-record.sql +22 -0
- package/src/migrations/012-pipeline-failure-record.sql +45 -0
- package/src/migrations/013-workflow-failure-record.sql +45 -0
- package/src/migrations/014-agent-failure-record.sql +59 -0
- package/src/migrations/015-projection-failure-record.sql +20 -0
- package/src/migrations/016-webhook-run-failure-record.sql +19 -0
- package/src/migrations/017-action-failure-record.sql +65 -0
- package/src/migrations/018-ontology-outbox-failure-record.sql +80 -0
- package/src/migrations/019-webhook-delivery-failure-record.sql +62 -0
- package/src/migrations/020-drop-run-usage-projections.sql +7 -0
- package/src/migrations/021-sync-pipeline-executions.sql +101 -0
- package/src/migrations/022-projection-executions.sql +244 -0
- package/src/migrations/023-webhook-executions.sql +51 -0
- package/src/migrations/024-ontology-commit-executions.sql +58 -0
- package/src/migrations/025-connector-connections.sql +257 -0
- package/src/migrations.ts +126 -5
- package/src/ontology-storage/materialization-session.ts +317 -143
- package/src/ontology-storage/materialization-state.ts +398 -150
- package/src/ontology-storage/materialization-writer.ts +142 -67
- package/src/ontology-storage/materializations.ts +67 -141
- package/src/ontology-storage/outbox.ts +10 -3
- package/src/ontology-storage/shared.ts +10 -4
- package/src/ontology-storage/sources.ts +4 -1
- package/src/pipeline-run-storage.ts +128 -43
- package/src/projection-run-storage.ts +209 -97
- package/src/run-list-query.ts +9 -4
- package/src/sync-run-storage.ts +120 -34
- package/src/testing.ts +20 -0
- package/src/transactions.ts +10 -3
- package/src/webhook-run-storage.ts +118 -74
- package/src/workflow-run-storage.ts +124 -133
- package/dist/validation.d.ts +0 -5
- package/dist/validation.d.ts.map +0 -1
- package/dist/webhook-delivery-storage.d.ts +0 -27
- package/dist/webhook-delivery-storage.d.ts.map +0 -1
- package/src/validation.ts +0 -30
- package/src/webhook-delivery-storage.ts +0 -272
|
@@ -5,11 +5,7 @@ import {
|
|
|
5
5
|
objectRefKey,
|
|
6
6
|
telemetryPointKey,
|
|
7
7
|
} from "@sixb/core/internal/materialization"
|
|
8
|
-
import {
|
|
9
|
-
effectiveConflict,
|
|
10
|
-
type OverrideEntity,
|
|
11
|
-
overrideEntityColumns as overrideColumns,
|
|
12
|
-
} from "@sixb/core/internal/ontology-storage-provider"
|
|
8
|
+
import { effectiveConflict } from "@sixb/core/internal/ontology-storage-provider"
|
|
13
9
|
import type {
|
|
14
10
|
ExactEffectiveLinkWrite,
|
|
15
11
|
ExactEffectiveObjectWrite,
|
|
@@ -17,6 +13,16 @@ import type {
|
|
|
17
13
|
} from "@sixb/core/storage"
|
|
18
14
|
import { assertTimestamp, canonicalJson, isSqliteConstraintError, requireChanges } from "./shared"
|
|
19
15
|
|
|
16
|
+
function linkOverrideConflictMessage(identityKind: "edge" | "slot"): string {
|
|
17
|
+
return identityKind === "edge"
|
|
18
|
+
? "Expected link edge override changed."
|
|
19
|
+
: "Expected link slot override changed."
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function linkOverrideConflict(identityKind: "edge" | "slot"): MaterializationConflictError {
|
|
23
|
+
return effectiveConflict(linkOverrideConflictMessage(identityKind))
|
|
24
|
+
}
|
|
25
|
+
|
|
20
26
|
/** Applies one already-validated exact plan chunk to SQLite authority tables. */
|
|
21
27
|
export class SqliteMaterializationWriter {
|
|
22
28
|
constructor(private readonly db: Database) {}
|
|
@@ -29,103 +35,172 @@ export class SqliteMaterializationWriter {
|
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
private applyOverrides(projectId: string, chunk: MaterializationPlanChunk): void {
|
|
32
|
-
const
|
|
33
|
-
entity: OverrideEntity,
|
|
34
|
-
item:
|
|
35
|
-
| (typeof chunk.overrides.objectUpserts)[number]
|
|
36
|
-
| (typeof chunk.overrides.linkUpserts)[number]
|
|
37
|
-
): void => {
|
|
38
|
-
const kind = entity.kind
|
|
39
|
-
const key = kind === "object" ? objectRefKey(entity.ref) : linkRefKey(entity.ref)
|
|
40
|
-
const columns = overrideColumns(entity)
|
|
38
|
+
for (const item of chunk.overrides.objects.upserts) {
|
|
41
39
|
if (item.expectedLastCommitId === null) {
|
|
42
40
|
try {
|
|
43
41
|
this.db
|
|
44
42
|
.query(
|
|
45
|
-
`
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
object_type_id, primary_id, source_type_id, source_primary_id,
|
|
49
|
-
link_id, target_type_id, target_primary_id,
|
|
50
|
-
value, last_commit_id, updated_at
|
|
51
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, json(?), ?, ?)
|
|
52
|
-
`
|
|
43
|
+
`INSERT INTO ontology_object_overrides (
|
|
44
|
+
project_id, object_type_id, primary_id, value, last_commit_id, updated_at
|
|
45
|
+
) VALUES (?, ?, ?, json(?), ?, ?)`
|
|
53
46
|
)
|
|
54
47
|
.run(
|
|
55
48
|
projectId,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
columns.sortKey,
|
|
59
|
-
columns.objectTypeId,
|
|
60
|
-
columns.primaryId,
|
|
61
|
-
columns.sourceTypeId,
|
|
62
|
-
columns.sourcePrimaryId,
|
|
63
|
-
columns.linkId,
|
|
64
|
-
columns.targetTypeId,
|
|
65
|
-
columns.targetPrimaryId,
|
|
49
|
+
item.ref.objectTypeId,
|
|
50
|
+
item.ref.primaryId,
|
|
66
51
|
canonicalJson(item.value),
|
|
67
52
|
item.lastCommitId,
|
|
68
53
|
item.updatedAt
|
|
69
54
|
)
|
|
70
55
|
} catch (error) {
|
|
71
56
|
if (isSqliteConstraintError(error)) {
|
|
72
|
-
throw effectiveConflict(
|
|
57
|
+
throw effectiveConflict("Expected object override changed.")
|
|
73
58
|
}
|
|
74
59
|
throw error
|
|
75
60
|
}
|
|
76
|
-
|
|
77
|
-
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
requireChanges(
|
|
64
|
+
this.db
|
|
65
|
+
.query(
|
|
66
|
+
`UPDATE ontology_object_overrides
|
|
67
|
+
SET value = json(?), last_commit_id = ?, updated_at = ?
|
|
68
|
+
WHERE project_id = ? AND object_type_id = ? AND primary_id = ?
|
|
69
|
+
AND last_commit_id = ?`
|
|
70
|
+
)
|
|
71
|
+
.run(
|
|
72
|
+
canonicalJson(item.value),
|
|
73
|
+
item.lastCommitId,
|
|
74
|
+
item.updatedAt,
|
|
75
|
+
projectId,
|
|
76
|
+
item.ref.objectTypeId,
|
|
77
|
+
item.ref.primaryId,
|
|
78
|
+
item.expectedLastCommitId
|
|
79
|
+
).changes,
|
|
80
|
+
"effective-state",
|
|
81
|
+
"Expected object override changed."
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
for (const item of chunk.overrides.objects.deletes) {
|
|
85
|
+
requireChanges(
|
|
86
|
+
this.db
|
|
87
|
+
.query(
|
|
88
|
+
`DELETE FROM ontology_object_overrides
|
|
89
|
+
WHERE project_id = ? AND object_type_id = ? AND primary_id = ?
|
|
90
|
+
AND last_commit_id = ?`
|
|
91
|
+
)
|
|
92
|
+
.run(projectId, item.ref.objectTypeId, item.ref.primaryId, item.expectedLastCommitId)
|
|
93
|
+
.changes,
|
|
94
|
+
"effective-state",
|
|
95
|
+
"Expected object override changed."
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const upsertLink = (
|
|
100
|
+
identityKind: "edge" | "slot",
|
|
101
|
+
identityKey: string,
|
|
102
|
+
ref: {
|
|
103
|
+
readonly source: { readonly objectTypeId: string; readonly primaryId: string }
|
|
104
|
+
readonly linkId: string
|
|
105
|
+
readonly target: { readonly objectTypeId: string; readonly primaryId: string }
|
|
106
|
+
},
|
|
107
|
+
item:
|
|
108
|
+
| (typeof chunk.overrides.links.edges.upserts)[number]
|
|
109
|
+
| (typeof chunk.overrides.links.slots.upserts)[number]
|
|
110
|
+
): void => {
|
|
111
|
+
if (item.expectedLastCommitId === null) {
|
|
112
|
+
try {
|
|
78
113
|
this.db
|
|
79
114
|
.query(
|
|
80
|
-
`
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
`
|
|
115
|
+
`INSERT INTO ontology_link_overrides (
|
|
116
|
+
project_id, identity_kind, identity_key,
|
|
117
|
+
source_type_id, source_primary_id, link_id,
|
|
118
|
+
target_type_id, target_primary_id, value, last_commit_id, updated_at
|
|
119
|
+
) VALUES (?, ?, json(?), ?, ?, ?, ?, ?, json(?), ?, ?)`
|
|
86
120
|
)
|
|
87
121
|
.run(
|
|
122
|
+
projectId,
|
|
123
|
+
identityKind,
|
|
124
|
+
identityKey,
|
|
125
|
+
ref.source.objectTypeId,
|
|
126
|
+
ref.source.primaryId,
|
|
127
|
+
ref.linkId,
|
|
128
|
+
ref.target.objectTypeId,
|
|
129
|
+
ref.target.primaryId,
|
|
88
130
|
canonicalJson(item.value),
|
|
89
131
|
item.lastCommitId,
|
|
90
|
-
item.updatedAt
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
132
|
+
item.updatedAt
|
|
133
|
+
)
|
|
134
|
+
} catch (error) {
|
|
135
|
+
if (isSqliteConstraintError(error)) {
|
|
136
|
+
throw linkOverrideConflict(identityKind)
|
|
137
|
+
}
|
|
138
|
+
throw error
|
|
139
|
+
}
|
|
140
|
+
return
|
|
99
141
|
}
|
|
100
|
-
}
|
|
101
|
-
for (const item of chunk.overrides.objectUpserts) {
|
|
102
|
-
upsert({ kind: "object", ref: item.ref }, item)
|
|
103
|
-
}
|
|
104
|
-
for (const item of chunk.overrides.objectDeletes) {
|
|
105
142
|
requireChanges(
|
|
106
143
|
this.db
|
|
107
144
|
.query(
|
|
108
|
-
`
|
|
109
|
-
|
|
145
|
+
`UPDATE ontology_link_overrides
|
|
146
|
+
SET target_type_id = ?, target_primary_id = ?, value = json(?),
|
|
147
|
+
last_commit_id = ?, updated_at = ?
|
|
148
|
+
WHERE project_id = ? AND identity_kind = ? AND identity_key = json(?)
|
|
149
|
+
AND last_commit_id = ?`
|
|
110
150
|
)
|
|
111
|
-
.run(
|
|
151
|
+
.run(
|
|
152
|
+
ref.target.objectTypeId,
|
|
153
|
+
ref.target.primaryId,
|
|
154
|
+
canonicalJson(item.value),
|
|
155
|
+
item.lastCommitId,
|
|
156
|
+
item.updatedAt,
|
|
157
|
+
projectId,
|
|
158
|
+
identityKind,
|
|
159
|
+
identityKey,
|
|
160
|
+
item.expectedLastCommitId
|
|
161
|
+
).changes,
|
|
112
162
|
"effective-state",
|
|
113
|
-
|
|
163
|
+
linkOverrideConflictMessage(identityKind)
|
|
114
164
|
)
|
|
115
165
|
}
|
|
116
|
-
|
|
117
|
-
|
|
166
|
+
|
|
167
|
+
for (const item of chunk.overrides.links.edges.upserts) {
|
|
168
|
+
upsertLink("edge", linkRefKey(item.ref), item.ref, item)
|
|
169
|
+
}
|
|
170
|
+
for (const item of chunk.overrides.links.slots.upserts) {
|
|
171
|
+
upsertLink(
|
|
172
|
+
"slot",
|
|
173
|
+
JSON.stringify([item.ref.source.objectTypeId, item.ref.source.primaryId, item.ref.linkId]),
|
|
174
|
+
{ source: item.ref.source, linkId: item.ref.linkId, target: item.value.target },
|
|
175
|
+
item
|
|
176
|
+
)
|
|
118
177
|
}
|
|
119
|
-
|
|
178
|
+
|
|
179
|
+
const deleteLink = (
|
|
180
|
+
identityKind: "edge" | "slot",
|
|
181
|
+
identityKey: string,
|
|
182
|
+
expectedLastCommitId: string
|
|
183
|
+
): void => {
|
|
120
184
|
requireChanges(
|
|
121
185
|
this.db
|
|
122
186
|
.query(
|
|
123
|
-
`DELETE FROM
|
|
124
|
-
WHERE project_id = ? AND
|
|
187
|
+
`DELETE FROM ontology_link_overrides
|
|
188
|
+
WHERE project_id = ? AND identity_kind = ? AND identity_key = json(?)
|
|
189
|
+
AND last_commit_id = ?`
|
|
125
190
|
)
|
|
126
|
-
.run(projectId,
|
|
191
|
+
.run(projectId, identityKind, identityKey, expectedLastCommitId).changes,
|
|
127
192
|
"effective-state",
|
|
128
|
-
|
|
193
|
+
linkOverrideConflictMessage(identityKind)
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
for (const item of chunk.overrides.links.edges.deletes) {
|
|
197
|
+
deleteLink("edge", linkRefKey(item.ref), item.expectedLastCommitId)
|
|
198
|
+
}
|
|
199
|
+
for (const item of chunk.overrides.links.slots.deletes) {
|
|
200
|
+
deleteLink(
|
|
201
|
+
"slot",
|
|
202
|
+
JSON.stringify([item.ref.source.objectTypeId, item.ref.source.primaryId, item.ref.linkId]),
|
|
203
|
+
item.expectedLastCommitId
|
|
129
204
|
)
|
|
130
205
|
}
|
|
131
206
|
}
|
|
@@ -366,7 +441,7 @@ export class SqliteMaterializationWriter {
|
|
|
366
441
|
INSERT INTO ontology_outbox (
|
|
367
442
|
project_id, id, commit_id, commit_ordinal, envelope,
|
|
368
443
|
available_at, attempts, lease_id, lease_expires_at,
|
|
369
|
-
published_at,
|
|
444
|
+
published_at, last_failure, created_at
|
|
370
445
|
) VALUES (?, ?, ?, ?, json(?), ?, 0, NULL, NULL, NULL, NULL, ?)
|
|
371
446
|
`
|
|
372
447
|
)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { Database } from "bun:sqlite"
|
|
2
2
|
import type {
|
|
3
|
-
EffectiveChangeCounts,
|
|
4
3
|
ExpectedLinkRevision,
|
|
5
4
|
ExpectedObjectRevision,
|
|
6
5
|
} from "@sixb/core/internal/materialization"
|
|
@@ -30,11 +29,8 @@ import type {
|
|
|
30
29
|
ApplyMaterializationChunkInput,
|
|
31
30
|
ApplyMaterializationResult,
|
|
32
31
|
FinalizeMaterializationInput,
|
|
33
|
-
MaterializationCardinalityOccupantWorkRecord,
|
|
34
|
-
MaterializationClassificationWorkRecord,
|
|
35
32
|
MaterializationObjectExistence,
|
|
36
33
|
MaterializationPlanHeader,
|
|
37
|
-
MaterializationPlanWorkRecord,
|
|
38
34
|
MaterializationSession,
|
|
39
35
|
MaterializationStatePage,
|
|
40
36
|
MaterializationWorkPage,
|
|
@@ -48,14 +44,13 @@ import type {
|
|
|
48
44
|
StreamMaterializationWorkInput,
|
|
49
45
|
StreamSourceReplacementStateInput,
|
|
50
46
|
} from "@sixb/core/storage"
|
|
51
|
-
import {
|
|
47
|
+
import { yieldSqliteEventLoop } from "../transactions"
|
|
52
48
|
import {
|
|
53
49
|
type SqliteMaterializationSessionState,
|
|
54
50
|
SqliteMaterializationSessions,
|
|
55
51
|
type SqliteOntologyTransactionContext,
|
|
56
52
|
} from "./materialization-session"
|
|
57
53
|
import {
|
|
58
|
-
linkSortExpression,
|
|
59
54
|
SQLITE_MATERIALIZATION_WORK_TABLE,
|
|
60
55
|
SqliteMaterializationStateReader,
|
|
61
56
|
} from "./materialization-state"
|
|
@@ -202,7 +197,6 @@ export class SqliteOntologyMaterializationStorage implements OntologyMaterializa
|
|
|
202
197
|
pageRows: input.pageRows,
|
|
203
198
|
})) {
|
|
204
199
|
this.sessions.require(input.session)
|
|
205
|
-
this.sessions.recordReplacementIdentities(session, identities)
|
|
206
200
|
const refs = identities.map((identity) => {
|
|
207
201
|
if (identity.kind !== "object") {
|
|
208
202
|
invalidCorrelation("Object replacement returned a link identity.")
|
|
@@ -247,7 +241,6 @@ export class SqliteOntologyMaterializationStorage implements OntologyMaterializa
|
|
|
247
241
|
pageRows: input.pageRows,
|
|
248
242
|
})) {
|
|
249
243
|
this.sessions.require(input.session)
|
|
250
|
-
this.sessions.recordReplacementIdentities(session, identities)
|
|
251
244
|
const linkIdentities = identities.map((identity) => {
|
|
252
245
|
if (identity.kind !== "link") {
|
|
253
246
|
invalidCorrelation("Link replacement returned an object identity.")
|
|
@@ -268,6 +261,7 @@ export class SqliteOntologyMaterializationStorage implements OntologyMaterializa
|
|
|
268
261
|
|
|
269
262
|
async stageWork(input: StageMaterializationWorkInput): Promise<void> {
|
|
270
263
|
this.sessions.stage(input)
|
|
264
|
+
await yieldSqliteEventLoop()
|
|
271
265
|
}
|
|
272
266
|
|
|
273
267
|
streamWork(input: StreamMaterializationWorkInput): AsyncIterable<MaterializationWorkPage> {
|
|
@@ -286,26 +280,24 @@ export class SqliteOntologyMaterializationStorage implements OntologyMaterializa
|
|
|
286
280
|
const session = this.sessions.require(input.session)
|
|
287
281
|
const { commit } = session.header
|
|
288
282
|
assertPlanChunkCorrelations(input.chunk, commit)
|
|
289
|
-
const
|
|
290
|
-
const outboxCount = session.appliedOutboxCount
|
|
291
|
-
this.sessions.assertChunkSequence(session, input.chunk)
|
|
283
|
+
const sequence = this.sessions.prepareChunkSequence(session, input.chunk)
|
|
292
284
|
this.db.run("SAVEPOINT sixb_ontology_apply_chunk")
|
|
293
285
|
try {
|
|
294
286
|
this.writer.apply(commit.projectId, commit.id, input.chunk)
|
|
295
287
|
this.db.run("RELEASE SAVEPOINT sixb_ontology_apply_chunk")
|
|
288
|
+
this.sessions.commitChunkSequence(session, sequence)
|
|
296
289
|
} catch (error) {
|
|
297
290
|
this.db.run("ROLLBACK TO SAVEPOINT sixb_ontology_apply_chunk")
|
|
298
291
|
this.db.run("RELEASE SAVEPOINT sixb_ontology_apply_chunk")
|
|
299
|
-
session.appliedPlanCount = planCount
|
|
300
|
-
session.appliedOutboxCount = outboxCount
|
|
301
292
|
throw error
|
|
302
293
|
}
|
|
294
|
+
await yieldSqliteEventLoop()
|
|
303
295
|
}
|
|
304
296
|
|
|
305
297
|
async finalize(input: FinalizeMaterializationInput): Promise<ApplyMaterializationResult> {
|
|
306
298
|
const session = this.sessions.require(input.session)
|
|
307
299
|
this.assertCommitAbsent(session.header)
|
|
308
|
-
this.assertFinalization(session, input)
|
|
300
|
+
await this.assertFinalization(session, input)
|
|
309
301
|
for (const activation of input.finalization.sourceActivations) {
|
|
310
302
|
this.activateSource(session, activation)
|
|
311
303
|
}
|
|
@@ -484,16 +476,17 @@ export class SqliteOntologyMaterializationStorage implements OntologyMaterializa
|
|
|
484
476
|
}
|
|
485
477
|
}
|
|
486
478
|
|
|
487
|
-
private assertFinalization(
|
|
479
|
+
private async assertFinalization(
|
|
488
480
|
session: SqliteMaterializationSessionState,
|
|
489
481
|
input: FinalizeMaterializationInput
|
|
490
|
-
): void {
|
|
482
|
+
): Promise<void> {
|
|
491
483
|
const { commit } = session.header
|
|
492
484
|
const { result } = input.finalization
|
|
493
485
|
assertMaterializationFinalizationCorrelation(session, input)
|
|
494
486
|
const laneCounts = this.sessions.laneCounts(session)
|
|
495
487
|
assertMaterializationLaneCompletion(session, laneCounts)
|
|
496
488
|
this.assertFinalCardinality(session)
|
|
489
|
+
await yieldSqliteEventLoop()
|
|
497
490
|
const eventCount = laneCounts.event
|
|
498
491
|
const outbox = this.db
|
|
499
492
|
.query(
|
|
@@ -513,149 +506,85 @@ export class SqliteOntologyMaterializationStorage implements OntologyMaterializa
|
|
|
513
506
|
) {
|
|
514
507
|
invalidCorrelation("Outbox event ordinals must be contiguous from zero.")
|
|
515
508
|
}
|
|
509
|
+
await yieldSqliteEventLoop()
|
|
516
510
|
|
|
517
511
|
if (commit.intent.kind === "telemetry") {
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
SELECT COUNT(*) AS count FROM ${SQLITE_MATERIALIZATION_WORK_TABLE}
|
|
522
|
-
WHERE session_id = ? AND kind = 'classification'
|
|
523
|
-
AND json_extract(payload, '$.entityKind') = 'point'
|
|
524
|
-
`
|
|
525
|
-
)
|
|
526
|
-
.get(session.id) as { readonly count: number }
|
|
527
|
-
if (classifiedPoints.count !== commit.intent.pointCount) {
|
|
512
|
+
const summary = this.sessions.telemetrySummary(session, commit.intent.pointCount)
|
|
513
|
+
await yieldSqliteEventLoop()
|
|
514
|
+
if (summary.classifiedPoints !== commit.intent.pointCount) {
|
|
528
515
|
invalidCorrelation(
|
|
529
516
|
"Telemetry point classification coverage does not match the commit intent."
|
|
530
517
|
)
|
|
531
518
|
}
|
|
532
|
-
if (
|
|
533
|
-
result.kind !== "telemetry" ||
|
|
534
|
-
!sameCounts(result, this.telemetryCounts(session, commit.intent.pointCount))
|
|
535
|
-
) {
|
|
519
|
+
if (result.kind !== "telemetry" || !sameCounts(result, summary.counts)) {
|
|
536
520
|
invalidCorrelation("Telemetry result counts do not correlate with finalized work.")
|
|
537
521
|
}
|
|
538
522
|
}
|
|
539
523
|
if (commit.intent.kind === "projection") {
|
|
540
524
|
if (result.kind !== "projection") return
|
|
541
525
|
this.sessions.assertClassificationCoverage(session)
|
|
542
|
-
|
|
526
|
+
await yieldSqliteEventLoop()
|
|
527
|
+
const counts = this.sessions.projectionCounts(session)
|
|
528
|
+
await yieldSqliteEventLoop()
|
|
529
|
+
if (!sameCounts(result.counts, counts)) {
|
|
543
530
|
invalidCorrelation("Projection result counts do not correlate with finalized work.")
|
|
544
531
|
}
|
|
545
532
|
}
|
|
546
533
|
}
|
|
547
534
|
|
|
548
535
|
private assertFinalCardinality(session: SqliteMaterializationSessionState): void {
|
|
549
|
-
const
|
|
536
|
+
const violation = this.db
|
|
550
537
|
.query(
|
|
551
538
|
`
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
539
|
+
WITH scopes AS (
|
|
540
|
+
SELECT sort_one AS scope_sort_key,
|
|
541
|
+
cardinality_source_type_id AS source_type_id,
|
|
542
|
+
cardinality_source_primary_id AS source_id,
|
|
543
|
+
cardinality_link_id AS link_id,
|
|
544
|
+
SUM(cardinality_occupied) AS expected_count,
|
|
545
|
+
MAX(CASE WHEN cardinality_occupied = 1
|
|
546
|
+
THEN cardinality_target_type_id END) AS expected_target_type_id,
|
|
547
|
+
MAX(CASE WHEN cardinality_occupied = 1
|
|
548
|
+
THEN cardinality_target_primary_id END) AS expected_target_id
|
|
549
|
+
FROM ${SQLITE_MATERIALIZATION_WORK_TABLE}
|
|
550
|
+
WHERE session_id = ? AND kind = 'cardinality'
|
|
551
|
+
AND cardinality_view = 'effective'
|
|
552
|
+
GROUP BY sort_one, cardinality_source_type_id,
|
|
553
|
+
cardinality_source_primary_id, cardinality_link_id
|
|
554
|
+
), validated AS (
|
|
555
|
+
SELECT scopes.scope_sort_key, scopes.expected_count,
|
|
556
|
+
COUNT(links.source_id) AS actual_count,
|
|
557
|
+
COUNT(links.source_id) FILTER (
|
|
558
|
+
WHERE links.target_type_id = scopes.expected_target_type_id
|
|
559
|
+
AND links.target_id = scopes.expected_target_id
|
|
560
|
+
) AS matching_count
|
|
561
|
+
FROM scopes
|
|
562
|
+
LEFT JOIN links
|
|
563
|
+
ON links.project_id = ?
|
|
564
|
+
AND links.source_type_id = scopes.source_type_id
|
|
565
|
+
AND links.source_id = scopes.source_id
|
|
566
|
+
AND links.link_id = scopes.link_id
|
|
567
|
+
GROUP BY scopes.scope_sort_key, scopes.expected_count,
|
|
568
|
+
scopes.expected_target_type_id, scopes.expected_target_id
|
|
569
|
+
)
|
|
570
|
+
SELECT CASE WHEN expected_count > 1 THEN 'duplicate' ELSE 'mismatch' END AS reason
|
|
571
|
+
FROM validated
|
|
572
|
+
WHERE expected_count > 1
|
|
573
|
+
OR actual_count <> expected_count
|
|
574
|
+
OR matching_count <> expected_count
|
|
575
|
+
LIMIT 1
|
|
557
576
|
`
|
|
558
577
|
)
|
|
559
|
-
.get(session.id)
|
|
560
|
-
|
|
578
|
+
.get(session.id, session.header.commit.projectId) as {
|
|
579
|
+
readonly reason: "duplicate" | "mismatch"
|
|
580
|
+
} | null
|
|
581
|
+
if (violation?.reason === "duplicate") {
|
|
561
582
|
invalidCorrelation("Materialization cardinality work violates cardinality-one.")
|
|
562
583
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
const scopes = new Map<string, MaterializationCardinalityOccupantWorkRecord>()
|
|
568
|
-
for (const record of records) scopes.set(record.scopeSortKey, record)
|
|
569
|
-
for (const record of scopes.values()) {
|
|
570
|
-
const effective = this.db
|
|
571
|
-
.query(
|
|
572
|
-
`
|
|
573
|
-
SELECT ${linkSortExpression()} AS sort_key FROM links
|
|
574
|
-
WHERE project_id = ? AND source_type_id = ? AND source_id = ? AND link_id = ?
|
|
575
|
-
ORDER BY sort_key
|
|
576
|
-
`
|
|
577
|
-
)
|
|
578
|
-
.all(
|
|
579
|
-
session.header.commit.projectId,
|
|
580
|
-
record.ref.source.objectTypeId,
|
|
581
|
-
record.ref.source.primaryId,
|
|
582
|
-
record.ref.linkId
|
|
583
|
-
) as { readonly sort_key: string }[]
|
|
584
|
-
const occupied = records
|
|
585
|
-
.filter((candidate) => candidate.scopeSortKey === record.scopeSortKey && candidate.occupied)
|
|
586
|
-
.map((candidate) => candidate.linkSortKey)
|
|
587
|
-
.sort()
|
|
588
|
-
if (
|
|
589
|
-
stableJsonStringify(effective.map((row) => row.sort_key)) !== stableJsonStringify(occupied)
|
|
590
|
-
) {
|
|
591
|
-
invalidCorrelation(
|
|
592
|
-
"Materialization cardinality work does not match the final effective link scope."
|
|
593
|
-
)
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
private projectionCounts(session: SqliteMaterializationSessionState): EffectiveChangeCounts {
|
|
599
|
-
const classifications = this.sessions.records(
|
|
600
|
-
session,
|
|
601
|
-
"classification"
|
|
602
|
-
) as MaterializationClassificationWorkRecord[]
|
|
603
|
-
const plans = this.sessions.records(session, "plan") as MaterializationPlanWorkRecord[]
|
|
604
|
-
const counts: MutableEffectiveChangeCounts = {
|
|
605
|
-
objectsCreated: 0,
|
|
606
|
-
objectsUpdated: 0,
|
|
607
|
-
objectsDeleted: 0,
|
|
608
|
-
objectsUnchanged: classifications.filter((record) => record.entityKind === "object").length,
|
|
609
|
-
linksCreated: 0,
|
|
610
|
-
linksUpdated: 0,
|
|
611
|
-
linksDeleted: 0,
|
|
612
|
-
linksUnchanged: classifications.filter((record) => record.entityKind === "link").length,
|
|
613
|
-
}
|
|
614
|
-
for (const record of plans) {
|
|
615
|
-
switch (record.item.kind) {
|
|
616
|
-
case "object-upsert":
|
|
617
|
-
if (record.item.value.expected.exists) counts.objectsUpdated += 1
|
|
618
|
-
else counts.objectsCreated += 1
|
|
619
|
-
counts.objectsUnchanged -= 1
|
|
620
|
-
break
|
|
621
|
-
case "object-delete":
|
|
622
|
-
counts.objectsDeleted += 1
|
|
623
|
-
counts.objectsUnchanged -= 1
|
|
624
|
-
break
|
|
625
|
-
case "link-upsert":
|
|
626
|
-
if (record.item.value.expected.exists) counts.linksUpdated += 1
|
|
627
|
-
else counts.linksCreated += 1
|
|
628
|
-
counts.linksUnchanged -= 1
|
|
629
|
-
break
|
|
630
|
-
case "link-delete":
|
|
631
|
-
counts.linksDeleted += 1
|
|
632
|
-
counts.linksUnchanged -= 1
|
|
633
|
-
break
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
return counts
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
private telemetryCounts(session: SqliteMaterializationSessionState, pointCount: number) {
|
|
640
|
-
let pointsCreated = 0
|
|
641
|
-
let pointsUpdated = 0
|
|
642
|
-
let latestObjectsChanged = 0
|
|
643
|
-
for (const record of this.sessions.records(
|
|
644
|
-
session,
|
|
645
|
-
"plan"
|
|
646
|
-
) as MaterializationPlanWorkRecord[]) {
|
|
647
|
-
if (record.item.kind === "point-upsert") {
|
|
648
|
-
if (record.item.value.expected.lastCommitId === null) pointsCreated += 1
|
|
649
|
-
else pointsUpdated += 1
|
|
650
|
-
} else if (record.item.kind === "object-upsert") {
|
|
651
|
-
latestObjectsChanged += 1
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
return {
|
|
655
|
-
pointsCreated,
|
|
656
|
-
pointsUpdated,
|
|
657
|
-
pointsUnchanged: pointCount - pointsCreated - pointsUpdated,
|
|
658
|
-
latestObjectsChanged,
|
|
584
|
+
if (violation) {
|
|
585
|
+
invalidCorrelation(
|
|
586
|
+
"Materialization cardinality work does not match the final effective link scope."
|
|
587
|
+
)
|
|
659
588
|
}
|
|
660
589
|
}
|
|
661
590
|
|
|
@@ -771,11 +700,11 @@ export class SqliteOntologyMaterializationStorage implements OntologyMaterializa
|
|
|
771
700
|
.query(
|
|
772
701
|
`
|
|
773
702
|
INSERT INTO ontology_commits (
|
|
774
|
-
project_id, id, idempotency_key, request_hash,
|
|
703
|
+
project_id, id, idempotency_key, request_hash, execution_id,
|
|
775
704
|
origin_kind, origin_run_id, origin_batch_ordinal, origin, actor,
|
|
776
705
|
ontology_revision, projection_revision, ownership_hash,
|
|
777
706
|
intent, result, committed_at
|
|
778
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, json(?), json(?), ?, ?, ?, json(?), json(?), ?)
|
|
707
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, json(?), json(?), ?, ?, ?, json(?), json(?), ?)
|
|
779
708
|
`
|
|
780
709
|
)
|
|
781
710
|
.run(
|
|
@@ -783,6 +712,7 @@ export class SqliteOntologyMaterializationStorage implements OntologyMaterializa
|
|
|
783
712
|
commit.id,
|
|
784
713
|
commit.idempotencyKey,
|
|
785
714
|
commit.requestHash,
|
|
715
|
+
commit.executionId,
|
|
786
716
|
origin.kind,
|
|
787
717
|
origin.runId,
|
|
788
718
|
origin.batchOrdinal,
|
|
@@ -832,7 +762,3 @@ export class SqliteOntologyMaterializationStorage implements OntologyMaterializa
|
|
|
832
762
|
.get(projectId, sourceId, materializationId) as SqliteOntologySourceRow | null
|
|
833
763
|
}
|
|
834
764
|
}
|
|
835
|
-
|
|
836
|
-
type MutableEffectiveChangeCounts = {
|
|
837
|
-
-readonly [TKey in keyof EffectiveChangeCounts]: EffectiveChangeCounts[TKey]
|
|
838
|
-
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Database } from "bun:sqlite"
|
|
2
|
+
import { serializeSixbFailure } from "@sixb/core/internal/errors"
|
|
2
3
|
import {
|
|
3
4
|
MaterializationConflictError,
|
|
4
5
|
MaterializationValidationError,
|
|
@@ -13,6 +14,7 @@ import type {
|
|
|
13
14
|
RescheduleOntologyOutboxLeaseInput,
|
|
14
15
|
SummarizeOntologyOutboxInput,
|
|
15
16
|
} from "@sixb/core/storage"
|
|
17
|
+
import { ONTOLOGY_OUTBOX_FAILURE_CODES } from "@sixb/core/storage"
|
|
16
18
|
import {
|
|
17
19
|
assertNonblank,
|
|
18
20
|
assertPositiveInteger,
|
|
@@ -55,7 +57,7 @@ export class SqliteOntologyOutboxStorage implements OntologyOutboxStorage {
|
|
|
55
57
|
LIMIT ?
|
|
56
58
|
)
|
|
57
59
|
RETURNING envelope, available_at, attempts, lease_id, lease_expires_at,
|
|
58
|
-
published_at,
|
|
60
|
+
published_at, last_failure, created_at
|
|
59
61
|
`
|
|
60
62
|
)
|
|
61
63
|
.all(
|
|
@@ -127,6 +129,10 @@ export class SqliteOntologyOutboxStorage implements OntologyOutboxStorage {
|
|
|
127
129
|
assertLeaseInput(input)
|
|
128
130
|
assertTimestamp(input.availableAt, "Ontology outbox availableAt")
|
|
129
131
|
const ids = validateLeaseIds(input.ids)
|
|
132
|
+
const failure =
|
|
133
|
+
input.failure === undefined
|
|
134
|
+
? null
|
|
135
|
+
: serializeSixbFailure(input.failure, ONTOLOGY_OUTBOX_FAILURE_CODES)
|
|
130
136
|
const changed = this.db
|
|
131
137
|
.query(
|
|
132
138
|
`
|
|
@@ -141,7 +147,8 @@ export class SqliteOntologyOutboxStorage implements OntologyOutboxStorage {
|
|
|
141
147
|
SELECT id FROM leased WHERE (SELECT COUNT(*) FROM leased) = ?
|
|
142
148
|
)
|
|
143
149
|
UPDATE ontology_outbox
|
|
144
|
-
SET available_at = ?,
|
|
150
|
+
SET available_at = ?, last_failure = COALESCE(?, last_failure),
|
|
151
|
+
lease_id = NULL, lease_expires_at = NULL
|
|
145
152
|
WHERE project_id = ? AND id IN (SELECT id FROM eligible)
|
|
146
153
|
AND lease_id = ? AND lease_expires_at IS NOT NULL
|
|
147
154
|
`
|
|
@@ -152,7 +159,7 @@ export class SqliteOntologyOutboxStorage implements OntologyOutboxStorage {
|
|
|
152
159
|
input.leaseId,
|
|
153
160
|
ids.length,
|
|
154
161
|
input.availableAt,
|
|
155
|
-
|
|
162
|
+
failure,
|
|
156
163
|
input.projectId,
|
|
157
164
|
input.leaseId
|
|
158
165
|
).changes
|