@sixb/core 0.0.1 → 0.1.1
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 +24 -4
- package/dist/actions/index.js +17 -17
- package/dist/actions/worker.js +7 -7
- package/dist/actions/worker.js.map +1 -1
- package/dist/agents/adapters.d.ts.map +1 -1
- package/dist/agents/builders.d.ts +8 -1
- package/dist/agents/builders.d.ts.map +1 -1
- package/dist/agents/context.js +2 -2
- package/dist/agents/errors.d.ts +15 -0
- package/dist/agents/errors.d.ts.map +1 -1
- package/dist/agents/index.d.ts +4 -4
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +30 -11
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/tool-definition.d.ts +9 -0
- package/dist/agents/tool-definition.d.ts.map +1 -0
- package/dist/agents/types.d.ts +64 -0
- package/dist/agents/types.d.ts.map +1 -1
- package/dist/agents/validation.d.ts +14 -1
- package/dist/agents/validation.d.ts.map +1 -1
- package/dist/auth/index.js +4 -4
- package/dist/authorization/index.js +3 -3
- package/dist/blob-storage/browser.js +3 -2
- package/dist/blob-storage/browser.js.map +1 -1
- package/dist/blob-storage/index.js +4 -3
- package/dist/blob-storage/index.js.map +1 -1
- package/dist/blob-storage/validation.d.ts.map +1 -1
- package/dist/bootstrap/index.js +25 -25
- package/dist/broker/index.js +2 -2
- package/dist/{chunk-590dejbp.js → chunk-4hwxq5e6.js} +1 -1
- package/dist/{chunk-wj01s5hr.js → chunk-4tvj3ate.js} +1 -1
- package/dist/{chunk-tsk2bxs3.js → chunk-53s4asfm.js} +13 -13
- package/dist/{chunk-tsk2bxs3.js.map → chunk-53s4asfm.js.map} +3 -3
- package/dist/{chunk-8r8cw2xw.js → chunk-63te770v.js} +40 -5
- package/dist/{chunk-8r8cw2xw.js.map → chunk-63te770v.js.map} +3 -3
- package/dist/{chunk-vj723r3p.js → chunk-6rzdtvn5.js} +1 -1
- package/dist/{chunk-5yny9z0e.js → chunk-6zrpewy4.js} +1 -1
- package/dist/{chunk-nvamz5dv.js → chunk-86459jn0.js} +294 -22
- package/dist/chunk-86459jn0.js.map +22 -0
- package/dist/{chunk-p6r5g77m.js → chunk-8kv2sez0.js} +1 -1
- package/dist/{chunk-k41ajtz7.js → chunk-9k3hgv8h.js} +1 -1
- package/dist/{chunk-jdwx7vzs.js → chunk-avppcgt6.js} +7 -7
- package/dist/{chunk-jdwx7vzs.js.map → chunk-avppcgt6.js.map} +1 -1
- package/dist/{chunk-2gpr6jsz.js → chunk-b1bqvhpe.js} +2 -2
- package/dist/{chunk-pv1yad91.js → chunk-b5dp7gaf.js} +17 -90
- package/dist/{chunk-pv1yad91.js.map → chunk-b5dp7gaf.js.map} +5 -6
- package/dist/{chunk-1vffvydj.js → chunk-be9vm29v.js} +6 -6
- package/dist/chunk-be9vm29v.js.map +10 -0
- package/dist/{chunk-wxhdnwh4.js → chunk-bh8bwma3.js} +1 -1
- package/dist/{chunk-0psb6kdw.js → chunk-btcsywk4.js} +12 -16
- package/dist/{chunk-0psb6kdw.js.map → chunk-btcsywk4.js.map} +3 -3
- package/dist/{chunk-0fygrz37.js → chunk-exrfbt2d.js} +1 -1
- package/dist/{chunk-c3zd880g.js → chunk-f6kyyeef.js} +2 -2
- package/dist/{chunk-q4qzq0ea.js → chunk-hb9xcx9c.js} +3 -3
- package/dist/{chunk-4pycxcj3.js → chunk-hz8m927w.js} +6 -9
- package/dist/{chunk-4pycxcj3.js.map → chunk-hz8m927w.js.map} +3 -3
- package/dist/{chunk-btpe64e4.js → chunk-m9w0artv.js} +1 -1
- package/dist/{chunk-45tw90k7.js → chunk-md9jbrm9.js} +1 -1
- package/dist/{chunk-dhmj53mx.js → chunk-nbywkytr.js} +3 -3
- package/dist/{chunk-t358gywy.js → chunk-p7vj8xgb.js} +8 -8
- package/dist/{chunk-2s6sydsh.js → chunk-qzyemcj7.js} +294 -4
- package/dist/chunk-qzyemcj7.js.map +13 -0
- package/dist/{chunk-r65yscvk.js → chunk-r82k4z7z.js} +131 -13
- package/dist/chunk-r82k4z7z.js.map +13 -0
- package/dist/{chunk-aebzcsxr.js → chunk-raq0x6bd.js} +11 -9
- package/dist/{chunk-aebzcsxr.js.map → chunk-raq0x6bd.js.map} +4 -4
- package/dist/{chunk-hsv43v58.js → chunk-rgzed50y.js} +77 -22
- package/dist/chunk-rgzed50y.js.map +22 -0
- package/dist/{chunk-yvef9xwc.js → chunk-rjqm6ksg.js} +7 -7
- package/dist/{chunk-f2b5e2gf.js → chunk-sb0shdvv.js} +18 -3
- package/dist/{chunk-f2b5e2gf.js.map → chunk-sb0shdvv.js.map} +3 -3
- package/dist/{chunk-dcgrzj1t.js → chunk-xt6qx9sh.js} +1 -1
- package/dist/{chunk-mx84y05q.js → chunk-y2ygpd0m.js} +2 -2
- package/dist/{chunk-yxss87qa.js → chunk-yn947ck3.js} +1 -1
- package/dist/datasets/builders.d.ts +18 -5
- package/dist/datasets/builders.d.ts.map +1 -1
- package/dist/datasets/changes.d.ts +18 -0
- package/dist/datasets/changes.d.ts.map +1 -0
- package/dist/datasets/index.d.ts +3 -1
- package/dist/datasets/index.d.ts.map +1 -1
- package/dist/datasets/types.d.ts +2 -0
- package/dist/datasets/types.d.ts.map +1 -1
- package/dist/datasets/validation.d.ts.map +1 -1
- package/dist/events/index.js +2 -2
- package/dist/http/api-routes.d.ts.map +1 -1
- package/dist/http/index.js +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +64 -58
- package/dist/index.js.map +1 -1
- package/dist/json.d.ts +8 -1
- package/dist/json.d.ts.map +1 -1
- package/dist/lake-storage/definition-updates.d.ts.map +1 -1
- package/dist/lake-storage/in-memory.d.ts +15 -1
- package/dist/lake-storage/in-memory.d.ts.map +1 -1
- package/dist/lake-storage/index.d.ts +3 -1
- package/dist/lake-storage/index.d.ts.map +1 -1
- package/dist/lake-storage/index.js +15 -7
- package/dist/lake-storage/index.js.map +1 -1
- package/dist/lake-storage/merge-validation.d.ts +10 -0
- package/dist/lake-storage/merge-validation.d.ts.map +1 -0
- package/dist/lake-storage/merge.d.ts +32 -0
- package/dist/lake-storage/merge.d.ts.map +1 -0
- package/dist/lake-storage/types.d.ts +3 -1
- package/dist/lake-storage/types.d.ts.map +1 -1
- package/dist/logging/index.js +3 -3
- package/dist/logging/types.js +2 -2
- package/dist/materialization/index.js +2 -2
- package/dist/materializer/index.js +15 -15
- package/dist/materializer/index.js.map +1 -1
- package/dist/objects/index.js +17 -17
- package/dist/objects/query/index.js +8 -8
- package/dist/ontology/index.js +5 -5
- package/dist/ontology/internal.js +7 -6
- package/dist/ontology/internal.js.map +3 -3
- package/dist/ontology/validation/normalize.d.ts.map +1 -1
- package/dist/projections/internal.js +4 -4
- package/dist/runtime/sixb.d.ts.map +1 -1
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +16 -16
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/ontology/provider.js +4 -4
- package/dist/storage/sync-runs/index.d.ts +1 -1
- package/dist/storage/sync-runs/index.d.ts.map +1 -1
- package/dist/storage/sync-runs/types.d.ts +7 -3
- package/dist/storage/sync-runs/types.d.ts.map +1 -1
- package/dist/storage/types.d.ts +1 -1
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/storage/workflow-runs/in-memory.d.ts.map +1 -1
- package/dist/storage/workflow-runs/types.d.ts +2 -0
- package/dist/storage/workflow-runs/types.d.ts.map +1 -1
- package/dist/syncs/builders.d.ts +11 -3
- package/dist/syncs/builders.d.ts.map +1 -1
- package/dist/syncs/index.d.ts +1 -1
- package/dist/syncs/index.d.ts.map +1 -1
- package/dist/syncs/types.d.ts +24 -19
- package/dist/syncs/types.d.ts.map +1 -1
- package/dist/syncs/validation.d.ts +14 -0
- package/dist/syncs/validation.d.ts.map +1 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +837 -583
- package/dist/testing/index.js.map +5 -4
- package/dist/testing/lake-merge-storage-contract.d.ts +7 -0
- package/dist/testing/lake-merge-storage-contract.d.ts.map +1 -0
- package/dist/testing/lake-storage-contract.d.ts.map +1 -1
- package/dist/workflows/index.js +23 -23
- package/package.json +1 -1
- package/src/agents/adapters.ts +1 -9
- package/src/agents/builders.ts +53 -3
- package/src/agents/errors.ts +22 -0
- package/src/agents/index.ts +22 -2
- package/src/agents/prompt.ts +2 -2
- package/src/agents/tool-definition.ts +93 -0
- package/src/agents/types.ts +101 -0
- package/src/agents/validation.ts +287 -3
- package/src/blob-storage/validation.ts +2 -10
- package/src/datasets/builders.ts +49 -5
- package/src/datasets/changes.ts +13 -0
- package/src/datasets/index.ts +3 -0
- package/src/datasets/types.ts +5 -0
- package/src/datasets/validation.ts +53 -11
- package/src/http/api-routes.ts +39 -4
- package/src/index.ts +18 -1
- package/src/json.ts +11 -4
- package/src/lake-storage/definition-updates.ts +21 -0
- package/src/lake-storage/in-memory.ts +284 -2
- package/src/lake-storage/index.ts +14 -0
- package/src/lake-storage/merge-validation.ts +117 -0
- package/src/lake-storage/merge.ts +35 -0
- package/src/lake-storage/types.ts +3 -1
- package/src/ontology/json-schema.ts +2 -1
- package/src/ontology/validation/normalize.ts +9 -15
- package/src/runtime/sixb.ts +16 -1
- package/src/storage/index.ts +1 -0
- package/src/storage/sync-runs/in-memory.ts +2 -2
- package/src/storage/sync-runs/index.ts +1 -0
- package/src/storage/sync-runs/types.ts +9 -4
- package/src/storage/types.ts +1 -0
- package/src/storage/workflow-runs/in-memory.ts +2 -0
- package/src/storage/workflow-runs/types.ts +2 -0
- package/src/syncs/builders.ts +31 -13
- package/src/syncs/index.ts +1 -0
- package/src/syncs/types.ts +44 -18
- package/src/syncs/validation.ts +77 -0
- package/src/testing/index.ts +4 -0
- package/src/testing/lake-merge-storage-contract.ts +302 -0
- package/src/testing/lake-storage-contract.ts +23 -0
- package/src/workflows/snapshot.ts +1 -10
- package/dist/chunk-1vffvydj.js.map +0 -10
- package/dist/chunk-2s6sydsh.js.map +0 -12
- package/dist/chunk-hsv43v58.js.map +0 -21
- package/dist/chunk-nvamz5dv.js.map +0 -21
- package/dist/chunk-r65yscvk.js.map +0 -11
- /package/dist/{chunk-590dejbp.js.map → chunk-4hwxq5e6.js.map} +0 -0
- /package/dist/{chunk-wj01s5hr.js.map → chunk-4tvj3ate.js.map} +0 -0
- /package/dist/{chunk-vj723r3p.js.map → chunk-6rzdtvn5.js.map} +0 -0
- /package/dist/{chunk-5yny9z0e.js.map → chunk-6zrpewy4.js.map} +0 -0
- /package/dist/{chunk-p6r5g77m.js.map → chunk-8kv2sez0.js.map} +0 -0
- /package/dist/{chunk-k41ajtz7.js.map → chunk-9k3hgv8h.js.map} +0 -0
- /package/dist/{chunk-2gpr6jsz.js.map → chunk-b1bqvhpe.js.map} +0 -0
- /package/dist/{chunk-wxhdnwh4.js.map → chunk-bh8bwma3.js.map} +0 -0
- /package/dist/{chunk-0fygrz37.js.map → chunk-exrfbt2d.js.map} +0 -0
- /package/dist/{chunk-c3zd880g.js.map → chunk-f6kyyeef.js.map} +0 -0
- /package/dist/{chunk-q4qzq0ea.js.map → chunk-hb9xcx9c.js.map} +0 -0
- /package/dist/{chunk-btpe64e4.js.map → chunk-m9w0artv.js.map} +0 -0
- /package/dist/{chunk-45tw90k7.js.map → chunk-md9jbrm9.js.map} +0 -0
- /package/dist/{chunk-dhmj53mx.js.map → chunk-nbywkytr.js.map} +0 -0
- /package/dist/{chunk-t358gywy.js.map → chunk-p7vj8xgb.js.map} +0 -0
- /package/dist/{chunk-yvef9xwc.js.map → chunk-rjqm6ksg.js.map} +0 -0
- /package/dist/{chunk-dcgrzj1t.js.map → chunk-xt6qx9sh.js.map} +0 -0
- /package/dist/{chunk-mx84y05q.js.map → chunk-y2ygpd0m.js.map} +0 -0
- /package/dist/{chunk-yxss87qa.js.map → chunk-yn947ck3.js.map} +0 -0
package/dist/testing/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import"../chunk-
|
|
2
|
+
import"../chunk-rgzed50y.js";
|
|
3
3
|
import {
|
|
4
4
|
SandboxNotRunningError
|
|
5
5
|
} from "../chunk-8ydjhh27.js";
|
|
6
6
|
import"../chunk-ya6brh8a.js";
|
|
7
|
-
import"../chunk-
|
|
7
|
+
import"../chunk-p7vj8xgb.js";
|
|
8
8
|
import"../chunk-v5c085yp.js";
|
|
9
9
|
import {
|
|
10
10
|
collectObjectQueryValidationIssues,
|
|
@@ -13,8 +13,8 @@ import {
|
|
|
13
13
|
existsObjects,
|
|
14
14
|
facetObjects,
|
|
15
15
|
validateObjectQuery
|
|
16
|
-
} from "../chunk-
|
|
17
|
-
import"../chunk-
|
|
16
|
+
} from "../chunk-b1bqvhpe.js";
|
|
17
|
+
import"../chunk-4hwxq5e6.js";
|
|
18
18
|
import"../chunk-gqdd7508.js";
|
|
19
19
|
import {
|
|
20
20
|
ObjectQueryPlanningError,
|
|
@@ -25,79 +25,80 @@ import {
|
|
|
25
25
|
} from "../chunk-xz5g49bp.js";
|
|
26
26
|
import {
|
|
27
27
|
LakeStorageError
|
|
28
|
-
} from "../chunk-
|
|
29
|
-
import"../chunk-
|
|
30
|
-
import"../chunk-
|
|
31
|
-
import
|
|
32
|
-
|
|
33
|
-
defineDataset
|
|
34
|
-
} from "../chunk-pv1yad91.js";
|
|
35
|
-
import"../chunk-0psb6kdw.js";
|
|
28
|
+
} from "../chunk-qzyemcj7.js";
|
|
29
|
+
import"../chunk-f6kyyeef.js";
|
|
30
|
+
import"../chunk-6zrpewy4.js";
|
|
31
|
+
import"../chunk-b5dp7gaf.js";
|
|
32
|
+
import"../chunk-btcsywk4.js";
|
|
36
33
|
import"../chunk-867jxh7c.js";
|
|
37
34
|
import"../chunk-k6j72mtt.js";
|
|
38
35
|
import"../chunk-2ab72m2h.js";
|
|
39
|
-
import
|
|
36
|
+
import {
|
|
37
|
+
change,
|
|
38
|
+
col,
|
|
39
|
+
defineDataset
|
|
40
|
+
} from "../chunk-r82k4z7z.js";
|
|
40
41
|
import {
|
|
41
42
|
BrokerError
|
|
42
|
-
} from "../chunk-
|
|
43
|
-
import"../chunk-
|
|
43
|
+
} from "../chunk-4tvj3ate.js";
|
|
44
|
+
import"../chunk-yn947ck3.js";
|
|
44
45
|
import"../chunk-74drx9gd.js";
|
|
45
46
|
import"../chunk-b49dgvsj.js";
|
|
46
|
-
import"../chunk-
|
|
47
|
-
import"../chunk-
|
|
47
|
+
import"../chunk-nbywkytr.js";
|
|
48
|
+
import"../chunk-avppcgt6.js";
|
|
48
49
|
import {
|
|
49
50
|
ProjectionRegistry,
|
|
50
51
|
createOntologyMaterializer
|
|
51
|
-
} from "../chunk-
|
|
52
|
-
import"../chunk-
|
|
53
|
-
import"../chunk-
|
|
52
|
+
} from "../chunk-rjqm6ksg.js";
|
|
53
|
+
import"../chunk-raq0x6bd.js";
|
|
54
|
+
import"../chunk-hb9xcx9c.js";
|
|
54
55
|
import"../chunk-xqbtf4wn.js";
|
|
55
56
|
import"../chunk-50kv63vj.js";
|
|
56
57
|
import"../chunk-6wetht33.js";
|
|
57
|
-
import {
|
|
58
|
-
OntologyRegistry,
|
|
59
|
-
defineObjectType,
|
|
60
|
-
link,
|
|
61
|
-
prop,
|
|
62
|
-
stringEnum
|
|
63
|
-
} from "../chunk-mx84y05q.js";
|
|
64
|
-
import"../chunk-45tw90k7.js";
|
|
65
58
|
import {
|
|
66
59
|
createEventId
|
|
67
|
-
} from "../chunk-
|
|
60
|
+
} from "../chunk-bh8bwma3.js";
|
|
68
61
|
import"../chunk-9dzba0f2.js";
|
|
69
|
-
import"../chunk-
|
|
62
|
+
import"../chunk-8kv2sez0.js";
|
|
70
63
|
import"../chunk-ry24jbr1.js";
|
|
71
64
|
import"../chunk-1evzqhks.js";
|
|
72
65
|
import"../chunk-t72dvahb.js";
|
|
73
|
-
import"../chunk-tsk2bxs3.js";
|
|
74
|
-
import"../chunk-04b07a9q.js";
|
|
75
|
-
import"../chunk-dktq02ne.js";
|
|
76
66
|
import"../chunk-yt8wphsx.js";
|
|
77
|
-
import"../chunk-
|
|
78
|
-
import"../chunk-
|
|
67
|
+
import"../chunk-86459jn0.js";
|
|
68
|
+
import"../chunk-63te770v.js";
|
|
79
69
|
import {
|
|
80
70
|
computeBlobDigest
|
|
81
|
-
} from "../chunk-
|
|
71
|
+
} from "../chunk-6rzdtvn5.js";
|
|
82
72
|
import"../chunk-51fjndv0.js";
|
|
83
73
|
import {
|
|
84
74
|
AGENT_MESSAGE_CONTENT_VERSION,
|
|
85
75
|
AgentStorageError
|
|
86
|
-
} from "../chunk-
|
|
76
|
+
} from "../chunk-9k3hgv8h.js";
|
|
77
|
+
import {
|
|
78
|
+
OntologyRegistry,
|
|
79
|
+
defineObjectType,
|
|
80
|
+
link,
|
|
81
|
+
prop,
|
|
82
|
+
stringEnum
|
|
83
|
+
} from "../chunk-y2ygpd0m.js";
|
|
84
|
+
import"../chunk-md9jbrm9.js";
|
|
85
|
+
import"../chunk-53s4asfm.js";
|
|
86
|
+
import"../chunk-04b07a9q.js";
|
|
87
|
+
import"../chunk-dktq02ne.js";
|
|
87
88
|
import {
|
|
88
89
|
blobIdFromDigest
|
|
89
|
-
} from "../chunk-
|
|
90
|
-
import"../chunk-
|
|
90
|
+
} from "../chunk-hz8m927w.js";
|
|
91
|
+
import"../chunk-sb0shdvv.js";
|
|
91
92
|
import"../chunk-k25jm1x5.js";
|
|
92
93
|
import {
|
|
93
94
|
AuthStorageError
|
|
94
|
-
} from "../chunk-
|
|
95
|
+
} from "../chunk-xt6qx9sh.js";
|
|
95
96
|
import"../chunk-xwnf87e1.js";
|
|
96
|
-
import"../chunk-
|
|
97
|
+
import"../chunk-exrfbt2d.js";
|
|
97
98
|
import {
|
|
98
99
|
defineProjection
|
|
99
|
-
} from "../chunk-
|
|
100
|
-
import"../chunk-
|
|
100
|
+
} from "../chunk-m9w0artv.js";
|
|
101
|
+
import"../chunk-be9vm29v.js";
|
|
101
102
|
import"../chunk-y64skrwp.js";
|
|
102
103
|
|
|
103
104
|
// src/testing/agent-storage-contract.ts
|
|
@@ -3224,8 +3225,244 @@ function temperature(objectId, value, at3) {
|
|
|
3224
3225
|
at: at3
|
|
3225
3226
|
};
|
|
3226
3227
|
}
|
|
3227
|
-
// src/testing/lake-storage-contract.ts
|
|
3228
|
+
// src/testing/lake-merge-storage-contract.ts
|
|
3228
3229
|
import { describe as describe6, expect as expect6, test as test6 } from "bun:test";
|
|
3230
|
+
var invoices = defineDataset("contract.merges.invoices", {
|
|
3231
|
+
schema: [col("id", "string"), col("status", "string"), col("note", "string", { nullable: true })],
|
|
3232
|
+
primaryKey: "id"
|
|
3233
|
+
});
|
|
3234
|
+
var lineItems = defineDataset("contract.merges.line_items", {
|
|
3235
|
+
schema: [col("invoiceId", "string"), col("lineItemId", "string"), col("description", "string")],
|
|
3236
|
+
primaryKey: ["invoiceId", "lineItemId"]
|
|
3237
|
+
});
|
|
3238
|
+
var unkeyed = defineDataset("contract.merges.unkeyed", {
|
|
3239
|
+
schema: [col("id", "string")]
|
|
3240
|
+
});
|
|
3241
|
+
function runLakeMergeStorageContractSuite(label, options) {
|
|
3242
|
+
const withStorage = async (body) => {
|
|
3243
|
+
const storage = await options.createStorage();
|
|
3244
|
+
try {
|
|
3245
|
+
await body(storage);
|
|
3246
|
+
} finally {
|
|
3247
|
+
await options.teardown?.(storage);
|
|
3248
|
+
}
|
|
3249
|
+
};
|
|
3250
|
+
describe6(label, () => {
|
|
3251
|
+
test6("creates an initial merge version from async changes", async () => {
|
|
3252
|
+
await withStorage(async (storage) => {
|
|
3253
|
+
await storage.createDataset(invoices);
|
|
3254
|
+
const merge = await storage.beginMerge({
|
|
3255
|
+
dataset: invoices,
|
|
3256
|
+
producer: { kind: "sync", id: "sync-invoices", runId: "run_1" },
|
|
3257
|
+
inputs: [{ datasetId: "source.events", versionId: "cursor_1" }]
|
|
3258
|
+
});
|
|
3259
|
+
async function* changes() {
|
|
3260
|
+
yield change.upsert({ id: "inv_1", status: "open", note: null });
|
|
3261
|
+
yield change.upsert({ id: "inv_2", status: "paid", note: "settled" });
|
|
3262
|
+
}
|
|
3263
|
+
await merge.writeChanges(changes());
|
|
3264
|
+
const result = await merge.commit({ commitMessage: "initial invoice changes" });
|
|
3265
|
+
const version = expectCreated(result);
|
|
3266
|
+
expect6(version).toMatchObject({
|
|
3267
|
+
datasetId: invoices.id,
|
|
3268
|
+
mode: "merge",
|
|
3269
|
+
rowCount: 2,
|
|
3270
|
+
producer: { kind: "sync", id: "sync-invoices", runId: "run_1" },
|
|
3271
|
+
inputs: [{ datasetId: "source.events", versionId: "cursor_1" }]
|
|
3272
|
+
});
|
|
3273
|
+
expect6(version.parentVersionId).toBeUndefined();
|
|
3274
|
+
await expect6(collectRows(storage.readRows({ datasetId: invoices.id }))).resolves.toEqual([
|
|
3275
|
+
{ id: "inv_1", status: "open", note: null },
|
|
3276
|
+
{ id: "inv_2", status: "paid", note: "settled" }
|
|
3277
|
+
]);
|
|
3278
|
+
});
|
|
3279
|
+
});
|
|
3280
|
+
test6("inserts, updates, and deletes while preserving the parent version", async () => {
|
|
3281
|
+
await withStorage(async (storage) => {
|
|
3282
|
+
await storage.createDataset(invoices);
|
|
3283
|
+
const seed = await storage.beginWrite({ dataset: invoices, mode: "snapshot" });
|
|
3284
|
+
await seed.writeRows([
|
|
3285
|
+
{ id: "inv_1", status: "open", note: null },
|
|
3286
|
+
{ id: "inv_2", status: "open", note: null }
|
|
3287
|
+
]);
|
|
3288
|
+
const parent = await seed.commit();
|
|
3289
|
+
const merge = await storage.beginMerge({ dataset: invoices });
|
|
3290
|
+
await merge.writeChanges([
|
|
3291
|
+
change.upsert({ id: "inv_1", status: "paid", note: null }),
|
|
3292
|
+
change.delete({ id: "inv_2" }),
|
|
3293
|
+
change.upsert({ id: "inv_2_moved", status: "open", note: "moved" })
|
|
3294
|
+
]);
|
|
3295
|
+
const version = expectCreated(await merge.commit());
|
|
3296
|
+
expect6(version.parentVersionId).toBe(parent.versionId);
|
|
3297
|
+
expect6(version.mode).toBe("merge");
|
|
3298
|
+
await expect6(collectRows(storage.readRows({ datasetId: invoices.id }))).resolves.toEqual([
|
|
3299
|
+
{ id: "inv_1", status: "paid", note: null },
|
|
3300
|
+
{ id: "inv_2_moved", status: "open", note: "moved" }
|
|
3301
|
+
]);
|
|
3302
|
+
await expect6(collectRows(storage.readRows({ datasetId: invoices.id, versionId: parent.versionId }))).resolves.toEqual([
|
|
3303
|
+
{ id: "inv_1", status: "open", note: null },
|
|
3304
|
+
{ id: "inv_2", status: "open", note: null }
|
|
3305
|
+
]);
|
|
3306
|
+
});
|
|
3307
|
+
});
|
|
3308
|
+
test6("applies the final change for repeated single and composite keys", async () => {
|
|
3309
|
+
await withStorage(async (storage) => {
|
|
3310
|
+
await storage.createDataset(lineItems);
|
|
3311
|
+
const merge = await storage.beginMerge({ dataset: lineItems });
|
|
3312
|
+
await merge.writeChanges([
|
|
3313
|
+
change.upsert({ invoiceId: "a|b", lineItemId: "c", description: "first" }),
|
|
3314
|
+
change.upsert({ invoiceId: "a", lineItemId: "b|c", description: "other key" })
|
|
3315
|
+
]);
|
|
3316
|
+
await merge.writeChanges([
|
|
3317
|
+
change.delete({ invoiceId: "a|b", lineItemId: "c" }),
|
|
3318
|
+
change.upsert({ invoiceId: "a|b", lineItemId: "c", description: "final" })
|
|
3319
|
+
]);
|
|
3320
|
+
expectCreated(await merge.commit());
|
|
3321
|
+
await expect6(collectRows(storage.readRows({ datasetId: lineItems.id }))).resolves.toEqual([
|
|
3322
|
+
{ invoiceId: "a|b", lineItemId: "c", description: "final" },
|
|
3323
|
+
{ invoiceId: "a", lineItemId: "b|c", description: "other key" }
|
|
3324
|
+
]);
|
|
3325
|
+
});
|
|
3326
|
+
});
|
|
3327
|
+
test6("does not create versions when visible rows do not change", async () => {
|
|
3328
|
+
await withStorage(async (storage) => {
|
|
3329
|
+
await storage.createDataset(invoices);
|
|
3330
|
+
const empty = await storage.beginMerge({ dataset: invoices });
|
|
3331
|
+
expect6(await empty.commit()).toEqual({ outcome: "unchanged", version: null });
|
|
3332
|
+
const initialNoOp = await storage.beginMerge({ dataset: invoices });
|
|
3333
|
+
await initialNoOp.writeChanges([change.delete({ id: "missing" })]);
|
|
3334
|
+
expect6(await initialNoOp.commit()).toEqual({ outcome: "unchanged", version: null });
|
|
3335
|
+
expect6(await storage.listVersions(invoices.id)).toEqual([]);
|
|
3336
|
+
const seed = await storage.beginMerge({ dataset: invoices });
|
|
3337
|
+
await seed.writeChanges([change.upsert({ id: "inv_1", status: "open", note: null })]);
|
|
3338
|
+
const seedVersion = expectCreated(await seed.commit());
|
|
3339
|
+
const identical = await storage.beginMerge({ dataset: invoices });
|
|
3340
|
+
await identical.writeChanges([change.upsert({ id: "inv_1", status: "open", note: null })]);
|
|
3341
|
+
expect6(await identical.commit()).toMatchObject({
|
|
3342
|
+
outcome: "unchanged",
|
|
3343
|
+
version: { versionId: seedVersion.versionId }
|
|
3344
|
+
});
|
|
3345
|
+
const laterNoOp = await storage.beginMerge({ dataset: invoices });
|
|
3346
|
+
await laterNoOp.writeChanges([
|
|
3347
|
+
change.upsert({ id: "inv_1", status: "paid", note: null }),
|
|
3348
|
+
change.upsert({ id: "inv_1", status: "open" }),
|
|
3349
|
+
change.delete({ id: "missing" })
|
|
3350
|
+
]);
|
|
3351
|
+
const noOpResult = await laterNoOp.commit();
|
|
3352
|
+
expect6(noOpResult).toMatchObject({ outcome: "unchanged" });
|
|
3353
|
+
expect6(noOpResult.version?.versionId).toBe(seedVersion.versionId);
|
|
3354
|
+
expect6(await storage.listVersions(invoices.id)).toHaveLength(1);
|
|
3355
|
+
});
|
|
3356
|
+
});
|
|
3357
|
+
test6("automatically rejects stale merge sessions, including an absent base", async () => {
|
|
3358
|
+
await withStorage(async (storage) => {
|
|
3359
|
+
await storage.createDataset(invoices);
|
|
3360
|
+
const first = await storage.beginMerge({ dataset: invoices });
|
|
3361
|
+
const stale = await storage.beginMerge({ dataset: invoices });
|
|
3362
|
+
await first.writeChanges([change.upsert({ id: "inv_1", status: "open", note: null })]);
|
|
3363
|
+
await first.commit();
|
|
3364
|
+
await stale.writeChanges([change.upsert({ id: "inv_2", status: "open" })]);
|
|
3365
|
+
await expect6(stale.commit()).rejects.toThrow("Optimistic merge commit failed");
|
|
3366
|
+
await expect6(collectRows(storage.readRows({ datasetId: invoices.id }))).resolves.toEqual([
|
|
3367
|
+
{ id: "inv_1", status: "open", note: null }
|
|
3368
|
+
]);
|
|
3369
|
+
});
|
|
3370
|
+
});
|
|
3371
|
+
test6("checks an explicit latest-version guard against the captured merge base", async () => {
|
|
3372
|
+
await withStorage(async (storage) => {
|
|
3373
|
+
await storage.createDataset(invoices);
|
|
3374
|
+
const seed = await storage.beginMerge({ dataset: invoices });
|
|
3375
|
+
await seed.writeChanges([change.upsert({ id: "inv_1", status: "open" })]);
|
|
3376
|
+
const version = expectCreated(await seed.commit());
|
|
3377
|
+
await expect6(storage.beginMerge({ dataset: invoices, expectedLatestVersionId: "stale-version" })).rejects.toThrow("Optimistic merge start failed");
|
|
3378
|
+
const guarded = await storage.beginMerge({
|
|
3379
|
+
dataset: invoices,
|
|
3380
|
+
expectedLatestVersionId: version.versionId
|
|
3381
|
+
});
|
|
3382
|
+
await guarded.abort();
|
|
3383
|
+
});
|
|
3384
|
+
});
|
|
3385
|
+
test6("serializes concurrent commits from the same base", async () => {
|
|
3386
|
+
await withStorage(async (storage) => {
|
|
3387
|
+
await storage.createDataset(invoices);
|
|
3388
|
+
const left = await storage.beginMerge({ dataset: invoices });
|
|
3389
|
+
const right = await storage.beginMerge({ dataset: invoices });
|
|
3390
|
+
await left.writeChanges([change.upsert({ id: "inv_left", status: "open" })]);
|
|
3391
|
+
await right.writeChanges([change.upsert({ id: "inv_right", status: "open" })]);
|
|
3392
|
+
const results = await Promise.allSettled([left.commit(), right.commit()]);
|
|
3393
|
+
expect6(results.filter((result) => result.status === "fulfilled")).toHaveLength(1);
|
|
3394
|
+
expect6(results.filter((result) => result.status === "rejected")).toHaveLength(1);
|
|
3395
|
+
expect6(await storage.listVersions(invoices.id)).toHaveLength(1);
|
|
3396
|
+
expect6(await collectRows(storage.readRows({ datasetId: invoices.id }))).toHaveLength(1);
|
|
3397
|
+
});
|
|
3398
|
+
});
|
|
3399
|
+
test6("rejects unkeyed datasets and malformed changes", async () => {
|
|
3400
|
+
await withStorage(async (storage) => {
|
|
3401
|
+
await storage.createDataset(unkeyed);
|
|
3402
|
+
await expect6(storage.beginMerge({ dataset: unkeyed })).rejects.toThrow("must define a primaryKey");
|
|
3403
|
+
await storage.createDataset(invoices);
|
|
3404
|
+
const merge = await storage.beginMerge({ dataset: invoices });
|
|
3405
|
+
await expect6(merge.writeChanges([change.upsert({ id: "inv_1" })])).rejects.toThrow("missing required column 'status'");
|
|
3406
|
+
await expect6(merge.writeChanges([change.delete({})])).rejects.toThrow("missing primary-key column 'id'");
|
|
3407
|
+
await expect6(merge.writeChanges([{ kind: "delete", key: { id: "inv_1", extra: "x" } }])).rejects.toThrow("unknown column 'extra'");
|
|
3408
|
+
await merge.abort();
|
|
3409
|
+
});
|
|
3410
|
+
});
|
|
3411
|
+
test6("enforces unique keys for snapshot and append baselines", async () => {
|
|
3412
|
+
await withStorage(async (storage) => {
|
|
3413
|
+
await storage.createDataset(invoices);
|
|
3414
|
+
const duplicateSnapshot = await storage.beginWrite({ dataset: invoices, mode: "snapshot" });
|
|
3415
|
+
await expect6(duplicateSnapshot.writeRows([
|
|
3416
|
+
{ id: "inv_1", status: "open" },
|
|
3417
|
+
{ id: "inv_1", status: "paid" }
|
|
3418
|
+
])).rejects.toThrow("duplicate primary key");
|
|
3419
|
+
await duplicateSnapshot.abort();
|
|
3420
|
+
const seed = await storage.beginWrite({ dataset: invoices, mode: "snapshot" });
|
|
3421
|
+
await seed.writeRows([{ id: "inv_1", status: "open" }]);
|
|
3422
|
+
await seed.commit();
|
|
3423
|
+
const conflictingAppend = await storage.beginWrite({ dataset: invoices, mode: "append" });
|
|
3424
|
+
await conflictingAppend.writeRows([{ id: "inv_1", status: "paid" }]);
|
|
3425
|
+
await expect6(conflictingAppend.commit()).rejects.toThrow("duplicate primary key");
|
|
3426
|
+
});
|
|
3427
|
+
});
|
|
3428
|
+
test6("clones staged changes and enforces the session lifecycle", async () => {
|
|
3429
|
+
await withStorage(async (storage) => {
|
|
3430
|
+
await storage.createDataset(invoices);
|
|
3431
|
+
const row = { id: "inv_1", status: "open", note: null };
|
|
3432
|
+
const merge = await storage.beginMerge({ dataset: invoices });
|
|
3433
|
+
await merge.writeChanges([change.upsert(row)]);
|
|
3434
|
+
row.status = "paid";
|
|
3435
|
+
expectCreated(await merge.commit());
|
|
3436
|
+
await merge.abort();
|
|
3437
|
+
await expect6(merge.writeChanges([])).rejects.toThrow("already closed");
|
|
3438
|
+
await expect6(collectRows(storage.readRows({ datasetId: invoices.id }))).resolves.toEqual([
|
|
3439
|
+
{ id: "inv_1", status: "open", note: null }
|
|
3440
|
+
]);
|
|
3441
|
+
const aborted = await storage.beginMerge({ dataset: invoices });
|
|
3442
|
+
await aborted.writeChanges([change.delete({ id: "inv_1" })]);
|
|
3443
|
+
await aborted.abort();
|
|
3444
|
+
await aborted.abort();
|
|
3445
|
+
expect6(await storage.listVersions(invoices.id)).toHaveLength(1);
|
|
3446
|
+
});
|
|
3447
|
+
});
|
|
3448
|
+
});
|
|
3449
|
+
}
|
|
3450
|
+
function expectCreated(result) {
|
|
3451
|
+
expect6(result.outcome).toBe("created");
|
|
3452
|
+
if (result.outcome !== "created") {
|
|
3453
|
+
throw new Error("Expected merge to create a dataset version");
|
|
3454
|
+
}
|
|
3455
|
+
return result.version;
|
|
3456
|
+
}
|
|
3457
|
+
async function collectRows(rows) {
|
|
3458
|
+
const result = [];
|
|
3459
|
+
for await (const row of rows) {
|
|
3460
|
+
result.push(row);
|
|
3461
|
+
}
|
|
3462
|
+
return result;
|
|
3463
|
+
}
|
|
3464
|
+
// src/testing/lake-storage-contract.ts
|
|
3465
|
+
import { describe as describe7, expect as expect7, test as test7 } from "bun:test";
|
|
3229
3466
|
var definitionDataset = defineDataset("contract.definitions.orders", {
|
|
3230
3467
|
schema: [col("orderId", "string"), col("customerName", "string")],
|
|
3231
3468
|
description: "Contract orders"
|
|
@@ -3234,6 +3471,11 @@ var reorderedDefinitionDataset = defineDataset("contract.definitions.orders", {
|
|
|
3234
3471
|
schema: [col("customerName", "string"), col("orderId", "string")],
|
|
3235
3472
|
description: "Contract orders"
|
|
3236
3473
|
});
|
|
3474
|
+
var keyedDefinitionDataset = defineDataset("contract.definitions.keyed_orders", {
|
|
3475
|
+
schema: [col("tenantId", "string"), col("orderId", "string"), col("customerName", "string")],
|
|
3476
|
+
primaryKey: ["tenantId", "orderId"],
|
|
3477
|
+
description: "Contract keyed orders"
|
|
3478
|
+
});
|
|
3237
3479
|
var writeDataset = defineDataset("contract.writes.orders", {
|
|
3238
3480
|
schema: [col("orderId", "string"), col("customerName", "string")]
|
|
3239
3481
|
});
|
|
@@ -3265,31 +3507,42 @@ function runLakeStorageContractSuite(label, options) {
|
|
|
3265
3507
|
await options.teardown?.(storage);
|
|
3266
3508
|
}
|
|
3267
3509
|
};
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3510
|
+
describe7(label, () => {
|
|
3511
|
+
describe7("dataset definitions", () => {
|
|
3512
|
+
test7("creates, gets, lists, and reuses dataset definitions", async () => {
|
|
3271
3513
|
await withStorage(async (storage) => {
|
|
3272
|
-
|
|
3514
|
+
expect7(await storage.getDataset(definitionDataset.id)).toBeNull();
|
|
3273
3515
|
const created = await storage.createDataset(definitionDataset);
|
|
3274
3516
|
const repeated = await storage.createDataset(definitionDataset);
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3517
|
+
expect7(created).toEqual(definitionDataset);
|
|
3518
|
+
expect7(repeated).toEqual(definitionDataset);
|
|
3519
|
+
expect7(await storage.getDataset(definitionDataset.id)).toEqual(definitionDataset);
|
|
3520
|
+
expect7(await storage.getDataset("missing.dataset")).toBeNull();
|
|
3521
|
+
expect7(await storage.listDatasets()).toEqual([definitionDataset]);
|
|
3522
|
+
expect7(await storage.listVersions(definitionDataset.id)).toEqual([]);
|
|
3523
|
+
});
|
|
3524
|
+
});
|
|
3525
|
+
test7("round-trips keyed definitions through create, get, list, and reuse", async () => {
|
|
3526
|
+
await withStorage(async (storage) => {
|
|
3527
|
+
const created = await storage.createDataset(keyedDefinitionDataset);
|
|
3528
|
+
const repeated = await storage.createDataset(keyedDefinitionDataset);
|
|
3529
|
+
expect7(created).toEqual(keyedDefinitionDataset);
|
|
3530
|
+
expect7(repeated).toEqual(keyedDefinitionDataset);
|
|
3531
|
+
expect7(await storage.getDataset(keyedDefinitionDataset.id)).toEqual(keyedDefinitionDataset);
|
|
3532
|
+
expect7(await storage.listDatasets()).toEqual([keyedDefinitionDataset]);
|
|
3533
|
+
expect7(await storage.listVersions(keyedDefinitionDataset.id)).toEqual([]);
|
|
3281
3534
|
});
|
|
3282
3535
|
});
|
|
3283
|
-
|
|
3536
|
+
test7("treats same-column declaration reorders as a no-op", async () => {
|
|
3284
3537
|
await withStorage(async (storage) => {
|
|
3285
3538
|
await storage.createDataset(definitionDataset);
|
|
3286
3539
|
await storage.assertDatasetDefinitionsCompatible([reorderedDefinitionDataset]);
|
|
3287
|
-
|
|
3288
|
-
await
|
|
3289
|
-
|
|
3540
|
+
expect7(await storage.getDataset(definitionDataset.id)).toEqual(definitionDataset);
|
|
3541
|
+
await expect7(storage.createDataset(reorderedDefinitionDataset)).resolves.toEqual(definitionDataset);
|
|
3542
|
+
expect7(await storage.listVersions(definitionDataset.id)).toEqual([]);
|
|
3290
3543
|
});
|
|
3291
3544
|
});
|
|
3292
|
-
|
|
3545
|
+
test7("persists compatible metadata additions", async () => {
|
|
3293
3546
|
await withStorage(async (storage) => {
|
|
3294
3547
|
const minimalDataset = defineDataset("contract.definitions.metadata", {
|
|
3295
3548
|
schema: [col("orderId", "string"), col("orderDate", "date")]
|
|
@@ -3301,24 +3554,24 @@ function runLakeStorageContractSuite(label, options) {
|
|
|
3301
3554
|
});
|
|
3302
3555
|
await storage.createDataset(minimalDataset);
|
|
3303
3556
|
await storage.assertDatasetDefinitionsCompatible([documentedDataset]);
|
|
3304
|
-
|
|
3305
|
-
await
|
|
3306
|
-
|
|
3557
|
+
expect7(await storage.getDataset(minimalDataset.id)).toEqual(minimalDataset);
|
|
3558
|
+
await expect7(storage.createDataset(documentedDataset)).resolves.toEqual(documentedDataset);
|
|
3559
|
+
expect7(await storage.getDataset(minimalDataset.id)).toEqual(documentedDataset);
|
|
3307
3560
|
});
|
|
3308
3561
|
});
|
|
3309
|
-
|
|
3562
|
+
test7("rejects incompatible definition changes without mutating the stored definition", async () => {
|
|
3310
3563
|
await withStorage(async (storage) => {
|
|
3311
3564
|
const changedDataset = defineDataset(definitionDataset.id, {
|
|
3312
3565
|
schema: [col("orderId", "int64"), col("customerName", "string")],
|
|
3313
3566
|
description: "Contract orders"
|
|
3314
3567
|
});
|
|
3315
3568
|
await storage.createDataset(definitionDataset);
|
|
3316
|
-
await
|
|
3317
|
-
await
|
|
3318
|
-
|
|
3569
|
+
await expect7(storage.assertDatasetDefinitionsCompatible([changedDataset])).rejects.toThrow("changing column 'orderId' type");
|
|
3570
|
+
await expect7(storage.createDataset(changedDataset)).rejects.toBeInstanceOf(LakeStorageError);
|
|
3571
|
+
expect7(await storage.getDataset(definitionDataset.id)).toEqual(definitionDataset);
|
|
3319
3572
|
});
|
|
3320
3573
|
});
|
|
3321
|
-
|
|
3574
|
+
test7("checks provider schema evolution policy", async () => {
|
|
3322
3575
|
await withStorage(async (storage) => {
|
|
3323
3576
|
const initialDataset = defineDataset("contract.definitions.schema_evolution", {
|
|
3324
3577
|
schema: [col("invoiceId", "string")]
|
|
@@ -3331,34 +3584,34 @@ function runLakeStorageContractSuite(label, options) {
|
|
|
3331
3584
|
});
|
|
3332
3585
|
await storage.createDataset(initialDataset);
|
|
3333
3586
|
if (schemaEvolution === "strict") {
|
|
3334
|
-
await
|
|
3335
|
-
await
|
|
3336
|
-
|
|
3587
|
+
await expect7(storage.assertDatasetDefinitionsCompatible([requestedDataset])).rejects.toThrow("incompatible schema");
|
|
3588
|
+
await expect7(storage.createDataset(requestedDataset)).rejects.toThrow("incompatible schema");
|
|
3589
|
+
expect7(await storage.getDataset(initialDataset.id)).toEqual(initialDataset);
|
|
3337
3590
|
return;
|
|
3338
3591
|
}
|
|
3339
3592
|
await storage.assertDatasetDefinitionsCompatible([requestedDataset]);
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
await
|
|
3343
|
-
|
|
3593
|
+
expect7(await storage.getDataset(initialDataset.id)).toEqual(initialDataset);
|
|
3594
|
+
expect7(await storage.listVersions(initialDataset.id)).toEqual([]);
|
|
3595
|
+
await expect7(storage.createDataset(requestedDataset)).resolves.toEqual(storedDataset);
|
|
3596
|
+
expect7(await storage.getDataset(initialDataset.id)).toEqual(storedDataset);
|
|
3344
3597
|
const [schemaVersion] = await storage.listVersions(initialDataset.id);
|
|
3345
|
-
|
|
3598
|
+
expect7(schemaVersion).toMatchObject({
|
|
3346
3599
|
datasetId: initialDataset.id,
|
|
3347
3600
|
mode: "schema",
|
|
3348
3601
|
schema: storedDataset.schema
|
|
3349
3602
|
});
|
|
3350
3603
|
});
|
|
3351
3604
|
});
|
|
3352
|
-
|
|
3605
|
+
test7("keeps compatibility checks read-only for missing datasets", async () => {
|
|
3353
3606
|
await withStorage(async (storage) => {
|
|
3354
3607
|
await storage.assertDatasetDefinitionsCompatible([definitionDataset]);
|
|
3355
|
-
|
|
3356
|
-
|
|
3608
|
+
expect7(await storage.getDataset(definitionDataset.id)).toBeNull();
|
|
3609
|
+
expect7(await storage.listDatasets()).toEqual([]);
|
|
3357
3610
|
});
|
|
3358
3611
|
});
|
|
3359
3612
|
});
|
|
3360
|
-
|
|
3361
|
-
|
|
3613
|
+
describe7("catalog state", () => {
|
|
3614
|
+
test7("reports materialized state and latest version summaries in bulk", async () => {
|
|
3362
3615
|
await withStorage(async (storage) => {
|
|
3363
3616
|
await storage.createDataset(writeDataset);
|
|
3364
3617
|
const write = await storage.beginWrite({ dataset: writeDataset, mode: "snapshot" });
|
|
@@ -3367,7 +3620,7 @@ function runLakeStorageContractSuite(label, options) {
|
|
|
3367
3620
|
{ orderId: "ord_2", customerName: "Grace" }
|
|
3368
3621
|
]);
|
|
3369
3622
|
const commit = await write.commit();
|
|
3370
|
-
|
|
3623
|
+
expect7(commit.outcome).toBe("created");
|
|
3371
3624
|
const version = commit;
|
|
3372
3625
|
await storage.createDataset(definitionDataset);
|
|
3373
3626
|
const state = await storage.listDatasetCatalogState([
|
|
@@ -3376,36 +3629,36 @@ function runLakeStorageContractSuite(label, options) {
|
|
|
3376
3629
|
"contract.missing.dataset"
|
|
3377
3630
|
]);
|
|
3378
3631
|
const byId = new Map(state.map((entry) => [entry.datasetId, entry]));
|
|
3379
|
-
|
|
3632
|
+
expect7(byId.get(writeDataset.id)).toMatchObject({
|
|
3380
3633
|
datasetId: writeDataset.id,
|
|
3381
3634
|
materialized: true
|
|
3382
3635
|
});
|
|
3383
|
-
|
|
3636
|
+
expect7(byId.get(writeDataset.id)?.latestVersion).toMatchObject({
|
|
3384
3637
|
datasetId: writeDataset.id,
|
|
3385
3638
|
versionId: version.versionId,
|
|
3386
3639
|
mode: "snapshot",
|
|
3387
3640
|
rowCount: 2
|
|
3388
3641
|
});
|
|
3389
|
-
|
|
3642
|
+
expect7(byId.get(definitionDataset.id)).toEqual({
|
|
3390
3643
|
datasetId: definitionDataset.id,
|
|
3391
3644
|
materialized: true,
|
|
3392
3645
|
latestVersion: null
|
|
3393
3646
|
});
|
|
3394
|
-
|
|
3647
|
+
expect7(byId.get("contract.missing.dataset")).toEqual({
|
|
3395
3648
|
datasetId: "contract.missing.dataset",
|
|
3396
3649
|
materialized: false,
|
|
3397
3650
|
latestVersion: null
|
|
3398
3651
|
});
|
|
3399
3652
|
});
|
|
3400
3653
|
});
|
|
3401
|
-
|
|
3654
|
+
test7("returns an empty result for an empty request", async () => {
|
|
3402
3655
|
await withStorage(async (storage) => {
|
|
3403
|
-
|
|
3656
|
+
expect7(await storage.listDatasetCatalogState([])).toEqual([]);
|
|
3404
3657
|
});
|
|
3405
3658
|
});
|
|
3406
3659
|
});
|
|
3407
|
-
|
|
3408
|
-
|
|
3660
|
+
describe7("writes and reads", () => {
|
|
3661
|
+
test7("keeps immutable-version row order and offsets stable", async () => {
|
|
3409
3662
|
await withStorage(async (storage) => {
|
|
3410
3663
|
await storage.createDataset(writeDataset);
|
|
3411
3664
|
const write = await storage.beginWrite({ dataset: writeDataset, mode: "snapshot" });
|
|
@@ -3419,20 +3672,20 @@ function runLakeStorageContractSuite(label, options) {
|
|
|
3419
3672
|
await write.writeRows(expected);
|
|
3420
3673
|
const version = await write.commit();
|
|
3421
3674
|
const pinned = { datasetId: writeDataset.id, versionId: version.versionId };
|
|
3422
|
-
await
|
|
3423
|
-
await
|
|
3675
|
+
await expect7(collectRows2(storage.readRows(pinned))).resolves.toEqual(expected);
|
|
3676
|
+
await expect7(collectRows2(storage.readRows(pinned))).resolves.toEqual(expected);
|
|
3424
3677
|
for (const offset of [0, 2, expected.length, expected.length + 3]) {
|
|
3425
|
-
await
|
|
3678
|
+
await expect7(collectRows2(storage.readRows({ ...pinned, offset }))).resolves.toEqual(expected.slice(offset));
|
|
3426
3679
|
}
|
|
3427
|
-
await
|
|
3680
|
+
await expect7(collectRows2(storage.readRows({ ...pinned, columns: ["orderId"], offset: 1, limit: 2 }))).resolves.toEqual([{ orderId: "ord_1" }, { orderId: "ord_5" }]);
|
|
3428
3681
|
});
|
|
3429
3682
|
});
|
|
3430
|
-
|
|
3683
|
+
test7("rejects writes for unknown datasets", async () => {
|
|
3431
3684
|
await withStorage(async (storage) => {
|
|
3432
|
-
await
|
|
3685
|
+
await expect7(storage.beginWrite({ dataset: writeDataset })).rejects.toThrow("Unknown dataset");
|
|
3433
3686
|
});
|
|
3434
3687
|
});
|
|
3435
|
-
|
|
3688
|
+
test7("commits snapshot and append versions with versioned reads", async () => {
|
|
3436
3689
|
await withStorage(async (storage) => {
|
|
3437
3690
|
await storage.createDataset(writeDataset);
|
|
3438
3691
|
const snapshotWrite = await storage.beginWrite({
|
|
@@ -3445,20 +3698,20 @@ function runLakeStorageContractSuite(label, options) {
|
|
|
3445
3698
|
{ orderId: "ord_2", customerName: "Grace" }
|
|
3446
3699
|
]);
|
|
3447
3700
|
const snapshotCommit = await snapshotWrite.commit({ commitMessage: "snapshot orders" });
|
|
3448
|
-
|
|
3701
|
+
expect7(snapshotCommit.outcome).toBe("created");
|
|
3449
3702
|
const version1 = snapshotCommit;
|
|
3450
|
-
|
|
3703
|
+
expect7(version1).toMatchObject({
|
|
3451
3704
|
datasetId: writeDataset.id,
|
|
3452
3705
|
mode: "snapshot",
|
|
3453
3706
|
schema: writeDataset.schema,
|
|
3454
3707
|
producer: { kind: "sync", id: "contract-sync", runId: "run_1" },
|
|
3455
3708
|
rowCount: 2
|
|
3456
3709
|
});
|
|
3457
|
-
|
|
3458
|
-
|
|
3710
|
+
expect7(version1.parentVersionId).toBeUndefined();
|
|
3711
|
+
expect7(await storage.getLatestVersion(writeDataset.id)).toMatchObject({
|
|
3459
3712
|
versionId: version1.versionId
|
|
3460
3713
|
});
|
|
3461
|
-
|
|
3714
|
+
expect7(await storage.getVersion(writeDataset.id, version1.versionId)).toMatchObject({
|
|
3462
3715
|
datasetId: writeDataset.id,
|
|
3463
3716
|
versionId: version1.versionId,
|
|
3464
3717
|
mode: "snapshot",
|
|
@@ -3474,9 +3727,9 @@ function runLakeStorageContractSuite(label, options) {
|
|
|
3474
3727
|
const appendCommit = await appendWrite.commit({
|
|
3475
3728
|
expectedLatestVersionId: version1.versionId
|
|
3476
3729
|
});
|
|
3477
|
-
|
|
3730
|
+
expect7(appendCommit.outcome).toBe("created");
|
|
3478
3731
|
const version2 = appendCommit;
|
|
3479
|
-
|
|
3732
|
+
expect7(version2).toMatchObject({
|
|
3480
3733
|
datasetId: writeDataset.id,
|
|
3481
3734
|
parentVersionId: version1.versionId,
|
|
3482
3735
|
mode: "append",
|
|
@@ -3484,42 +3737,42 @@ function runLakeStorageContractSuite(label, options) {
|
|
|
3484
3737
|
inputs: [{ datasetId: writeDataset.id, versionId: version1.versionId }],
|
|
3485
3738
|
rowCount: 3
|
|
3486
3739
|
});
|
|
3487
|
-
|
|
3740
|
+
expect7(await storage.getLatestVersion(writeDataset.id)).toMatchObject({
|
|
3488
3741
|
versionId: version2.versionId
|
|
3489
3742
|
});
|
|
3490
|
-
await
|
|
3743
|
+
await expect7(collectRows2(storage.readRows({ datasetId: writeDataset.id }))).resolves.toEqual([
|
|
3491
3744
|
{ orderId: "ord_1", customerName: "Ada" },
|
|
3492
3745
|
{ orderId: "ord_2", customerName: "Grace" },
|
|
3493
3746
|
{ orderId: "ord_3", customerName: "Katherine" }
|
|
3494
3747
|
]);
|
|
3495
|
-
await
|
|
3748
|
+
await expect7(collectRows2(storage.readRows({ datasetId: writeDataset.id, versionId: version1.versionId }))).resolves.toEqual([
|
|
3496
3749
|
{ orderId: "ord_1", customerName: "Ada" },
|
|
3497
3750
|
{ orderId: "ord_2", customerName: "Grace" }
|
|
3498
3751
|
]);
|
|
3499
|
-
await
|
|
3752
|
+
await expect7(collectRows2(storage.readRows({
|
|
3500
3753
|
datasetId: writeDataset.id,
|
|
3501
3754
|
columns: ["orderId"],
|
|
3502
3755
|
limit: 2
|
|
3503
3756
|
}))).resolves.toEqual([{ orderId: "ord_1" }, { orderId: "ord_2" }]);
|
|
3504
|
-
await
|
|
3757
|
+
await expect7(collectRows2(storage.readRows({
|
|
3505
3758
|
datasetId: writeDataset.id,
|
|
3506
3759
|
columns: ["orderId"],
|
|
3507
3760
|
limit: 1,
|
|
3508
3761
|
offset: 1
|
|
3509
3762
|
}))).resolves.toEqual([{ orderId: "ord_2" }]);
|
|
3510
|
-
await
|
|
3763
|
+
await expect7(collectRows2(storage.readRows({
|
|
3511
3764
|
datasetId: writeDataset.id,
|
|
3512
3765
|
columns: ["orderId"],
|
|
3513
3766
|
offset: 1
|
|
3514
3767
|
}))).resolves.toEqual([{ orderId: "ord_2" }, { orderId: "ord_3" }]);
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3768
|
+
expect7((await storage.listVersions(writeDataset.id)).map((version) => version.versionId)).toEqual([version2.versionId, version1.versionId]);
|
|
3769
|
+
expect7((await storage.listVersions(writeDataset.id, 1)).map((version) => version.versionId)).toEqual([version2.versionId]);
|
|
3770
|
+
expect7(await storage.listVersions("missing.dataset")).toEqual([]);
|
|
3771
|
+
expect7(await storage.getLatestVersion("missing.dataset")).toBeNull();
|
|
3772
|
+
expect7(await storage.getVersion(writeDataset.id, missingVersionId)).toBeNull();
|
|
3520
3773
|
});
|
|
3521
3774
|
});
|
|
3522
|
-
|
|
3775
|
+
test7("snapshot writes replace the visible latest rows", async () => {
|
|
3523
3776
|
await withStorage(async (storage) => {
|
|
3524
3777
|
await storage.createDataset(writeDataset);
|
|
3525
3778
|
const firstWrite = await storage.beginWrite({ dataset: writeDataset, mode: "snapshot" });
|
|
@@ -3531,14 +3784,14 @@ function runLakeStorageContractSuite(label, options) {
|
|
|
3531
3784
|
});
|
|
3532
3785
|
await replacementWrite.writeRows([{ orderId: "ord_9", customerName: "Dorothy" }]);
|
|
3533
3786
|
const replacementVersion = await replacementWrite.commit();
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
await
|
|
3538
|
-
await
|
|
3787
|
+
expect7(replacementVersion.versionId).not.toBe(firstVersion.versionId);
|
|
3788
|
+
expect7(replacementVersion.parentVersionId).toBeUndefined();
|
|
3789
|
+
expect7(replacementVersion.rowCount).toBe(1);
|
|
3790
|
+
await expect7(collectRows2(storage.readRows({ datasetId: writeDataset.id }))).resolves.toEqual([{ orderId: "ord_9", customerName: "Dorothy" }]);
|
|
3791
|
+
await expect7(collectRows2(storage.readRows({ datasetId: writeDataset.id, versionId: firstVersion.versionId }))).resolves.toEqual([{ orderId: "ord_1", customerName: "Ada" }]);
|
|
3539
3792
|
});
|
|
3540
3793
|
});
|
|
3541
|
-
|
|
3794
|
+
test7("reuses the latest version when a snapshot commits identical content", async () => {
|
|
3542
3795
|
await withStorage(async (storage) => {
|
|
3543
3796
|
await storage.createDataset(writeDataset);
|
|
3544
3797
|
const seedWrite = await storage.beginWrite({ dataset: writeDataset, mode: "snapshot" });
|
|
@@ -3547,7 +3800,7 @@ function runLakeStorageContractSuite(label, options) {
|
|
|
3547
3800
|
{ orderId: "ord_2", customerName: "Grace" }
|
|
3548
3801
|
]);
|
|
3549
3802
|
const seedVersion = await seedWrite.commit();
|
|
3550
|
-
|
|
3803
|
+
expect7(seedVersion.outcome).toBe("created");
|
|
3551
3804
|
const identicalWrite = await storage.beginWrite({
|
|
3552
3805
|
dataset: writeDataset,
|
|
3553
3806
|
mode: "snapshot"
|
|
@@ -3557,10 +3810,10 @@ function runLakeStorageContractSuite(label, options) {
|
|
|
3557
3810
|
{ orderId: "ord_1", customerName: "Ada" }
|
|
3558
3811
|
]);
|
|
3559
3812
|
const identicalVersion = await identicalWrite.commit();
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3813
|
+
expect7(identicalVersion.outcome).toBe("unchanged");
|
|
3814
|
+
expect7(identicalVersion.versionId).toBe(seedVersion.versionId);
|
|
3815
|
+
expect7((await storage.listVersions(writeDataset.id)).map((version) => version.versionId)).toEqual([seedVersion.versionId]);
|
|
3816
|
+
expect7(await storage.getLatestVersion(writeDataset.id)).toMatchObject({
|
|
3564
3817
|
versionId: seedVersion.versionId
|
|
3565
3818
|
});
|
|
3566
3819
|
await Bun.sleep(2);
|
|
@@ -3570,12 +3823,12 @@ function runLakeStorageContractSuite(label, options) {
|
|
|
3570
3823
|
{ orderId: "ord_2", customerName: "Hopper" }
|
|
3571
3824
|
]);
|
|
3572
3825
|
const changedVersion = await changedWrite.commit();
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3826
|
+
expect7(changedVersion.outcome).toBe("created");
|
|
3827
|
+
expect7(changedVersion.versionId).not.toBe(seedVersion.versionId);
|
|
3828
|
+
expect7((await storage.listVersions(writeDataset.id)).map((version) => version.versionId)).toEqual([changedVersion.versionId, seedVersion.versionId]);
|
|
3576
3829
|
});
|
|
3577
3830
|
});
|
|
3578
|
-
|
|
3831
|
+
test7("snapshot content comparison respects row multiplicities", async () => {
|
|
3579
3832
|
await withStorage(async (storage) => {
|
|
3580
3833
|
await storage.createDataset(writeDataset);
|
|
3581
3834
|
const rowA = { orderId: "ord_1", customerName: "Ada" };
|
|
@@ -3587,11 +3840,11 @@ function runLakeStorageContractSuite(label, options) {
|
|
|
3587
3840
|
const shiftedWrite = await storage.beginWrite({ dataset: writeDataset, mode: "snapshot" });
|
|
3588
3841
|
await shiftedWrite.writeRows([rowA, rowB, rowB]);
|
|
3589
3842
|
const shiftedVersion = await shiftedWrite.commit();
|
|
3590
|
-
|
|
3591
|
-
|
|
3843
|
+
expect7(shiftedVersion.outcome).toBe("created");
|
|
3844
|
+
expect7(shiftedVersion.versionId).not.toBe(seedVersion.versionId);
|
|
3592
3845
|
});
|
|
3593
3846
|
});
|
|
3594
|
-
|
|
3847
|
+
test7("treats omitted and null nullable values as the same snapshot content", async () => {
|
|
3595
3848
|
await withStorage(async (storage) => {
|
|
3596
3849
|
await storage.createDataset(nullableWriteDataset);
|
|
3597
3850
|
const seedWrite = await storage.beginWrite({
|
|
@@ -3606,11 +3859,11 @@ function runLakeStorageContractSuite(label, options) {
|
|
|
3606
3859
|
});
|
|
3607
3860
|
await omittedWrite.writeRows([{ orderId: "ord_1" }]);
|
|
3608
3861
|
const omittedVersion = await omittedWrite.commit();
|
|
3609
|
-
|
|
3610
|
-
|
|
3862
|
+
expect7(omittedVersion.outcome).toBe("unchanged");
|
|
3863
|
+
expect7(omittedVersion.versionId).toBe(seedVersion.versionId);
|
|
3611
3864
|
});
|
|
3612
3865
|
});
|
|
3613
|
-
|
|
3866
|
+
test7("reuses the latest version when an append commits zero rows", async () => {
|
|
3614
3867
|
await withStorage(async (storage) => {
|
|
3615
3868
|
await storage.createDataset(writeDataset);
|
|
3616
3869
|
const seedWrite = await storage.beginWrite({ dataset: writeDataset, mode: "snapshot" });
|
|
@@ -3618,14 +3871,14 @@ function runLakeStorageContractSuite(label, options) {
|
|
|
3618
3871
|
const seedVersion = await seedWrite.commit();
|
|
3619
3872
|
const emptyAppend = await storage.beginWrite({ dataset: writeDataset, mode: "append" });
|
|
3620
3873
|
const appendVersion = await emptyAppend.commit();
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
await
|
|
3874
|
+
expect7(appendVersion.outcome).toBe("unchanged");
|
|
3875
|
+
expect7(appendVersion.versionId).toBe(seedVersion.versionId);
|
|
3876
|
+
expect7(appendVersion.rowCount).toBe(1);
|
|
3877
|
+
expect7((await storage.listVersions(writeDataset.id)).map((version) => version.versionId)).toEqual([seedVersion.versionId]);
|
|
3878
|
+
await expect7(collectRows2(storage.readRows({ datasetId: writeDataset.id }))).resolves.toEqual([{ orderId: "ord_1", customerName: "Ada" }]);
|
|
3626
3879
|
});
|
|
3627
3880
|
});
|
|
3628
|
-
|
|
3881
|
+
test7("fails stale optimistic commits", async () => {
|
|
3629
3882
|
await withStorage(async (storage) => {
|
|
3630
3883
|
await storage.createDataset(writeDataset);
|
|
3631
3884
|
const seedWrite = await storage.beginWrite({ dataset: writeDataset, mode: "snapshot" });
|
|
@@ -3639,45 +3892,45 @@ function runLakeStorageContractSuite(label, options) {
|
|
|
3639
3892
|
});
|
|
3640
3893
|
await competingWrite.writeRows([{ orderId: "ord_3", customerName: "Katherine" }]);
|
|
3641
3894
|
await competingWrite.commit({ expectedLatestVersionId: initialVersion.versionId });
|
|
3642
|
-
await
|
|
3643
|
-
await
|
|
3895
|
+
await expect7(delayedWrite.commit({ expectedLatestVersionId: initialVersion.versionId })).rejects.toThrow("Optimistic commit failed");
|
|
3896
|
+
await expect7(collectRows2(storage.readRows({ datasetId: writeDataset.id }))).resolves.toEqual([
|
|
3644
3897
|
{ orderId: "ord_1", customerName: "Ada" },
|
|
3645
3898
|
{ orderId: "ord_3", customerName: "Katherine" }
|
|
3646
3899
|
]);
|
|
3647
3900
|
});
|
|
3648
3901
|
});
|
|
3649
|
-
|
|
3902
|
+
test7("abort closes a write session without committing rows", async () => {
|
|
3650
3903
|
await withStorage(async (storage) => {
|
|
3651
3904
|
await storage.createDataset(writeDataset);
|
|
3652
3905
|
const write = await storage.beginWrite({ dataset: writeDataset, mode: "snapshot" });
|
|
3653
3906
|
await write.writeRows([{ orderId: "ord_1", customerName: "Ada" }]);
|
|
3654
3907
|
await write.abort();
|
|
3655
3908
|
await write.abort();
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
await
|
|
3909
|
+
expect7(await storage.getLatestVersion(writeDataset.id)).toBeNull();
|
|
3910
|
+
expect7(await storage.listVersions(writeDataset.id)).toEqual([]);
|
|
3911
|
+
await expect7(collectRows2(storage.readRows({ datasetId: writeDataset.id }))).rejects.toThrow("No committed version found");
|
|
3659
3912
|
});
|
|
3660
3913
|
});
|
|
3661
|
-
|
|
3914
|
+
test7("commit closes a write session and leaves abort idempotent", async () => {
|
|
3662
3915
|
await withStorage(async (storage) => {
|
|
3663
3916
|
await storage.createDataset(writeDataset);
|
|
3664
3917
|
const write = await storage.beginWrite({ dataset: writeDataset, mode: "snapshot" });
|
|
3665
3918
|
await write.writeRows([{ orderId: "ord_1", customerName: "Ada" }]);
|
|
3666
3919
|
await write.commit();
|
|
3667
3920
|
await write.abort();
|
|
3668
|
-
await
|
|
3921
|
+
await expect7(write.writeRows([])).rejects.toThrow("already closed");
|
|
3669
3922
|
});
|
|
3670
3923
|
});
|
|
3671
|
-
|
|
3924
|
+
test7("rejects rows that do not match the dataset schema", async () => {
|
|
3672
3925
|
await withStorage(async (storage) => {
|
|
3673
3926
|
await storage.createDataset(writeDataset);
|
|
3674
3927
|
const write = await storage.beginWrite({ dataset: writeDataset, mode: "snapshot" });
|
|
3675
|
-
await
|
|
3676
|
-
await
|
|
3928
|
+
await expect7(write.writeRows([{ orderId: "ord_1" }])).rejects.toThrow("missing required column");
|
|
3929
|
+
await expect7(write.writeRows([{ orderId: "ord_1", customerName: "Ada", unexpected: true }])).rejects.toThrow("unknown column");
|
|
3677
3930
|
await write.abort();
|
|
3678
3931
|
});
|
|
3679
3932
|
});
|
|
3680
|
-
|
|
3933
|
+
test7("round-trips exact decimal strings in eager and streaming reads", async () => {
|
|
3681
3934
|
await withStorage(async (storage) => {
|
|
3682
3935
|
const exact = "9007199254740993.123456789";
|
|
3683
3936
|
await storage.createDataset(decimalWriteDataset);
|
|
@@ -3687,18 +3940,18 @@ function runLakeStorageContractSuite(label, options) {
|
|
|
3687
3940
|
{ id: "amount_2", amount: null }
|
|
3688
3941
|
]);
|
|
3689
3942
|
await write.commit();
|
|
3690
|
-
await
|
|
3943
|
+
await expect7(collectRows2(storage.readRows({ datasetId: decimalWriteDataset.id }))).resolves.toEqual([
|
|
3691
3944
|
{ id: "amount_1", amount: exact },
|
|
3692
3945
|
{ id: "amount_2", amount: null }
|
|
3693
3946
|
]);
|
|
3694
|
-
await
|
|
3947
|
+
await expect7(collectRows2(storage.readRows({
|
|
3695
3948
|
datasetId: decimalWriteDataset.id,
|
|
3696
3949
|
columns: ["amount"],
|
|
3697
3950
|
limit: 1
|
|
3698
3951
|
}))).resolves.toEqual([{ amount: exact }]);
|
|
3699
3952
|
});
|
|
3700
3953
|
});
|
|
3701
|
-
|
|
3954
|
+
test7("round-trips fileRef row values", async () => {
|
|
3702
3955
|
await withStorage(async (storage) => {
|
|
3703
3956
|
await storage.createDataset(fileRefDataset);
|
|
3704
3957
|
const write = await storage.beginWrite({ dataset: fileRefDataset, mode: "snapshot" });
|
|
@@ -3707,7 +3960,7 @@ function runLakeStorageContractSuite(label, options) {
|
|
|
3707
3960
|
{ id: "doc_2", attachment: null }
|
|
3708
3961
|
]);
|
|
3709
3962
|
await write.commit();
|
|
3710
|
-
await
|
|
3963
|
+
await expect7(collectRows2(storage.readRows({ datasetId: fileRefDataset.id }))).resolves.toEqual([
|
|
3711
3964
|
{ id: "doc_1", attachment: invoiceRef },
|
|
3712
3965
|
{ id: "doc_2", attachment: null }
|
|
3713
3966
|
]);
|
|
@@ -3716,7 +3969,7 @@ function runLakeStorageContractSuite(label, options) {
|
|
|
3716
3969
|
});
|
|
3717
3970
|
});
|
|
3718
3971
|
}
|
|
3719
|
-
async function
|
|
3972
|
+
async function collectRows2(rows) {
|
|
3720
3973
|
const result = [];
|
|
3721
3974
|
for await (const row of rows) {
|
|
3722
3975
|
result.push(row);
|
|
@@ -3724,7 +3977,7 @@ async function collectRows(rows) {
|
|
|
3724
3977
|
return result;
|
|
3725
3978
|
}
|
|
3726
3979
|
// src/testing/materialization-failure-contract.ts
|
|
3727
|
-
import { describe as
|
|
3980
|
+
import { describe as describe8, expect as expect8, test as test8 } from "bun:test";
|
|
3728
3981
|
|
|
3729
3982
|
// src/testing/ontology-contract-fixture.ts
|
|
3730
3983
|
var emptyChunk = {
|
|
@@ -3888,18 +4141,18 @@ async function commitExactObject(storage, id, options = {}) {
|
|
|
3888
4141
|
// src/testing/materialization-failure-contract.ts
|
|
3889
4142
|
function runMaterializationFailureContractSuite(label, options) {
|
|
3890
4143
|
const boundaries = options.boundaries ?? ["effective.object.upsert", "outbox.insert", "finalize"];
|
|
3891
|
-
|
|
4144
|
+
describe8(label, () => {
|
|
3892
4145
|
for (const boundary of boundaries) {
|
|
3893
|
-
|
|
4146
|
+
test8(`rolls back the complete transaction when ${boundary} fails`, async () => {
|
|
3894
4147
|
const storage = await options.createStorage();
|
|
3895
4148
|
try {
|
|
3896
4149
|
await options.setup?.(storage);
|
|
3897
4150
|
const before = await options.captureState(storage);
|
|
3898
4151
|
const failure = new Error(`contract injected ${boundary}`);
|
|
3899
4152
|
await options.injectFailure(storage, boundary, failure);
|
|
3900
|
-
await
|
|
4153
|
+
await expect8(commitExactObject(storage, `failure-${boundary}`)).rejects.toThrow(failure.message);
|
|
3901
4154
|
await options.clearFailure?.(storage);
|
|
3902
|
-
|
|
4155
|
+
expect8(await options.captureState(storage)).toEqual(before);
|
|
3903
4156
|
} finally {
|
|
3904
4157
|
await options.clearFailure?.(storage);
|
|
3905
4158
|
await options.cleanup?.(storage);
|
|
@@ -3909,7 +4162,7 @@ function runMaterializationFailureContractSuite(label, options) {
|
|
|
3909
4162
|
});
|
|
3910
4163
|
}
|
|
3911
4164
|
// src/testing/materializer-storage-contract.ts
|
|
3912
|
-
import { expect as
|
|
4165
|
+
import { expect as expect9, test as test9 } from "bun:test";
|
|
3913
4166
|
var Device2 = defineObjectType({
|
|
3914
4167
|
id: "StorageContractDevice",
|
|
3915
4168
|
name: "Device",
|
|
@@ -3948,7 +4201,7 @@ var projections = new ProjectionRegistry({
|
|
|
3948
4201
|
])
|
|
3949
4202
|
});
|
|
3950
4203
|
function runMaterializerStorageContractSuite(name, provider) {
|
|
3951
|
-
|
|
4204
|
+
test9(`${name} persists replacement, Action, and telemetry commits atomically`, async () => {
|
|
3952
4205
|
const createdStorage = await provider.createStorage();
|
|
3953
4206
|
const storage = requireContractStorage(createdStorage);
|
|
3954
4207
|
let materializationOrdinal = 0;
|
|
@@ -3981,8 +4234,8 @@ function runMaterializerStorageContractSuite(name, provider) {
|
|
|
3981
4234
|
execution: firstExecution,
|
|
3982
4235
|
entries: entries([sourceEntry("one", "One", "two"), sourceEntry("two", "Two")])
|
|
3983
4236
|
});
|
|
3984
|
-
|
|
3985
|
-
|
|
4237
|
+
expect9(first.counts).toMatchObject({ objectsCreated: 2, linksCreated: 1 });
|
|
4238
|
+
expect9(await storage.objects.getByPrimaryId({
|
|
3986
4239
|
projectId: "materializer-storage-contract",
|
|
3987
4240
|
objectTypeId: Device2.id,
|
|
3988
4241
|
primaryId: "one"
|
|
@@ -4004,17 +4257,17 @@ function runMaterializerStorageContractSuite(name, provider) {
|
|
|
4004
4257
|
execution: secondExecution,
|
|
4005
4258
|
entries: entries([sourceEntry("one", "One updated")])
|
|
4006
4259
|
});
|
|
4007
|
-
|
|
4260
|
+
expect9(second.counts).toMatchObject({
|
|
4008
4261
|
objectsUpdated: 1,
|
|
4009
4262
|
objectsDeleted: 1,
|
|
4010
4263
|
linksDeleted: 1
|
|
4011
4264
|
});
|
|
4012
|
-
|
|
4265
|
+
expect9(await storage.objects.getByPrimaryId({
|
|
4013
4266
|
projectId: "materializer-storage-contract",
|
|
4014
4267
|
objectTypeId: Device2.id,
|
|
4015
4268
|
primaryId: "two"
|
|
4016
4269
|
})).toBeNull();
|
|
4017
|
-
await
|
|
4270
|
+
await expect9(materializer.edits.commit({
|
|
4018
4271
|
mode: "atomic",
|
|
4019
4272
|
source: { kind: "action", actionId: "renameDevice", runId: "missing-action-run" },
|
|
4020
4273
|
operations: [
|
|
@@ -4060,7 +4313,7 @@ function runMaterializerStorageContractSuite(name, provider) {
|
|
|
4060
4313
|
expectedLinks: [],
|
|
4061
4314
|
expectedLinkScopes: []
|
|
4062
4315
|
});
|
|
4063
|
-
|
|
4316
|
+
expect9(await storage.ontology.commits.getByOrigin({
|
|
4064
4317
|
projectId: "materializer-storage-contract",
|
|
4065
4318
|
origin: { kind: "action", actionRunId: "action-run" }
|
|
4066
4319
|
})).toMatchObject({ id: actionCommit.commitId });
|
|
@@ -4069,7 +4322,7 @@ function runMaterializerStorageContractSuite(name, provider) {
|
|
|
4069
4322
|
versionId: "readings-v1",
|
|
4070
4323
|
createdAt: "2026-01-03T00:00:00.000Z"
|
|
4071
4324
|
};
|
|
4072
|
-
await
|
|
4325
|
+
await expect9(materializer.telemetry.append({
|
|
4073
4326
|
source: {
|
|
4074
4327
|
kind: "projection",
|
|
4075
4328
|
projection: { projectionId: temperatureProjection.id },
|
|
@@ -4127,8 +4380,8 @@ function runMaterializerStorageContractSuite(name, provider) {
|
|
|
4127
4380
|
}
|
|
4128
4381
|
]
|
|
4129
4382
|
});
|
|
4130
|
-
|
|
4131
|
-
|
|
4383
|
+
expect9(telemetry).toMatchObject({ pointsCreated: 1, latestObjectsChanged: 1 });
|
|
4384
|
+
expect9(await storage.timeseries.getLatest({
|
|
4132
4385
|
projectId: "materializer-storage-contract",
|
|
4133
4386
|
objectTypeId: Device2.id,
|
|
4134
4387
|
objectId: "one",
|
|
@@ -4213,7 +4466,7 @@ async function claim(storage, input) {
|
|
|
4213
4466
|
return claim2.execution;
|
|
4214
4467
|
}
|
|
4215
4468
|
// src/testing/object-query-contract.ts
|
|
4216
|
-
import { describe as
|
|
4469
|
+
import { describe as describe9, expect as expect10, test as test10 } from "bun:test";
|
|
4217
4470
|
var projectId4 = "object-query-contract";
|
|
4218
4471
|
var Device3 = defineObjectType({
|
|
4219
4472
|
id: "ContractDevice",
|
|
@@ -4321,8 +4574,8 @@ function runObjectQueryProviderContractSuite(label, options) {
|
|
|
4321
4574
|
await options.teardown?.(storage);
|
|
4322
4575
|
}
|
|
4323
4576
|
};
|
|
4324
|
-
|
|
4325
|
-
|
|
4577
|
+
describe9(label, () => {
|
|
4578
|
+
test10("normalizes query shape and resolves search profile defaults", () => {
|
|
4326
4579
|
const normalized = normalizeObjectQuery({
|
|
4327
4580
|
kind: "limit",
|
|
4328
4581
|
limit: 10,
|
|
@@ -4341,28 +4594,28 @@ function runObjectQueryProviderContractSuite(label, options) {
|
|
|
4341
4594
|
}
|
|
4342
4595
|
}
|
|
4343
4596
|
});
|
|
4344
|
-
|
|
4597
|
+
expect10(normalized.kind).toBe("limit");
|
|
4345
4598
|
if (normalized.kind !== "limit")
|
|
4346
4599
|
return;
|
|
4347
|
-
|
|
4348
|
-
|
|
4600
|
+
expect10(normalized.limit).toBe(3);
|
|
4601
|
+
expect10(normalized.input.kind).toBe("project");
|
|
4349
4602
|
if (normalized.input.kind !== "project")
|
|
4350
4603
|
return;
|
|
4351
|
-
|
|
4352
|
-
|
|
4604
|
+
expect10(normalized.input.properties).toEqual(["id", "name"]);
|
|
4605
|
+
expect10(normalized.input.input.kind).toBe("text");
|
|
4353
4606
|
if (normalized.input.input.kind !== "text")
|
|
4354
4607
|
return;
|
|
4355
|
-
|
|
4608
|
+
expect10(normalized.input.input.fields).toEqual(["name", "description"]);
|
|
4356
4609
|
const validated = validateObjectQuery({ kind: "text", query: "alpha", input: { kind: "start", objectTypeId: Room.id } }, { ontology: objectQueryContractOntology });
|
|
4357
|
-
|
|
4610
|
+
expect10(validated.query.kind).toBe("text");
|
|
4358
4611
|
if (validated.query.kind === "text") {
|
|
4359
|
-
|
|
4360
|
-
|
|
4612
|
+
expect10(validated.query.fields).toBeUndefined();
|
|
4613
|
+
expect10(validated.query.fieldsByObjectType).toEqual({
|
|
4361
4614
|
[Room.id]: ["name", "description"]
|
|
4362
4615
|
});
|
|
4363
4616
|
}
|
|
4364
4617
|
});
|
|
4365
|
-
|
|
4618
|
+
test10("collects validation failures before provider planning", () => {
|
|
4366
4619
|
const issues = collectObjectQueryValidationIssues({
|
|
4367
4620
|
kind: "filter",
|
|
4368
4621
|
predicate: { op: "eq", propertyId: "missing", value: "x" },
|
|
@@ -4373,19 +4626,19 @@ function runObjectQueryProviderContractSuite(label, options) {
|
|
|
4373
4626
|
input: { kind: "start", objectTypeId: Room.id }
|
|
4374
4627
|
}
|
|
4375
4628
|
}, { ontology: objectQueryContractOntology });
|
|
4376
|
-
|
|
4629
|
+
expect10(issues.map((issue) => issue.code)).toEqual(expect10.arrayContaining([
|
|
4377
4630
|
"unknown_property",
|
|
4378
4631
|
"query_field_not_enabled",
|
|
4379
4632
|
"text_field_not_string_like"
|
|
4380
4633
|
]));
|
|
4381
4634
|
});
|
|
4382
|
-
|
|
4635
|
+
test10("declares the portable V1 query capability surface", async () => {
|
|
4383
4636
|
await withStorage(async ({ objects: storage }) => {
|
|
4384
4637
|
const capabilities = storage.queryCapabilities();
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4638
|
+
expect10(capabilities.queryObjects).toBe(true);
|
|
4639
|
+
expect10(capabilities.countObjects).toBe(true);
|
|
4640
|
+
expect10(capabilities.existsObjects).toBe(true);
|
|
4641
|
+
expect10(capabilities.facetObjects).toBe(true);
|
|
4389
4642
|
for (const node of [
|
|
4390
4643
|
"start",
|
|
4391
4644
|
"filter",
|
|
@@ -4397,7 +4650,7 @@ function runObjectQueryProviderContractSuite(label, options) {
|
|
|
4397
4650
|
"set",
|
|
4398
4651
|
"project"
|
|
4399
4652
|
]) {
|
|
4400
|
-
|
|
4653
|
+
expect10(capabilities.nodes?.[node]).toBe(true);
|
|
4401
4654
|
}
|
|
4402
4655
|
for (const op of [
|
|
4403
4656
|
"and",
|
|
@@ -4413,17 +4666,17 @@ function runObjectQueryProviderContractSuite(label, options) {
|
|
|
4413
4666
|
"exists",
|
|
4414
4667
|
"contains"
|
|
4415
4668
|
]) {
|
|
4416
|
-
|
|
4669
|
+
expect10(capabilities.predicateOps?.[op]).toBe(true);
|
|
4417
4670
|
}
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4671
|
+
expect10(capabilities.sortKinds?.property).toBe(true);
|
|
4672
|
+
expect10(capabilities.traversalDirections?.outgoing).toBe(true);
|
|
4673
|
+
expect10(capabilities.traversalDirections?.incoming).toBe(true);
|
|
4674
|
+
expect10(capabilities.setOps?.union).toBe(true);
|
|
4675
|
+
expect10(capabilities.setOps?.intersect).toBe(true);
|
|
4676
|
+
expect10(capabilities.setOps?.subtract).toBe(true);
|
|
4424
4677
|
});
|
|
4425
4678
|
});
|
|
4426
|
-
|
|
4679
|
+
test10("pushes down scalar predicates, boolean groups, sorting, limits, and projections", async () => {
|
|
4427
4680
|
await withStorage(async ({ objects: storage, fixture }) => {
|
|
4428
4681
|
await seedObjectQueryContractData(fixture);
|
|
4429
4682
|
const result = await executeObjectQuery({
|
|
@@ -4464,18 +4717,18 @@ function runObjectQueryProviderContractSuite(label, options) {
|
|
|
4464
4717
|
}
|
|
4465
4718
|
}
|
|
4466
4719
|
}, { ontology: objectQueryContractOntology, storage });
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4720
|
+
expect10(result.plan.mode).toBe("pushdown");
|
|
4721
|
+
expect10(ids(result)).toEqual(["room-beta", "room-alpha"]);
|
|
4722
|
+
expect10(result.objects[0]?.properties).toEqual({
|
|
4470
4723
|
id: "room-beta",
|
|
4471
4724
|
name: "Beta Lab",
|
|
4472
4725
|
capacity: 30
|
|
4473
4726
|
});
|
|
4474
|
-
|
|
4475
|
-
|
|
4727
|
+
expect10(result.total).toBe(2);
|
|
4728
|
+
expect10(result.hasMore).toBe(false);
|
|
4476
4729
|
});
|
|
4477
4730
|
});
|
|
4478
|
-
|
|
4731
|
+
test10("matches null, missing, neq, in, and not predicate semantics", async () => {
|
|
4479
4732
|
await withStorage(async ({ objects: storage, fixture }) => {
|
|
4480
4733
|
await seedObjectQueryContractData(fixture);
|
|
4481
4734
|
const nullable = await executeObjectQuery({
|
|
@@ -4538,18 +4791,18 @@ function runObjectQueryProviderContractSuite(label, options) {
|
|
|
4538
4791
|
}
|
|
4539
4792
|
}
|
|
4540
4793
|
}, { ontology: objectQueryContractOntology, storage });
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4794
|
+
expect10(ids(nullable)).toEqual(["room-missing-floor", "room-null-floor"]);
|
|
4795
|
+
expect10(ids(notTwoOrNull)).toEqual(["room-gamma", "room-null-floor"]);
|
|
4796
|
+
expect10(ids(notNullFloor)).toEqual([
|
|
4544
4797
|
"room-alpha",
|
|
4545
4798
|
"room-beta",
|
|
4546
4799
|
"room-gamma",
|
|
4547
4800
|
"room-missing-floor"
|
|
4548
4801
|
]);
|
|
4549
|
-
|
|
4802
|
+
expect10(ids(notFloorTwo)).toEqual(["room-gamma", "room-missing-floor", "room-null-floor"]);
|
|
4550
4803
|
});
|
|
4551
4804
|
});
|
|
4552
|
-
|
|
4805
|
+
test10("keeps page tokens stable when projection hides sorted cursor fields", async () => {
|
|
4553
4806
|
await withStorage(async ({ objects: storage, fixture }) => {
|
|
4554
4807
|
await seedObjectQueryContractData(fixture);
|
|
4555
4808
|
const input = {
|
|
@@ -4577,18 +4830,18 @@ function runObjectQueryProviderContractSuite(label, options) {
|
|
|
4577
4830
|
input: { kind: "page", pageSize: 2, pageToken: page1.nextPageToken, input }
|
|
4578
4831
|
}
|
|
4579
4832
|
}, { ontology: objectQueryContractOntology, storage });
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4833
|
+
expect10(page1.plan.mode).toBe("pushdown");
|
|
4834
|
+
expect10(ids(page1)).toEqual(["room-beta", "room-gamma"]);
|
|
4835
|
+
expect10(page1.objects.map((row) => row.properties)).toEqual([
|
|
4583
4836
|
{ id: "room-beta" },
|
|
4584
4837
|
{ id: "room-gamma" }
|
|
4585
4838
|
]);
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4839
|
+
expect10(page1.hasMore).toBe(true);
|
|
4840
|
+
expect10(page1.nextPageToken).toBeTruthy();
|
|
4841
|
+
expect10(ids(page2)).toEqual(["room-alpha"]);
|
|
4589
4842
|
});
|
|
4590
4843
|
});
|
|
4591
|
-
|
|
4844
|
+
test10("omits totals when requested without losing hasMore", async () => {
|
|
4592
4845
|
await withStorage(async ({ objects: storage, fixture }) => {
|
|
4593
4846
|
await seedObjectQueryContractData(fixture);
|
|
4594
4847
|
const limited = await executeObjectQuery({
|
|
@@ -4610,20 +4863,20 @@ function runObjectQueryProviderContractSuite(label, options) {
|
|
|
4610
4863
|
input: { kind: "limit", limit: 2, input: { kind: "start", objectTypeId: Room.id } }
|
|
4611
4864
|
}
|
|
4612
4865
|
}, { ontology: objectQueryContractOntology, storage });
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
});
|
|
4625
|
-
});
|
|
4626
|
-
|
|
4866
|
+
expect10(limited.plan.mode).toBe("pushdown");
|
|
4867
|
+
expect10(ids(limited)).toEqual(["room-alpha", "room-beta"]);
|
|
4868
|
+
expect10(limited.total).toBeUndefined();
|
|
4869
|
+
expect10(limited.hasMore).toBe(true);
|
|
4870
|
+
expect10(ids(page)).toEqual(["room-alpha", "room-beta"]);
|
|
4871
|
+
expect10(page.total).toBeUndefined();
|
|
4872
|
+
expect10(page.hasMore).toBe(true);
|
|
4873
|
+
expect10(page.nextPageToken).toBeTruthy();
|
|
4874
|
+
expect10(ids(sortedLimited)).toEqual(["room-beta", "room-alpha"]);
|
|
4875
|
+
expect10(sortedLimited.total).toBeUndefined();
|
|
4876
|
+
expect10(sortedLimited.hasMore).toBe(true);
|
|
4877
|
+
});
|
|
4878
|
+
});
|
|
4879
|
+
test10("counts matching objects without returning rows", async () => {
|
|
4627
4880
|
await withStorage(async ({ objects: storage, fixture }) => {
|
|
4628
4881
|
await seedObjectQueryContractData(fixture);
|
|
4629
4882
|
const active = await countObjects({
|
|
@@ -4638,12 +4891,12 @@ function runObjectQueryProviderContractSuite(label, options) {
|
|
|
4638
4891
|
projectId: projectId4,
|
|
4639
4892
|
query: { kind: "limit", limit: 2, input: { kind: "start", objectTypeId: Room.id } }
|
|
4640
4893
|
}, { ontology: objectQueryContractOntology, storage });
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4894
|
+
expect10(active.plan.mode).toBe("pushdown");
|
|
4895
|
+
expect10(active.count).toBe(2);
|
|
4896
|
+
expect10(limited.count).toBe(5);
|
|
4644
4897
|
});
|
|
4645
4898
|
});
|
|
4646
|
-
|
|
4899
|
+
test10("checks existence without returning rows", async () => {
|
|
4647
4900
|
await withStorage(async ({ objects: storage, fixture }) => {
|
|
4648
4901
|
await seedObjectQueryContractData(fixture);
|
|
4649
4902
|
const active = await existsObjects({
|
|
@@ -4666,13 +4919,13 @@ function runObjectQueryProviderContractSuite(label, options) {
|
|
|
4666
4919
|
projectId: projectId4,
|
|
4667
4920
|
query: { kind: "limit", limit: 0, input: { kind: "start", objectTypeId: Room.id } }
|
|
4668
4921
|
}, { ontology: objectQueryContractOntology, storage });
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4922
|
+
expect10(active.plan.mode).toBe("pushdown");
|
|
4923
|
+
expect10(active.exists).toBe(true);
|
|
4924
|
+
expect10(missing.exists).toBe(false);
|
|
4925
|
+
expect10(limited.exists).toBe(true);
|
|
4673
4926
|
});
|
|
4674
4927
|
});
|
|
4675
|
-
|
|
4928
|
+
test10("plans aggregate operations without outer row-shaping requirements", async () => {
|
|
4676
4929
|
await withStorage(async ({ objects: storage, fixture }) => {
|
|
4677
4930
|
await seedObjectQueryContractData(fixture);
|
|
4678
4931
|
const query = {
|
|
@@ -4686,19 +4939,19 @@ function runObjectQueryProviderContractSuite(label, options) {
|
|
|
4686
4939
|
};
|
|
4687
4940
|
const count = await countObjects({ projectId: projectId4, query }, { ontology: objectQueryContractOntology, storage });
|
|
4688
4941
|
const exists = await existsObjects({ projectId: projectId4, query }, { ontology: objectQueryContractOntology, storage });
|
|
4689
|
-
|
|
4942
|
+
expect10(count.plan.mode).toBe("pushdown");
|
|
4690
4943
|
if (count.plan.mode !== "pushdown")
|
|
4691
4944
|
return;
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4945
|
+
expect10(count.plan.query.kind).toBe("text");
|
|
4946
|
+
expect10(count.count).toBe(1);
|
|
4947
|
+
expect10(exists.plan.mode).toBe("pushdown");
|
|
4695
4948
|
if (exists.plan.mode !== "pushdown")
|
|
4696
4949
|
return;
|
|
4697
|
-
|
|
4698
|
-
|
|
4950
|
+
expect10(exists.plan.query.kind).toBe("text");
|
|
4951
|
+
expect10(exists.exists).toBe(true);
|
|
4699
4952
|
});
|
|
4700
4953
|
});
|
|
4701
|
-
|
|
4954
|
+
test10("counts matching objects by facetable properties without returning rows", async () => {
|
|
4702
4955
|
await withStorage(async ({ objects: storage, fixture }) => {
|
|
4703
4956
|
await seedObjectQueryContractData(fixture);
|
|
4704
4957
|
const status = await facetObjects({
|
|
@@ -4717,8 +4970,8 @@ function runObjectQueryProviderContractSuite(label, options) {
|
|
|
4717
4970
|
{ propertyId: "occupied", limit: 10 }
|
|
4718
4971
|
]
|
|
4719
4972
|
}, { ontology: objectQueryContractOntology, storage });
|
|
4720
|
-
|
|
4721
|
-
|
|
4973
|
+
expect10(status.plan.mode).toBe("pushdown");
|
|
4974
|
+
expect10(status.facets).toEqual([
|
|
4722
4975
|
{
|
|
4723
4976
|
propertyId: "status",
|
|
4724
4977
|
buckets: [
|
|
@@ -4734,10 +4987,10 @@ function runObjectQueryProviderContractSuite(label, options) {
|
|
|
4734
4987
|
]
|
|
4735
4988
|
}
|
|
4736
4989
|
]);
|
|
4737
|
-
|
|
4990
|
+
expect10(limited.facets).toEqual(status.facets);
|
|
4738
4991
|
});
|
|
4739
4992
|
});
|
|
4740
|
-
|
|
4993
|
+
test10("pushes down traversal and set operations", async () => {
|
|
4741
4994
|
await withStorage(async ({ objects: storage, fixture }) => {
|
|
4742
4995
|
await seedObjectQueryContractData(fixture);
|
|
4743
4996
|
const outgoing = await executeObjectQuery({
|
|
@@ -4820,18 +5073,18 @@ function runObjectQueryProviderContractSuite(label, options) {
|
|
|
4820
5073
|
]
|
|
4821
5074
|
}
|
|
4822
5075
|
}, { ontology: objectQueryContractOntology, storage });
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
});
|
|
4833
|
-
});
|
|
4834
|
-
|
|
5076
|
+
expect10(outgoing.plan.mode).toBe("pushdown");
|
|
5077
|
+
expect10(incoming.plan.mode).toBe("pushdown");
|
|
5078
|
+
expect10(sortedIds(outgoing)).toEqual(["device-projector", "device-sensor"]);
|
|
5079
|
+
expect10(sortedIds(incoming)).toEqual(["room-alpha", "room-beta", "zone-one"]);
|
|
5080
|
+
expect10(incomingRooms.plan.mode).toBe("pushdown");
|
|
5081
|
+
expect10(sortedIds(incomingRooms)).toEqual(["room-alpha", "room-beta"]);
|
|
5082
|
+
expect10(sortedIds(incomingZones)).toEqual(["zone-one"]);
|
|
5083
|
+
expect10(sortedIds(intersect)).toEqual(["room-alpha", "room-beta"]);
|
|
5084
|
+
expect10(ids(subtract)).toEqual(["room-beta"]);
|
|
5085
|
+
});
|
|
5086
|
+
});
|
|
5087
|
+
test10("hydrates expand links identically via pushdown or bounded fallback", async () => {
|
|
4835
5088
|
await withStorage(async ({ objects: storage, fixture }) => {
|
|
4836
5089
|
await seedObjectQueryContractData(fixture);
|
|
4837
5090
|
const outgoing = await executeObjectQuery({
|
|
@@ -4851,9 +5104,9 @@ function runObjectQueryProviderContractSuite(label, options) {
|
|
|
4851
5104
|
}
|
|
4852
5105
|
}, { ontology: objectQueryContractOntology, storage });
|
|
4853
5106
|
const expandMode = storage.queryCapabilities().nodes?.expand ? "pushdown" : "fallback";
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
5107
|
+
expect10(outgoing.plan.mode).toBe(expandMode);
|
|
5108
|
+
expect10(expandedIds(outgoing, "room-alpha", "hasDevice")).toEqual(["device-projector"]);
|
|
5109
|
+
expect10(expandedIds(outgoing, "room-beta", "hasDevice")).toEqual(["device-sensor"]);
|
|
4857
5110
|
const incoming = await executeObjectQuery({
|
|
4858
5111
|
projectId: projectId4,
|
|
4859
5112
|
query: {
|
|
@@ -4866,15 +5119,15 @@ function runObjectQueryProviderContractSuite(label, options) {
|
|
|
4866
5119
|
}
|
|
4867
5120
|
}
|
|
4868
5121
|
}, { ontology: objectQueryContractOntology, storage });
|
|
4869
|
-
|
|
4870
|
-
|
|
5122
|
+
expect10(incoming.plan.mode).toBe(expandMode);
|
|
5123
|
+
expect10(expandedIds(incoming, "device-projector", "hasDevice")).toEqual([
|
|
4871
5124
|
"room-alpha",
|
|
4872
5125
|
"zone-one"
|
|
4873
5126
|
]);
|
|
4874
|
-
|
|
5127
|
+
expect10(expandedIds(incoming, "device-sensor", "hasDevice")).toEqual(["room-beta"]);
|
|
4875
5128
|
});
|
|
4876
5129
|
});
|
|
4877
|
-
|
|
5130
|
+
test10("expands includeSubtypes outside storage when the expanded query can push down", async () => {
|
|
4878
5131
|
await withStorage(async ({ objects: storage, fixture }) => {
|
|
4879
5132
|
await seedObjectQueryContractData(fixture);
|
|
4880
5133
|
const result = await executeObjectQuery({
|
|
@@ -4885,11 +5138,11 @@ function runObjectQueryProviderContractSuite(label, options) {
|
|
|
4885
5138
|
input: { kind: "start", objectTypeId: Asset.id, includeSubtypes: true }
|
|
4886
5139
|
}
|
|
4887
5140
|
}, { ontology: objectQueryContractOntology, storage });
|
|
4888
|
-
|
|
4889
|
-
|
|
5141
|
+
expect10(result.plan.mode).toBe("pushdown");
|
|
5142
|
+
expect10(sortedIds(result)).toEqual(["asset-base", "laptop-1"]);
|
|
4890
5143
|
});
|
|
4891
5144
|
});
|
|
4892
|
-
|
|
5145
|
+
test10("pushes down basic text search using search profile defaults", async () => {
|
|
4893
5146
|
await withStorage(async ({ objects: storage, fixture }) => {
|
|
4894
5147
|
await seedObjectQueryContractData(fixture);
|
|
4895
5148
|
const result = await executeObjectQuery({
|
|
@@ -4904,11 +5157,11 @@ function runObjectQueryProviderContractSuite(label, options) {
|
|
|
4904
5157
|
}
|
|
4905
5158
|
}
|
|
4906
5159
|
}, { ontology: objectQueryContractOntology, storage });
|
|
4907
|
-
|
|
4908
|
-
|
|
5160
|
+
expect10(result.plan.mode).toBe("pushdown");
|
|
5161
|
+
expect10(ids(result)).toEqual(["room-alpha"]);
|
|
4909
5162
|
});
|
|
4910
5163
|
});
|
|
4911
|
-
|
|
5164
|
+
test10("scopes search profile defaults by object type for subtype text queries", async () => {
|
|
4912
5165
|
await withStorage(async ({ objects: storage, fixture }) => {
|
|
4913
5166
|
await fixture.seed({
|
|
4914
5167
|
objects: [
|
|
@@ -4934,10 +5187,10 @@ function runObjectQueryProviderContractSuite(label, options) {
|
|
|
4934
5187
|
}
|
|
4935
5188
|
}
|
|
4936
5189
|
}, { ontology: objectQueryContractOntology, storage });
|
|
4937
|
-
|
|
5190
|
+
expect10(ids(result)).toEqual(["default-child"]);
|
|
4938
5191
|
});
|
|
4939
5192
|
});
|
|
4940
|
-
|
|
5193
|
+
test10("executes or rejects relevance sorting according to provider capabilities", async () => {
|
|
4941
5194
|
await withStorage(async ({ objects: storage, fixture }) => {
|
|
4942
5195
|
await seedObjectQueryContractData(fixture);
|
|
4943
5196
|
const query = {
|
|
@@ -4951,14 +5204,14 @@ function runObjectQueryProviderContractSuite(label, options) {
|
|
|
4951
5204
|
};
|
|
4952
5205
|
if (storage.queryCapabilities().sortKinds?.relevance === true) {
|
|
4953
5206
|
const result = await executeObjectQuery({ projectId: projectId4, query }, { ontology: objectQueryContractOntology, storage });
|
|
4954
|
-
|
|
4955
|
-
|
|
5207
|
+
expect10(result.plan.mode).toBe("pushdown");
|
|
5208
|
+
expect10(ids(result)).toEqual(["room-alpha"]);
|
|
4956
5209
|
return;
|
|
4957
5210
|
}
|
|
4958
5211
|
await expectPlanningIssue(executeObjectQuery({ projectId: projectId4, query }, { ontology: objectQueryContractOntology, storage }), "sort_kind_not_supported");
|
|
4959
5212
|
});
|
|
4960
5213
|
});
|
|
4961
|
-
|
|
5214
|
+
test10("executes or rejects vector search according to provider capabilities", async () => {
|
|
4962
5215
|
await withStorage(async ({ objects: storage, fixture }) => {
|
|
4963
5216
|
await seedObjectQueryContractData(fixture);
|
|
4964
5217
|
const query = {
|
|
@@ -4970,14 +5223,14 @@ function runObjectQueryProviderContractSuite(label, options) {
|
|
|
4970
5223
|
};
|
|
4971
5224
|
if (storage.queryCapabilities().nodes?.vector === true) {
|
|
4972
5225
|
const result = await executeObjectQuery({ projectId: projectId4, query }, { ontology: objectQueryContractOntology, storage });
|
|
4973
|
-
|
|
4974
|
-
|
|
5226
|
+
expect10(result.plan.mode).toBe("pushdown");
|
|
5227
|
+
expect10(ids(result)).toEqual(["room-alpha", "room-beta"]);
|
|
4975
5228
|
return;
|
|
4976
5229
|
}
|
|
4977
5230
|
await expectPlanningIssue(executeObjectQuery({ projectId: projectId4, query }, { ontology: objectQueryContractOntology, storage }), "query_node_not_supported");
|
|
4978
5231
|
});
|
|
4979
5232
|
});
|
|
4980
|
-
|
|
5233
|
+
test10("executes bounded fallback when pushdown is disabled for a safe query", async () => {
|
|
4981
5234
|
await withStorage(async ({ objects: storage, fixture }) => {
|
|
4982
5235
|
await seedObjectQueryContractData(fixture);
|
|
4983
5236
|
const fallbackStorage = withoutQueryPushdown(storage);
|
|
@@ -4997,11 +5250,11 @@ function runObjectQueryProviderContractSuite(label, options) {
|
|
|
4997
5250
|
}
|
|
4998
5251
|
}
|
|
4999
5252
|
}, { ontology: objectQueryContractOntology, storage: fallbackStorage, maxFallbackRows: 20 });
|
|
5000
|
-
|
|
5001
|
-
|
|
5253
|
+
expect10(result.plan.mode).toBe("fallback");
|
|
5254
|
+
expect10(ids(result)).toEqual(["room-alpha", "room-beta"]);
|
|
5002
5255
|
});
|
|
5003
5256
|
});
|
|
5004
|
-
|
|
5257
|
+
test10("rejects fallback for unsupported query nodes when pushdown is disabled", async () => {
|
|
5005
5258
|
await withStorage(async ({ objects: storage, fixture }) => {
|
|
5006
5259
|
await seedObjectQueryContractData(fixture);
|
|
5007
5260
|
const fallbackStorage = withoutQueryPushdown(storage);
|
|
@@ -5144,16 +5397,16 @@ async function expectPlanningIssue(promise, code) {
|
|
|
5144
5397
|
try {
|
|
5145
5398
|
await promise;
|
|
5146
5399
|
} catch (error) {
|
|
5147
|
-
|
|
5400
|
+
expect10(error).toBeInstanceOf(ObjectQueryPlanningError);
|
|
5148
5401
|
if (error instanceof ObjectQueryPlanningError) {
|
|
5149
|
-
|
|
5402
|
+
expect10(error.issues.map((issue) => issue.code)).toContain(code);
|
|
5150
5403
|
}
|
|
5151
5404
|
return;
|
|
5152
5405
|
}
|
|
5153
5406
|
throw new Error(`Expected ObjectQueryPlanningError with code '${code}'`);
|
|
5154
5407
|
}
|
|
5155
5408
|
// src/testing/ontology-storage-contract.ts
|
|
5156
|
-
import { describe as
|
|
5409
|
+
import { describe as describe10, expect as expect11, test as test11 } from "bun:test";
|
|
5157
5410
|
var replacementIdentity = (versionId, projectionId = "contract.devices") => ({
|
|
5158
5411
|
projectionId,
|
|
5159
5412
|
projectionKind: "object",
|
|
@@ -5187,31 +5440,31 @@ function runOntologyStorageContractSuite(label, options) {
|
|
|
5187
5440
|
await options.cleanup?.(storage);
|
|
5188
5441
|
}
|
|
5189
5442
|
};
|
|
5190
|
-
|
|
5191
|
-
|
|
5443
|
+
describe10(label, () => {
|
|
5444
|
+
test11("stores one authoritative commit and rejects duplicate commit identities", async () => {
|
|
5192
5445
|
await withStorage(async (storage) => {
|
|
5193
5446
|
await commitEmptyEdit(storage, "commit-one");
|
|
5194
5447
|
const stored = await storage.ontology.commits.getById({
|
|
5195
5448
|
projectId: "contract-project",
|
|
5196
5449
|
id: "commit-one"
|
|
5197
5450
|
});
|
|
5198
|
-
|
|
5451
|
+
expect11(stored).toMatchObject({
|
|
5199
5452
|
id: "commit-one",
|
|
5200
5453
|
idempotencyKey: "runtime:commit-one",
|
|
5201
5454
|
requestHash: "hash:commit-one",
|
|
5202
5455
|
result: { commitId: "commit-one" }
|
|
5203
5456
|
});
|
|
5204
|
-
|
|
5457
|
+
expect11(await storage.ontology.commits.getByIdempotencyKey({
|
|
5205
5458
|
projectId: "contract-project",
|
|
5206
5459
|
idempotencyKey: "runtime:commit-one"
|
|
5207
5460
|
})).toEqual(stored);
|
|
5208
|
-
await
|
|
5461
|
+
await expect11(commitEmptyEdit(storage, "commit-one")).rejects.toMatchObject({
|
|
5209
5462
|
kind: "idempotency"
|
|
5210
5463
|
});
|
|
5211
5464
|
const divergent = structuredClone(contractEditHeader("different-id"));
|
|
5212
5465
|
divergent.commit.idempotencyKey = "runtime:commit-one";
|
|
5213
5466
|
divergent.commit.requestHash = "different-hash";
|
|
5214
|
-
await
|
|
5467
|
+
await expect11(storage.transaction(async (tx) => {
|
|
5215
5468
|
await tx.ontology.materializations.begin(divergent);
|
|
5216
5469
|
})).rejects.toMatchObject({ kind: "idempotency" });
|
|
5217
5470
|
const list = await storage.ontology.commits.list({
|
|
@@ -5219,25 +5472,25 @@ function runOntologyStorageContractSuite(label, options) {
|
|
|
5219
5472
|
limit: 1,
|
|
5220
5473
|
offset: 0
|
|
5221
5474
|
});
|
|
5222
|
-
|
|
5223
|
-
|
|
5475
|
+
expect11(list).toMatchObject({ total: 1, hasMore: false });
|
|
5476
|
+
expect11(list.commits.map((commit) => commit.id)).toEqual(["commit-one"]);
|
|
5224
5477
|
});
|
|
5225
5478
|
});
|
|
5226
|
-
|
|
5479
|
+
test11("rejects unfinished sessions and rolls back their authoritative writes", async () => {
|
|
5227
5480
|
await withStorage(async (storage) => {
|
|
5228
5481
|
const header = contractEditHeader("session-contract");
|
|
5229
|
-
await
|
|
5230
|
-
await
|
|
5231
|
-
|
|
5482
|
+
await expect11(storage.ontology.materializations.begin(header)).rejects.toThrow("active storage transaction");
|
|
5483
|
+
await expect11(commitExactObject(storage, "unfinished-session", { omitFinalize: true })).rejects.toThrow("unfinished materialization session");
|
|
5484
|
+
expect11(await storage.objects.getByPrimaryId({
|
|
5232
5485
|
projectId: "contract-project",
|
|
5233
5486
|
objectTypeId: "ContractDevice",
|
|
5234
5487
|
primaryId: "unfinished-session"
|
|
5235
5488
|
})).toBeNull();
|
|
5236
|
-
|
|
5489
|
+
expect11(await storage.ontology.commits.getById({
|
|
5237
5490
|
projectId: "contract-project",
|
|
5238
5491
|
id: "unfinished-session"
|
|
5239
5492
|
})).toBeNull();
|
|
5240
|
-
|
|
5493
|
+
expect11(await storage.ontology.outbox.claim({
|
|
5241
5494
|
projectId: "contract-project",
|
|
5242
5495
|
now: "2027-01-01T00:00:00.000Z",
|
|
5243
5496
|
limit: 10,
|
|
@@ -5245,14 +5498,14 @@ function runOntologyStorageContractSuite(label, options) {
|
|
|
5245
5498
|
leaseExpiresAt: "2027-01-01T01:00:00.000Z"
|
|
5246
5499
|
})).toEqual([]);
|
|
5247
5500
|
let leakedSession;
|
|
5248
|
-
await
|
|
5501
|
+
await expect11(storage.transaction(async (tx) => {
|
|
5249
5502
|
leakedSession = await tx.ontology.materializations.begin(contractEditHeader("leaked-session"));
|
|
5250
5503
|
})).rejects.toThrow("unfinished materialization session");
|
|
5251
5504
|
if (!leakedSession)
|
|
5252
5505
|
throw new Error("Expected an unfinished session handle");
|
|
5253
5506
|
const staleSession = leakedSession;
|
|
5254
5507
|
await storage.transaction(async (tx) => {
|
|
5255
|
-
await
|
|
5508
|
+
await expect11(tx.ontology.materializations.finalize({
|
|
5256
5509
|
session: staleSession,
|
|
5257
5510
|
finalization: {
|
|
5258
5511
|
sourceActivations: [],
|
|
@@ -5262,7 +5515,7 @@ function runOntologyStorageContractSuite(label, options) {
|
|
|
5262
5515
|
});
|
|
5263
5516
|
});
|
|
5264
5517
|
});
|
|
5265
|
-
|
|
5518
|
+
test11("stages insert-only source rows, seals counts, and fences stale execution tokens", async () => {
|
|
5266
5519
|
await withStorage(async (storage) => {
|
|
5267
5520
|
const identity = replacementIdentity("01");
|
|
5268
5521
|
const claimed = await storage.projectionRuns.startOrReclaim({
|
|
@@ -5273,7 +5526,7 @@ function runOntologyStorageContractSuite(label, options) {
|
|
|
5273
5526
|
});
|
|
5274
5527
|
const execution2 = claimed.execution;
|
|
5275
5528
|
const source = { projectionId: identity.projectionId };
|
|
5276
|
-
await
|
|
5529
|
+
await expect11(storage.ontology.sources.beginMaterialization({
|
|
5277
5530
|
projectId: "contract-project",
|
|
5278
5531
|
source,
|
|
5279
5532
|
materializationId: "mismatched-candidate",
|
|
@@ -5299,7 +5552,7 @@ function runOntologyStorageContractSuite(label, options) {
|
|
|
5299
5552
|
ontologyRevision: identity.ontologyRevision,
|
|
5300
5553
|
createdAt: "2026-01-01T00:00:00.000Z"
|
|
5301
5554
|
});
|
|
5302
|
-
|
|
5555
|
+
expect11(await storage.ontology.sources.getActive({ projectId: "contract-project", source })).toBeNull();
|
|
5303
5556
|
const row = {
|
|
5304
5557
|
root: {
|
|
5305
5558
|
kind: "object",
|
|
@@ -5312,21 +5565,21 @@ function runOntologyStorageContractSuite(label, options) {
|
|
|
5312
5565
|
},
|
|
5313
5566
|
stagingOrdinal: 0
|
|
5314
5567
|
};
|
|
5315
|
-
|
|
5568
|
+
expect11(await storage.ontology.sources.stageRows({
|
|
5316
5569
|
projectId: "contract-project",
|
|
5317
5570
|
source,
|
|
5318
5571
|
materializationId: "staging-candidate",
|
|
5319
5572
|
execution: execution2,
|
|
5320
5573
|
rows: [row]
|
|
5321
5574
|
})).toEqual({ inserted: 1, unchanged: 0 });
|
|
5322
|
-
|
|
5575
|
+
expect11(await storage.ontology.sources.stageRows({
|
|
5323
5576
|
projectId: "contract-project",
|
|
5324
5577
|
source,
|
|
5325
5578
|
materializationId: "staging-candidate",
|
|
5326
5579
|
execution: execution2,
|
|
5327
5580
|
rows: [row]
|
|
5328
5581
|
})).toEqual({ inserted: 0, unchanged: 1 });
|
|
5329
|
-
await
|
|
5582
|
+
await expect11(storage.ontology.sources.stageRows({
|
|
5330
5583
|
projectId: "contract-project",
|
|
5331
5584
|
source,
|
|
5332
5585
|
materializationId: "staging-candidate",
|
|
@@ -5347,21 +5600,21 @@ function runOntologyStorageContractSuite(label, options) {
|
|
|
5347
5600
|
assertionCount: 1,
|
|
5348
5601
|
readyAt: "2026-01-01T00:01:00.000Z"
|
|
5349
5602
|
});
|
|
5350
|
-
|
|
5603
|
+
expect11(ready).toMatchObject({ status: "ready", rootCount: 1, assertionCount: 1 });
|
|
5351
5604
|
const reclaimed = await storage.projectionRuns.startOrReclaim({
|
|
5352
5605
|
id: claimed.run.id,
|
|
5353
5606
|
projectId: claimed.run.projectId,
|
|
5354
5607
|
identity,
|
|
5355
5608
|
target: { objectTypeId: "ContractDevice" }
|
|
5356
5609
|
});
|
|
5357
|
-
await
|
|
5610
|
+
await expect11(storage.ontology.sources.stageRows({
|
|
5358
5611
|
projectId: "contract-project",
|
|
5359
5612
|
source,
|
|
5360
5613
|
materializationId: "staging-candidate",
|
|
5361
5614
|
execution: execution2,
|
|
5362
5615
|
rows: []
|
|
5363
5616
|
})).rejects.toMatchObject({ kind: "execution-lost" });
|
|
5364
|
-
await
|
|
5617
|
+
await expect11(storage.ontology.sources.markReady({
|
|
5365
5618
|
projectId: "contract-project",
|
|
5366
5619
|
source,
|
|
5367
5620
|
materializationId: "staging-candidate",
|
|
@@ -5370,7 +5623,7 @@ function runOntologyStorageContractSuite(label, options) {
|
|
|
5370
5623
|
assertionCount: 1,
|
|
5371
5624
|
readyAt: "2026-01-01T00:03:00.000Z"
|
|
5372
5625
|
})).rejects.toMatchObject({ kind: "execution-lost" });
|
|
5373
|
-
await
|
|
5626
|
+
await expect11(storage.ontology.sources.abandon({
|
|
5374
5627
|
kind: "candidate",
|
|
5375
5628
|
projectId: "contract-project",
|
|
5376
5629
|
source,
|
|
@@ -5385,22 +5638,22 @@ function runOntologyStorageContractSuite(label, options) {
|
|
|
5385
5638
|
execution: reclaimed.execution,
|
|
5386
5639
|
abandonedAt: "2026-01-01T00:02:00.000Z"
|
|
5387
5640
|
});
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5641
|
+
expect11(abandoned).toMatchObject({ status: "abandoned", executionToken: null });
|
|
5642
|
+
expect11(await storage.ontology.sources.summarizeTerminal({ projectId: "contract-project" })).toEqual({ count: 1, oldestTerminalAt: "2026-01-01T00:02:00.000Z" });
|
|
5643
|
+
expect11(await storage.ontology.sources.cleanupTerminal({
|
|
5391
5644
|
projectId: "contract-project",
|
|
5392
5645
|
terminalBefore: "2026-02-01T00:00:00.000Z",
|
|
5393
5646
|
limit: 1
|
|
5394
5647
|
})).toEqual({ rowsDeleted: 1, materializationsDeleted: 0 });
|
|
5395
|
-
|
|
5648
|
+
expect11(await storage.ontology.sources.cleanupTerminal({
|
|
5396
5649
|
projectId: "contract-project",
|
|
5397
5650
|
terminalBefore: "2026-02-01T00:00:00.000Z",
|
|
5398
5651
|
limit: 1
|
|
5399
5652
|
})).toEqual({ rowsDeleted: 0, materializationsDeleted: 1 });
|
|
5400
|
-
|
|
5653
|
+
expect11(await storage.ontology.sources.summarizeTerminal({ projectId: "contract-project" })).toEqual({ count: 0, oldestTerminalAt: null });
|
|
5401
5654
|
});
|
|
5402
5655
|
});
|
|
5403
|
-
|
|
5656
|
+
test11("atomically activates explicit empty candidates and rejects competing stale heads", async () => {
|
|
5404
5657
|
await withStorage(async (storage) => {
|
|
5405
5658
|
const first = await createReadyEmptyCandidate(storage, "run-one", "candidate-one", "01");
|
|
5406
5659
|
await activateEmptyCandidate(storage, first, null, "commit-source-one", "2026-01-03T00:00:00.000Z");
|
|
@@ -5408,27 +5661,27 @@ function runOntologyStorageContractSuite(label, options) {
|
|
|
5408
5661
|
projectId: "contract-project",
|
|
5409
5662
|
source: first.source
|
|
5410
5663
|
});
|
|
5411
|
-
|
|
5664
|
+
expect11(activeOne).toMatchObject({
|
|
5412
5665
|
materializationId: first.materializationId,
|
|
5413
5666
|
status: "active",
|
|
5414
5667
|
rootCount: 0,
|
|
5415
5668
|
assertionCount: 0,
|
|
5416
5669
|
executionToken: null
|
|
5417
5670
|
});
|
|
5418
|
-
|
|
5671
|
+
expect11(await storage.ontology.commits.getByOrigin({
|
|
5419
5672
|
projectId: "contract-project",
|
|
5420
5673
|
origin: { kind: "projection", projectionRunId: first.execution.projectionRunId }
|
|
5421
5674
|
})).toMatchObject({ id: "commit-source-one" });
|
|
5422
|
-
await
|
|
5675
|
+
await expect11(activateEmptyCandidate(storage, first, activeOne, "duplicate-projection-origin", "2026-01-03T01:00:00.000Z")).rejects.toMatchObject({ kind: "run-correlation" });
|
|
5423
5676
|
const left = await createReadyEmptyCandidate(storage, "run-left", "candidate-left", "02");
|
|
5424
5677
|
const right = await createReadyEmptyCandidate(storage, "run-right", "candidate-right", "03");
|
|
5425
5678
|
await activateEmptyCandidate(storage, left, activeOne, "commit-source-left", "2026-01-04T00:00:00.000Z");
|
|
5426
|
-
await
|
|
5679
|
+
await expect11(activateEmptyCandidate(storage, right, activeOne, "commit-source-right", "2026-01-05T00:00:00.000Z")).rejects.toMatchObject({ kind: "projection-fence" });
|
|
5427
5680
|
const currentActive = await storage.ontology.sources.getActive({
|
|
5428
5681
|
projectId: "contract-project",
|
|
5429
5682
|
source: first.source
|
|
5430
5683
|
});
|
|
5431
|
-
|
|
5684
|
+
expect11(currentActive).toMatchObject({ materializationId: left.materializationId });
|
|
5432
5685
|
const stale = await createReadyEmptyCandidate(storage, "run-stale-activation", "candidate-stale-activation", "04");
|
|
5433
5686
|
const reclaimed = await storage.projectionRuns.startOrReclaim({
|
|
5434
5687
|
id: stale.execution.projectionRunId,
|
|
@@ -5436,7 +5689,7 @@ function runOntologyStorageContractSuite(label, options) {
|
|
|
5436
5689
|
identity: stale.identity,
|
|
5437
5690
|
target: { objectTypeId: "ContractDevice" }
|
|
5438
5691
|
});
|
|
5439
|
-
await
|
|
5692
|
+
await expect11(activateEmptyCandidate(storage, stale, currentActive, "commit-stale-activation", "2026-01-06T00:00:00.000Z")).rejects.toThrow("execution token is stale");
|
|
5440
5693
|
await storage.ontology.sources.abandon({
|
|
5441
5694
|
kind: "reclaim",
|
|
5442
5695
|
projectId: "contract-project",
|
|
@@ -5449,12 +5702,12 @@ function runOntologyStorageContractSuite(label, options) {
|
|
|
5449
5702
|
terminalBefore: "2026-02-01T00:00:00.000Z",
|
|
5450
5703
|
limit: 10
|
|
5451
5704
|
});
|
|
5452
|
-
|
|
5453
|
-
|
|
5705
|
+
expect11(cleanup.materializationsDeleted).toBeGreaterThanOrEqual(1);
|
|
5706
|
+
expect11(await storage.ontology.sources.getActive({
|
|
5454
5707
|
projectId: "contract-project",
|
|
5455
5708
|
source: first.source
|
|
5456
5709
|
})).toMatchObject({ materializationId: left.materializationId });
|
|
5457
|
-
|
|
5710
|
+
expect11(await storage.ontology.sources.abandon({
|
|
5458
5711
|
kind: "candidate",
|
|
5459
5712
|
projectId: "contract-project",
|
|
5460
5713
|
source: right.source,
|
|
@@ -5464,19 +5717,19 @@ function runOntologyStorageContractSuite(label, options) {
|
|
|
5464
5717
|
})).toMatchObject({ status: "abandoned" });
|
|
5465
5718
|
});
|
|
5466
5719
|
});
|
|
5467
|
-
|
|
5720
|
+
test11("rolls back exact rows, commits, and outbox writes through one transaction facade", async () => {
|
|
5468
5721
|
await withStorage(async (storage) => {
|
|
5469
|
-
await
|
|
5470
|
-
|
|
5722
|
+
await expect11(commitExactObject(storage, "rolled-back", { throwAfterFinalize: true })).rejects.toThrow("contract rollback");
|
|
5723
|
+
expect11(await storage.objects.getByPrimaryId({
|
|
5471
5724
|
projectId: "contract-project",
|
|
5472
5725
|
objectTypeId: "ContractDevice",
|
|
5473
5726
|
primaryId: "rolled-back"
|
|
5474
5727
|
})).toBeNull();
|
|
5475
|
-
|
|
5728
|
+
expect11(await storage.ontology.commits.getById({
|
|
5476
5729
|
projectId: "contract-project",
|
|
5477
5730
|
id: "rolled-back"
|
|
5478
5731
|
})).toBeNull();
|
|
5479
|
-
|
|
5732
|
+
expect11(await storage.ontology.outbox.claim({
|
|
5480
5733
|
projectId: "contract-project",
|
|
5481
5734
|
now: "2027-01-01T00:00:00.000Z",
|
|
5482
5735
|
limit: 10,
|
|
@@ -5484,28 +5737,28 @@ function runOntologyStorageContractSuite(label, options) {
|
|
|
5484
5737
|
leaseExpiresAt: "2027-01-01T01:00:00.000Z"
|
|
5485
5738
|
})).toEqual([]);
|
|
5486
5739
|
await commitExactObject(storage, "committed");
|
|
5487
|
-
|
|
5740
|
+
expect11(await storage.objects.getByPrimaryId({
|
|
5488
5741
|
projectId: "contract-project",
|
|
5489
5742
|
objectTypeId: "ContractDevice",
|
|
5490
5743
|
primaryId: "committed"
|
|
5491
5744
|
})).toMatchObject({ properties: { name: "committed" }, lastCommitId: "committed" });
|
|
5492
|
-
await
|
|
5493
|
-
|
|
5745
|
+
await expect11(commitExactObject(storage, "stale-object-cas", { primaryId: "committed" })).rejects.toThrow("to be absent");
|
|
5746
|
+
expect11(await storage.ontology.commits.getById({
|
|
5494
5747
|
projectId: "contract-project",
|
|
5495
5748
|
id: "stale-object-cas"
|
|
5496
5749
|
})).toBeNull();
|
|
5497
|
-
|
|
5750
|
+
expect11(await storage.objects.getByPrimaryId({
|
|
5498
5751
|
projectId: "contract-project",
|
|
5499
5752
|
objectTypeId: "ContractDevice",
|
|
5500
5753
|
primaryId: "committed"
|
|
5501
5754
|
})).toMatchObject({ properties: { name: "committed" }, lastCommitId: "committed" });
|
|
5502
5755
|
});
|
|
5503
5756
|
});
|
|
5504
|
-
|
|
5757
|
+
test11("leases outbox rows exclusively, reclaims expiry, fences stale workers, and purges", async () => {
|
|
5505
5758
|
await withStorage(async (storage) => {
|
|
5506
5759
|
const outboxB = await commitExactObject(storage, "outbox-b");
|
|
5507
5760
|
const outboxA = await commitExactObject(storage, "outbox-a");
|
|
5508
|
-
await
|
|
5761
|
+
await expect11(storage.ontology.outbox.claim({
|
|
5509
5762
|
projectId: "contract-project",
|
|
5510
5763
|
now: "2026-01-02T19:00:00-05:00",
|
|
5511
5764
|
limit: 1,
|
|
@@ -5519,9 +5772,9 @@ function runOntologyStorageContractSuite(label, options) {
|
|
|
5519
5772
|
leaseId: "lease-one",
|
|
5520
5773
|
leaseExpiresAt: "2026-01-03T01:00:00.000Z"
|
|
5521
5774
|
});
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5775
|
+
expect11(first).toHaveLength(1);
|
|
5776
|
+
expect11(first[0]).toMatchObject({ attempts: 1, leaseId: "lease-one" });
|
|
5777
|
+
expect11(first[0].envelope.id).toBe([outboxA.eventId, outboxB.eventId].sort()[0]);
|
|
5525
5778
|
const concurrent = await storage.ontology.outbox.claim({
|
|
5526
5779
|
projectId: "contract-project",
|
|
5527
5780
|
now: "2026-01-03T00:30:00.000Z",
|
|
@@ -5529,8 +5782,8 @@ function runOntologyStorageContractSuite(label, options) {
|
|
|
5529
5782
|
leaseId: "lease-concurrent",
|
|
5530
5783
|
leaseExpiresAt: "2026-01-03T01:30:00.000Z"
|
|
5531
5784
|
});
|
|
5532
|
-
|
|
5533
|
-
|
|
5785
|
+
expect11(concurrent).toHaveLength(1);
|
|
5786
|
+
expect11(concurrent[0].envelope.id).not.toBe(first[0].envelope.id);
|
|
5534
5787
|
const reclaimed = await storage.ontology.outbox.claim({
|
|
5535
5788
|
projectId: "contract-project",
|
|
5536
5789
|
now: "2026-01-03T02:00:00.000Z",
|
|
@@ -5538,8 +5791,8 @@ function runOntologyStorageContractSuite(label, options) {
|
|
|
5538
5791
|
leaseId: "lease-two",
|
|
5539
5792
|
leaseExpiresAt: "2026-01-03T03:00:00.000Z"
|
|
5540
5793
|
});
|
|
5541
|
-
|
|
5542
|
-
await
|
|
5794
|
+
expect11(reclaimed[0].envelope.id).toBe(first[0].envelope.id);
|
|
5795
|
+
await expect11(storage.ontology.outbox.markPublished({
|
|
5543
5796
|
projectId: "contract-project",
|
|
5544
5797
|
ids: [first[0].envelope.id],
|
|
5545
5798
|
leaseId: "lease-one",
|
|
@@ -5559,22 +5812,22 @@ function runOntologyStorageContractSuite(label, options) {
|
|
|
5559
5812
|
leaseId: "lease-three",
|
|
5560
5813
|
leaseExpiresAt: "2026-01-04T01:00:00.000Z"
|
|
5561
5814
|
});
|
|
5562
|
-
|
|
5563
|
-
|
|
5815
|
+
expect11(retried[0]).toMatchObject({ attempts: 3, lastError: "broker unavailable" });
|
|
5816
|
+
expect11(await storage.ontology.outbox.summarize({ projectId: "contract-project" })).toMatchObject({ pendingCount: 2, retryingCount: 2, maxAttempts: 3 });
|
|
5564
5817
|
await storage.ontology.outbox.markPublished({
|
|
5565
5818
|
projectId: "contract-project",
|
|
5566
5819
|
ids: [retried[0].envelope.id],
|
|
5567
5820
|
leaseId: "lease-three",
|
|
5568
5821
|
publishedAt: "2026-01-04T00:30:00.000Z"
|
|
5569
5822
|
});
|
|
5570
|
-
|
|
5823
|
+
expect11(await storage.ontology.outbox.purgePublished({
|
|
5571
5824
|
projectId: "contract-project",
|
|
5572
5825
|
publishedBefore: "2026-01-05T00:00:00.000Z",
|
|
5573
5826
|
limit: 1
|
|
5574
5827
|
})).toBe(1);
|
|
5575
5828
|
});
|
|
5576
5829
|
});
|
|
5577
|
-
|
|
5830
|
+
test11("keeps batch outbox settlement atomic when callers catch a stale lease", async () => {
|
|
5578
5831
|
await withStorage(async (storage) => {
|
|
5579
5832
|
await commitExactObject(storage, "lease-atomic-a");
|
|
5580
5833
|
await commitExactObject(storage, "lease-atomic-b");
|
|
@@ -5585,7 +5838,7 @@ function runOntologyStorageContractSuite(label, options) {
|
|
|
5585
5838
|
leaseId: "lease-atomic-old",
|
|
5586
5839
|
leaseExpiresAt: "2026-01-03T01:00:00.000Z"
|
|
5587
5840
|
});
|
|
5588
|
-
|
|
5841
|
+
expect11(claimed).toHaveLength(2);
|
|
5589
5842
|
const [reclaimed] = await storage.ontology.outbox.claim({
|
|
5590
5843
|
projectId: "contract-project",
|
|
5591
5844
|
now: "2026-01-03T02:00:00.000Z",
|
|
@@ -5607,7 +5860,7 @@ function runOntologyStorageContractSuite(label, options) {
|
|
|
5607
5860
|
conflict = error;
|
|
5608
5861
|
}
|
|
5609
5862
|
});
|
|
5610
|
-
|
|
5863
|
+
expect11(conflict).toMatchObject({ kind: "outbox-lease" });
|
|
5611
5864
|
await storage.ontology.outbox.markPublished({
|
|
5612
5865
|
projectId: "contract-project",
|
|
5613
5866
|
ids: [untouched.envelope.id],
|
|
@@ -5616,7 +5869,7 @@ function runOntologyStorageContractSuite(label, options) {
|
|
|
5616
5869
|
});
|
|
5617
5870
|
});
|
|
5618
5871
|
});
|
|
5619
|
-
|
|
5872
|
+
test11("rolls back a telemetry checkpoint with its authoritative ontology commit", async () => {
|
|
5620
5873
|
await withStorage(async (storage) => {
|
|
5621
5874
|
const identity = {
|
|
5622
5875
|
projectionId: "contract.telemetry",
|
|
@@ -5676,20 +5929,20 @@ function runOntologyStorageContractSuite(label, options) {
|
|
|
5676
5929
|
throw new Error("telemetry rollback");
|
|
5677
5930
|
});
|
|
5678
5931
|
};
|
|
5679
|
-
await
|
|
5680
|
-
|
|
5932
|
+
await expect11(execute(true)).rejects.toThrow("telemetry rollback");
|
|
5933
|
+
expect11(await storage.ontology.commits.getByOrigin({
|
|
5681
5934
|
projectId: "contract-project",
|
|
5682
5935
|
origin: { kind: "telemetry", projectionRunId: run.run.id, batchOrdinal: 0 }
|
|
5683
5936
|
})).toBeNull();
|
|
5684
|
-
|
|
5937
|
+
expect11(await storage.projectionRuns.getById({ projectId: run.run.projectId, id: run.run.id })).toMatchObject({
|
|
5685
5938
|
telemetryCheckpoint: { nextBatchOrdinal: 0, nextRowOffset: 0, inputExhausted: false }
|
|
5686
5939
|
});
|
|
5687
5940
|
await execute(false);
|
|
5688
|
-
|
|
5941
|
+
expect11(await storage.ontology.commits.getByOrigin({
|
|
5689
5942
|
projectId: "contract-project",
|
|
5690
5943
|
origin: { kind: "telemetry", projectionRunId: run.run.id, batchOrdinal: 0 }
|
|
5691
5944
|
})).toMatchObject({ id: "telemetry-commit" });
|
|
5692
|
-
|
|
5945
|
+
expect11(await storage.projectionRuns.getById({ projectId: run.run.projectId, id: run.run.id })).toMatchObject({
|
|
5693
5946
|
telemetryCheckpoint: { nextBatchOrdinal: 1, nextRowOffset: 2, inputExhausted: true }
|
|
5694
5947
|
});
|
|
5695
5948
|
});
|
|
@@ -5853,7 +6106,7 @@ function telemetryHeader(identity, projectionRunId) {
|
|
|
5853
6106
|
};
|
|
5854
6107
|
}
|
|
5855
6108
|
// src/testing/projection-run-storage-contract.ts
|
|
5856
|
-
import { describe as
|
|
6109
|
+
import { describe as describe11, expect as expect12, test as test12 } from "bun:test";
|
|
5857
6110
|
var projectId5 = "contract-project";
|
|
5858
6111
|
var objectTarget = { objectTypeId: "Device" };
|
|
5859
6112
|
var replacementIdentity2 = {
|
|
@@ -5892,62 +6145,62 @@ function runProjectionRunStorageContractSuite(label, options) {
|
|
|
5892
6145
|
await options.cleanup?.(storage);
|
|
5893
6146
|
}
|
|
5894
6147
|
};
|
|
5895
|
-
|
|
5896
|
-
|
|
6148
|
+
describe11(label, () => {
|
|
6149
|
+
test12("keeps one run while reclaim rotates its execution", async () => {
|
|
5897
6150
|
await withStorage(async (storage) => {
|
|
5898
6151
|
const input = replacementInput("replacement-run");
|
|
5899
6152
|
const first = await storage.startOrReclaim(input);
|
|
5900
6153
|
const second = await storage.startOrReclaim(input);
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
6154
|
+
expect12(first.run).toMatchObject({ id: input.id, attempt: 1, status: "running" });
|
|
6155
|
+
expect12(second.run).toMatchObject({ id: input.id, attempt: 2, status: "running" });
|
|
6156
|
+
expect12(second.execution.executionToken).not.toBe(first.execution.executionToken);
|
|
6157
|
+
expect12(second.run).not.toHaveProperty("executionToken");
|
|
6158
|
+
expect12(await storage.getById({ projectId: projectId5, id: input.id })).toMatchObject({
|
|
5906
6159
|
identity: replacementIdentity2,
|
|
5907
6160
|
target: objectTarget,
|
|
5908
6161
|
progress: { sourceRowsRead: 0, sourceRowsSkipped: 0 }
|
|
5909
6162
|
});
|
|
5910
6163
|
});
|
|
5911
6164
|
});
|
|
5912
|
-
|
|
6165
|
+
test12("fences every stale lock, progress, and terminal write", async () => {
|
|
5913
6166
|
await withStorage(async (storage) => {
|
|
5914
6167
|
const input = replacementInput("fenced-run");
|
|
5915
6168
|
const stale = await storage.startOrReclaim(input);
|
|
5916
6169
|
const current = await storage.startOrReclaim(input);
|
|
5917
|
-
await
|
|
5918
|
-
await
|
|
6170
|
+
await expect12(storage.lockForMaterialization(executionInput(stale))).rejects.toThrow("execution token is stale");
|
|
6171
|
+
await expect12(storage.update({
|
|
5919
6172
|
...executionInput(stale),
|
|
5920
6173
|
progress: { sourceRowsRead: 1 }
|
|
5921
6174
|
})).rejects.toThrow("execution token is stale");
|
|
5922
|
-
await
|
|
6175
|
+
await expect12(storage.finish({
|
|
5923
6176
|
...executionInput(stale),
|
|
5924
6177
|
protocol: "replacement",
|
|
5925
6178
|
status: "cancelled"
|
|
5926
6179
|
})).rejects.toThrow("execution token is stale");
|
|
5927
|
-
await
|
|
6180
|
+
await expect12(storage.update({
|
|
5928
6181
|
...executionInput(current),
|
|
5929
6182
|
progress: { sourceRowsRead: 3 }
|
|
5930
6183
|
})).resolves.toMatchObject({ progress: { sourceRowsRead: 3 } });
|
|
5931
6184
|
});
|
|
5932
6185
|
});
|
|
5933
|
-
|
|
6186
|
+
test12("rejects immutable identity and target drift", async () => {
|
|
5934
6187
|
await withStorage(async (storage) => {
|
|
5935
6188
|
const input = replacementInput("identity-run");
|
|
5936
6189
|
const claim2 = await storage.startOrReclaim(input);
|
|
5937
6190
|
const changedIdentity = { ...replacementIdentity2, ownershipHash: "different-ownership" };
|
|
5938
|
-
await
|
|
5939
|
-
await
|
|
5940
|
-
await
|
|
6191
|
+
await expect12(storage.startOrReclaim({ ...input, identity: changedIdentity })).rejects.toThrow("identity does not match");
|
|
6192
|
+
await expect12(storage.startOrReclaim({ ...input, target: { objectTypeId: "OtherDevice" } })).rejects.toThrow("target object types do not match");
|
|
6193
|
+
await expect12(storage.lockForMaterialization({
|
|
5941
6194
|
...executionInput(claim2),
|
|
5942
6195
|
identity: changedIdentity
|
|
5943
6196
|
})).rejects.toThrow("identity does not match");
|
|
5944
6197
|
});
|
|
5945
6198
|
});
|
|
5946
|
-
|
|
6199
|
+
test12("keeps physical progress monotone and internally consistent", async () => {
|
|
5947
6200
|
await withStorage(async (storage) => {
|
|
5948
6201
|
const claim2 = await storage.startOrReclaim(replacementInput("progress-run"));
|
|
5949
6202
|
const execution2 = executionInput(claim2);
|
|
5950
|
-
await
|
|
6203
|
+
await expect12(storage.update({
|
|
5951
6204
|
...execution2,
|
|
5952
6205
|
progress: { sourceRowsRead: 1, sourceRowsSkipped: 2 }
|
|
5953
6206
|
})).rejects.toThrow("sourceRowsSkipped must not exceed sourceRowsRead");
|
|
@@ -5955,13 +6208,13 @@ function runProjectionRunStorageContractSuite(label, options) {
|
|
|
5955
6208
|
...execution2,
|
|
5956
6209
|
progress: { sourceRowsRead: 5, sourceRowsSkipped: 1 }
|
|
5957
6210
|
});
|
|
5958
|
-
await
|
|
6211
|
+
await expect12(storage.update({ ...execution2, progress: { sourceRowsRead: 4 } })).rejects.toThrow("must not decrease");
|
|
5959
6212
|
});
|
|
5960
6213
|
});
|
|
5961
|
-
|
|
6214
|
+
test12("rejects immutable dataset-version metadata reuse", async () => {
|
|
5962
6215
|
await withStorage(async (storage) => {
|
|
5963
6216
|
await storage.startOrReclaim(replacementInput("dataset-metadata-first"));
|
|
5964
|
-
await
|
|
6217
|
+
await expect12(storage.startOrReclaim({
|
|
5965
6218
|
...replacementInput("dataset-metadata-conflict"),
|
|
5966
6219
|
identity: {
|
|
5967
6220
|
...replacementIdentity2,
|
|
@@ -5973,7 +6226,7 @@ function runProjectionRunStorageContractSuite(label, options) {
|
|
|
5973
6226
|
})).rejects.toThrow("immutable dataset version id with different metadata");
|
|
5974
6227
|
});
|
|
5975
6228
|
});
|
|
5976
|
-
|
|
6229
|
+
test12("advances telemetry in contiguous fixed physical batches", async () => {
|
|
5977
6230
|
await withStorage(async (storage) => {
|
|
5978
6231
|
const claim2 = await storage.startOrReclaim({
|
|
5979
6232
|
id: "telemetry-run",
|
|
@@ -5983,7 +6236,7 @@ function runProjectionRunStorageContractSuite(label, options) {
|
|
|
5983
6236
|
fixedBatchSize: 3
|
|
5984
6237
|
});
|
|
5985
6238
|
const execution2 = executionInput(claim2);
|
|
5986
|
-
|
|
6239
|
+
expect12(claim2.run).toMatchObject({
|
|
5987
6240
|
telemetryCheckpoint: {
|
|
5988
6241
|
fixedBatchSize: 3,
|
|
5989
6242
|
nextBatchOrdinal: 0,
|
|
@@ -5991,7 +6244,7 @@ function runProjectionRunStorageContractSuite(label, options) {
|
|
|
5991
6244
|
inputExhausted: false
|
|
5992
6245
|
}
|
|
5993
6246
|
});
|
|
5994
|
-
await
|
|
6247
|
+
await expect12(storage.update({ ...execution2, progress: { sourceRowsRead: 1 } })).rejects.toThrow("can only advance with its checkpoint");
|
|
5995
6248
|
await storage.advanceTelemetryCheckpoint({
|
|
5996
6249
|
...execution2,
|
|
5997
6250
|
batchOrdinal: 0,
|
|
@@ -5999,14 +6252,14 @@ function runProjectionRunStorageContractSuite(label, options) {
|
|
|
5999
6252
|
batchRowsSkipped: 1,
|
|
6000
6253
|
inputExhausted: false
|
|
6001
6254
|
});
|
|
6002
|
-
await
|
|
6255
|
+
await expect12(storage.advanceTelemetryCheckpoint({
|
|
6003
6256
|
...execution2,
|
|
6004
6257
|
batchOrdinal: 2,
|
|
6005
6258
|
batchRowCount: 1,
|
|
6006
6259
|
batchRowsSkipped: 0,
|
|
6007
6260
|
inputExhausted: true
|
|
6008
6261
|
})).rejects.toThrow("expected batch ordinal 1");
|
|
6009
|
-
await
|
|
6262
|
+
await expect12(storage.advanceTelemetryCheckpoint({
|
|
6010
6263
|
...execution2,
|
|
6011
6264
|
batchOrdinal: 1,
|
|
6012
6265
|
batchRowCount: 1,
|
|
@@ -6020,13 +6273,13 @@ function runProjectionRunStorageContractSuite(label, options) {
|
|
|
6020
6273
|
batchRowsSkipped: 1,
|
|
6021
6274
|
inputExhausted: true
|
|
6022
6275
|
});
|
|
6023
|
-
|
|
6276
|
+
expect12(exhausted).toMatchObject({
|
|
6024
6277
|
progress: { sourceRowsRead: 5, sourceRowsSkipped: 2 },
|
|
6025
6278
|
telemetryCheckpoint: { nextBatchOrdinal: 2, nextRowOffset: 5, inputExhausted: true }
|
|
6026
6279
|
});
|
|
6027
6280
|
});
|
|
6028
6281
|
});
|
|
6029
|
-
|
|
6282
|
+
test12("keeps a missing target until the batch it blocks commits", async () => {
|
|
6030
6283
|
await withStorage(async (storage) => {
|
|
6031
6284
|
const claim2 = await storage.startOrReclaim({
|
|
6032
6285
|
id: "telemetry-missing-target-run",
|
|
@@ -6044,13 +6297,13 @@ function runProjectionRunStorageContractSuite(label, options) {
|
|
|
6044
6297
|
firstSeenAt
|
|
6045
6298
|
};
|
|
6046
6299
|
const waiting = await storage.recordMissingTarget({ ...execution2, missingTarget });
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
await
|
|
6300
|
+
expect12(waiting.missingTarget).toEqual(missingTarget);
|
|
6301
|
+
expect12((await storage.getById({ projectId: projectId5, id: "telemetry-missing-target-run" }))?.missingTarget).toEqual(missingTarget);
|
|
6302
|
+
await expect12(storage.recordMissingTarget({
|
|
6050
6303
|
...execution2,
|
|
6051
6304
|
missingTarget: { ...missingTarget, batchOrdinal: 1 }
|
|
6052
6305
|
})).rejects.toThrow("cannot wait on batch 1");
|
|
6053
|
-
await
|
|
6306
|
+
await expect12(storage.recordMissingTarget({
|
|
6054
6307
|
...execution2,
|
|
6055
6308
|
missingTarget: { ...missingTarget, objectTypeId: "Building" }
|
|
6056
6309
|
})).rejects.toThrow("cannot wait on 'Building'");
|
|
@@ -6059,7 +6312,7 @@ function runProjectionRunStorageContractSuite(label, options) {
|
|
|
6059
6312
|
{ objectTypeId: "" },
|
|
6060
6313
|
{ firstSeenAt: new Date(Number.NaN) }
|
|
6061
6314
|
]) {
|
|
6062
|
-
await
|
|
6315
|
+
await expect12(storage.recordMissingTarget({
|
|
6063
6316
|
...execution2,
|
|
6064
6317
|
missingTarget: { ...missingTarget, ...partial }
|
|
6065
6318
|
})).rejects.toThrow();
|
|
@@ -6068,7 +6321,7 @@ function runProjectionRunStorageContractSuite(label, options) {
|
|
|
6068
6321
|
...execution2,
|
|
6069
6322
|
missingTarget: { ...missingTarget, firstSeenAt: new Date("2026-06-01T12:01:00.000Z") }
|
|
6070
6323
|
});
|
|
6071
|
-
|
|
6324
|
+
expect12(rerecorded.missingTarget?.firstSeenAt).toEqual(new Date("2026-06-01T12:01:00.000Z"));
|
|
6072
6325
|
const advanced = await storage.advanceTelemetryCheckpoint({
|
|
6073
6326
|
...execution2,
|
|
6074
6327
|
batchOrdinal: 0,
|
|
@@ -6076,10 +6329,10 @@ function runProjectionRunStorageContractSuite(label, options) {
|
|
|
6076
6329
|
batchRowsSkipped: 0,
|
|
6077
6330
|
inputExhausted: true
|
|
6078
6331
|
});
|
|
6079
|
-
|
|
6332
|
+
expect12(advanced.missingTarget).toBeUndefined();
|
|
6080
6333
|
});
|
|
6081
6334
|
});
|
|
6082
|
-
|
|
6335
|
+
test12("requires and records explicit telemetry EOF with terminal success", async () => {
|
|
6083
6336
|
await withStorage(async (storage) => {
|
|
6084
6337
|
const claim2 = await storage.startOrReclaim({
|
|
6085
6338
|
id: "empty-telemetry-run",
|
|
@@ -6088,12 +6341,12 @@ function runProjectionRunStorageContractSuite(label, options) {
|
|
|
6088
6341
|
target: objectTarget,
|
|
6089
6342
|
fixedBatchSize: 10
|
|
6090
6343
|
});
|
|
6091
|
-
await
|
|
6344
|
+
await expect12(storage.finish({
|
|
6092
6345
|
...executionInput(claim2),
|
|
6093
6346
|
protocol: "telemetry",
|
|
6094
6347
|
status: "succeeded"
|
|
6095
6348
|
})).rejects.toThrow("cannot succeed before input exhaustion");
|
|
6096
|
-
await
|
|
6349
|
+
await expect12(storage.getById({ projectId: projectId5, id: claim2.run.id })).resolves.toMatchObject({
|
|
6097
6350
|
status: "running",
|
|
6098
6351
|
telemetryCheckpoint: { inputExhausted: false }
|
|
6099
6352
|
});
|
|
@@ -6103,21 +6356,21 @@ function runProjectionRunStorageContractSuite(label, options) {
|
|
|
6103
6356
|
status: "succeeded",
|
|
6104
6357
|
inputExhausted: true
|
|
6105
6358
|
});
|
|
6106
|
-
|
|
6359
|
+
expect12(finished).toMatchObject({
|
|
6107
6360
|
status: "succeeded",
|
|
6108
6361
|
progress: { sourceRowsRead: 0 },
|
|
6109
6362
|
telemetryCheckpoint: { inputExhausted: true }
|
|
6110
6363
|
});
|
|
6111
|
-
await
|
|
6364
|
+
await expect12(storage.lockForMaterialization(executionInput(claim2))).rejects.toThrow("already terminal");
|
|
6112
6365
|
});
|
|
6113
6366
|
});
|
|
6114
|
-
|
|
6367
|
+
test12("keeps equal run ids isolated by project", async () => {
|
|
6115
6368
|
await withStorage(async (storage) => {
|
|
6116
6369
|
await storage.startOrReclaim({ ...replacementInput("shared-id"), projectId: "project-a" });
|
|
6117
6370
|
await storage.startOrReclaim({ ...replacementInput("shared-id"), projectId: "project-b" });
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6371
|
+
expect12(await storage.getById({ projectId: "project-a", id: "shared-id" })).not.toBeNull();
|
|
6372
|
+
expect12(await storage.getById({ projectId: "project-b", id: "shared-id" })).not.toBeNull();
|
|
6373
|
+
expect12(await storage.getById({ projectId: "project-c", id: "shared-id" })).toBeNull();
|
|
6121
6374
|
});
|
|
6122
6375
|
});
|
|
6123
6376
|
});
|
|
@@ -6134,7 +6387,7 @@ function executionInput(claim2) {
|
|
|
6134
6387
|
};
|
|
6135
6388
|
}
|
|
6136
6389
|
// src/testing/queues-contract.ts
|
|
6137
|
-
import { describe as
|
|
6390
|
+
import { describe as describe12, expect as expect13, test as test13 } from "bun:test";
|
|
6138
6391
|
function runQueueContractSuite(label, options) {
|
|
6139
6392
|
const shortLeaseMs = options.shortLeaseMs ?? 20;
|
|
6140
6393
|
const leaseExpiryRedeliveryMs = options.leaseExpiryRedeliveryMs ?? shortLeaseMs + 10;
|
|
@@ -6147,9 +6400,9 @@ function runQueueContractSuite(label, options) {
|
|
|
6147
6400
|
await options.teardown?.(queues);
|
|
6148
6401
|
}
|
|
6149
6402
|
};
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6403
|
+
describe12(label, () => {
|
|
6404
|
+
describe12("enqueue", () => {
|
|
6405
|
+
test13("returns envelopes with generated id, createdAt, availableAt, attempt=0", async () => {
|
|
6153
6406
|
await withQueues(async (queues) => {
|
|
6154
6407
|
const before = Date.now();
|
|
6155
6408
|
const [job] = await queues.syncRuns.enqueue({
|
|
@@ -6161,16 +6414,16 @@ function runQueueContractSuite(label, options) {
|
|
|
6161
6414
|
}
|
|
6162
6415
|
]
|
|
6163
6416
|
});
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6417
|
+
expect13(job?.id).toMatch(/^[0-9a-f-]{36}$/);
|
|
6418
|
+
expect13(job?.projectId).toBe("project-a");
|
|
6419
|
+
expect13(job?.attempt).toBe(0);
|
|
6420
|
+
expect13(job?.type).toBe("sync.run.requested");
|
|
6421
|
+
expect13(job?.payload.syncId).toBe("sync-1");
|
|
6422
|
+
expect13(Date.parse(job.createdAt)).toBeGreaterThanOrEqual(before);
|
|
6423
|
+
expect13(job?.availableAt).toBe(job?.createdAt);
|
|
6171
6424
|
});
|
|
6172
6425
|
});
|
|
6173
|
-
|
|
6426
|
+
test13("deduplicates repeated enqueue with a caller-supplied id", async () => {
|
|
6174
6427
|
await withQueues(async (queues) => {
|
|
6175
6428
|
const input = {
|
|
6176
6429
|
id: "dispatch-run-1",
|
|
@@ -6181,12 +6434,12 @@ function runQueueContractSuite(label, options) {
|
|
|
6181
6434
|
queues.syncRuns.enqueue({ projectId: "project-a", jobs: [input] }),
|
|
6182
6435
|
queues.syncRuns.enqueue({ projectId: "project-a", jobs: [input] })
|
|
6183
6436
|
]);
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
await
|
|
6437
|
+
expect13(first?.id).toBe(input.id);
|
|
6438
|
+
expect13(second?.id).toBe(input.id);
|
|
6439
|
+
await expect13(queues.syncRuns.claim({ projectId: "project-a", workerId: "worker-1", limit: 2 })).resolves.toHaveLength(1);
|
|
6187
6440
|
});
|
|
6188
6441
|
});
|
|
6189
|
-
|
|
6442
|
+
test13("treats caller-supplied ids as opaque across the job lifecycle", async () => {
|
|
6190
6443
|
await withQueues(async (queues) => {
|
|
6191
6444
|
const id = "workflow:review-meeting:node:0";
|
|
6192
6445
|
const input = {
|
|
@@ -6203,11 +6456,11 @@ function runQueueContractSuite(label, options) {
|
|
|
6203
6456
|
workerId: "worker-1",
|
|
6204
6457
|
limit: 2
|
|
6205
6458
|
});
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6459
|
+
expect13(first?.id).toBe(id);
|
|
6460
|
+
expect13(duplicate?.id).toBe(id);
|
|
6461
|
+
expect13(claimed?.job.id).toBe(id);
|
|
6209
6462
|
if (queues.syncRuns.renewLease) {
|
|
6210
|
-
await
|
|
6463
|
+
await expect13(queues.syncRuns.renewLease({
|
|
6211
6464
|
projectId: "project-a",
|
|
6212
6465
|
jobId: id,
|
|
6213
6466
|
leaseId: claimed.leaseId,
|
|
@@ -6219,26 +6472,26 @@ function runQueueContractSuite(label, options) {
|
|
|
6219
6472
|
jobId: id,
|
|
6220
6473
|
leaseId: claimed.leaseId
|
|
6221
6474
|
});
|
|
6222
|
-
await
|
|
6475
|
+
await expect13(queues.syncRuns.claim({ projectId: "project-a", workerId: "worker-2" })).resolves.toHaveLength(0);
|
|
6223
6476
|
});
|
|
6224
6477
|
});
|
|
6225
|
-
|
|
6478
|
+
test13("returns empty array when no jobs are passed", async () => {
|
|
6226
6479
|
await withQueues(async (queues) => {
|
|
6227
6480
|
const result = await queues.syncRuns.enqueue({ projectId: "project-a", jobs: [] });
|
|
6228
|
-
|
|
6481
|
+
expect13(result).toEqual([]);
|
|
6229
6482
|
});
|
|
6230
6483
|
});
|
|
6231
|
-
|
|
6484
|
+
test13("rejects empty projectId with QueueError", async () => {
|
|
6232
6485
|
await withQueues(async (queues) => {
|
|
6233
|
-
await
|
|
6486
|
+
await expect13(queues.syncRuns.enqueue({
|
|
6234
6487
|
projectId: " ",
|
|
6235
6488
|
jobs: [{ type: "sync.run.requested", payload: { syncId: "sync-1" } }]
|
|
6236
6489
|
})).rejects.toBeInstanceOf(QueueError);
|
|
6237
6490
|
});
|
|
6238
6491
|
});
|
|
6239
6492
|
});
|
|
6240
|
-
|
|
6241
|
-
|
|
6493
|
+
describe12("claim", () => {
|
|
6494
|
+
test13("increments attempt to 1 on first successful claim", async () => {
|
|
6242
6495
|
await withQueues(async (queues) => {
|
|
6243
6496
|
await queues.pipelines.enqueue({
|
|
6244
6497
|
projectId: "project-a",
|
|
@@ -6248,13 +6501,13 @@ function runQueueContractSuite(label, options) {
|
|
|
6248
6501
|
projectId: "project-a",
|
|
6249
6502
|
workerId: "worker-1"
|
|
6250
6503
|
});
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
|
|
6504
|
+
expect13(claimed?.job.attempt).toBe(1);
|
|
6505
|
+
expect13(claimed?.job.payload.pipelineId).toBe("p-1");
|
|
6506
|
+
expect13(claimed?.leaseId).toBeTruthy();
|
|
6507
|
+
expect13(Date.parse(claimed.leaseExpiresAt)).toBeGreaterThan(Date.parse(claimed.claimedAt));
|
|
6255
6508
|
});
|
|
6256
6509
|
});
|
|
6257
|
-
|
|
6510
|
+
test13("returns empty array when limit is 0 or negative", async () => {
|
|
6258
6511
|
await withQueues(async (queues) => {
|
|
6259
6512
|
await queues.syncRuns.enqueue({
|
|
6260
6513
|
projectId: "project-a",
|
|
@@ -6270,21 +6523,21 @@ function runQueueContractSuite(label, options) {
|
|
|
6270
6523
|
workerId: "worker-1",
|
|
6271
6524
|
limit: -5
|
|
6272
6525
|
});
|
|
6273
|
-
|
|
6274
|
-
|
|
6526
|
+
expect13(zero).toEqual([]);
|
|
6527
|
+
expect13(negative).toEqual([]);
|
|
6275
6528
|
});
|
|
6276
6529
|
});
|
|
6277
|
-
|
|
6530
|
+
test13("rejects non-positive leaseMs with QueueError", async () => {
|
|
6278
6531
|
await withQueues(async (queues) => {
|
|
6279
6532
|
await queues.syncRuns.enqueue({
|
|
6280
6533
|
projectId: "project-a",
|
|
6281
6534
|
jobs: [{ type: "sync.run.requested", payload: { syncId: "s-1" } }]
|
|
6282
6535
|
});
|
|
6283
|
-
await
|
|
6284
|
-
await
|
|
6536
|
+
await expect13(queues.syncRuns.claim({ projectId: "project-a", workerId: "w-1", leaseMs: 0 })).rejects.toBeInstanceOf(QueueError);
|
|
6537
|
+
await expect13(queues.syncRuns.claim({ projectId: "project-a", workerId: "w-1", leaseMs: -1 })).rejects.toBeInstanceOf(QueueError);
|
|
6285
6538
|
});
|
|
6286
6539
|
});
|
|
6287
|
-
|
|
6540
|
+
test13("respects delayed visibility via availableAt", async () => {
|
|
6288
6541
|
await withQueues(async (queues) => {
|
|
6289
6542
|
const availableAt = new Date(Date.now() + shortLeaseMs + 20).toISOString();
|
|
6290
6543
|
await queues.syncRuns.enqueue({
|
|
@@ -6300,12 +6553,12 @@ function runQueueContractSuite(label, options) {
|
|
|
6300
6553
|
projectId: "project-a",
|
|
6301
6554
|
workerId: "worker-1"
|
|
6302
6555
|
});
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
|
|
6556
|
+
expect13(before).toHaveLength(0);
|
|
6557
|
+
expect13(after).toHaveLength(1);
|
|
6558
|
+
expect13(after[0]?.job.availableAt).toBe(availableAt);
|
|
6306
6559
|
});
|
|
6307
6560
|
});
|
|
6308
|
-
|
|
6561
|
+
test13("isolates jobs across distinct projectIds", async () => {
|
|
6309
6562
|
await withQueues(async (queues) => {
|
|
6310
6563
|
await queues.syncRuns.enqueue({
|
|
6311
6564
|
projectId: "project-a",
|
|
@@ -6320,12 +6573,12 @@ function runQueueContractSuite(label, options) {
|
|
|
6320
6573
|
workerId: "worker-b",
|
|
6321
6574
|
limit: 10
|
|
6322
6575
|
});
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6576
|
+
expect13(claimedB).toHaveLength(1);
|
|
6577
|
+
expect13(claimedB[0]?.job.payload.syncId).toBe("b-1");
|
|
6578
|
+
expect13(claimedB[0]?.job.projectId).toBe("project-b");
|
|
6326
6579
|
});
|
|
6327
6580
|
});
|
|
6328
|
-
|
|
6581
|
+
test13("keeps syncRuns, pipelines, projections, workflows, and actions lanes independent", async () => {
|
|
6329
6582
|
await withQueues(async (queues) => {
|
|
6330
6583
|
await queues.syncRuns.enqueue({
|
|
6331
6584
|
projectId: "project-a",
|
|
@@ -6407,20 +6660,20 @@ function runQueueContractSuite(label, options) {
|
|
|
6407
6660
|
workerId: "sync-worker-1",
|
|
6408
6661
|
limit: 10
|
|
6409
6662
|
});
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6663
|
+
expect13(crossLane).toHaveLength(1);
|
|
6664
|
+
expect13(crossLane[0]?.job.payload.pipelineId).toBe("p-1");
|
|
6665
|
+
expect13(projectionLane).toHaveLength(1);
|
|
6666
|
+
expect13(projectionLane[0]?.job.payload.projectionId).toBe("room-projection");
|
|
6667
|
+
expect13(workflowLane).toHaveLength(1);
|
|
6668
|
+
expect13(workflowLane[0]?.job.payload.workflowId).toBe("reconcile-transaction");
|
|
6669
|
+
expect13(actionLane).toHaveLength(1);
|
|
6670
|
+
expect13(actionLane[0]?.job.payload.actionId).toBe("mark-paid");
|
|
6671
|
+
expect13(sameLane).toHaveLength(1);
|
|
6419
6672
|
});
|
|
6420
6673
|
});
|
|
6421
6674
|
});
|
|
6422
|
-
|
|
6423
|
-
|
|
6675
|
+
describe12("complete", () => {
|
|
6676
|
+
test13("removes the job from the lane permanently", async () => {
|
|
6424
6677
|
await withQueues(async (queues) => {
|
|
6425
6678
|
const [job] = await queues.syncRuns.enqueue({
|
|
6426
6679
|
projectId: "project-a",
|
|
@@ -6440,10 +6693,10 @@ function runQueueContractSuite(label, options) {
|
|
|
6440
6693
|
workerId: "worker-2",
|
|
6441
6694
|
limit: 10
|
|
6442
6695
|
});
|
|
6443
|
-
|
|
6696
|
+
expect13(later).toHaveLength(0);
|
|
6444
6697
|
});
|
|
6445
6698
|
});
|
|
6446
|
-
|
|
6699
|
+
test13("rejects a wrong leaseId with QueueError", async () => {
|
|
6447
6700
|
await withQueues(async (queues) => {
|
|
6448
6701
|
const [job] = await queues.syncRuns.enqueue({
|
|
6449
6702
|
projectId: "project-a",
|
|
@@ -6454,7 +6707,7 @@ function runQueueContractSuite(label, options) {
|
|
|
6454
6707
|
workerId: "worker-1",
|
|
6455
6708
|
leaseMs: shortLeaseMs * 10
|
|
6456
6709
|
});
|
|
6457
|
-
await
|
|
6710
|
+
await expect13(queues.syncRuns.complete({
|
|
6458
6711
|
projectId: "project-a",
|
|
6459
6712
|
jobId: job.id,
|
|
6460
6713
|
leaseId: "not-the-right-lease"
|
|
@@ -6462,8 +6715,8 @@ function runQueueContractSuite(label, options) {
|
|
|
6462
6715
|
});
|
|
6463
6716
|
});
|
|
6464
6717
|
});
|
|
6465
|
-
|
|
6466
|
-
|
|
6718
|
+
describe12("retry", () => {
|
|
6719
|
+
test13("reschedules the job and bumps attempt on next claim", async () => {
|
|
6467
6720
|
await withQueues(async (queues) => {
|
|
6468
6721
|
const [job] = await queues.syncRuns.enqueue({
|
|
6469
6722
|
projectId: "project-a",
|
|
@@ -6488,11 +6741,11 @@ function runQueueContractSuite(label, options) {
|
|
|
6488
6741
|
workerId: "worker-2",
|
|
6489
6742
|
leaseMs: shortLeaseMs
|
|
6490
6743
|
});
|
|
6491
|
-
|
|
6492
|
-
|
|
6744
|
+
expect13(secondClaim?.job.id).toBe(job?.id);
|
|
6745
|
+
expect13(secondClaim?.job.attempt).toBe(2);
|
|
6493
6746
|
});
|
|
6494
6747
|
});
|
|
6495
|
-
|
|
6748
|
+
test13("rejects invalid availableAt with QueueError", async () => {
|
|
6496
6749
|
await withQueues(async (queues) => {
|
|
6497
6750
|
const [job] = await queues.syncRuns.enqueue({
|
|
6498
6751
|
projectId: "project-a",
|
|
@@ -6503,7 +6756,7 @@ function runQueueContractSuite(label, options) {
|
|
|
6503
6756
|
workerId: "worker-1",
|
|
6504
6757
|
leaseMs: shortLeaseMs * 10
|
|
6505
6758
|
});
|
|
6506
|
-
await
|
|
6759
|
+
await expect13(queues.syncRuns.retry({
|
|
6507
6760
|
projectId: "project-a",
|
|
6508
6761
|
jobId: job.id,
|
|
6509
6762
|
leaseId: claim2.leaseId,
|
|
@@ -6512,8 +6765,8 @@ function runQueueContractSuite(label, options) {
|
|
|
6512
6765
|
});
|
|
6513
6766
|
});
|
|
6514
6767
|
});
|
|
6515
|
-
|
|
6516
|
-
|
|
6768
|
+
describe12("fail", () => {
|
|
6769
|
+
test13("marks the job as terminally failed", async () => {
|
|
6517
6770
|
await withQueues(async (queues) => {
|
|
6518
6771
|
const [job] = await queues.pipelines.enqueue({
|
|
6519
6772
|
projectId: "project-a",
|
|
@@ -6534,12 +6787,12 @@ function runQueueContractSuite(label, options) {
|
|
|
6534
6787
|
workerId: "pipeline-worker-2",
|
|
6535
6788
|
limit: 10
|
|
6536
6789
|
});
|
|
6537
|
-
|
|
6790
|
+
expect13(later).toHaveLength(0);
|
|
6538
6791
|
});
|
|
6539
6792
|
});
|
|
6540
6793
|
});
|
|
6541
|
-
|
|
6542
|
-
|
|
6794
|
+
describe12("renewLease", () => {
|
|
6795
|
+
test13("extends the lease and returns an updated claim", async () => {
|
|
6543
6796
|
await withQueues(async (queues) => {
|
|
6544
6797
|
const [job] = await queues.syncRuns.enqueue({
|
|
6545
6798
|
projectId: "project-a",
|
|
@@ -6556,12 +6809,12 @@ function runQueueContractSuite(label, options) {
|
|
|
6556
6809
|
leaseId: claim2.leaseId,
|
|
6557
6810
|
leaseMs: shortLeaseMs * 5
|
|
6558
6811
|
});
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6812
|
+
expect13(renewed?.job.id).toBe(job?.id);
|
|
6813
|
+
expect13(renewed?.leaseId).toBe(claim2?.leaseId);
|
|
6814
|
+
expect13(Date.parse(renewed.leaseExpiresAt)).toBeGreaterThan(Date.parse(claim2.leaseExpiresAt));
|
|
6562
6815
|
});
|
|
6563
6816
|
});
|
|
6564
|
-
|
|
6817
|
+
test13("returns null for unknown jobId", async () => {
|
|
6565
6818
|
await withQueues(async (queues) => {
|
|
6566
6819
|
const result = await queues.syncRuns.renewLease?.({
|
|
6567
6820
|
projectId: "project-a",
|
|
@@ -6569,10 +6822,10 @@ function runQueueContractSuite(label, options) {
|
|
|
6569
6822
|
leaseId: "no-such-lease",
|
|
6570
6823
|
leaseMs: shortLeaseMs
|
|
6571
6824
|
});
|
|
6572
|
-
|
|
6825
|
+
expect13(result).toBeNull();
|
|
6573
6826
|
});
|
|
6574
6827
|
});
|
|
6575
|
-
|
|
6828
|
+
test13("returns null when the leaseId does not match", async () => {
|
|
6576
6829
|
await withQueues(async (queues) => {
|
|
6577
6830
|
const [job] = await queues.syncRuns.enqueue({
|
|
6578
6831
|
projectId: "project-a",
|
|
@@ -6589,12 +6842,12 @@ function runQueueContractSuite(label, options) {
|
|
|
6589
6842
|
leaseId: "not-the-right-lease",
|
|
6590
6843
|
leaseMs: shortLeaseMs
|
|
6591
6844
|
});
|
|
6592
|
-
|
|
6845
|
+
expect13(renewed).toBeNull();
|
|
6593
6846
|
});
|
|
6594
6847
|
});
|
|
6595
6848
|
});
|
|
6596
|
-
|
|
6597
|
-
|
|
6849
|
+
describe12("redelivery", () => {
|
|
6850
|
+
test13("redelivers after lease expiry with attempt incremented", async () => {
|
|
6598
6851
|
await withQueues(async (queues) => {
|
|
6599
6852
|
const [job] = await queues.syncRuns.enqueue({
|
|
6600
6853
|
projectId: "project-a",
|
|
@@ -6611,12 +6864,12 @@ function runQueueContractSuite(label, options) {
|
|
|
6611
6864
|
workerId: "worker-2",
|
|
6612
6865
|
leaseMs: shortLeaseMs
|
|
6613
6866
|
});
|
|
6614
|
-
|
|
6615
|
-
|
|
6616
|
-
|
|
6867
|
+
expect13(first?.job.attempt).toBe(1);
|
|
6868
|
+
expect13(second?.job.id).toBe(job?.id);
|
|
6869
|
+
expect13(second.job.attempt).toBeGreaterThanOrEqual(2);
|
|
6617
6870
|
});
|
|
6618
6871
|
});
|
|
6619
|
-
|
|
6872
|
+
test13("rejects complete on an already-expired lease", async () => {
|
|
6620
6873
|
await withQueues(async (queues) => {
|
|
6621
6874
|
const [job] = await queues.syncRuns.enqueue({
|
|
6622
6875
|
projectId: "project-a",
|
|
@@ -6628,7 +6881,7 @@ function runQueueContractSuite(label, options) {
|
|
|
6628
6881
|
leaseMs: shortLeaseMs
|
|
6629
6882
|
});
|
|
6630
6883
|
await Bun.sleep(leaseExpiryRedeliveryMs);
|
|
6631
|
-
await
|
|
6884
|
+
await expect13(queues.syncRuns.complete({
|
|
6632
6885
|
projectId: "project-a",
|
|
6633
6886
|
jobId: job.id,
|
|
6634
6887
|
leaseId: claim2.leaseId
|
|
@@ -6636,30 +6889,30 @@ function runQueueContractSuite(label, options) {
|
|
|
6636
6889
|
});
|
|
6637
6890
|
});
|
|
6638
6891
|
});
|
|
6639
|
-
|
|
6640
|
-
|
|
6892
|
+
describe12("health", () => {
|
|
6893
|
+
test13("resolves against a reachable backend", async () => {
|
|
6641
6894
|
await withQueues(async (queues) => {
|
|
6642
|
-
await
|
|
6895
|
+
await expect13(queues.health()).resolves.toBeUndefined();
|
|
6643
6896
|
});
|
|
6644
6897
|
});
|
|
6645
6898
|
});
|
|
6646
|
-
|
|
6647
|
-
|
|
6899
|
+
describe12("scope", () => {
|
|
6900
|
+
test13("declares whether it can be shared across processes", async () => {
|
|
6648
6901
|
await withQueues(async (queues) => {
|
|
6649
|
-
|
|
6902
|
+
expect13(["process", "shared"]).toContain(queues.scope);
|
|
6650
6903
|
});
|
|
6651
6904
|
});
|
|
6652
6905
|
});
|
|
6653
6906
|
});
|
|
6654
6907
|
}
|
|
6655
6908
|
// src/testing/sandboxes-contract.ts
|
|
6656
|
-
import { afterEach, beforeEach, describe as
|
|
6909
|
+
import { afterEach, beforeEach, describe as describe13, expect as expect14, test as test14 } from "bun:test";
|
|
6657
6910
|
import { realpath } from "fs/promises";
|
|
6658
6911
|
var SLEEP_BUDGET_SECONDS = 5;
|
|
6659
6912
|
function runSandboxesContractSuite(label, options) {
|
|
6660
6913
|
const shortTimeoutMs = options.shortTimeoutMs ?? 200;
|
|
6661
6914
|
const capabilities = options.capabilities ?? {};
|
|
6662
|
-
|
|
6915
|
+
describe13(label, () => {
|
|
6663
6916
|
let factory;
|
|
6664
6917
|
beforeEach(async () => {
|
|
6665
6918
|
factory = await options.createFactory();
|
|
@@ -6667,108 +6920,108 @@ function runSandboxesContractSuite(label, options) {
|
|
|
6667
6920
|
afterEach(async () => {
|
|
6668
6921
|
await options.teardown?.(factory);
|
|
6669
6922
|
});
|
|
6670
|
-
|
|
6671
|
-
|
|
6923
|
+
describe13("lifecycle", () => {
|
|
6924
|
+
test14("create returns a running sandbox with a non-empty id", async () => {
|
|
6672
6925
|
const sandbox = await factory.create();
|
|
6673
6926
|
try {
|
|
6674
|
-
|
|
6675
|
-
|
|
6676
|
-
|
|
6927
|
+
expect14(sandbox.status).toBe("running");
|
|
6928
|
+
expect14(sandbox.id.length).toBeGreaterThan(0);
|
|
6929
|
+
expect14(sandbox.workingDirectory.length).toBeGreaterThan(0);
|
|
6677
6930
|
} finally {
|
|
6678
6931
|
await sandbox.destroy();
|
|
6679
6932
|
}
|
|
6680
6933
|
});
|
|
6681
|
-
|
|
6934
|
+
test14("runCommand returns exit code 0 and captured stdout for echo", async () => {
|
|
6682
6935
|
const sandbox = await factory.create();
|
|
6683
6936
|
try {
|
|
6684
6937
|
const result = await sandbox.runCommand("echo", ["hello"]);
|
|
6685
|
-
|
|
6686
|
-
|
|
6687
|
-
|
|
6688
|
-
|
|
6689
|
-
|
|
6938
|
+
expect14(result.exitCode).toBe(0);
|
|
6939
|
+
expect14(result.stdout.trim()).toBe("hello");
|
|
6940
|
+
expect14(result.stderr).toBe("");
|
|
6941
|
+
expect14(result.durationMs).toBeGreaterThanOrEqual(0);
|
|
6942
|
+
expect14(result.timedOut).not.toBe(true);
|
|
6690
6943
|
} finally {
|
|
6691
6944
|
await sandbox.destroy();
|
|
6692
6945
|
}
|
|
6693
6946
|
});
|
|
6694
|
-
|
|
6947
|
+
test14("nonexistent binary resolves with non-zero exit code", async () => {
|
|
6695
6948
|
const sandbox = await factory.create();
|
|
6696
6949
|
try {
|
|
6697
6950
|
const result = await sandbox.runCommand("does-not-exist-xyz-sixb-test", []);
|
|
6698
|
-
|
|
6951
|
+
expect14(result.exitCode).not.toBe(0);
|
|
6699
6952
|
} finally {
|
|
6700
6953
|
await sandbox.destroy();
|
|
6701
6954
|
}
|
|
6702
6955
|
});
|
|
6703
|
-
|
|
6956
|
+
test14("captures stdout and stderr as separate streams", async () => {
|
|
6704
6957
|
const sandbox = await factory.create();
|
|
6705
6958
|
try {
|
|
6706
6959
|
const result = await sandbox.runCommand("/bin/sh", [
|
|
6707
6960
|
"-c",
|
|
6708
6961
|
"echo out-line; echo err-line >&2; exit 0"
|
|
6709
6962
|
]);
|
|
6710
|
-
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
|
|
6963
|
+
expect14(result.exitCode).toBe(0);
|
|
6964
|
+
expect14(result.stdout).toContain("out-line");
|
|
6965
|
+
expect14(result.stderr).toContain("err-line");
|
|
6966
|
+
expect14(result.stdout).not.toContain("err-line");
|
|
6967
|
+
expect14(result.stderr).not.toContain("out-line");
|
|
6715
6968
|
} finally {
|
|
6716
6969
|
await sandbox.destroy();
|
|
6717
6970
|
}
|
|
6718
6971
|
});
|
|
6719
|
-
|
|
6972
|
+
test14("stop transitions status and rejects subsequent runCommand", async () => {
|
|
6720
6973
|
const sandbox = await factory.create();
|
|
6721
6974
|
try {
|
|
6722
6975
|
await sandbox.stop();
|
|
6723
|
-
|
|
6724
|
-
await
|
|
6976
|
+
expect14(sandbox.status).toBe("stopped");
|
|
6977
|
+
await expect14(sandbox.runCommand("echo", ["nope"])).rejects.toBeInstanceOf(SandboxNotRunningError);
|
|
6725
6978
|
} finally {
|
|
6726
6979
|
await sandbox.destroy();
|
|
6727
6980
|
}
|
|
6728
6981
|
});
|
|
6729
|
-
|
|
6982
|
+
test14("destroy is idempotent", async () => {
|
|
6730
6983
|
const sandbox = await factory.create();
|
|
6731
6984
|
await sandbox.destroy();
|
|
6732
6985
|
await sandbox.destroy();
|
|
6733
|
-
await
|
|
6986
|
+
await expect14(sandbox.runCommand("echo", ["x"])).rejects.toBeInstanceOf(SandboxNotRunningError);
|
|
6734
6987
|
});
|
|
6735
6988
|
});
|
|
6736
|
-
|
|
6737
|
-
|
|
6989
|
+
describe13("working directory", () => {
|
|
6990
|
+
test14("pwd reflects the configured workingDirectory", async () => {
|
|
6738
6991
|
const sandbox = await factory.create();
|
|
6739
6992
|
try {
|
|
6740
6993
|
const result = await sandbox.runCommand("pwd");
|
|
6741
6994
|
const expected = await realpath(sandbox.workingDirectory);
|
|
6742
|
-
|
|
6743
|
-
|
|
6995
|
+
expect14(result.exitCode).toBe(0);
|
|
6996
|
+
expect14(result.stdout.trim()).toBe(expected);
|
|
6744
6997
|
} finally {
|
|
6745
6998
|
await sandbox.destroy();
|
|
6746
6999
|
}
|
|
6747
7000
|
});
|
|
6748
|
-
|
|
7001
|
+
test14("per-call cwd overrides the sandbox-level workingDirectory", async () => {
|
|
6749
7002
|
const sandbox = await factory.create();
|
|
6750
7003
|
try {
|
|
6751
7004
|
const expected = await realpath("/tmp");
|
|
6752
7005
|
const result = await sandbox.runCommand("pwd", [], { cwd: "/tmp" });
|
|
6753
|
-
|
|
6754
|
-
|
|
7006
|
+
expect14(result.exitCode).toBe(0);
|
|
7007
|
+
expect14(result.stdout.trim()).toBe(expected);
|
|
6755
7008
|
} finally {
|
|
6756
7009
|
await sandbox.destroy();
|
|
6757
7010
|
}
|
|
6758
7011
|
});
|
|
6759
7012
|
});
|
|
6760
|
-
|
|
6761
|
-
|
|
7013
|
+
describe13("env merging", () => {
|
|
7014
|
+
test14("sandbox-level env is visible to commands", async () => {
|
|
6762
7015
|
const sandbox = await factory.create({ env: { SIXB_TEST_A: "alpha" } });
|
|
6763
7016
|
try {
|
|
6764
7017
|
const result = await sandbox.runCommand("printenv", ["SIXB_TEST_A"]);
|
|
6765
|
-
|
|
6766
|
-
|
|
7018
|
+
expect14(result.exitCode).toBe(0);
|
|
7019
|
+
expect14(result.stdout.trim()).toBe("alpha");
|
|
6767
7020
|
} finally {
|
|
6768
7021
|
await sandbox.destroy();
|
|
6769
7022
|
}
|
|
6770
7023
|
});
|
|
6771
|
-
|
|
7024
|
+
test14("per-call env wins on collision and adds new keys", async () => {
|
|
6772
7025
|
const sandbox = await factory.create({
|
|
6773
7026
|
env: { SIXB_TEST_A: "alpha", SIXB_TEST_B: "beta" }
|
|
6774
7027
|
});
|
|
@@ -6782,14 +7035,14 @@ function runSandboxesContractSuite(label, options) {
|
|
|
6782
7035
|
const c = await sandbox.runCommand("printenv", ["SIXB_TEST_C"], {
|
|
6783
7036
|
env: { SIXB_TEST_A: "alpha2", SIXB_TEST_C: "gamma" }
|
|
6784
7037
|
});
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
7038
|
+
expect14(a.stdout.trim()).toBe("alpha2");
|
|
7039
|
+
expect14(b.stdout.trim()).toBe("beta");
|
|
7040
|
+
expect14(c.stdout.trim()).toBe("gamma");
|
|
6788
7041
|
} finally {
|
|
6789
7042
|
await sandbox.destroy();
|
|
6790
7043
|
}
|
|
6791
7044
|
});
|
|
6792
|
-
|
|
7045
|
+
test14("host secrets are not leaked into the sandbox", async () => {
|
|
6793
7046
|
const secretKey = "SIXB_HOST_SECRET_DO_NOT_LEAK";
|
|
6794
7047
|
const previous = process.env[secretKey];
|
|
6795
7048
|
process.env[secretKey] = "host-only-value";
|
|
@@ -6797,8 +7050,8 @@ function runSandboxesContractSuite(label, options) {
|
|
|
6797
7050
|
const sandbox = await factory.create();
|
|
6798
7051
|
try {
|
|
6799
7052
|
const result = await sandbox.runCommand("printenv", [secretKey]);
|
|
6800
|
-
|
|
6801
|
-
|
|
7053
|
+
expect14(result.exitCode).not.toBe(0);
|
|
7054
|
+
expect14(result.stdout).not.toContain("host-only-value");
|
|
6802
7055
|
} finally {
|
|
6803
7056
|
await sandbox.destroy();
|
|
6804
7057
|
}
|
|
@@ -6811,8 +7064,8 @@ function runSandboxesContractSuite(label, options) {
|
|
|
6811
7064
|
}
|
|
6812
7065
|
});
|
|
6813
7066
|
});
|
|
6814
|
-
|
|
6815
|
-
|
|
7067
|
+
describe13("timeout and abort", () => {
|
|
7068
|
+
test14("sleep beyond timeout marks the result timedOut and exits non-zero", async () => {
|
|
6816
7069
|
const sandbox = await factory.create();
|
|
6817
7070
|
try {
|
|
6818
7071
|
const start = Date.now();
|
|
@@ -6820,14 +7073,14 @@ function runSandboxesContractSuite(label, options) {
|
|
|
6820
7073
|
timeout: shortTimeoutMs
|
|
6821
7074
|
});
|
|
6822
7075
|
const elapsed = Date.now() - start;
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
|
|
7076
|
+
expect14(result.timedOut).toBe(true);
|
|
7077
|
+
expect14(result.exitCode).not.toBe(0);
|
|
7078
|
+
expect14(elapsed).toBeLessThan(SLEEP_BUDGET_SECONDS * 1000);
|
|
6826
7079
|
} finally {
|
|
6827
7080
|
await sandbox.destroy();
|
|
6828
7081
|
}
|
|
6829
7082
|
});
|
|
6830
|
-
|
|
7083
|
+
test14("abort signal aborts an in-flight command", async () => {
|
|
6831
7084
|
const sandbox = await factory.create();
|
|
6832
7085
|
try {
|
|
6833
7086
|
const ac = new AbortController;
|
|
@@ -6837,26 +7090,26 @@ function runSandboxesContractSuite(label, options) {
|
|
|
6837
7090
|
signal: ac.signal
|
|
6838
7091
|
});
|
|
6839
7092
|
const elapsed = Date.now() - start;
|
|
6840
|
-
|
|
6841
|
-
|
|
7093
|
+
expect14(result.exitCode).not.toBe(0);
|
|
7094
|
+
expect14(elapsed).toBeLessThan(SLEEP_BUDGET_SECONDS * 1000);
|
|
6842
7095
|
} finally {
|
|
6843
7096
|
await sandbox.destroy();
|
|
6844
7097
|
}
|
|
6845
7098
|
});
|
|
6846
|
-
|
|
7099
|
+
test14("per-call timeout overrides sandbox-level timeout", async () => {
|
|
6847
7100
|
const sandbox = await factory.create({ timeout: SLEEP_BUDGET_SECONDS * 1000 });
|
|
6848
7101
|
try {
|
|
6849
7102
|
const result = await sandbox.runCommand("sleep", [String(SLEEP_BUDGET_SECONDS)], {
|
|
6850
7103
|
timeout: shortTimeoutMs
|
|
6851
7104
|
});
|
|
6852
|
-
|
|
7105
|
+
expect14(result.timedOut).toBe(true);
|
|
6853
7106
|
} finally {
|
|
6854
7107
|
await sandbox.destroy();
|
|
6855
7108
|
}
|
|
6856
7109
|
});
|
|
6857
7110
|
});
|
|
6858
|
-
|
|
6859
|
-
|
|
7111
|
+
describe13("file materialization", () => {
|
|
7112
|
+
test14("writeFiles materializes files a subsequent command can read", async () => {
|
|
6860
7113
|
const sandbox = await factory.create();
|
|
6861
7114
|
try {
|
|
6862
7115
|
const flat = `${sandbox.workingDirectory}/materialized.txt`;
|
|
@@ -6866,32 +7119,32 @@ function runSandboxesContractSuite(label, options) {
|
|
|
6866
7119
|
{ path: nested, contents: "nested-contents" }
|
|
6867
7120
|
]);
|
|
6868
7121
|
const flatResult = await sandbox.runCommand("cat", [flat]);
|
|
6869
|
-
|
|
6870
|
-
|
|
7122
|
+
expect14(flatResult.exitCode).toBe(0);
|
|
7123
|
+
expect14(flatResult.stdout).toContain("flat-contents");
|
|
6871
7124
|
const nestedResult = await sandbox.runCommand("cat", [nested]);
|
|
6872
|
-
|
|
6873
|
-
|
|
7125
|
+
expect14(nestedResult.exitCode).toBe(0);
|
|
7126
|
+
expect14(nestedResult.stdout).toContain("nested-contents");
|
|
6874
7127
|
} finally {
|
|
6875
7128
|
await sandbox.destroy();
|
|
6876
7129
|
}
|
|
6877
7130
|
});
|
|
6878
|
-
|
|
7131
|
+
test14("writeFiles round-trips binary contents faithfully", async () => {
|
|
6879
7132
|
const sandbox = await factory.create();
|
|
6880
7133
|
try {
|
|
6881
7134
|
const path = `${sandbox.workingDirectory}/binary.dat`;
|
|
6882
7135
|
const bytes = new Uint8Array([0, 1, 254, 255, 10, 127]);
|
|
6883
7136
|
await sandbox.writeFiles([{ path, contents: bytes }]);
|
|
6884
7137
|
const size = await sandbox.runCommand("wc", ["-c", path]);
|
|
6885
|
-
|
|
6886
|
-
|
|
7138
|
+
expect14(size.exitCode).toBe(0);
|
|
7139
|
+
expect14(size.stdout.trim().split(/\s+/)[0]).toBe(String(bytes.length));
|
|
6887
7140
|
const hex = await sandbox.runCommand("od", ["-An", "-v", "-tx1", path]);
|
|
6888
|
-
|
|
6889
|
-
|
|
7141
|
+
expect14(hex.exitCode).toBe(0);
|
|
7142
|
+
expect14(hex.stdout.replace(/\s+/g, " ").trim()).toBe("00 01 fe ff 0a 7f");
|
|
6890
7143
|
} finally {
|
|
6891
7144
|
await sandbox.destroy();
|
|
6892
7145
|
}
|
|
6893
7146
|
});
|
|
6894
|
-
|
|
7147
|
+
test14("writeFiles applies an explicit file mode", async () => {
|
|
6895
7148
|
const sandbox = await factory.create();
|
|
6896
7149
|
try {
|
|
6897
7150
|
const path = `${sandbox.workingDirectory}/run.sh`;
|
|
@@ -6899,25 +7152,25 @@ function runSandboxesContractSuite(label, options) {
|
|
|
6899
7152
|
echo ran
|
|
6900
7153
|
`, mode: 493 }]);
|
|
6901
7154
|
const result = await sandbox.runCommand("/bin/sh", ["-c", `test -x ${path} && echo yes`]);
|
|
6902
|
-
|
|
6903
|
-
|
|
7155
|
+
expect14(result.exitCode).toBe(0);
|
|
7156
|
+
expect14(result.stdout).toContain("yes");
|
|
6904
7157
|
} finally {
|
|
6905
7158
|
await sandbox.destroy();
|
|
6906
7159
|
}
|
|
6907
7160
|
});
|
|
6908
|
-
|
|
7161
|
+
test14("writeFiles after stop rejects", async () => {
|
|
6909
7162
|
const sandbox = await factory.create();
|
|
6910
7163
|
await sandbox.stop();
|
|
6911
7164
|
try {
|
|
6912
|
-
await
|
|
7165
|
+
await expect14(sandbox.writeFiles([{ path: `${sandbox.workingDirectory}/x.txt`, contents: "x" }])).rejects.toThrow(SandboxNotRunningError);
|
|
6913
7166
|
} finally {
|
|
6914
7167
|
await sandbox.destroy();
|
|
6915
7168
|
}
|
|
6916
7169
|
});
|
|
6917
7170
|
});
|
|
6918
7171
|
if (capabilities.networkBlocking) {
|
|
6919
|
-
|
|
6920
|
-
|
|
7172
|
+
describe13("network isolation", () => {
|
|
7173
|
+
test14('network.mode="none" blocks outbound DNS / TCP', async () => {
|
|
6921
7174
|
const sandbox = await factory.create({ network: { mode: "none" } });
|
|
6922
7175
|
try {
|
|
6923
7176
|
const result = await sandbox.runCommand("curl", [
|
|
@@ -6926,12 +7179,12 @@ echo ran
|
|
|
6926
7179
|
"2",
|
|
6927
7180
|
"https://example.com"
|
|
6928
7181
|
]);
|
|
6929
|
-
|
|
7182
|
+
expect14(result.exitCode).not.toBe(0);
|
|
6930
7183
|
} finally {
|
|
6931
7184
|
await sandbox.destroy();
|
|
6932
7185
|
}
|
|
6933
7186
|
});
|
|
6934
|
-
|
|
7187
|
+
test14('network.mode="all" permits outbound traffic', async () => {
|
|
6935
7188
|
const sandbox = await factory.create({ network: { mode: "all" } });
|
|
6936
7189
|
try {
|
|
6937
7190
|
const result = await sandbox.runCommand("curl", [
|
|
@@ -6940,7 +7193,7 @@ echo ran
|
|
|
6940
7193
|
"5",
|
|
6941
7194
|
"https://example.com"
|
|
6942
7195
|
]);
|
|
6943
|
-
|
|
7196
|
+
expect14(result.exitCode).not.toBe(6);
|
|
6944
7197
|
} finally {
|
|
6945
7198
|
await sandbox.destroy();
|
|
6946
7199
|
}
|
|
@@ -6948,28 +7201,28 @@ echo ran
|
|
|
6948
7201
|
});
|
|
6949
7202
|
}
|
|
6950
7203
|
if (capabilities.readOnlyEnforcement) {
|
|
6951
|
-
|
|
6952
|
-
|
|
7204
|
+
describe13("filesystem isolation", () => {
|
|
7205
|
+
test14("writing inside readWritePaths succeeds", async () => {
|
|
6953
7206
|
const sandbox = await factory.create();
|
|
6954
7207
|
try {
|
|
6955
7208
|
const result = await sandbox.runCommand("/bin/sh", [
|
|
6956
7209
|
"-c",
|
|
6957
7210
|
`echo content > ${sandbox.workingDirectory}/probe.txt && cat ${sandbox.workingDirectory}/probe.txt`
|
|
6958
7211
|
]);
|
|
6959
|
-
|
|
6960
|
-
|
|
7212
|
+
expect14(result.exitCode).toBe(0);
|
|
7213
|
+
expect14(result.stdout.trim()).toBe("content");
|
|
6961
7214
|
} finally {
|
|
6962
7215
|
await sandbox.destroy();
|
|
6963
7216
|
}
|
|
6964
7217
|
});
|
|
6965
|
-
|
|
7218
|
+
test14("writing outside readWritePaths fails", async () => {
|
|
6966
7219
|
const sandbox = await factory.create();
|
|
6967
7220
|
try {
|
|
6968
7221
|
const result = await sandbox.runCommand("/bin/sh", [
|
|
6969
7222
|
"-c",
|
|
6970
7223
|
"echo nope > /etc/sixb-sandbox-must-not-write"
|
|
6971
7224
|
]);
|
|
6972
|
-
|
|
7225
|
+
expect14(result.exitCode).not.toBe(0);
|
|
6973
7226
|
} finally {
|
|
6974
7227
|
await sandbox.destroy();
|
|
6975
7228
|
}
|
|
@@ -6988,6 +7241,7 @@ export {
|
|
|
6988
7241
|
runMaterializerStorageContractSuite,
|
|
6989
7242
|
runMaterializationFailureContractSuite,
|
|
6990
7243
|
runLakeStorageContractSuite,
|
|
7244
|
+
runLakeMergeStorageContractSuite,
|
|
6991
7245
|
runEffectiveStorageContractSuite,
|
|
6992
7246
|
runBrokerContractSuite,
|
|
6993
7247
|
runBlobStorageContractSuite,
|
|
@@ -6997,4 +7251,4 @@ export {
|
|
|
6997
7251
|
createMaterializerTestFixture
|
|
6998
7252
|
};
|
|
6999
7253
|
|
|
7000
|
-
//# debugId=
|
|
7254
|
+
//# debugId=9AC10C19BCFC4FB264756E2164756E21
|