@sixb/core 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/index.js +19 -17
- package/dist/actions/index.js.map +1 -1
- package/dist/actions/worker.js +4 -4
- package/dist/actions/worker.js.map +1 -1
- package/dist/agents/adapters.d.ts +11 -1
- package/dist/agents/adapters.d.ts.map +1 -1
- package/dist/agents/context-compaction.d.ts +40 -0
- package/dist/agents/context-compaction.d.ts.map +1 -0
- package/dist/agents/context.js +1 -1
- package/dist/agents/errors.d.ts +1 -1
- package/dist/agents/errors.d.ts.map +1 -1
- package/dist/agents/execution.d.ts +3 -0
- package/dist/agents/execution.d.ts.map +1 -1
- package/dist/agents/ids.d.ts +2 -0
- package/dist/agents/ids.d.ts.map +1 -1
- package/dist/agents/index.d.ts +10 -6
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +51 -35
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/request.d.ts.map +1 -1
- package/dist/agents/streams.d.ts +17 -1
- package/dist/agents/streams.d.ts.map +1 -1
- package/dist/agents/streams.js +5 -3
- package/dist/agents/streams.js.map +1 -1
- package/dist/agents/thread-context-projection.d.ts +19 -0
- package/dist/agents/thread-context-projection.d.ts.map +1 -0
- package/dist/agents/tool-definition.d.ts.map +1 -1
- package/dist/agents/tool-result.d.ts +6 -0
- package/dist/agents/tool-result.d.ts.map +1 -0
- package/dist/agents/types.d.ts +46 -1
- package/dist/agents/types.d.ts.map +1 -1
- package/dist/agents/validation.d.ts +2 -2
- package/dist/agents/validation.d.ts.map +1 -1
- package/dist/auth/index.js +11 -5
- package/dist/auth/index.js.map +1 -1
- package/dist/authorization/index.js +6 -2
- package/dist/authorization/index.js.map +1 -1
- package/dist/bootstrap/index.js +33 -31
- package/dist/bootstrap/index.js.map +1 -1
- package/dist/{chunk-ys1nse2v.js → chunk-1wk20wn3.js} +438 -246
- package/dist/{chunk-ys1nse2v.js.map → chunk-1wk20wn3.js.map} +11 -11
- package/dist/{chunk-sa4340y1.js → chunk-1yz3x65y.js} +7 -7
- package/dist/{chunk-gqdd7508.js → chunk-4rh2m6zg.js} +1 -1
- package/dist/{chunk-fmx1sz2h.js → chunk-72q2y8hn.js} +3 -3
- package/dist/{chunk-fmx1sz2h.js.map → chunk-72q2y8hn.js.map} +4 -4
- package/dist/{chunk-nnyp53zt.js → chunk-7gw7mepb.js} +259 -9
- package/dist/chunk-7gw7mepb.js.map +14 -0
- package/dist/{chunk-nten49ta.js → chunk-819wnjz8.js} +11 -5
- package/dist/chunk-819wnjz8.js.map +12 -0
- package/dist/chunk-96swp2wd.js +47 -0
- package/dist/chunk-96swp2wd.js.map +10 -0
- package/dist/{chunk-4x43bkbp.js → chunk-a09jx26t.js} +1 -1
- package/dist/{chunk-st4xgxey.js → chunk-afjpa2hs.js} +205 -48
- package/dist/chunk-afjpa2hs.js.map +45 -0
- package/dist/{chunk-0mbx247x.js → chunk-c43qr6s0.js} +427 -99
- package/dist/chunk-c43qr6s0.js.map +46 -0
- package/dist/{chunk-zg9yryhz.js → chunk-cbkm2vam.js} +3 -3
- package/dist/{chunk-57zpb99z.js → chunk-dm72y8wt.js} +26 -3
- package/dist/chunk-dm72y8wt.js.map +10 -0
- package/dist/chunk-drjqm1aa.js +913 -0
- package/dist/chunk-drjqm1aa.js.map +15 -0
- package/dist/chunk-dysw7nsm.js.map +1 -1
- package/dist/{chunk-8ryen3p3.js → chunk-f6erf2vv.js} +1 -1
- package/dist/{chunk-34bqh420.js → chunk-f83ka50g.js} +188 -44
- package/dist/chunk-f83ka50g.js.map +23 -0
- package/dist/{chunk-bct7hmfc.js → chunk-g13869yf.js} +3 -3
- package/dist/{chunk-0k4avqr2.js → chunk-hbeh8j9w.js} +2 -2
- package/dist/{chunk-qvybnnpe.js → chunk-nzw7sm3b.js} +2 -2
- package/dist/{chunk-k9pts48t.js → chunk-pn8n8sba.js} +16 -15
- package/dist/{chunk-k9pts48t.js.map → chunk-pn8n8sba.js.map} +3 -3
- package/dist/{chunk-63te770v.js → chunk-qewtks41.js} +8 -8
- package/dist/{chunk-63te770v.js.map → chunk-qewtks41.js.map} +2 -2
- package/dist/{chunk-85j5ybfj.js → chunk-rdeagz6b.js} +2 -2
- package/dist/{chunk-85j5ybfj.js.map → chunk-rdeagz6b.js.map} +3 -3
- package/dist/chunk-rhrbz7d4.js +36 -0
- package/dist/chunk-rhrbz7d4.js.map +10 -0
- package/dist/{chunk-bh6k3nc6.js → chunk-rs1wbvy6.js} +1 -1
- package/dist/{chunk-88ppkr54.js → chunk-rte5sm3z.js} +26 -2
- package/dist/chunk-rte5sm3z.js.map +12 -0
- package/dist/{chunk-n8herkry.js → chunk-s31d0qmq.js} +57 -20
- package/dist/{chunk-n8herkry.js.map → chunk-s31d0qmq.js.map} +8 -7
- package/dist/{chunk-7pc8b818.js → chunk-sxwk41qa.js} +1 -1
- package/dist/{chunk-663csy96.js → chunk-t0jjkep3.js} +2 -1
- package/dist/chunk-t0jjkep3.js.map +10 -0
- package/dist/{chunk-c6qd89g5.js → chunk-tek1qwgm.js} +6 -6
- package/dist/{chunk-c6qd89g5.js.map → chunk-tek1qwgm.js.map} +1 -1
- package/dist/{chunk-jaj13078.js → chunk-tkdscva6.js} +3 -33
- package/dist/{chunk-jaj13078.js.map → chunk-tkdscva6.js.map} +3 -4
- package/dist/{chunk-5egfka8j.js → chunk-tycbzedm.js} +2 -2
- package/dist/{chunk-g9t3kdtx.js → chunk-x2yw73hy.js} +1 -1
- package/dist/{chunk-57r1cpxx.js → chunk-xc7hq65b.js} +3 -2
- package/dist/{chunk-57r1cpxx.js.map → chunk-xc7hq65b.js.map} +3 -3
- package/dist/chunk-xdf9yswn.js +133 -0
- package/dist/chunk-xdf9yswn.js.map +11 -0
- package/dist/execution/agent.js +29 -27
- package/dist/execution/agent.js.map +1 -1
- package/dist/execution/primitive.js +29 -27
- package/dist/execution/primitive.js.map +2 -2
- package/dist/execution/request.js +30 -28
- package/dist/execution/request.js.map +2 -2
- package/dist/http/index.js +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +60 -58
- package/dist/index.js.map +1 -1
- package/dist/lake-storage/types.d.ts +2 -1
- package/dist/lake-storage/types.d.ts.map +1 -1
- package/dist/materialization/model.d.ts +4 -0
- package/dist/materialization/model.d.ts.map +1 -1
- package/dist/materializer/edits/apply.d.ts +6 -1
- package/dist/materializer/edits/apply.d.ts.map +1 -1
- package/dist/materializer/edits/working-state.d.ts +2 -0
- package/dist/materializer/edits/working-state.d.ts.map +1 -1
- package/dist/materializer/effective/resolve.d.ts +3 -1
- package/dist/materializer/effective/resolve.d.ts.map +1 -1
- package/dist/materializer/execution/work-records.d.ts.map +1 -1
- package/dist/materializer/index.js +19 -17
- package/dist/materializer/index.js.map +1 -1
- package/dist/materializer/projections/replacement-plan.d.ts.map +1 -1
- package/dist/materializer/shared/normalize.d.ts.map +1 -1
- package/dist/models/index.d.ts +40 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/objects/execution.d.ts +6 -1
- package/dist/objects/execution.d.ts.map +1 -1
- package/dist/objects/index.js +21 -19
- package/dist/objects/index.js.map +1 -1
- package/dist/objects/link/endpoints.d.ts.map +1 -1
- package/dist/objects/query/executor.d.ts +1 -0
- package/dist/objects/query/executor.d.ts.map +1 -1
- package/dist/objects/query/explain.d.ts +1 -0
- package/dist/objects/query/explain.d.ts.map +1 -1
- package/dist/objects/query/index.d.ts +3 -1
- package/dist/objects/query/index.d.ts.map +1 -1
- package/dist/objects/query/index.js +30 -5
- package/dist/objects/query/index.js.map +1 -1
- package/dist/objects/query/ir.d.ts +14 -1
- package/dist/objects/query/ir.d.ts.map +1 -1
- package/dist/objects/query/links.d.ts +25 -0
- package/dist/objects/query/links.d.ts.map +1 -0
- package/dist/objects/query/normalize.d.ts.map +1 -1
- package/dist/objects/query/planner.d.ts.map +1 -1
- package/dist/objects/query/validate.d.ts +1 -0
- package/dist/objects/query/validate.d.ts.map +1 -1
- package/dist/pipelines/index.js +16 -15
- package/dist/pipelines/index.js.map +1 -1
- package/dist/projections/builders.d.ts +52 -10
- package/dist/projections/builders.d.ts.map +1 -1
- package/dist/projections/index.d.ts +2 -1
- package/dist/projections/index.d.ts.map +1 -1
- package/dist/projections/internal.d.ts +4 -1
- package/dist/projections/internal.d.ts.map +1 -1
- package/dist/projections/internal.js +22 -17
- package/dist/projections/internal.js.map +1 -1
- package/dist/projections/run-dispatch.d.ts +1 -0
- package/dist/projections/run-dispatch.d.ts.map +1 -1
- package/dist/projections/timestamp.d.ts +3 -0
- package/dist/projections/timestamp.d.ts.map +1 -0
- package/dist/projections/types.d.ts +21 -9
- package/dist/projections/types.d.ts.map +1 -1
- package/dist/projections/validation.d.ts +6 -5
- package/dist/projections/validation.d.ts.map +1 -1
- package/dist/query.d.ts +2 -1
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +2 -2
- package/dist/queues/index.d.ts +1 -1
- package/dist/queues/index.d.ts.map +1 -1
- package/dist/queues/types.d.ts +7 -0
- package/dist/queues/types.d.ts.map +1 -1
- package/dist/runtime/ai-cost.d.ts +8 -0
- package/dist/runtime/ai-cost.d.ts.map +1 -0
- package/dist/runtime/create.d.ts +3 -0
- package/dist/runtime/create.d.ts.map +1 -1
- package/dist/runtime/definitions.d.ts +3 -0
- package/dist/runtime/definitions.d.ts.map +1 -1
- package/dist/runtime/host.d.ts +2 -0
- package/dist/runtime/host.d.ts.map +1 -1
- package/dist/runtime/resolve-definitions.d.ts +2 -0
- package/dist/runtime/resolve-definitions.d.ts.map +1 -1
- package/dist/storage/agents/in-memory.d.ts +22 -4
- package/dist/storage/agents/in-memory.d.ts.map +1 -1
- package/dist/storage/agents/index.d.ts +1 -1
- package/dist/storage/agents/index.d.ts.map +1 -1
- package/dist/storage/agents/provider.d.ts +31 -0
- package/dist/storage/agents/provider.d.ts.map +1 -1
- package/dist/storage/agents/provider.js +22 -7
- package/dist/storage/agents/provider.js.map +1 -1
- package/dist/storage/agents/types.d.ts +75 -4
- package/dist/storage/agents/types.d.ts.map +1 -1
- package/dist/storage/ai-cost/analytics.d.ts +61 -0
- package/dist/storage/ai-cost/analytics.d.ts.map +1 -0
- package/dist/storage/ai-cost/codec.d.ts +14 -0
- package/dist/storage/ai-cost/codec.d.ts.map +1 -0
- package/dist/storage/ai-cost/errors.d.ts +7 -0
- package/dist/storage/ai-cost/errors.d.ts.map +1 -0
- package/dist/storage/ai-cost/in-memory.d.ts +32 -0
- package/dist/storage/ai-cost/in-memory.d.ts.map +1 -0
- package/dist/storage/ai-cost/index.d.ts +6 -0
- package/dist/storage/ai-cost/index.d.ts.map +1 -0
- package/dist/storage/ai-cost/provider.d.ts +7 -0
- package/dist/storage/ai-cost/provider.d.ts.map +1 -0
- package/dist/storage/ai-cost/provider.js +132 -0
- package/dist/storage/ai-cost/provider.js.map +10 -0
- package/dist/storage/ai-cost/types.d.ts +154 -0
- package/dist/storage/ai-cost/types.d.ts.map +1 -0
- package/dist/storage/ai-cost/validation.d.ts +7 -0
- package/dist/storage/ai-cost/validation.d.ts.map +1 -0
- package/dist/storage/ai-usage/in-memory.d.ts +2 -1
- package/dist/storage/ai-usage/in-memory.d.ts.map +1 -1
- package/dist/storage/ai-usage/index.d.ts +1 -1
- package/dist/storage/ai-usage/index.d.ts.map +1 -1
- package/dist/storage/ai-usage/types.d.ts +6 -0
- package/dist/storage/ai-usage/types.d.ts.map +1 -1
- package/dist/storage/in-memory/index.d.ts +5 -0
- package/dist/storage/in-memory/index.d.ts.map +1 -1
- package/dist/storage/index.d.ts +6 -3
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +33 -21
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/objects/in-memory.d.ts +6 -3
- package/dist/storage/objects/in-memory.d.ts.map +1 -1
- package/dist/storage/objects/index.d.ts +3 -1
- package/dist/storage/objects/index.d.ts.map +1 -1
- package/dist/storage/objects/keys.d.ts +16 -0
- package/dist/storage/objects/keys.d.ts.map +1 -0
- package/dist/storage/objects/types.d.ts +33 -6
- package/dist/storage/objects/types.d.ts.map +1 -1
- package/dist/storage/ontology/in-memory/materializations.d.ts.map +1 -1
- package/dist/storage/ontology/materializations.d.ts +3 -0
- package/dist/storage/ontology/materializations.d.ts.map +1 -1
- package/dist/storage/ontology/provider.js +1 -1
- package/dist/storage/operation-scope.d.ts.map +1 -1
- package/dist/storage/operation-scope.js +1 -1
- package/dist/storage/types.d.ts +2 -0
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/storage/workflow-runs/provider.js +10 -5
- package/dist/storage/workflow-runs/provider.js.map +1 -1
- package/dist/syncs/index.js +16 -15
- package/dist/syncs/index.js.map +1 -1
- package/dist/testing/agent-storage-contract.d.ts +3 -2
- package/dist/testing/agent-storage-contract.d.ts.map +1 -1
- package/dist/testing/ai-cost-storage-contract.d.ts +12 -0
- package/dist/testing/ai-cost-storage-contract.d.ts.map +1 -0
- package/dist/testing/ai-usage-storage-contract.d.ts.map +1 -1
- package/dist/testing/effective-storage-contract.d.ts +1 -1
- package/dist/testing/effective-storage-contract.d.ts.map +1 -1
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +2525 -1480
- package/dist/testing/index.js.map +10 -9
- package/dist/testing/lake-storage-contract.d.ts.map +1 -1
- package/dist/testing/materializer-storage-contract.d.ts.map +1 -1
- package/dist/testing/object-query-contract.d.ts +1 -1
- package/dist/testing/object-query-contract.d.ts.map +1 -1
- package/dist/webhooks/internal.js +14 -13
- package/dist/webhooks/internal.js.map +2 -2
- package/dist/workers/index.js +12 -2
- package/dist/workers/index.js.map +3 -3
- package/dist/workers/queue-worker.d.ts +2 -0
- package/dist/workers/queue-worker.d.ts.map +1 -1
- package/dist/workflows/execution.d.ts +3 -0
- package/dist/workflows/execution.d.ts.map +1 -1
- package/dist/workflows/index.js +30 -28
- package/dist/workflows/index.js.map +1 -1
- package/package.json +7 -1
- package/src/agents/adapters.ts +64 -23
- package/src/agents/builders.ts +3 -3
- package/src/agents/context-compaction.ts +261 -0
- package/src/agents/errors.ts +2 -2
- package/src/agents/execution.ts +14 -2
- package/src/agents/ids.ts +5 -0
- package/src/agents/index.ts +32 -6
- package/src/agents/request.ts +5 -0
- package/src/agents/streams.ts +56 -0
- package/src/agents/thread-context-projection.ts +93 -0
- package/src/agents/tool-definition.ts +17 -2
- package/src/agents/tool-result.ts +61 -0
- package/src/agents/types.ts +57 -1
- package/src/agents/validation.ts +36 -6
- package/src/http/api-routes.ts +7 -7
- package/src/index.ts +27 -0
- package/src/lake-storage/types.ts +2 -1
- package/src/materialization/model.ts +4 -0
- package/src/materializer/edits/apply.ts +55 -2
- package/src/materializer/edits/operations.ts +8 -1
- package/src/materializer/edits/working-state.ts +7 -0
- package/src/materializer/effective/resolve.ts +78 -9
- package/src/materializer/execution/work-records.ts +3 -1
- package/src/materializer/projections/entry-validator.ts +23 -1
- package/src/materializer/projections/replacement-plan.ts +2 -0
- package/src/materializer/shared/normalize.ts +16 -0
- package/src/materializer/telemetry/append.ts +33 -16
- package/src/models/index.ts +71 -0
- package/src/objects/execution.ts +20 -0
- package/src/objects/link/endpoints.ts +2 -1
- package/src/objects/query/executor.ts +42 -11
- package/src/objects/query/explain.ts +11 -0
- package/src/objects/query/index.ts +6 -0
- package/src/objects/query/ir.ts +16 -0
- package/src/objects/query/links.ts +308 -0
- package/src/objects/query/normalize.ts +40 -1
- package/src/objects/query/planner.ts +5 -0
- package/src/objects/query/validate.ts +58 -2
- package/src/projections/builders.ts +320 -50
- package/src/projections/index.ts +7 -0
- package/src/projections/internal.ts +4 -1
- package/src/projections/ownership.ts +5 -5
- package/src/projections/registry.ts +16 -3
- package/src/projections/revision.ts +35 -5
- package/src/projections/run-dispatch.ts +10 -1
- package/src/projections/timestamp.ts +66 -0
- package/src/projections/types.ts +20 -9
- package/src/projections/validation.ts +85 -19
- package/src/query.ts +2 -0
- package/src/queues/index.ts +1 -0
- package/src/queues/types.ts +8 -0
- package/src/runtime/ai-cost.ts +15 -0
- package/src/runtime/create.ts +4 -0
- package/src/runtime/definitions.ts +3 -0
- package/src/runtime/host.ts +2 -0
- package/src/runtime/resolve-definitions.ts +28 -0
- package/src/storage/agents/in-memory.ts +186 -4
- package/src/storage/agents/index.ts +5 -0
- package/src/storage/agents/provider.ts +225 -0
- package/src/storage/agents/types.ts +82 -3
- package/src/storage/ai-cost/analytics.ts +602 -0
- package/src/storage/ai-cost/codec.ts +136 -0
- package/src/storage/ai-cost/errors.ts +12 -0
- package/src/storage/ai-cost/in-memory.ts +164 -0
- package/src/storage/ai-cost/index.ts +35 -0
- package/src/storage/ai-cost/provider.ts +20 -0
- package/src/storage/ai-cost/types.ts +193 -0
- package/src/storage/ai-cost/validation.ts +337 -0
- package/src/storage/ai-usage/in-memory.ts +23 -0
- package/src/storage/ai-usage/index.ts +1 -0
- package/src/storage/ai-usage/types.ts +10 -0
- package/src/storage/in-memory/index.ts +50 -0
- package/src/storage/index.ts +48 -0
- package/src/storage/objects/in-memory.ts +103 -16
- package/src/storage/objects/index.ts +9 -0
- package/src/storage/objects/keys.ts +36 -0
- package/src/storage/objects/types.ts +34 -6
- package/src/storage/ontology/in-memory/materializations.ts +1 -0
- package/src/storage/ontology/materializations.ts +3 -0
- package/src/storage/ontology/provider-header-validation.ts +2 -2
- package/src/storage/operation-scope.ts +1 -0
- package/src/storage/types.ts +2 -0
- package/src/testing/agent-storage-contract.ts +353 -2
- package/src/testing/ai-cost-storage-contract.ts +320 -0
- package/src/testing/ai-usage-storage-contract.ts +35 -0
- package/src/testing/effective-storage-contract.ts +194 -4
- package/src/testing/index.ts +5 -0
- package/src/testing/lake-storage-contract.ts +56 -0
- package/src/testing/materializer-storage-contract.ts +120 -2
- package/src/testing/object-query-contract.ts +208 -1
- package/src/workers/queue-worker.ts +14 -1
- package/src/workflows/execution.ts +31 -10
- package/dist/agents/prompt.d.ts +0 -15
- package/dist/agents/prompt.d.ts.map +0 -1
- package/dist/chunk-0mbx247x.js.map +0 -43
- package/dist/chunk-34bqh420.js.map +0 -23
- package/dist/chunk-57zpb99z.js.map +0 -10
- package/dist/chunk-663csy96.js.map +0 -10
- package/dist/chunk-6wetht33.js +0 -89
- package/dist/chunk-6wetht33.js.map +0 -10
- package/dist/chunk-88ppkr54.js.map +0 -12
- package/dist/chunk-nnyp53zt.js.map +0 -13
- package/dist/chunk-nten49ta.js.map +0 -12
- package/dist/chunk-st4xgxey.js.map +0 -45
- package/dist/chunk-vp1vc6ee.js +0 -35
- package/dist/chunk-vp1vc6ee.js.map +0 -11
- package/src/agents/prompt.ts +0 -57
- /package/dist/{chunk-sa4340y1.js.map → chunk-1yz3x65y.js.map} +0 -0
- /package/dist/{chunk-gqdd7508.js.map → chunk-4rh2m6zg.js.map} +0 -0
- /package/dist/{chunk-4x43bkbp.js.map → chunk-a09jx26t.js.map} +0 -0
- /package/dist/{chunk-zg9yryhz.js.map → chunk-cbkm2vam.js.map} +0 -0
- /package/dist/{chunk-8ryen3p3.js.map → chunk-f6erf2vv.js.map} +0 -0
- /package/dist/{chunk-bct7hmfc.js.map → chunk-g13869yf.js.map} +0 -0
- /package/dist/{chunk-0k4avqr2.js.map → chunk-hbeh8j9w.js.map} +0 -0
- /package/dist/{chunk-qvybnnpe.js.map → chunk-nzw7sm3b.js.map} +0 -0
- /package/dist/{chunk-bh6k3nc6.js.map → chunk-rs1wbvy6.js.map} +0 -0
- /package/dist/{chunk-7pc8b818.js.map → chunk-sxwk41qa.js.map} +0 -0
- /package/dist/{chunk-5egfka8j.js.map → chunk-tycbzedm.js.map} +0 -0
- /package/dist/{chunk-g9t3kdtx.js.map → chunk-x2yw73hy.js.map} +0 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import type { AiCostStorage, AiModelCallCostRecord } from "../storage/ai-cost"
|
|
3
|
+
import { AiCostStorageError } from "../storage/ai-cost"
|
|
4
|
+
import type { AiUsageStorage } from "../storage/ai-usage"
|
|
5
|
+
import type { ExecutionStorage } from "../storage/executions"
|
|
6
|
+
|
|
7
|
+
export interface AiStorageContractSuiteOptions<TStorage> {
|
|
8
|
+
readonly createStorage: () => TStorage | Promise<TStorage>
|
|
9
|
+
readonly setup?: (storage: TStorage) => void | Promise<void>
|
|
10
|
+
readonly cleanup?: (storage: TStorage) => void | Promise<void>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const projectId = "cost-contract-project"
|
|
14
|
+
const executionId = "exec_cost_1"
|
|
15
|
+
const otherExecutionId = "exec_cost_2"
|
|
16
|
+
|
|
17
|
+
function priceSource(entry: string) {
|
|
18
|
+
return {
|
|
19
|
+
sourceId: "test-catalog",
|
|
20
|
+
sourceEntryId: entry,
|
|
21
|
+
sourceVersion: "test-catalog-v1",
|
|
22
|
+
sourceUrl: "https://example.test/ai-pricing.json",
|
|
23
|
+
observedAt: new Date("2026-08-01T00:00:00.000Z"),
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function ratedCost(
|
|
28
|
+
usageRecordId = "usage_1",
|
|
29
|
+
overrides: Partial<Extract<AiModelCallCostRecord, { status: "rated" }>> = {}
|
|
30
|
+
): Extract<AiModelCallCostRecord, { status: "rated" }> {
|
|
31
|
+
return {
|
|
32
|
+
projectId,
|
|
33
|
+
usageRecordId,
|
|
34
|
+
status: "rated",
|
|
35
|
+
billingIdentity: { providerId: "vercel", modelId: "openai/gpt-5" },
|
|
36
|
+
pricingContext: {},
|
|
37
|
+
priceSource: priceSource("vercel/openai/gpt-5"),
|
|
38
|
+
money: { currency: "USD", amountNanos: "95000" },
|
|
39
|
+
components: [
|
|
40
|
+
{
|
|
41
|
+
meter: "tokens.input.uncached",
|
|
42
|
+
quantity: "12",
|
|
43
|
+
rateAmountNanosPerMillion: "1250000000",
|
|
44
|
+
chargeAmountNanos: "15000",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
meter: "tokens.input.cacheRead",
|
|
48
|
+
quantity: "0",
|
|
49
|
+
rateAmountNanosPerMillion: "125000000",
|
|
50
|
+
chargeAmountNanos: "0",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
meter: "tokens.output.total",
|
|
54
|
+
quantity: "8",
|
|
55
|
+
rateAmountNanosPerMillion: "10000000000",
|
|
56
|
+
chargeAmountNanos: "80000",
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
ratedAt: new Date("2026-08-01T12:00:00.200Z"),
|
|
60
|
+
...overrides,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function unpriceableCost(): Extract<AiModelCallCostRecord, { status: "unpriceable" }> {
|
|
65
|
+
return {
|
|
66
|
+
projectId,
|
|
67
|
+
usageRecordId: "usage_2",
|
|
68
|
+
status: "unpriceable",
|
|
69
|
+
billingIdentity: { providerId: "vercel", modelId: "unpriced/model" },
|
|
70
|
+
pricingContext: {},
|
|
71
|
+
priceSource: priceSource("vercel/unpriced/model"),
|
|
72
|
+
reason: "missingCatalogEntry",
|
|
73
|
+
ratedAt: new Date("2026-08-01T12:00:00.200Z"),
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function runAiCostStorageContractSuite<TStorage extends AiCostStorage>(
|
|
78
|
+
name: string,
|
|
79
|
+
options: AiStorageContractSuiteOptions<TStorage>
|
|
80
|
+
): void {
|
|
81
|
+
describe(name, () => {
|
|
82
|
+
async function fixture(): Promise<TStorage> {
|
|
83
|
+
const storage = await options.createStorage()
|
|
84
|
+
await options.setup?.(storage)
|
|
85
|
+
return storage
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
test("records model-call costs idempotently", async () => {
|
|
89
|
+
const storage = await fixture()
|
|
90
|
+
try {
|
|
91
|
+
await expect(storage.recordModelCallCost(ratedCost())).resolves.toBeUndefined()
|
|
92
|
+
await expect(storage.recordModelCallCost(ratedCost())).resolves.toBeUndefined()
|
|
93
|
+
await expect(
|
|
94
|
+
storage.listModelCalls({
|
|
95
|
+
projectId,
|
|
96
|
+
from: new Date("2026-08-01T00:00:00.000Z"),
|
|
97
|
+
to: new Date("2026-08-02T00:00:00.000Z"),
|
|
98
|
+
valuationStatus: "rated",
|
|
99
|
+
})
|
|
100
|
+
).resolves.toMatchObject({
|
|
101
|
+
total: 1,
|
|
102
|
+
items: [{ usage: { id: "usage_1" }, cost: ratedCost(), valuationStatus: "rated" }],
|
|
103
|
+
})
|
|
104
|
+
} finally {
|
|
105
|
+
await options.cleanup?.(storage)
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test("stores explicit unpriceable results and pricing context", async () => {
|
|
110
|
+
const storage = await fixture()
|
|
111
|
+
try {
|
|
112
|
+
const deploymentCost: AiModelCallCostRecord = {
|
|
113
|
+
projectId,
|
|
114
|
+
usageRecordId: "usage_deployment",
|
|
115
|
+
status: "unpriceable",
|
|
116
|
+
billingIdentity: { providerId: "openai", modelId: "gpt-5" },
|
|
117
|
+
pricingContext: { deploymentId: "production" },
|
|
118
|
+
priceSource: priceSource("openai/gpt-5"),
|
|
119
|
+
reason: "unsupportedPricingDimension",
|
|
120
|
+
ratedAt: new Date("2026-08-03T12:00:00.200Z"),
|
|
121
|
+
}
|
|
122
|
+
await storage.recordModelCallCost(unpriceableCost())
|
|
123
|
+
await storage.recordModelCallCost(deploymentCost)
|
|
124
|
+
await expect(
|
|
125
|
+
storage.listModelCalls({
|
|
126
|
+
projectId,
|
|
127
|
+
from: new Date("2026-08-03T00:00:00.000Z"),
|
|
128
|
+
to: new Date("2026-08-04T00:00:00.000Z"),
|
|
129
|
+
valuationStatus: "unpriceable",
|
|
130
|
+
})
|
|
131
|
+
).resolves.toMatchObject({
|
|
132
|
+
total: 1,
|
|
133
|
+
items: [{ cost: deploymentCost, valuationStatus: "unpriceable" }],
|
|
134
|
+
})
|
|
135
|
+
} finally {
|
|
136
|
+
await options.cleanup?.(storage)
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test("summarizes and queries accounting without inventing missing costs", async () => {
|
|
141
|
+
const storage = await fixture()
|
|
142
|
+
try {
|
|
143
|
+
await storage.recordModelCallCost(ratedCost())
|
|
144
|
+
await storage.recordModelCallCost(unpriceableCost())
|
|
145
|
+
await expect(
|
|
146
|
+
storage.summarizeExecutions({ projectId, executionIds: [executionId] })
|
|
147
|
+
).resolves.toEqual([
|
|
148
|
+
{
|
|
149
|
+
amounts: [{ currency: "USD", amountNanos: "95000" }],
|
|
150
|
+
ratedCallCount: 1,
|
|
151
|
+
unpriceableCallCount: 1,
|
|
152
|
+
unvaluedCallCount: 1,
|
|
153
|
+
},
|
|
154
|
+
])
|
|
155
|
+
|
|
156
|
+
const overview = await storage.queryProjectOverview({
|
|
157
|
+
projectId,
|
|
158
|
+
from: new Date("2026-08-01T00:00:00.000Z"),
|
|
159
|
+
to: new Date("2026-08-02T00:00:00.000Z"),
|
|
160
|
+
bucket: "day",
|
|
161
|
+
})
|
|
162
|
+
expect(overview.totals).toEqual({
|
|
163
|
+
modelCallCount: 3,
|
|
164
|
+
usage: {
|
|
165
|
+
inputTokens: 15,
|
|
166
|
+
outputTokens: 12,
|
|
167
|
+
totalTokens: 27,
|
|
168
|
+
reportingStatus: "complete",
|
|
169
|
+
},
|
|
170
|
+
costs: {
|
|
171
|
+
amounts: [{ currency: "USD", amountNanos: "95000" }],
|
|
172
|
+
ratedCallCount: 1,
|
|
173
|
+
unpriceableCallCount: 1,
|
|
174
|
+
unvaluedCallCount: 1,
|
|
175
|
+
},
|
|
176
|
+
})
|
|
177
|
+
expect(overview.series).toMatchObject([{ modelCallCount: 3 }])
|
|
178
|
+
expect(overview.models).toMatchObject([
|
|
179
|
+
{ providerId: "gateway", modelId: "openai/gpt-5", modelCallCount: 2 },
|
|
180
|
+
{ providerId: "gateway", modelId: "unpriced/model", modelCallCount: 1 },
|
|
181
|
+
])
|
|
182
|
+
await expect(
|
|
183
|
+
storage.queryProjectOverview({
|
|
184
|
+
projectId,
|
|
185
|
+
from: new Date("2026-08-01T00:00:00.000Z"),
|
|
186
|
+
to: new Date("2026-08-02T00:00:00.000Z"),
|
|
187
|
+
bucket: "day",
|
|
188
|
+
providerId: "gateway",
|
|
189
|
+
modelId: "unpriced/model",
|
|
190
|
+
})
|
|
191
|
+
).resolves.toMatchObject({ totals: { modelCallCount: 1 } })
|
|
192
|
+
await expect(
|
|
193
|
+
storage.listModelCalls({
|
|
194
|
+
projectId,
|
|
195
|
+
from: new Date("2026-08-01T00:00:00.000Z"),
|
|
196
|
+
to: new Date("2026-08-02T00:00:00.000Z"),
|
|
197
|
+
limit: 1,
|
|
198
|
+
offset: 1,
|
|
199
|
+
})
|
|
200
|
+
).resolves.toMatchObject({
|
|
201
|
+
total: 3,
|
|
202
|
+
hasMore: true,
|
|
203
|
+
items: [{ usage: { id: "usage_2" } }],
|
|
204
|
+
})
|
|
205
|
+
await expect(
|
|
206
|
+
storage.listModelCalls({
|
|
207
|
+
projectId,
|
|
208
|
+
from: new Date("2026-08-01T00:00:00.000Z"),
|
|
209
|
+
to: new Date("2026-08-02T00:00:00.000Z"),
|
|
210
|
+
valuationStatus: "unpriceable",
|
|
211
|
+
})
|
|
212
|
+
).resolves.toMatchObject({
|
|
213
|
+
total: 1,
|
|
214
|
+
hasMore: false,
|
|
215
|
+
items: [{ usage: { id: "usage_2" }, valuationStatus: "unpriceable" }],
|
|
216
|
+
})
|
|
217
|
+
} finally {
|
|
218
|
+
await options.cleanup?.(storage)
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
test("rejects missing usage and mismatched billed quantities", async () => {
|
|
223
|
+
const storage = await fixture()
|
|
224
|
+
try {
|
|
225
|
+
const missing = ratedCost("missing")
|
|
226
|
+
const missingError = await storage.recordModelCallCost(missing).catch((value) => value)
|
|
227
|
+
expect(missingError).toBeInstanceOf(AiCostStorageError)
|
|
228
|
+
expect((missingError as AiCostStorageError).code).toBe("missing_usage")
|
|
229
|
+
|
|
230
|
+
const base = ratedCost()
|
|
231
|
+
const mismatch = ratedCost("usage_1", {
|
|
232
|
+
money: { currency: "USD", amountNanos: "96250" },
|
|
233
|
+
components: [
|
|
234
|
+
{ ...base.components[0]!, quantity: "13", chargeAmountNanos: "16250" },
|
|
235
|
+
...base.components.slice(1),
|
|
236
|
+
],
|
|
237
|
+
})
|
|
238
|
+
const mismatchError = await storage.recordModelCallCost(mismatch).catch((value) => value)
|
|
239
|
+
expect(mismatchError).toBeInstanceOf(AiCostStorageError)
|
|
240
|
+
expect((mismatchError as AiCostStorageError).code).toBe("cost_mismatch")
|
|
241
|
+
} finally {
|
|
242
|
+
await options.cleanup?.(storage)
|
|
243
|
+
}
|
|
244
|
+
})
|
|
245
|
+
})
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Seed immutable usage used by every cost-storage provider contract. */
|
|
249
|
+
export async function seedAiCostStorageContractUsage(
|
|
250
|
+
executions: ExecutionStorage,
|
|
251
|
+
usage: AiUsageStorage
|
|
252
|
+
): Promise<void> {
|
|
253
|
+
for (const fixture of [
|
|
254
|
+
{ projectId, executionId },
|
|
255
|
+
{ projectId, executionId: otherExecutionId },
|
|
256
|
+
]) {
|
|
257
|
+
if (await executions.getById({ projectId: fixture.projectId, id: fixture.executionId }))
|
|
258
|
+
continue
|
|
259
|
+
const primitive = {
|
|
260
|
+
kind: "workflow" as const,
|
|
261
|
+
id: "ai-cost-contract",
|
|
262
|
+
runId: fixture.executionId,
|
|
263
|
+
}
|
|
264
|
+
await executions.create({
|
|
265
|
+
id: fixture.executionId,
|
|
266
|
+
projectId: fixture.projectId,
|
|
267
|
+
executor: { type: "primitive", kind: primitive.kind, runId: primitive.runId },
|
|
268
|
+
source: { type: "event", eventId: `test_event:${fixture.executionId}` },
|
|
269
|
+
correlationId: `test_correlation:${fixture.executionId}`,
|
|
270
|
+
authorizationRef: { type: "trustedPrimitive", primitive },
|
|
271
|
+
})
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
for (const fixture of [
|
|
275
|
+
{ id: "usage_1", model: "openai/gpt-5", input: 12, output: 8 },
|
|
276
|
+
{ id: "usage_2", model: "unpriced/model", input: 1, output: 1 },
|
|
277
|
+
{ id: "usage_3", model: "openai/gpt-5", input: 2, output: 3 },
|
|
278
|
+
]) {
|
|
279
|
+
await usage.recordModelCall({
|
|
280
|
+
id: fixture.id,
|
|
281
|
+
projectId,
|
|
282
|
+
executionId,
|
|
283
|
+
attempt: 1,
|
|
284
|
+
callId: `call_${fixture.id}`,
|
|
285
|
+
requesterGroupIds: [],
|
|
286
|
+
providerId: "gateway",
|
|
287
|
+
requestedModelId: fixture.model,
|
|
288
|
+
responseId: `response_${projectId}_${fixture.id}`,
|
|
289
|
+
usage: {
|
|
290
|
+
inputTokens: fixture.input,
|
|
291
|
+
outputTokens: fixture.output,
|
|
292
|
+
...(fixture.id === "usage_1"
|
|
293
|
+
? { uncachedInputTokens: fixture.input, cacheReadInputTokens: 0 }
|
|
294
|
+
: {}),
|
|
295
|
+
},
|
|
296
|
+
occurredAt: new Date("2026-08-01T12:00:00.000Z"),
|
|
297
|
+
recordedAt: new Date("2026-08-01T12:00:00.100Z"),
|
|
298
|
+
})
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
await usage.recordModelCall({
|
|
302
|
+
id: "usage_deployment",
|
|
303
|
+
projectId,
|
|
304
|
+
executionId: otherExecutionId,
|
|
305
|
+
attempt: 1,
|
|
306
|
+
callId: "call_usage_deployment",
|
|
307
|
+
requesterGroupIds: [],
|
|
308
|
+
providerId: "openai.responses",
|
|
309
|
+
requestedModelId: "gpt-5",
|
|
310
|
+
responseId: "response_usage_deployment",
|
|
311
|
+
usage: {
|
|
312
|
+
inputTokens: 2,
|
|
313
|
+
outputTokens: 3,
|
|
314
|
+
uncachedInputTokens: 2,
|
|
315
|
+
cacheReadInputTokens: 0,
|
|
316
|
+
},
|
|
317
|
+
occurredAt: new Date("2026-08-03T12:00:00.000Z"),
|
|
318
|
+
recordedAt: new Date("2026-08-03T12:00:00.100Z"),
|
|
319
|
+
})
|
|
320
|
+
}
|
|
@@ -145,6 +145,41 @@ export function runAiUsageStorageContractSuite<TStorage extends AiUsageStorage>(
|
|
|
145
145
|
})
|
|
146
146
|
})
|
|
147
147
|
|
|
148
|
+
test("returns the latest completed call for one execution", async () => {
|
|
149
|
+
await withStorage(async (storage) => {
|
|
150
|
+
await expect(
|
|
151
|
+
storage.getLatestForExecution({ projectId, executionId: agentExecutionId })
|
|
152
|
+
).resolves.toBeNull()
|
|
153
|
+
|
|
154
|
+
await storage.recordModelCall(
|
|
155
|
+
modelCallInput({
|
|
156
|
+
id: "usage_later",
|
|
157
|
+
callId: "call_later",
|
|
158
|
+
responseId: "response_later",
|
|
159
|
+
occurredAt: at("2026-06-23T10:02:00.000Z"),
|
|
160
|
+
})
|
|
161
|
+
)
|
|
162
|
+
await storage.recordModelCall(
|
|
163
|
+
modelCallInput({
|
|
164
|
+
id: "usage_earlier",
|
|
165
|
+
callId: "call_earlier",
|
|
166
|
+
responseId: "response_earlier",
|
|
167
|
+
occurredAt: at("2026-06-23T10:01:00.000Z"),
|
|
168
|
+
})
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
await expect(
|
|
172
|
+
storage.getLatestForExecution({ projectId, executionId: agentExecutionId })
|
|
173
|
+
).resolves.toMatchObject({ id: "usage_later", responseId: "response_later" })
|
|
174
|
+
await expect(
|
|
175
|
+
storage.getLatestForExecution({
|
|
176
|
+
projectId: "other-project",
|
|
177
|
+
executionId: agentExecutionId,
|
|
178
|
+
})
|
|
179
|
+
).resolves.toBeNull()
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
|
|
148
183
|
test("bills the same call and response IDs again on a later delivery attempt", async () => {
|
|
149
184
|
// Regression guard: remove `attempt` from a provider's idempotency key/query/index and the
|
|
150
185
|
// second append collapses into the first, making the aggregate assertions fail.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test"
|
|
2
2
|
import { defineObjectType, link, OntologyRegistry, prop } from "../ontology"
|
|
3
|
-
import type
|
|
3
|
+
import { linkBatchKey, objectBatchKey, objectLinkCursor, type Storage } from "../storage"
|
|
4
4
|
import { createMaterializerTestFixture } from "./materializer-fixture"
|
|
5
5
|
|
|
6
6
|
export interface EffectiveStorageContractSuiteOptions<TStorage extends Storage> {
|
|
@@ -19,7 +19,25 @@ const Device = defineObjectType({
|
|
|
19
19
|
],
|
|
20
20
|
links: [link.self("peers", { cardinality: "many" })],
|
|
21
21
|
})
|
|
22
|
-
const
|
|
22
|
+
const OpaqueA = defineObjectType({
|
|
23
|
+
id: "A",
|
|
24
|
+
name: "Opaque A",
|
|
25
|
+
properties: [
|
|
26
|
+
prop("id", "string", { primary: true, required: true }),
|
|
27
|
+
prop("name", "string", { required: true }),
|
|
28
|
+
],
|
|
29
|
+
links: [link.self("D", { cardinality: "many" })],
|
|
30
|
+
})
|
|
31
|
+
const OpaqueAB = defineObjectType({
|
|
32
|
+
id: "A:B",
|
|
33
|
+
name: "Opaque AB",
|
|
34
|
+
properties: [
|
|
35
|
+
prop("id", "string", { primary: true, required: true }),
|
|
36
|
+
prop("name", "string", { required: true }),
|
|
37
|
+
],
|
|
38
|
+
links: [link.self("D", { cardinality: "many" })],
|
|
39
|
+
})
|
|
40
|
+
const ontology = new OntologyRegistry({ sources: [Device, OpaqueA, OpaqueAB] })
|
|
23
41
|
|
|
24
42
|
export function runEffectiveStorageContractSuite<TStorage extends Storage>(
|
|
25
43
|
name: string,
|
|
@@ -56,7 +74,7 @@ export function runEffectiveStorageContractSuite<TStorage extends Storage>(
|
|
|
56
74
|
{ objectTypeId: Device.id, primaryId: "missing" },
|
|
57
75
|
],
|
|
58
76
|
})
|
|
59
|
-
expect([...batch.keys()]).toEqual([
|
|
77
|
+
expect([...batch.keys()]).toEqual([objectBatchKey(Device.id, "a")])
|
|
60
78
|
})
|
|
61
79
|
})
|
|
62
80
|
|
|
@@ -81,7 +99,179 @@ export function runEffectiveStorageContractSuite<TStorage extends Storage>(
|
|
|
81
99
|
projectId,
|
|
82
100
|
items: [{ objectTypeId: Device.id, objectId: "a", linkId: "peers" }],
|
|
83
101
|
})
|
|
84
|
-
expect(linksByScope.get(
|
|
102
|
+
expect(linksByScope.get(linkBatchKey(Device.id, "a", "peers"))).toHaveLength(2)
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test("pages and filters incident links in the provider", async () => {
|
|
107
|
+
await withStorage(async (storage) => {
|
|
108
|
+
const first = await storage.objects.queryLinks({
|
|
109
|
+
projectId,
|
|
110
|
+
objectRefs: [{ objectTypeId: Device.id, primaryId: "a" }],
|
|
111
|
+
direction: "outgoing",
|
|
112
|
+
linkId: "peers",
|
|
113
|
+
endpointObjectTypeIds: [Device.id],
|
|
114
|
+
limit: 1,
|
|
115
|
+
})
|
|
116
|
+
expect(first.links.map((row) => row.targetId)).toEqual(["b"])
|
|
117
|
+
expect(first.hasMore).toBe(true)
|
|
118
|
+
|
|
119
|
+
const second = await storage.objects.queryLinks({
|
|
120
|
+
projectId,
|
|
121
|
+
objectRefs: [{ objectTypeId: Device.id, primaryId: "a" }],
|
|
122
|
+
direction: "outgoing",
|
|
123
|
+
linkId: "peers",
|
|
124
|
+
endpointObjectTypeIds: [Device.id],
|
|
125
|
+
after: objectLinkCursor(first.links[0]),
|
|
126
|
+
limit: 1,
|
|
127
|
+
})
|
|
128
|
+
expect(second.links.map((row) => row.targetId)).toEqual(["c"])
|
|
129
|
+
expect(second.hasMore).toBe(false)
|
|
130
|
+
|
|
131
|
+
const duplicateRefs = await storage.objects.queryLinks({
|
|
132
|
+
projectId,
|
|
133
|
+
objectRefs: [
|
|
134
|
+
{ objectTypeId: Device.id, primaryId: "a" },
|
|
135
|
+
{ objectTypeId: Device.id, primaryId: "a" },
|
|
136
|
+
],
|
|
137
|
+
direction: "outgoing",
|
|
138
|
+
limit: 10,
|
|
139
|
+
})
|
|
140
|
+
expect(duplicateRefs.links.map((row) => row.targetId)).toEqual(["b", "c"])
|
|
141
|
+
|
|
142
|
+
const incoming = await storage.objects.queryLinks({
|
|
143
|
+
projectId,
|
|
144
|
+
objectRefs: [{ objectTypeId: Device.id, primaryId: "b" }],
|
|
145
|
+
direction: "incoming",
|
|
146
|
+
limit: 10,
|
|
147
|
+
})
|
|
148
|
+
expect(incoming.links.map((row) => [row.sourceId, row.targetId])).toEqual([["a", "b"]])
|
|
149
|
+
|
|
150
|
+
const bothDirections = await storage.objects.queryLinks({
|
|
151
|
+
projectId,
|
|
152
|
+
objectRefs: [
|
|
153
|
+
{ objectTypeId: Device.id, primaryId: "a" },
|
|
154
|
+
{ objectTypeId: Device.id, primaryId: "b" },
|
|
155
|
+
],
|
|
156
|
+
direction: "both",
|
|
157
|
+
endpointObjectTypeIds: [Device.id],
|
|
158
|
+
limit: 10,
|
|
159
|
+
})
|
|
160
|
+
expect(bothDirections.links.map((row) => [row.sourceId, row.targetId])).toEqual([
|
|
161
|
+
["a", "b"],
|
|
162
|
+
["a", "c"],
|
|
163
|
+
])
|
|
164
|
+
|
|
165
|
+
const unauthorized = await storage.objects.queryLinks({
|
|
166
|
+
projectId,
|
|
167
|
+
objectRefs: [{ objectTypeId: Device.id, primaryId: "a" }],
|
|
168
|
+
direction: "both",
|
|
169
|
+
endpointObjectTypeIds: [],
|
|
170
|
+
limit: 10,
|
|
171
|
+
})
|
|
172
|
+
expect(unauthorized).toEqual({ links: [], hasMore: false })
|
|
173
|
+
})
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
test("keeps delimiter-bearing object and batch-link identities distinct", async () => {
|
|
177
|
+
await withStorage(async (storage) => {
|
|
178
|
+
const opaqueProjectId = `${projectId}-opaque-batch-keys`
|
|
179
|
+
const fixture = createMaterializerTestFixture({
|
|
180
|
+
projectId: opaqueProjectId,
|
|
181
|
+
ontology,
|
|
182
|
+
storage,
|
|
183
|
+
})
|
|
184
|
+
await fixture.seed({
|
|
185
|
+
objects: [
|
|
186
|
+
{
|
|
187
|
+
ref: { objectTypeId: OpaqueA.id, primaryId: "B:C" },
|
|
188
|
+
properties: { id: "B:C", name: "First" },
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
ref: { objectTypeId: OpaqueAB.id, primaryId: "C" },
|
|
192
|
+
properties: { id: "C", name: "Second" },
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
links: [
|
|
196
|
+
{
|
|
197
|
+
ref: {
|
|
198
|
+
source: { objectTypeId: OpaqueA.id, primaryId: "B:C" },
|
|
199
|
+
linkId: "D",
|
|
200
|
+
target: { objectTypeId: OpaqueA.id, primaryId: "B:C" },
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
ref: {
|
|
205
|
+
source: { objectTypeId: OpaqueAB.id, primaryId: "C" },
|
|
206
|
+
linkId: "D",
|
|
207
|
+
target: { objectTypeId: OpaqueAB.id, primaryId: "C" },
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
const objects = await storage.objects.getByPrimaryIdBatch({
|
|
214
|
+
projectId: opaqueProjectId,
|
|
215
|
+
items: [
|
|
216
|
+
{ objectTypeId: OpaqueA.id, primaryId: "B:C" },
|
|
217
|
+
{ objectTypeId: OpaqueAB.id, primaryId: "C" },
|
|
218
|
+
],
|
|
219
|
+
})
|
|
220
|
+
expect(objects.get(objectBatchKey(OpaqueA.id, "B:C"))?.properties.name).toBe("First")
|
|
221
|
+
expect(objects.get(objectBatchKey(OpaqueAB.id, "C"))?.properties.name).toBe("Second")
|
|
222
|
+
|
|
223
|
+
const links = await storage.objects.listLinksBatch({
|
|
224
|
+
projectId: opaqueProjectId,
|
|
225
|
+
items: [
|
|
226
|
+
{ objectTypeId: OpaqueA.id, objectId: "B:C", linkId: "D" },
|
|
227
|
+
{ objectTypeId: OpaqueAB.id, objectId: "C", linkId: "D" },
|
|
228
|
+
],
|
|
229
|
+
})
|
|
230
|
+
expect(links.get(linkBatchKey(OpaqueA.id, "B:C", "D"))).toHaveLength(1)
|
|
231
|
+
expect(links.get(linkBatchKey(OpaqueAB.id, "C", "D"))).toHaveLength(1)
|
|
232
|
+
})
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
test("preserves distinct incident links when opaque ids contain delimiters", async () => {
|
|
236
|
+
await withStorage(async (storage) => {
|
|
237
|
+
const opaqueIdsProjectId = `${projectId}-opaque-link-ids`
|
|
238
|
+
const firstSourceId = "opaque:source"
|
|
239
|
+
const firstTargetId = `target:peers:${Device.id}:opaque:end`
|
|
240
|
+
const secondSourceId = `${firstSourceId}:peers:${Device.id}:target`
|
|
241
|
+
const secondTargetId = "opaque:end"
|
|
242
|
+
const fixture = createMaterializerTestFixture({
|
|
243
|
+
projectId: opaqueIdsProjectId,
|
|
244
|
+
ontology,
|
|
245
|
+
storage,
|
|
246
|
+
})
|
|
247
|
+
await fixture.seed({
|
|
248
|
+
objects: [
|
|
249
|
+
device(firstSourceId, "First source"),
|
|
250
|
+
device(firstTargetId, "First target"),
|
|
251
|
+
device(secondSourceId, "Second source"),
|
|
252
|
+
device(secondTargetId, "Second target"),
|
|
253
|
+
],
|
|
254
|
+
links: [peer(firstSourceId, firstTargetId), peer(secondSourceId, secondTargetId)],
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
const incident = await storage.objects.listIncidentLinksBatch({
|
|
258
|
+
projectId: opaqueIdsProjectId,
|
|
259
|
+
items: [
|
|
260
|
+
{ objectTypeId: Device.id, objectId: firstSourceId },
|
|
261
|
+
{ objectTypeId: Device.id, objectId: secondSourceId },
|
|
262
|
+
],
|
|
263
|
+
})
|
|
264
|
+
const matching = incident.filter(
|
|
265
|
+
(row) => row.sourceId === firstSourceId || row.sourceId === secondSourceId
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
expect(matching.map((row) => [row.sourceId, row.linkId, row.targetId])).toEqual(
|
|
269
|
+
expect.arrayContaining([
|
|
270
|
+
[firstSourceId, "peers", firstTargetId],
|
|
271
|
+
[secondSourceId, "peers", secondTargetId],
|
|
272
|
+
])
|
|
273
|
+
)
|
|
274
|
+
expect(matching).toHaveLength(2)
|
|
85
275
|
})
|
|
86
276
|
})
|
|
87
277
|
|
package/src/testing/index.ts
CHANGED
|
@@ -7,6 +7,11 @@ export {
|
|
|
7
7
|
type AgentStorageContractSuiteOptions,
|
|
8
8
|
runAgentStorageContractSuite,
|
|
9
9
|
} from "./agent-storage-contract"
|
|
10
|
+
export {
|
|
11
|
+
type AiStorageContractSuiteOptions,
|
|
12
|
+
runAiCostStorageContractSuite,
|
|
13
|
+
seedAiCostStorageContractUsage,
|
|
14
|
+
} from "./ai-cost-storage-contract"
|
|
10
15
|
export {
|
|
11
16
|
type AiUsageStorageContractSuiteOptions,
|
|
12
17
|
runAiUsageStorageContractSuite,
|
|
@@ -438,6 +438,62 @@ export function runLakeStorageContractSuite<TStorage extends LakeStorage>(
|
|
|
438
438
|
})
|
|
439
439
|
})
|
|
440
440
|
|
|
441
|
+
test("creates an initial empty snapshot version", async () => {
|
|
442
|
+
await withStorage(async (storage) => {
|
|
443
|
+
await storage.createDataset(writeDataset)
|
|
444
|
+
|
|
445
|
+
const write = await storage.beginWrite({ dataset: writeDataset, mode: "snapshot" })
|
|
446
|
+
const version = await write.commit()
|
|
447
|
+
|
|
448
|
+
expect(version).toMatchObject({
|
|
449
|
+
datasetId: writeDataset.id,
|
|
450
|
+
mode: "snapshot",
|
|
451
|
+
outcome: "created",
|
|
452
|
+
rowCount: 0,
|
|
453
|
+
})
|
|
454
|
+
expect(await storage.getLatestVersion(writeDataset.id)).toMatchObject({
|
|
455
|
+
versionId: version.versionId,
|
|
456
|
+
})
|
|
457
|
+
expect(await storage.listVersions(writeDataset.id)).toHaveLength(1)
|
|
458
|
+
|
|
459
|
+
const repeatedWrite = await storage.beginWrite({
|
|
460
|
+
dataset: writeDataset,
|
|
461
|
+
mode: "snapshot",
|
|
462
|
+
})
|
|
463
|
+
const repeatedVersion = await repeatedWrite.commit()
|
|
464
|
+
expect(repeatedVersion.outcome).toBe("unchanged")
|
|
465
|
+
expect(repeatedVersion.versionId).toBe(version.versionId)
|
|
466
|
+
expect(await storage.listVersions(writeDataset.id)).toHaveLength(1)
|
|
467
|
+
|
|
468
|
+
await expect(
|
|
469
|
+
collectRows(storage.readRows({ datasetId: writeDataset.id }))
|
|
470
|
+
).resolves.toEqual([])
|
|
471
|
+
})
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
test("creates an initial empty append version", async () => {
|
|
475
|
+
await withStorage(async (storage) => {
|
|
476
|
+
await storage.createDataset(writeDataset)
|
|
477
|
+
|
|
478
|
+
const write = await storage.beginWrite({ dataset: writeDataset, mode: "append" })
|
|
479
|
+
const version = await write.commit()
|
|
480
|
+
|
|
481
|
+
expect(version).toMatchObject({
|
|
482
|
+
datasetId: writeDataset.id,
|
|
483
|
+
mode: "append",
|
|
484
|
+
outcome: "created",
|
|
485
|
+
})
|
|
486
|
+
expect(version.parentVersionId).toBeUndefined()
|
|
487
|
+
expect(await storage.getLatestVersion(writeDataset.id)).toMatchObject({
|
|
488
|
+
versionId: version.versionId,
|
|
489
|
+
})
|
|
490
|
+
expect(await storage.listVersions(writeDataset.id)).toHaveLength(1)
|
|
491
|
+
await expect(
|
|
492
|
+
collectRows(storage.readRows({ datasetId: writeDataset.id }))
|
|
493
|
+
).resolves.toEqual([])
|
|
494
|
+
})
|
|
495
|
+
})
|
|
496
|
+
|
|
441
497
|
test("snapshot writes replace the visible latest rows", async () => {
|
|
442
498
|
await withStorage(async (storage) => {
|
|
443
499
|
await storage.createDataset(writeDataset)
|