@cavelang/cli 0.35.0 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +224 -12
- package/dist/internal/act/action.js +2 -1
- package/dist/internal/act/action.js.map +1 -1
- package/dist/internal/act/declare.d.ts.map +1 -1
- package/dist/internal/act/declare.js +82 -53
- package/dist/internal/act/declare.js.map +1 -1
- package/dist/internal/act/engine.d.ts +4 -3
- package/dist/internal/act/engine.d.ts.map +1 -1
- package/dist/internal/act/engine.js +121 -32
- package/dist/internal/act/engine.js.map +1 -1
- package/dist/internal/automate/cancellation.d.ts +3 -0
- package/dist/internal/automate/cancellation.d.ts.map +1 -0
- package/dist/internal/automate/cancellation.js +38 -0
- package/dist/internal/automate/cancellation.js.map +1 -0
- package/dist/internal/automate/declare.d.ts.map +1 -1
- package/dist/internal/automate/declare.js +49 -24
- package/dist/internal/automate/declare.js.map +1 -1
- package/dist/internal/automate/engine.d.ts +7 -3
- package/dist/internal/automate/engine.d.ts.map +1 -1
- package/dist/internal/automate/engine.js +196 -93
- package/dist/internal/automate/engine.js.map +1 -1
- package/dist/internal/automate/error-message.d.ts +3 -0
- package/dist/internal/automate/error-message.d.ts.map +1 -0
- package/dist/internal/automate/error-message.js +10 -0
- package/dist/internal/automate/error-message.js.map +1 -0
- package/dist/internal/automate/main.d.ts.map +1 -1
- package/dist/internal/automate/main.js +238 -35
- package/dist/internal/automate/main.js.map +1 -1
- package/dist/internal/automate/options.d.ts +4 -0
- package/dist/internal/automate/options.d.ts.map +1 -0
- package/dist/internal/automate/options.js +19 -0
- package/dist/internal/automate/options.js.map +1 -0
- package/dist/internal/connect/declared.d.ts +6 -27
- package/dist/internal/connect/declared.d.ts.map +1 -1
- package/dist/internal/connect/declared.js +165 -76
- package/dist/internal/connect/declared.js.map +1 -1
- package/dist/internal/connect/error-message.d.ts +3 -0
- package/dist/internal/connect/error-message.d.ts.map +1 -0
- package/dist/internal/connect/error-message.js +10 -0
- package/dist/internal/connect/error-message.js.map +1 -0
- package/dist/internal/connect/federation.d.ts +9 -0
- package/dist/internal/connect/federation.d.ts.map +1 -0
- package/dist/internal/connect/federation.js +19 -0
- package/dist/internal/connect/federation.js.map +1 -0
- package/dist/internal/connect/fetch-failure.d.ts +3 -0
- package/dist/internal/connect/fetch-failure.d.ts.map +1 -0
- package/dist/internal/connect/fetch-failure.js +36 -0
- package/dist/internal/connect/fetch-failure.js.map +1 -0
- package/dist/internal/connect/main.d.ts +1 -0
- package/dist/internal/connect/main.d.ts.map +1 -1
- package/dist/internal/connect/main.js +288 -114
- package/dist/internal/connect/main.js.map +1 -1
- package/dist/internal/connect/options.d.ts +3 -0
- package/dist/internal/connect/options.d.ts.map +1 -0
- package/dist/internal/connect/options.js +9 -0
- package/dist/internal/connect/options.js.map +1 -0
- package/dist/internal/connect/records.d.ts +3 -0
- package/dist/internal/connect/records.d.ts.map +1 -0
- package/dist/internal/connect/records.js +21 -0
- package/dist/internal/connect/records.js.map +1 -0
- package/dist/internal/connect/run.d.ts +1 -18
- package/dist/internal/connect/run.d.ts.map +1 -1
- package/dist/internal/connect/run.js +115 -55
- package/dist/internal/connect/run.js.map +1 -1
- package/dist/internal/connect/source.d.ts +3 -1
- package/dist/internal/connect/source.d.ts.map +1 -1
- package/dist/internal/connect/source.js +226 -47
- package/dist/internal/connect/source.js.map +1 -1
- package/dist/internal/connect/template.d.ts +2 -2
- package/dist/internal/connect/template.d.ts.map +1 -1
- package/dist/internal/connect/template.js +25 -9
- package/dist/internal/connect/template.js.map +1 -1
- package/dist/internal/connect/utf8.d.ts +3 -0
- package/dist/internal/connect/utf8.d.ts.map +1 -0
- package/dist/internal/connect/utf8.js +12 -0
- package/dist/internal/connect/utf8.js.map +1 -0
- package/dist/internal/eval/cleanup.d.ts +5 -0
- package/dist/internal/eval/cleanup.d.ts.map +1 -0
- package/dist/internal/eval/cleanup.js +50 -0
- package/dist/internal/eval/cleanup.js.map +1 -0
- package/dist/internal/eval/error-message.d.ts +3 -0
- package/dist/internal/eval/error-message.d.ts.map +1 -0
- package/dist/internal/eval/error-message.js +10 -0
- package/dist/internal/eval/error-message.js.map +1 -0
- package/dist/internal/eval/judge.d.ts.map +1 -1
- package/dist/internal/eval/judge.js +3 -31
- package/dist/internal/eval/judge.js.map +1 -1
- package/dist/internal/eval/loop.d.ts.map +1 -1
- package/dist/internal/eval/loop.js +18 -4
- package/dist/internal/eval/loop.js.map +1 -1
- package/dist/internal/eval/main.d.ts.map +1 -1
- package/dist/internal/eval/main.js +22 -10
- package/dist/internal/eval/main.js.map +1 -1
- package/dist/internal/eval/options.d.ts +6 -0
- package/dist/internal/eval/options.d.ts.map +1 -0
- package/dist/internal/eval/options.js +31 -0
- package/dist/internal/eval/options.js.map +1 -0
- package/dist/internal/eval/queries.d.ts.map +1 -1
- package/dist/internal/eval/queries.js +9 -6
- package/dist/internal/eval/queries.js.map +1 -1
- package/dist/internal/eval/run.d.ts.map +1 -1
- package/dist/internal/eval/run.js +112 -24
- package/dist/internal/eval/run.js.map +1 -1
- package/dist/internal/eval/score.d.ts.map +1 -1
- package/dist/internal/eval/score.js +9 -2
- package/dist/internal/eval/score.js.map +1 -1
- package/dist/internal/eval/suite.d.ts.map +1 -1
- package/dist/internal/eval/suite.js +14 -2
- package/dist/internal/eval/suite.js.map +1 -1
- package/dist/internal/ingest/cleanup.d.ts +5 -0
- package/dist/internal/ingest/cleanup.d.ts.map +1 -0
- package/dist/internal/ingest/cleanup.js +50 -0
- package/dist/internal/ingest/cleanup.js.map +1 -0
- package/dist/internal/ingest/content.d.ts +6 -0
- package/dist/internal/ingest/content.d.ts.map +1 -0
- package/dist/internal/ingest/content.js +25 -0
- package/dist/internal/ingest/content.js.map +1 -0
- package/dist/internal/ingest/context.d.ts.map +1 -1
- package/dist/internal/ingest/context.js +25 -7
- package/dist/internal/ingest/context.js.map +1 -1
- package/dist/internal/ingest/error-message.d.ts +3 -0
- package/dist/internal/ingest/error-message.d.ts.map +1 -0
- package/dist/internal/ingest/error-message.js +10 -0
- package/dist/internal/ingest/error-message.js.map +1 -0
- package/dist/internal/ingest/files.d.ts +4 -12
- package/dist/internal/ingest/files.d.ts.map +1 -1
- package/dist/internal/ingest/files.js +22 -10
- package/dist/internal/ingest/files.js.map +1 -1
- package/dist/internal/ingest/main.d.ts.map +1 -1
- package/dist/internal/ingest/main.js +46 -16
- package/dist/internal/ingest/main.js.map +1 -1
- package/dist/internal/ingest/options.d.ts +6 -0
- package/dist/internal/ingest/options.d.ts.map +1 -0
- package/dist/internal/ingest/options.js +27 -0
- package/dist/internal/ingest/options.js.map +1 -0
- package/dist/internal/ingest/prompt.d.ts.map +1 -1
- package/dist/internal/ingest/prompt.js +14 -10
- package/dist/internal/ingest/prompt.js.map +1 -1
- package/dist/internal/ingest/run.d.ts +9 -2
- package/dist/internal/ingest/run.d.ts.map +1 -1
- package/dist/internal/ingest/run.js +206 -62
- package/dist/internal/ingest/run.js.map +1 -1
- package/dist/internal/ingest/timeout.d.ts +3 -0
- package/dist/internal/ingest/timeout.d.ts.map +1 -0
- package/dist/internal/ingest/timeout.js +14 -0
- package/dist/internal/ingest/timeout.js.map +1 -0
- package/dist/internal/ingest/web.d.ts +2 -11
- package/dist/internal/ingest/web.d.ts.map +1 -1
- package/dist/internal/ingest/web.js +131 -25
- package/dist/internal/ingest/web.js.map +1 -1
- package/dist/internal/loop/budgets.d.ts +3 -0
- package/dist/internal/loop/budgets.d.ts.map +1 -0
- package/dist/internal/loop/budgets.js +10 -0
- package/dist/internal/loop/budgets.js.map +1 -0
- package/dist/internal/loop/index.d.ts +2 -0
- package/dist/internal/loop/index.d.ts.map +1 -1
- package/dist/internal/loop/index.js +2 -0
- package/dist/internal/loop/index.js.map +1 -1
- package/dist/internal/loop/json-answer.d.ts +7 -0
- package/dist/internal/loop/json-answer.d.ts.map +1 -0
- package/dist/internal/loop/json-answer.js +79 -0
- package/dist/internal/loop/json-answer.js.map +1 -0
- package/dist/internal/loop/json-spans.d.ts +3 -0
- package/dist/internal/loop/json-spans.d.ts.map +1 -0
- package/dist/internal/loop/json-spans.js +88 -0
- package/dist/internal/loop/json-spans.js.map +1 -0
- package/dist/internal/loop/llm.d.ts +3 -2
- package/dist/internal/loop/llm.d.ts.map +1 -1
- package/dist/internal/loop/llm.js +115 -32
- package/dist/internal/loop/llm.js.map +1 -1
- package/dist/internal/loop/process.d.ts +3 -1
- package/dist/internal/loop/process.d.ts.map +1 -1
- package/dist/internal/loop/process.js +159 -59
- package/dist/internal/loop/process.js.map +1 -1
- package/dist/internal/loop/read-snapshot.d.ts +4 -0
- package/dist/internal/loop/read-snapshot.d.ts.map +1 -0
- package/dist/internal/loop/read-snapshot.js +29 -0
- package/dist/internal/loop/read-snapshot.js.map +1 -0
- package/dist/internal/loop/reconstruct.d.ts +6 -5
- package/dist/internal/loop/reconstruct.d.ts.map +1 -1
- package/dist/internal/loop/reconstruct.js +19 -8
- package/dist/internal/loop/reconstruct.js.map +1 -1
- package/dist/internal/loop/score-settings.d.ts +5 -0
- package/dist/internal/loop/score-settings.d.ts.map +1 -0
- package/dist/internal/loop/score-settings.js +14 -0
- package/dist/internal/loop/score-settings.js.map +1 -0
- package/dist/internal/loop/sqlite.d.ts.map +1 -1
- package/dist/internal/loop/sqlite.js +15 -7
- package/dist/internal/loop/sqlite.js.map +1 -1
- package/dist/internal/loop/store.d.ts.map +1 -1
- package/dist/internal/loop/store.js +11 -7
- package/dist/internal/loop/store.js.map +1 -1
- package/dist/internal/loop/timeout.d.ts +3 -0
- package/dist/internal/loop/timeout.d.ts.map +1 -0
- package/dist/internal/loop/timeout.js +13 -0
- package/dist/internal/loop/timeout.js.map +1 -0
- package/dist/internal/mcp/error-message.d.ts +3 -0
- package/dist/internal/mcp/error-message.d.ts.map +1 -0
- package/dist/internal/mcp/error-message.js +10 -0
- package/dist/internal/mcp/error-message.js.map +1 -0
- package/dist/internal/mcp/main.d.ts.map +1 -1
- package/dist/internal/mcp/main.js +42 -27
- package/dist/internal/mcp/main.js.map +1 -1
- package/dist/internal/mcp/read-snapshot.d.ts +4 -0
- package/dist/internal/mcp/read-snapshot.d.ts.map +1 -0
- package/dist/internal/mcp/read-snapshot.js +22 -0
- package/dist/internal/mcp/read-snapshot.js.map +1 -0
- package/dist/internal/mcp/server.d.ts.map +1 -1
- package/dist/internal/mcp/server.js +181 -21
- package/dist/internal/mcp/server.js.map +1 -1
- package/dist/internal/mcp/source.d.ts +3 -0
- package/dist/internal/mcp/source.d.ts.map +1 -0
- package/dist/internal/mcp/source.js +13 -0
- package/dist/internal/mcp/source.js.map +1 -0
- package/dist/internal/mcp/tools.d.ts.map +1 -1
- package/dist/internal/mcp/tools.js +135 -72
- package/dist/internal/mcp/tools.js.map +1 -1
- package/dist/internal/rules/declarations.d.ts +6 -0
- package/dist/internal/rules/declarations.d.ts.map +1 -0
- package/dist/internal/rules/declarations.js +14 -0
- package/dist/internal/rules/declarations.js.map +1 -0
- package/dist/internal/rules/declare.d.ts.map +1 -1
- package/dist/internal/rules/declare.js +30 -16
- package/dist/internal/rules/declare.js.map +1 -1
- package/dist/internal/rules/engine.d.ts +8 -7
- package/dist/internal/rules/engine.d.ts.map +1 -1
- package/dist/internal/rules/engine.js +150 -44
- package/dist/internal/rules/engine.js.map +1 -1
- package/dist/internal/shape/alias-root.d.ts +3 -0
- package/dist/internal/shape/alias-root.d.ts.map +1 -0
- package/dist/internal/shape/alias-root.js +13 -0
- package/dist/internal/shape/alias-root.js.map +1 -0
- package/dist/internal/shape/check.d.ts +15 -3
- package/dist/internal/shape/check.d.ts.map +1 -1
- package/dist/internal/shape/check.js +230 -100
- package/dist/internal/shape/check.js.map +1 -1
- package/dist/internal/shape/client.d.ts.map +1 -1
- package/dist/internal/shape/client.js +42 -20
- package/dist/internal/shape/client.js.map +1 -1
- package/dist/internal/shape/constraints.d.ts +8 -0
- package/dist/internal/shape/constraints.d.ts.map +1 -0
- package/dist/internal/shape/constraints.js +17 -0
- package/dist/internal/shape/constraints.js.map +1 -0
- package/dist/internal/shape/edit-distance.d.ts +3 -0
- package/dist/internal/shape/edit-distance.d.ts.map +1 -0
- package/dist/internal/shape/edit-distance.js +33 -0
- package/dist/internal/shape/edit-distance.js.map +1 -0
- package/dist/internal/shape/gate.d.ts.map +1 -1
- package/dist/internal/shape/gate.js +0 -0
- package/dist/internal/shape/gate.js.map +1 -1
- package/dist/internal/shape/snapshot.d.ts +4 -0
- package/dist/internal/shape/snapshot.d.ts.map +1 -0
- package/dist/internal/shape/snapshot.js +20 -0
- package/dist/internal/shape/snapshot.js.map +1 -0
- package/dist/internal/shape/suggest.d.ts +8 -6
- package/dist/internal/shape/suggest.d.ts.map +1 -1
- package/dist/internal/shape/suggest.js +208 -92
- package/dist/internal/shape/suggest.js.map +1 -1
- package/dist/internal/sync/error-message.d.ts +3 -0
- package/dist/internal/sync/error-message.d.ts.map +1 -0
- package/dist/internal/sync/error-message.js +10 -0
- package/dist/internal/sync/error-message.js.map +1 -0
- package/dist/internal/sync/index.d.ts +3 -2
- package/dist/internal/sync/index.d.ts.map +1 -1
- package/dist/internal/sync/index.js +3 -2
- package/dist/internal/sync/index.js.map +1 -1
- package/dist/internal/sync/sync.d.ts +8 -5
- package/dist/internal/sync/sync.d.ts.map +1 -1
- package/dist/internal/sync/sync.js +322 -65
- package/dist/internal/sync/sync.js.map +1 -1
- package/dist/internal/view/api.d.ts +3 -1
- package/dist/internal/view/api.d.ts.map +1 -1
- package/dist/internal/view/api.js +154 -90
- package/dist/internal/view/api.js.map +1 -1
- package/dist/internal/view/error-message.d.ts +3 -0
- package/dist/internal/view/error-message.d.ts.map +1 -0
- package/dist/internal/view/error-message.js +10 -0
- package/dist/internal/view/error-message.js.map +1 -0
- package/dist/internal/view/main.d.ts.map +1 -1
- package/dist/internal/view/main.js +61 -8
- package/dist/internal/view/main.js.map +1 -1
- package/dist/internal/view/page.d.ts.map +1 -1
- package/dist/internal/view/page.js +119 -20
- package/dist/internal/view/page.js.map +1 -1
- package/dist/internal/view/report.d.ts.map +1 -1
- package/dist/internal/view/report.js +180 -145
- package/dist/internal/view/report.js.map +1 -1
- package/dist/internal/view/scope.d.ts +6 -2
- package/dist/internal/view/scope.d.ts.map +1 -1
- package/dist/internal/view/scope.js +107 -24
- package/dist/internal/view/scope.js.map +1 -1
- package/dist/internal/view/sensitivity.d.ts +4 -0
- package/dist/internal/view/sensitivity.d.ts.map +1 -0
- package/dist/internal/view/sensitivity.js +11 -0
- package/dist/internal/view/sensitivity.js.map +1 -0
- package/dist/internal/view/server.d.ts.map +1 -1
- package/dist/internal/view/server.js +57 -20
- package/dist/internal/view/server.js.map +1 -1
- package/dist/src/atomic-output.d.ts +3 -0
- package/dist/src/atomic-output.d.ts.map +1 -0
- package/dist/src/atomic-output.js +67 -0
- package/dist/src/atomic-output.js.map +1 -0
- package/dist/src/cli.d.ts +3 -2
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +188 -173
- package/dist/src/cli.js.map +1 -1
- package/dist/src/dispatch.d.ts.map +1 -1
- package/dist/src/dispatch.js +3 -2
- package/dist/src/dispatch.js.map +1 -1
- package/dist/src/doctor.d.ts.map +1 -1
- package/dist/src/doctor.js +178 -102
- package/dist/src/doctor.js.map +1 -1
- package/dist/src/error-message.d.ts +3 -0
- package/dist/src/error-message.d.ts.map +1 -0
- package/dist/src/error-message.js +20 -0
- package/dist/src/error-message.js.map +1 -0
- package/dist/src/hooks-config.d.ts +3 -0
- package/dist/src/hooks-config.d.ts.map +1 -0
- package/dist/src/hooks-config.js +23 -0
- package/dist/src/hooks-config.js.map +1 -0
- package/package.json +23 -20
- package/src/atomic-output.ts +47 -0
- package/src/cli.ts +170 -147
- package/src/dispatch.ts +3 -2
- package/src/doctor.ts +192 -111
- package/src/error-message.ts +13 -0
- package/src/hooks-config.ts +22 -0
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A row's UUIDv7 — one value serving as both `id` and `tx` — is its global
|
|
5
5
|
* identity (§28.1): merging copies rows absent by id verbatim (side tables
|
|
6
|
-
* included) and skips rows the target already has
|
|
7
|
-
*
|
|
6
|
+
* included) and skips matching rows the target already has. A reused id
|
|
7
|
+
* with different content is rejected. Valid replicas are idempotent,
|
|
8
|
+
* transitive and bidirectional —
|
|
8
9
|
* coexisting contradictions are legal data (§9.4), resolved at read time
|
|
9
10
|
* (§26). Merged transaction ids feed the generator's receive rule (§28.2),
|
|
10
11
|
* so everything appended after a merge sorts after everything merged,
|
|
@@ -34,6 +35,7 @@ export type SyncOptions = {
|
|
|
34
35
|
readonly dryRun?: boolean;
|
|
35
36
|
};
|
|
36
37
|
export type SyncProblem = {
|
|
38
|
+
/** Source text line, or zero for a database-source problem. */
|
|
37
39
|
readonly line: number;
|
|
38
40
|
readonly message: string;
|
|
39
41
|
};
|
|
@@ -47,12 +49,12 @@ export type SyncReport = {
|
|
|
47
49
|
readonly dryRun: boolean;
|
|
48
50
|
/** The §28.3 merge record appended, when the merge was effective and records are on. */
|
|
49
51
|
readonly record?: string;
|
|
50
|
-
/**
|
|
52
|
+
/** Source identity or text-validation problems; nothing merges when non-empty. */
|
|
51
53
|
readonly problems: readonly SyncProblem[];
|
|
52
54
|
};
|
|
53
55
|
/**
|
|
54
56
|
* @returns text usable as one entity token in a §28.3 record subject:
|
|
55
|
-
* whitespace and comment/metadata sigils normalized to `-`, never empty.
|
|
57
|
+
* whitespace, literal delimiters, attribute colons and comment/metadata sigils normalized to `-`, never empty.
|
|
56
58
|
*/
|
|
57
59
|
export declare const sanitizeLabel: (text: string) => string;
|
|
58
60
|
/**
|
|
@@ -66,7 +68,8 @@ export declare const labelOf: (path: string) => string;
|
|
|
66
68
|
* tags, FTS — edges dedupe against stored edges, merged in-band
|
|
67
69
|
* declarations reload the registry, and the generator observes the merged
|
|
68
70
|
* transaction ids (§28.2). Idempotent: a re-run merges nothing and appends
|
|
69
|
-
* no record.
|
|
71
|
+
* no record. Conflicting existing identities reject the whole source before
|
|
72
|
+
* copying, with database problems reported at line zero.
|
|
70
73
|
*/
|
|
71
74
|
export declare const syncDb: (store: Store, sourcePath: string, options?: SyncOptions) => SyncReport;
|
|
72
75
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/sync.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAMH,OAAO,EAA2B,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,iBAAiB,CAAA;AAElF,OAAO,EAAE,WAAW,EAAE,CAAA;AAEtB,MAAM,MAAM,WAAW,GAAG;IACxB,oHAAoH;IACpH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,gEAAgE;IAChE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IACzB,kFAAkF;IAClF,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAC1B,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,sDAAsD;IACtD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,oDAAoD;IACpD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;IACxB,wFAAwF;IACxF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,kFAAkF;IAClF,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAA;CAC1C,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,MAG5C,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,KAAG,MAGtC,CAAA;AA+DD;;;;;;;;GAQG;AACH,eAAO,MAAM,MAAM,GAAI,OAAO,KAAK,EAAE,YAAY,MAAM,EAAE,UAAS,WAAgB,KAAG,UAmRpF,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,QAAQ,GAAI,OAAO,KAAK,EAAE,MAAM,MAAM,EAAE,UAAS,WAAgB,KAAG,UAOhF,CAAA;AAsID;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,GAAI,OAAO,KAAK,EAAE,YAAY,MAAM,EAAE,UAAS,WAAgB,KAAG,UAgBtF,CAAA"}
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A row's UUIDv7 — one value serving as both `id` and `tx` — is its global
|
|
5
5
|
* identity (§28.1): merging copies rows absent by id verbatim (side tables
|
|
6
|
-
* included) and skips rows the target already has
|
|
7
|
-
*
|
|
6
|
+
* included) and skips matching rows the target already has. A reused id
|
|
7
|
+
* with different content is rejected. Valid replicas are idempotent,
|
|
8
|
+
* transitive and bidirectional —
|
|
8
9
|
* coexisting contradictions are legal data (§9.4), resolved at read time
|
|
9
10
|
* (§26). Merged transaction ids feed the generator's receive rule (§28.2),
|
|
10
11
|
* so everything appended after a merge sorts after everything merged,
|
|
@@ -21,17 +22,18 @@
|
|
|
21
22
|
* `store/<from> SYNCED-INTO store/<into> @src:sync` — whose belief series
|
|
22
23
|
* is the sync log; a merge that changed nothing appends nothing.
|
|
23
24
|
*/
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
25
|
+
import { errorMessage } from "./error-message.js";
|
|
26
|
+
import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
27
|
+
import { Claim, Key, Uuidv7 } from '@cavelang/core';
|
|
26
28
|
import * as Canonical from '@cavelang/canonical';
|
|
27
|
-
import { Provenance, Schema, isStoreFile } from '@cavelang/store';
|
|
29
|
+
import { Provenance, Row, Schema, isStoreFile } from '@cavelang/store';
|
|
28
30
|
export { isStoreFile };
|
|
29
31
|
/**
|
|
30
32
|
* @returns text usable as one entity token in a §28.3 record subject:
|
|
31
|
-
* whitespace and comment/metadata sigils normalized to `-`, never empty.
|
|
33
|
+
* whitespace, literal delimiters, attribute colons and comment/metadata sigils normalized to `-`, never empty.
|
|
32
34
|
*/
|
|
33
35
|
export const sanitizeLabel = (text) => {
|
|
34
|
-
const label = text.replaceAll(/[\s
|
|
36
|
+
const label = text.replaceAll(/[\s;:@#"`]+/g, '-');
|
|
35
37
|
return label === '' ? 'store' : label;
|
|
36
38
|
};
|
|
37
39
|
/**
|
|
@@ -65,7 +67,7 @@ const rolledBack = (store, body) => {
|
|
|
65
67
|
* §9.5 path.
|
|
66
68
|
*/
|
|
67
69
|
const appendRecord = (store, from, into, merged, edges) => {
|
|
68
|
-
const declared =
|
|
70
|
+
const declared = Canonical.Registry.isDeclared(store.registry(), 'SYNCED-INTO');
|
|
69
71
|
const line = `store/${sanitizeLabel(from)} SYNCED-INTO store/${sanitizeLabel(into)} ; +${merged} claim(s), +${edges} edge(s)`;
|
|
70
72
|
const text = declared ?
|
|
71
73
|
line :
|
|
@@ -80,40 +82,85 @@ const claimColumns = [
|
|
|
80
82
|
'delta_text', 'delta_num', 'delta_unit', 'sigma_level',
|
|
81
83
|
'conf', 'importance', 'comment', 'raw_line', 'claim_key'
|
|
82
84
|
].join(', ');
|
|
85
|
+
/** Preserve operation failures when releasing a sync-owned SQL resource also fails. */
|
|
86
|
+
const withSyncCleanup = (run, cleanup) => {
|
|
87
|
+
let result;
|
|
88
|
+
try {
|
|
89
|
+
result = run();
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
try {
|
|
93
|
+
cleanup();
|
|
94
|
+
}
|
|
95
|
+
catch (cleanupError) {
|
|
96
|
+
const messages = [error, cleanupError].map(errorMessage);
|
|
97
|
+
throw new AggregateError([error, cleanupError], `database sync failed: ${messages[0]}; cleanup also failed: ${messages[1]}`, { cause: error });
|
|
98
|
+
}
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
cleanup();
|
|
102
|
+
return result;
|
|
103
|
+
};
|
|
104
|
+
const booleanOption = (value, name, fallback) => {
|
|
105
|
+
if (value === undefined)
|
|
106
|
+
return fallback;
|
|
107
|
+
if (typeof value !== 'boolean')
|
|
108
|
+
throw new TypeError(`${name} must be a boolean`);
|
|
109
|
+
return value;
|
|
110
|
+
};
|
|
83
111
|
/**
|
|
84
112
|
* Merges another CAVE store file into `store` (spec §28.1): rows absent by
|
|
85
113
|
* id are copied verbatim — `id`, `tx`, `claim_key`, `raw_line`, contexts,
|
|
86
114
|
* tags, FTS — edges dedupe against stored edges, merged in-band
|
|
87
115
|
* declarations reload the registry, and the generator observes the merged
|
|
88
116
|
* transaction ids (§28.2). Idempotent: a re-run merges nothing and appends
|
|
89
|
-
* no record.
|
|
117
|
+
* no record. Conflicting existing identities reject the whole source before
|
|
118
|
+
* copying, with database problems reported at line zero.
|
|
90
119
|
*/
|
|
91
120
|
export const syncDb = (store, sourcePath, options = {}) => {
|
|
121
|
+
const dryRun = booleanOption(options.dryRun, 'dryRun', false);
|
|
122
|
+
const recordEnabled = booleanOption(options.record, 'record', true);
|
|
92
123
|
if (!existsSync(sourcePath)) {
|
|
93
124
|
// ATTACH would create an empty database at the missing path.
|
|
94
125
|
throw new Error(`${sourcePath}: no such file`);
|
|
95
126
|
}
|
|
96
127
|
const db = store.db;
|
|
97
|
-
const
|
|
128
|
+
const from = options.from ?? 'origin';
|
|
129
|
+
const into = options.into ?? 'local';
|
|
98
130
|
const mainFile = db.prepare("SELECT file FROM pragma_database_list WHERE name = 'main'").get().file;
|
|
99
|
-
|
|
131
|
+
const sameFile = () => {
|
|
132
|
+
if (mainFile === '')
|
|
133
|
+
return false;
|
|
134
|
+
if (realpathSync(sourcePath) === realpathSync(mainFile))
|
|
135
|
+
return true;
|
|
136
|
+
const source = statSync(sourcePath);
|
|
137
|
+
const main = statSync(mainFile);
|
|
138
|
+
return source.dev === main.dev && source.ino === main.ino;
|
|
139
|
+
};
|
|
140
|
+
if (sameFile()) {
|
|
100
141
|
// Attaching the store to itself would deadlock on its own lock; a
|
|
101
142
|
// store trivially holds every row it holds.
|
|
102
143
|
return { merged: 0, skipped: 0, edges: 0, dryRun, problems: [] };
|
|
103
144
|
}
|
|
145
|
+
// SQLite cannot detach an attachment read by an uncommitted outer transaction.
|
|
146
|
+
store.transaction(({ outermost }) => {
|
|
147
|
+
if (!outermost) {
|
|
148
|
+
throw new Error('database sync cannot run inside a caller-owned transaction; sync after it commits or use annotated-text sync');
|
|
149
|
+
}
|
|
150
|
+
});
|
|
104
151
|
try {
|
|
105
152
|
db.prepare('ATTACH DATABASE ? AS cave_sync_src').run(sourcePath);
|
|
106
153
|
}
|
|
107
|
-
catch {
|
|
108
|
-
throw new Error(`${sourcePath}:
|
|
154
|
+
catch (cause) {
|
|
155
|
+
throw new Error(`${sourcePath}: cannot attach sync source: ${errorMessage(cause)}`, { cause });
|
|
109
156
|
}
|
|
110
|
-
|
|
157
|
+
return withSyncCleanup(() => {
|
|
111
158
|
let isStore = false;
|
|
112
159
|
try {
|
|
113
160
|
isStore = db.prepare("SELECT 1 FROM cave_sync_src.sqlite_master WHERE type = 'table' AND name = 'cave_claim'").get() !== undefined;
|
|
114
161
|
}
|
|
115
|
-
catch {
|
|
116
|
-
|
|
162
|
+
catch (cause) {
|
|
163
|
+
throw new Error(`${sourcePath}: cannot inspect sync source: ${errorMessage(cause)}`, { cause });
|
|
117
164
|
}
|
|
118
165
|
if (!isStore) {
|
|
119
166
|
throw new Error(`${sourcePath}: not a CAVE store (no cave_claim table)`);
|
|
@@ -123,20 +170,150 @@ export const syncDb = (store, sourcePath, options = {}) => {
|
|
|
123
170
|
throw new Error(`${sourcePath}: schema version ${sourceVersion} is newer than this runtime supports ` +
|
|
124
171
|
`(${Schema.currentVersion}); upgrade CAVE`);
|
|
125
172
|
}
|
|
126
|
-
if (sourceVersion
|
|
173
|
+
if (sourceVersion >= 1) {
|
|
127
174
|
try {
|
|
128
175
|
Schema.validate(db, sourceVersion, 'cave_sync_src');
|
|
129
176
|
}
|
|
130
177
|
catch (error) {
|
|
131
|
-
throw new Error(`${sourcePath}: ${error
|
|
178
|
+
throw new Error(`${sourcePath}: ${errorMessage(error)}`, { cause: error });
|
|
132
179
|
}
|
|
133
180
|
}
|
|
181
|
+
const hasProvenance = db.prepare(`
|
|
182
|
+
SELECT 1 FROM cave_sync_src.sqlite_master
|
|
183
|
+
WHERE type = 'table' AND name = 'cave_provenance'
|
|
184
|
+
`).get() !== undefined;
|
|
134
185
|
const body = () => {
|
|
186
|
+
const references = [
|
|
187
|
+
['cave_context', ['claim_id']], ['cave_tag', ['claim_id']],
|
|
188
|
+
['cave_edge', ['parent_id', 'child_id']],
|
|
189
|
+
...(hasProvenance ? [['cave_provenance', ['claim_id']]] : [])
|
|
190
|
+
];
|
|
191
|
+
for (const [table, fields] of references) {
|
|
192
|
+
// Validate source relationships independently of what the target contains.
|
|
193
|
+
// Explicit queries also cover legacy tables without declared foreign keys.
|
|
194
|
+
const missing = fields.map(field => `NOT EXISTS (SELECT 1 FROM cave_sync_src.cave_claim c WHERE c.id = s.${field})`).join(' OR ');
|
|
195
|
+
if (db.prepare(`SELECT 1 FROM cave_sync_src.${table} s WHERE ${missing} LIMIT 1`).get() !== undefined) {
|
|
196
|
+
return { merged: 0, skipped: 0, edges: 0, dryRun, problems: [{ line: 0,
|
|
197
|
+
message: `source ${table} contains references to missing claims` }] };
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (db.prepare(`SELECT 1 FROM cave_sync_src.cave_edge
|
|
201
|
+
WHERE role IS NULL OR role NOT IN ('WHEN', 'VIA', 'BECAUSE', 'QUALIFIES') LIMIT 1`).get() !== undefined) {
|
|
202
|
+
return { merged: 0, skipped: 0, edges: 0, dryRun, problems: [{ line: 0,
|
|
203
|
+
message: 'source contains an invalid edge role — expected WHEN, VIA, BECAUSE or QUALIFIES' }] };
|
|
204
|
+
}
|
|
205
|
+
const identities = db.prepare('SELECT id, tx FROM cave_sync_src.cave_claim ORDER BY id').all();
|
|
206
|
+
const invalid = identities.filter(row => !Uuidv7.is(row.id) || row.tx !== row.id);
|
|
207
|
+
if (invalid.length > 0) {
|
|
208
|
+
return { merged: 0, skipped: 0, edges: 0, dryRun, problems: invalid.map(row => ({
|
|
209
|
+
line: 0,
|
|
210
|
+
message: `row ${JSON.stringify(row.id)} has invalid transaction identity — expected id = tx as a canonical lowercase UUIDv7 (spec §28.1)`,
|
|
211
|
+
})) };
|
|
212
|
+
}
|
|
213
|
+
if (hasProvenance) {
|
|
214
|
+
const malformed = db.prepare(`
|
|
215
|
+
SELECT DISTINCT claim_id AS id FROM cave_sync_src.cave_provenance
|
|
216
|
+
WHERE typeof(value) <> 'text' OR value = '' ORDER BY claim_id
|
|
217
|
+
`).all();
|
|
218
|
+
if (malformed.length > 0) {
|
|
219
|
+
return { merged: 0, skipped: 0, edges: 0, dryRun, problems: malformed.map(row => ({
|
|
220
|
+
line: 0,
|
|
221
|
+
message: `row ${JSON.stringify(row.id)} has invalid stored provenance — values must be nonempty text`,
|
|
222
|
+
})) };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// Compare persisted claim data under the same reservation as the copy.
|
|
226
|
+
// Raw spelling can change during text replay; metadata order is immaterial.
|
|
227
|
+
// Keep normalized value fields and claim_key in the comparison: divergent
|
|
228
|
+
// query data must not hide behind equal emitted text.
|
|
229
|
+
const columns = claimColumns.split(', ').filter(column => column !== 'raw_line');
|
|
230
|
+
const fieldsDiffer = columns.map(column => column === 'sigma_level' ?
|
|
231
|
+
'COALESCE(s.sigma_level, 2) IS NOT COALESCE(t.sigma_level, 2)' :
|
|
232
|
+
`s.${column} IS NOT t.${column}`).join(' OR ');
|
|
233
|
+
const setsDiffer = (table, fields) => `
|
|
234
|
+
EXISTS (SELECT ${fields} FROM cave_sync_src.${table} WHERE claim_id = s.id
|
|
235
|
+
EXCEPT SELECT ${fields} FROM main.${table} WHERE claim_id = t.id)
|
|
236
|
+
OR EXISTS (SELECT ${fields} FROM main.${table} WHERE claim_id = t.id
|
|
237
|
+
EXCEPT SELECT ${fields} FROM cave_sync_src.${table} WHERE claim_id = s.id)`;
|
|
238
|
+
const conflicts = db.prepare(`
|
|
239
|
+
SELECT s.id FROM cave_sync_src.cave_claim s
|
|
240
|
+
JOIN main.cave_claim t ON t.id = s.id
|
|
241
|
+
WHERE ${fieldsDiffer}
|
|
242
|
+
OR ${setsDiffer('cave_context', 'context')}
|
|
243
|
+
OR ${setsDiffer('cave_tag', 'key, value')}
|
|
244
|
+
ORDER BY s.id
|
|
245
|
+
`).all();
|
|
246
|
+
if (hasProvenance) {
|
|
247
|
+
// Sync/backfill can add safely inferred dimensions. Ignore only those
|
|
248
|
+
// differences; explicit provenance on the same identity must agree.
|
|
249
|
+
const differences = db.prepare(`
|
|
250
|
+
SELECT claim_id, dimension, value FROM (
|
|
251
|
+
SELECT p.claim_id, p.dimension, p.value FROM cave_sync_src.cave_provenance p
|
|
252
|
+
JOIN main.cave_claim t ON t.id = p.claim_id
|
|
253
|
+
EXCEPT SELECT claim_id, dimension, value FROM main.cave_provenance
|
|
254
|
+
)
|
|
255
|
+
UNION
|
|
256
|
+
SELECT claim_id, dimension, value FROM (
|
|
257
|
+
SELECT p.claim_id, p.dimension, p.value FROM main.cave_provenance p
|
|
258
|
+
JOIN cave_sync_src.cave_claim s ON s.id = p.claim_id
|
|
259
|
+
EXCEPT SELECT claim_id, dimension, value FROM cave_sync_src.cave_provenance
|
|
260
|
+
)
|
|
261
|
+
ORDER BY claim_id
|
|
262
|
+
`).all();
|
|
263
|
+
const contextsOf = db.prepare('SELECT context FROM main.cave_context WHERE claim_id = ?');
|
|
264
|
+
const inferred = new Map();
|
|
265
|
+
const ids = new Set(conflicts.map(row => row.id));
|
|
266
|
+
for (const difference of differences) {
|
|
267
|
+
let entries = inferred.get(difference.claim_id);
|
|
268
|
+
if (entries === undefined) {
|
|
269
|
+
const contexts = contextsOf.all(difference.claim_id);
|
|
270
|
+
entries = new Set(Provenance.entries(contexts.map(row => row.context))
|
|
271
|
+
.map(entry => JSON.stringify([entry.dimension, entry.value])));
|
|
272
|
+
inferred.set(difference.claim_id, entries);
|
|
273
|
+
}
|
|
274
|
+
if (!entries.has(JSON.stringify([difference.dimension, difference.value])) && !ids.has(difference.claim_id)) {
|
|
275
|
+
ids.add(difference.claim_id);
|
|
276
|
+
conflicts.push({ id: difference.claim_id });
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (conflicts.length > 0) {
|
|
281
|
+
conflicts.sort((a, b) => a.id.localeCompare(b.id));
|
|
282
|
+
return { merged: 0, skipped: 0, edges: 0, dryRun, problems: conflicts.map(({ id }) => ({
|
|
283
|
+
line: 0,
|
|
284
|
+
message: `transaction id ${id} is already stored with different content — a row has one identity (spec §28.1)`,
|
|
285
|
+
})) };
|
|
286
|
+
}
|
|
287
|
+
const sourceClaims = db.prepare('SELECT * FROM cave_sync_src.cave_claim WHERE id > ? ORDER BY id LIMIT 512');
|
|
288
|
+
const sourceContexts = db.prepare('SELECT context FROM cave_sync_src.cave_context WHERE claim_id = ?');
|
|
289
|
+
const sourceTags = db.prepare('SELECT key, value FROM cave_sync_src.cave_tag WHERE claim_id = ?');
|
|
290
|
+
let after = '';
|
|
291
|
+
for (;;) {
|
|
292
|
+
const batch = sourceClaims.all(after);
|
|
293
|
+
if (batch.length === 0)
|
|
294
|
+
break;
|
|
295
|
+
const problems = [];
|
|
296
|
+
for (const row of batch) {
|
|
297
|
+
const contexts = sourceContexts.all(row.id).map(entry => entry.context);
|
|
298
|
+
const tags = sourceTags.all(row.id);
|
|
299
|
+
try {
|
|
300
|
+
const claim = Row.toClaim(row, contexts, tags);
|
|
301
|
+
Canonical.emitClaim(claim);
|
|
302
|
+
if (Key.of(claim) === row.claim_key)
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
catch { /* Report malformed stored data without exposing claim contents. */ }
|
|
306
|
+
problems.push({ line: 0, message: `row ${JSON.stringify(row.id)} has an invalid stored claim or inconsistent semantic key` });
|
|
307
|
+
}
|
|
308
|
+
if (problems.length > 0)
|
|
309
|
+
return { merged: 0, skipped: 0, edges: 0, dryRun, problems };
|
|
310
|
+
after = batch[batch.length - 1].id;
|
|
311
|
+
}
|
|
135
312
|
db.exec('DROP TABLE IF EXISTS temp.cave_sync_new');
|
|
136
313
|
db.exec(`CREATE TEMP TABLE cave_sync_new AS
|
|
137
314
|
SELECT id FROM cave_sync_src.cave_claim
|
|
138
315
|
WHERE id NOT IN (SELECT id FROM main.cave_claim)`);
|
|
139
|
-
|
|
316
|
+
return withSyncCleanup(() => {
|
|
140
317
|
const count = (sql) => db.prepare(sql).get().n;
|
|
141
318
|
const total = count('SELECT COUNT(*) AS n FROM cave_sync_src.cave_claim');
|
|
142
319
|
const merged = count('SELECT COUNT(*) AS n FROM temp.cave_sync_new');
|
|
@@ -146,26 +323,27 @@ export const syncDb = (store, sourcePath, options = {}) => {
|
|
|
146
323
|
db.exec(`INSERT INTO main.cave_context (claim_id, context)
|
|
147
324
|
SELECT claim_id, context FROM cave_sync_src.cave_context
|
|
148
325
|
WHERE claim_id IN (SELECT id FROM temp.cave_sync_new)`);
|
|
149
|
-
const hasProvenance = db.prepare(`
|
|
150
|
-
SELECT 1 FROM cave_sync_src.sqlite_master
|
|
151
|
-
WHERE type = 'table' AND name = 'cave_provenance'
|
|
152
|
-
`).get() !== undefined;
|
|
153
326
|
if (hasProvenance) {
|
|
154
327
|
db.exec(`INSERT OR IGNORE INTO main.cave_provenance (claim_id, dimension, value)
|
|
155
328
|
SELECT claim_id, dimension, value FROM cave_sync_src.cave_provenance
|
|
156
329
|
WHERE claim_id IN (SELECT id FROM temp.cave_sync_new)`);
|
|
157
330
|
}
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
|
|
161
|
-
const contextRows = db.prepare(`
|
|
331
|
+
// Only legacy stores without a dimension table need inference.
|
|
332
|
+
// A present table is authoritative, including explicit empty sets.
|
|
333
|
+
const contextRows = hasProvenance ? [] : db.prepare(`
|
|
162
334
|
SELECT n.id, ctx.context FROM temp.cave_sync_new n
|
|
163
335
|
LEFT JOIN main.cave_context ctx ON ctx.claim_id = n.id
|
|
164
336
|
ORDER BY n.id, ctx.rowid
|
|
165
337
|
`).all();
|
|
166
338
|
const contexts = new Map();
|
|
167
339
|
for (const row of contextRows) {
|
|
168
|
-
|
|
340
|
+
let values = contexts.get(row.id);
|
|
341
|
+
if (values === undefined) {
|
|
342
|
+
values = [];
|
|
343
|
+
contexts.set(row.id, values);
|
|
344
|
+
}
|
|
345
|
+
if (row.context !== null)
|
|
346
|
+
values.push(row.context);
|
|
169
347
|
}
|
|
170
348
|
const insertProvenance = db.prepare(`
|
|
171
349
|
INSERT OR IGNORE INTO main.cave_provenance (claim_id, dimension, value) VALUES (?, ?, ?)
|
|
@@ -204,8 +382,8 @@ export const syncDb = (store, sourcePath, options = {}) => {
|
|
|
204
382
|
// dry runs and failures.
|
|
205
383
|
store.reloadRegistry();
|
|
206
384
|
const effective = merged + edges > 0;
|
|
207
|
-
const record = effective &&
|
|
208
|
-
appendRecord(store,
|
|
385
|
+
const record = effective && recordEnabled ?
|
|
386
|
+
appendRecord(store, from, into, merged, edges) :
|
|
209
387
|
undefined;
|
|
210
388
|
return {
|
|
211
389
|
merged,
|
|
@@ -215,18 +393,12 @@ export const syncDb = (store, sourcePath, options = {}) => {
|
|
|
215
393
|
...record === undefined ? {} : { record },
|
|
216
394
|
problems: []
|
|
217
395
|
};
|
|
218
|
-
}
|
|
219
|
-
finally {
|
|
220
|
-
db.exec('DROP TABLE IF EXISTS temp.cave_sync_new');
|
|
221
|
-
}
|
|
396
|
+
}, () => db.exec('DROP TABLE IF EXISTS temp.cave_sync_new'));
|
|
222
397
|
};
|
|
223
398
|
return dryRun ?
|
|
224
399
|
Uuidv7.withStatePreserved(() => rolledBack(store, body)) :
|
|
225
400
|
store.transaction(body);
|
|
226
|
-
}
|
|
227
|
-
finally {
|
|
228
|
-
db.exec('DETACH DATABASE cave_sync_src');
|
|
229
|
-
}
|
|
401
|
+
}, () => db.exec('DETACH DATABASE cave_sync_src'));
|
|
230
402
|
};
|
|
231
403
|
/**
|
|
232
404
|
* Merges §28.4 transaction-annotated canonical text (`cave export --tx`)
|
|
@@ -243,25 +415,71 @@ export const syncDb = (store, sourcePath, options = {}) => {
|
|
|
243
415
|
* row, contributing its edge.
|
|
244
416
|
*/
|
|
245
417
|
export const syncText = (store, text, options = {}) => {
|
|
246
|
-
const dryRun = options.dryRun
|
|
418
|
+
const dryRun = booleanOption(options.dryRun, 'dryRun', false);
|
|
419
|
+
const recordOptions = { record: booleanOption(options.record, 'record', true), from: options.from, into: options.into };
|
|
420
|
+
const body = () => syncTextReserved(store, text, recordOptions, dryRun);
|
|
421
|
+
return dryRun ?
|
|
422
|
+
Uuidv7.withStatePreserved(() => rolledBack(store, body)) :
|
|
423
|
+
store.transaction(body);
|
|
424
|
+
};
|
|
425
|
+
/** Compare canonical interchange content, independent of raw spelling and metadata order. */
|
|
426
|
+
const identityContent = (claim) => Canonical.emitClaim({
|
|
427
|
+
...claim,
|
|
428
|
+
contexts: [...claim.contexts].sort(),
|
|
429
|
+
tags: [...claim.tags].sort((a, b) => {
|
|
430
|
+
const left = JSON.stringify([a.key, a.value]);
|
|
431
|
+
const right = JSON.stringify([b.key, b.value]);
|
|
432
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
433
|
+
}),
|
|
434
|
+
});
|
|
435
|
+
/** Validation and identity replay use the same reserved vocabulary snapshot. */
|
|
436
|
+
const syncTextReserved = (store, text, options, dryRun) => {
|
|
247
437
|
const problems = [];
|
|
248
438
|
const txByLine = new Map();
|
|
439
|
+
const provenanceByLine = new Map();
|
|
249
440
|
text.split(/\r?\n/).forEach((raw, at) => {
|
|
250
441
|
const tx = Canonical.txOfLine(raw);
|
|
251
442
|
if (tx === undefined) {
|
|
443
|
+
// A malformed annotation must not become comment prose merely because
|
|
444
|
+
// a later valid annotation supplies the claim's identity.
|
|
445
|
+
if (/^\s*;@/.test(raw)) {
|
|
446
|
+
problems.push({ line: at + 1, message: 'malformed transaction annotation — expected `;@ <uuidv7>` with optional provenance JSON (spec §28.4)' });
|
|
447
|
+
}
|
|
252
448
|
return;
|
|
253
449
|
}
|
|
254
450
|
if (Uuidv7.is(tx)) {
|
|
255
451
|
txByLine.set(at + 1, tx);
|
|
452
|
+
const data = Canonical.txDataOfLine(raw);
|
|
453
|
+
if (data !== undefined) {
|
|
454
|
+
let provenance;
|
|
455
|
+
try {
|
|
456
|
+
const payload = JSON.parse(data);
|
|
457
|
+
if (payload !== null && typeof payload === 'object' && !Array.isArray(payload) &&
|
|
458
|
+
Object.keys(payload).length === 1 && Object.hasOwn(payload, 'provenance')) {
|
|
459
|
+
provenance = Provenance.parse(payload.provenance);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
catch { /* Report malformed JSON alongside other source problems. */ }
|
|
463
|
+
if (provenance === undefined) {
|
|
464
|
+
problems.push({ line: at + 1, message: 'malformed provenance annotation — expected a JSON object with complete provenance dimensions (spec §28.4)' });
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
provenanceByLine.set(at + 1, provenance);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
256
470
|
}
|
|
257
471
|
else {
|
|
258
472
|
problems.push({ line: at + 1, message: `malformed transaction annotation — expected \`;@ <uuidv7>\` (spec §28.4)` });
|
|
259
473
|
}
|
|
260
474
|
});
|
|
261
475
|
const result = Canonical.canonicalizeText(text, store.registry());
|
|
262
|
-
|
|
476
|
+
for (const problem of result.problems)
|
|
477
|
+
problems.push(problem);
|
|
263
478
|
const consumed = new Set();
|
|
264
479
|
const seen = new Map();
|
|
480
|
+
const newProvenance = new Map();
|
|
481
|
+
const existingById = store.db.prepare('SELECT * FROM cave_claim WHERE id = ?');
|
|
482
|
+
const stored = new Map();
|
|
265
483
|
const ids = result.claims.map(entry => {
|
|
266
484
|
const line = entry.line - 1;
|
|
267
485
|
const tx = txByLine.get(line);
|
|
@@ -277,13 +495,33 @@ export const syncText = (store, text, options = {}) => {
|
|
|
277
495
|
// cited by several parents (§28.4) — and unions back into one row, the
|
|
278
496
|
// same §28.1 rule that makes re-syncs idempotent. Only a repeat that
|
|
279
497
|
// disagrees on content forks identity, and rejects.
|
|
280
|
-
const rendered =
|
|
498
|
+
const rendered = identityContent(entry.claim);
|
|
499
|
+
const explicitProvenance = provenanceByLine.get(line);
|
|
500
|
+
const provenance = JSON.stringify(explicitProvenance ??
|
|
501
|
+
Provenance.normalize(Provenance.fromEntries(Provenance.entries(entry.claim.contexts))));
|
|
281
502
|
const first = seen.get(tx);
|
|
282
|
-
if (first !== undefined && first.rendered !== rendered) {
|
|
503
|
+
if (first !== undefined && (first.rendered !== rendered || first.provenance !== provenance)) {
|
|
283
504
|
problems.push({ line: entry.line, message: `transaction annotation repeats line ${first.line}'s id with different content — a row has one identity (spec §28.1)` });
|
|
284
505
|
}
|
|
285
506
|
else if (first === undefined) {
|
|
286
|
-
seen.set(tx, { line: entry.line, rendered });
|
|
507
|
+
seen.set(tx, { line: entry.line, rendered, provenance });
|
|
508
|
+
}
|
|
509
|
+
// Validation runs under one reservation and precedes all writes. Repeated
|
|
510
|
+
// graph references can reuse the stored identity, including absent rows.
|
|
511
|
+
let existing = stored.get(tx);
|
|
512
|
+
if (!stored.has(tx)) {
|
|
513
|
+
const row = existingById.get(tx);
|
|
514
|
+
existing = row === undefined ? undefined : { rendered: identityContent(store.toClaim(row)) };
|
|
515
|
+
stored.set(tx, existing);
|
|
516
|
+
}
|
|
517
|
+
if (existing === undefined && explicitProvenance !== undefined)
|
|
518
|
+
newProvenance.set(tx, explicitProvenance);
|
|
519
|
+
if (existing !== undefined && existing.rendered === rendered && explicitProvenance !== undefined && existing.provenance === undefined) {
|
|
520
|
+
existing.provenance = JSON.stringify(Provenance.normalize(store.provenanceOf(tx)));
|
|
521
|
+
}
|
|
522
|
+
if (existing !== undefined && (existing.rendered !== rendered ||
|
|
523
|
+
(explicitProvenance !== undefined && existing.provenance !== provenance))) {
|
|
524
|
+
problems.push({ line: entry.line, message: `transaction id ${tx} is already stored with different content — a row has one identity (spec §28.1)` });
|
|
287
525
|
}
|
|
288
526
|
return tx;
|
|
289
527
|
});
|
|
@@ -295,25 +533,31 @@ export const syncText = (store, text, options = {}) => {
|
|
|
295
533
|
if (problems.length > 0) {
|
|
296
534
|
return { merged: 0, skipped: 0, edges: 0, dryRun, problems: problems.sort((a, b) => a.line - b.line) };
|
|
297
535
|
}
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
536
|
+
const outcome = store.insertResult(result, { ids });
|
|
537
|
+
// Explicit annotation dimensions replace legacy inference only for newly
|
|
538
|
+
// inserted identities. All validation above precedes any mutation.
|
|
539
|
+
const clearProvenance = store.db.prepare('DELETE FROM cave_provenance WHERE claim_id = ?');
|
|
540
|
+
const insertProvenance = store.db.prepare('INSERT INTO cave_provenance (claim_id, dimension, value) VALUES (?, ?, ?)');
|
|
541
|
+
for (const [id, value] of newProvenance) {
|
|
542
|
+
clearProvenance.run(id);
|
|
543
|
+
for (const [key, dimension] of [['actors', 'actor'], ['sources', 'source'], ['runs', 'run'], ['domains', 'domain']]) {
|
|
544
|
+
for (const entry of value[key])
|
|
545
|
+
insertProvenance.run(id, dimension, entry);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
const merged = result.claims.length - outcome.skipped;
|
|
549
|
+
const effective = merged + outcome.edges > 0;
|
|
550
|
+
const record = effective && options.record !== false ?
|
|
551
|
+
appendRecord(store, options.from ?? 'origin', options.into ?? 'local', merged, outcome.edges) :
|
|
552
|
+
undefined;
|
|
553
|
+
return {
|
|
554
|
+
merged,
|
|
555
|
+
skipped: outcome.skipped,
|
|
556
|
+
edges: outcome.edges,
|
|
557
|
+
dryRun,
|
|
558
|
+
...record === undefined ? {} : { record },
|
|
559
|
+
problems: []
|
|
313
560
|
};
|
|
314
|
-
return dryRun ?
|
|
315
|
-
Uuidv7.withStatePreserved(() => rolledBack(store, body)) :
|
|
316
|
-
store.transaction(body);
|
|
317
561
|
};
|
|
318
562
|
/**
|
|
319
563
|
* Merges a source file of either shape: a CAVE store (recognized by the
|
|
@@ -322,9 +566,22 @@ export const syncText = (store, text, options = {}) => {
|
|
|
322
566
|
* basename stem.
|
|
323
567
|
*/
|
|
324
568
|
export const syncFile = (store, sourcePath, options = {}) => {
|
|
325
|
-
const withLabel = {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
569
|
+
const withLabel = {
|
|
570
|
+
from: options.from ?? labelOf(sourcePath),
|
|
571
|
+
into: options.into,
|
|
572
|
+
record: booleanOption(options.record, 'record', true),
|
|
573
|
+
dryRun: booleanOption(options.dryRun, 'dryRun', false)
|
|
574
|
+
};
|
|
575
|
+
if (isStoreFile(sourcePath))
|
|
576
|
+
return syncDb(store, sourcePath, withLabel);
|
|
577
|
+
const bytes = readFileSync(sourcePath);
|
|
578
|
+
let text;
|
|
579
|
+
try {
|
|
580
|
+
text = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
581
|
+
}
|
|
582
|
+
catch (cause) {
|
|
583
|
+
throw new TypeError(`${sourcePath}: invalid UTF-8 sync text`, { cause });
|
|
584
|
+
}
|
|
585
|
+
return syncText(store, text, withLabel);
|
|
329
586
|
};
|
|
330
587
|
//# sourceMappingURL=sync.js.map
|