@tangleai/store 0.21.1 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +76 -0
- package/README.md +68 -0
- package/package.json +15 -9
- package/src/index.d.ts +2 -0
- package/src/index.js +1 -0
- package/src/mas-jobs.js +1 -1
- package/src/mas-store.d.ts +1 -1
- package/src/mas-store.js +53 -20
- package/src/model.d.ts +83 -0
- package/src/model.js +20 -0
- package/src/outcome-store.d.ts +7 -0
- package/src/outcome-store.js +32 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,81 @@
|
|
|
1
1
|
# @tangleai/store
|
|
2
2
|
|
|
3
|
+
## 0.25.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Enforce semantic template binding targets, monotone caps and tool subsets at admission and instantiation. Pin and capture host message adapter versions, check child graph bindings, and preserve optional no-change instantiation.
|
|
8
|
+
|
|
9
|
+
Enforce workflow concurrency through Jaren scheduling, retain failed physical request costs in durable receipts, restore switch outputs from committed branches, and preserve hierarchical message paths. Advance the runtime checkpoint ABI for the changed execution semantics.
|
|
10
|
+
|
|
11
|
+
Compose durable human waits through nested graphs, switches and loop iterations.
|
|
12
|
+
Derive interaction ids from full paths, reconcile the current reserved resume
|
|
13
|
+
segment, and reject conflicting response bytes under an existing key. Drain
|
|
14
|
+
started host lifetimes before reporting failure or suspension.
|
|
15
|
+
|
|
16
|
+
Enforce physical-request context and retained trace quotas, retain the shared
|
|
17
|
+
account's provider-total or estimated token charge, and claim-fence cumulative
|
|
18
|
+
active-time settlement across resumable segments. Quota refusals roll back the
|
|
19
|
+
attempted payload while retaining actual failure costs.
|
|
20
|
+
- Updated dependencies
|
|
21
|
+
- @tangleai/mas@0.25.0
|
|
22
|
+
- @tangleai/config@0.25.0
|
|
23
|
+
- @tangleai/core@0.25.0
|
|
24
|
+
- @tangleai/documents@0.25.0
|
|
25
|
+
- @tangleai/memory@0.25.0
|
|
26
|
+
- @tangleai/outcomes@0.25.0
|
|
27
|
+
|
|
28
|
+
## 0.24.1
|
|
29
|
+
|
|
30
|
+
### Patch Changes
|
|
31
|
+
|
|
32
|
+
- Update the Jaren foundation to 0.86.0 with exact registry and source pins. Qualify supervised Node SQLite execution, outcome replay after native backups, and physical-model authoring through the shared Data editor. Document the available relational, migration, cursor and collection-drag mechanisms and their host boundaries.
|
|
33
|
+
- @tangleai/config@0.24.1
|
|
34
|
+
- @tangleai/core@0.24.1
|
|
35
|
+
- @tangleai/documents@0.24.1
|
|
36
|
+
- @tangleai/mas@0.24.1
|
|
37
|
+
- @tangleai/memory@0.24.1
|
|
38
|
+
- @tangleai/outcomes@0.24.1
|
|
39
|
+
|
|
40
|
+
## 0.24.0
|
|
41
|
+
|
|
42
|
+
### Minor Changes
|
|
43
|
+
|
|
44
|
+
- Add the outcomes package for independently evidenced decisions, deterministic scoring, atomic confidence projection, bounded artifact refinement and explicitly approved promotion and rollback. The public operation contract, two-domain adapter kit and keyless example share the same scoped request receipts, one-use held-out gates, full head revision checks and interruption recovery.
|
|
45
|
+
|
|
46
|
+
The store adapter owns outcome records and memory changes in one SQLite transaction. The new pure confidence helper preserves fact fields; existing applyOutcome calls retain their original duplicate-citation and timestamp behavior and do not acquire a durable replay guarantee. No existing persisted memory format changes. Hosts opt into the new lifecycle, provide evidence and approval authority, and use a new artifact key for schema or policy changes. Scripted paired measurements and Node/Bun packed consumers qualify the mechanism; downstream domain integrations and automatic promotion remain separate work.
|
|
47
|
+
|
|
48
|
+
### Patch Changes
|
|
49
|
+
|
|
50
|
+
- Updated dependencies
|
|
51
|
+
- @tangleai/outcomes@0.24.0
|
|
52
|
+
- @tangleai/memory@0.24.0
|
|
53
|
+
- @tangleai/config@0.24.0
|
|
54
|
+
- @tangleai/core@0.24.0
|
|
55
|
+
- @tangleai/documents@0.24.0
|
|
56
|
+
- @tangleai/mas@0.24.0
|
|
57
|
+
|
|
58
|
+
## 0.23.0
|
|
59
|
+
|
|
60
|
+
### Patch Changes
|
|
61
|
+
|
|
62
|
+
- Updated dependencies
|
|
63
|
+
- @tangleai/memory@0.23.0
|
|
64
|
+
- @tangleai/config@0.23.0
|
|
65
|
+
- @tangleai/core@0.23.0
|
|
66
|
+
- @tangleai/documents@0.23.0
|
|
67
|
+
- @tangleai/mas@0.23.0
|
|
68
|
+
|
|
69
|
+
## 0.22.0
|
|
70
|
+
|
|
71
|
+
### Patch Changes
|
|
72
|
+
|
|
73
|
+
- @tangleai/config@0.22.0
|
|
74
|
+
- @tangleai/core@0.22.0
|
|
75
|
+
- @tangleai/documents@0.22.0
|
|
76
|
+
- @tangleai/mas@0.22.0
|
|
77
|
+
- @tangleai/memory@0.22.0
|
|
78
|
+
|
|
3
79
|
## 0.21.1
|
|
4
80
|
|
|
5
81
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -5,3 +5,71 @@ Tangle AI persistence — the MemoryStore contract over SQLite via @jarenjs/db,
|
|
|
5
5
|
Install with `npm install @tangleai/store`. The npm distribution provides ESM JavaScript, TypeScript declarations, and the documented package subpaths for Node 24 and Bun 1.4 or newer.
|
|
6
6
|
|
|
7
7
|
See the [Tangle documentation](https://github.com/jklarenbeek/tangleai#readme) for architecture, examples, and runtime requirements. All public Tangle packages use one coordinated version.
|
|
8
|
+
|
|
9
|
+
## Execution hosts and backups
|
|
10
|
+
|
|
11
|
+
`openTangleDb` accepts the published Jaren `OpenStoreOptions`, with an optional
|
|
12
|
+
driver. Its default selects the synchronous Node or Bun SQLite driver. Injected
|
|
13
|
+
drivers use the same Store, transactions, memory, outcome and MAS adapters.
|
|
14
|
+
|
|
15
|
+
Jaren 0.86.0 adds `nodeProcessDriver` from `@jarenjs/db/node-process`. Retain the
|
|
16
|
+
opened owner and pass it through a driver adapter to `openTangleDb`; supervise
|
|
17
|
+
ordinary store/service calls with `owner.supervise`. The
|
|
18
|
+
[runnable Node example](../../examples/supervised-store.ts) exercises both outcome
|
|
19
|
+
domains, waits for owner exit, reopens and proves zero-effect replay:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npm run store:supervised:smoke
|
|
23
|
+
node examples/supervised-store.ts --db /tmp/supervised-outcomes.sqlite
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Share one process driver per host ownership domain. A response deadline fences
|
|
27
|
+
the owner; `settled()` separately reports OS exit and replacement eligibility.
|
|
28
|
+
Reopen and inspect durable receipts before retrying an uncertain write. No
|
|
29
|
+
automatic replay or restart is added. This host has no synchronous/live-query
|
|
30
|
+
surface; importing it on Bun works, while opening refuses with `JD0003`.
|
|
31
|
+
|
|
32
|
+
`db.backupTo(path)` retains the ordinary Jaren maintenance contract, including
|
|
33
|
+
atomic destination replacement. Node uses online backup; Bun uses disk-backed
|
|
34
|
+
VACUUM INTO without materializing the database in JavaScript. A Bun copy remains
|
|
35
|
+
synchronous native work, without incremental native cancellation. Tests restore
|
|
36
|
+
WAL-backed outcome receipts on both runtimes and verify zero-effect replay.
|
|
37
|
+
|
|
38
|
+
Native physical schemas and migrations remain direct Jaren operations; this
|
|
39
|
+
package does not convert existing collections or grant raw SQL callers outcome
|
|
40
|
+
service authority. See the [integration audit](../../docs/JARENJS_INTEGRATION.md)
|
|
41
|
+
for ownership, compatibility and available mechanisms.
|
|
42
|
+
|
|
43
|
+
## Atomic outcome storage
|
|
44
|
+
|
|
45
|
+
`createOutcomeStore(db)` implements `@tangleai/outcomes`' atomic owner over Jaren
|
|
46
|
+
immediate transactions, with `outcome_records`, `outcome_keys`, `outcome_heads`
|
|
47
|
+
and `outcome_operations` alongside the existing `memories` collection. Immutable
|
|
48
|
+
records carry per-scope sequence values. Head activation compares version and
|
|
49
|
+
revision; projection changes current memory confidence and its terminal receipt
|
|
50
|
+
in the same transaction. Its `memories` property is that same owner's memory
|
|
51
|
+
view, not a separately attached store.
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { openTangleDb, createOutcomeStore } from '@tangleai/store';
|
|
55
|
+
const db = await openTangleDb({ path: '/tmp/outcomes.sqlite' });
|
|
56
|
+
const store = createOutcomeStore(db);
|
|
57
|
+
// Pass store to createOutcomeService with the host's scope, adapters and resolver.
|
|
58
|
+
// Close db when the host is finished.
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Node and Bun use the installed Jaren SQLite drivers. Tests cover independent
|
|
62
|
+
handles, rollback faults, abrupt process exit, reopening, and zero-effect
|
|
63
|
+
completed replay. A driver busy refusal can require an explicit retry after
|
|
64
|
+
contention. Unknown external completion remains uncertain until host
|
|
65
|
+
reconciliation; SQLite does not make remote effects transactional. A reservation
|
|
66
|
+
is audited before external work, and no-dispatch reconciliation fences the old
|
|
67
|
+
worker. Do not combine this receipt store with unrelated nontransactional memory
|
|
68
|
+
and claim atomic projection.
|
|
69
|
+
|
|
70
|
+
History filtering and pagination execute as indexed scope/sequence SQL queries
|
|
71
|
+
with LIMIT. That bounds returned records; it is not a driver-wide CPU or scan
|
|
72
|
+
budget. The service caps ancestry traversal and detects corrupt immutable bytes.
|
|
73
|
+
Raw database writes remain trusted-host administration, outside supported API
|
|
74
|
+
authority guarantees. See [the outcome kit](../outcomes/docs/ADAPTERS.md) and the
|
|
75
|
+
[measured replay](../../docs/OUTCOME_BENCHMARK.md).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangleai/store",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "Tangle AI persistence — the MemoryStore contract over SQLite via @jarenjs/db, plus the run/event log the DAG surface reads",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -46,6 +46,11 @@
|
|
|
46
46
|
"types": "./src/ledger-storage.d.ts",
|
|
47
47
|
"import": "./src/ledger-storage.js",
|
|
48
48
|
"default": "./src/ledger-storage.js"
|
|
49
|
+
},
|
|
50
|
+
"./outcome-store": {
|
|
51
|
+
"types": "./src/outcome-store.d.ts",
|
|
52
|
+
"import": "./src/outcome-store.js",
|
|
53
|
+
"default": "./src/outcome-store.js"
|
|
49
54
|
}
|
|
50
55
|
},
|
|
51
56
|
"engines": {
|
|
@@ -53,14 +58,15 @@
|
|
|
53
58
|
},
|
|
54
59
|
"sideEffects": false,
|
|
55
60
|
"dependencies": {
|
|
56
|
-
"@tangleai/documents": "^0.
|
|
57
|
-
"@tangleai/core": "^0.
|
|
58
|
-
"@tangleai/config": "^0.
|
|
59
|
-
"@jarenjs/db": "0.
|
|
60
|
-
"@jarenjs/validate": "0.
|
|
61
|
-
"@tangleai/mas": "^0.
|
|
62
|
-
"@jarenjs/core": "0.
|
|
63
|
-
"@tangleai/memory": "^0.
|
|
61
|
+
"@tangleai/documents": "^0.25.0",
|
|
62
|
+
"@tangleai/core": "^0.25.0",
|
|
63
|
+
"@tangleai/config": "^0.25.0",
|
|
64
|
+
"@jarenjs/db": "0.86.0",
|
|
65
|
+
"@jarenjs/validate": "0.86.0",
|
|
66
|
+
"@tangleai/mas": "^0.25.0",
|
|
67
|
+
"@jarenjs/core": "0.86.0",
|
|
68
|
+
"@tangleai/memory": "^0.25.0",
|
|
69
|
+
"@tangleai/outcomes": "^0.25.0"
|
|
64
70
|
},
|
|
65
71
|
"private": false,
|
|
66
72
|
"types": "./src/index.d.ts",
|
package/src/index.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export { TANGLE_DB_MODEL } from './model.ts';
|
|
|
3
3
|
export { openTangleDb, pickDriver } from './db.ts';
|
|
4
4
|
export type { TangleDb, DbCollection, OpenTangleDbOptions } from './db.ts';
|
|
5
5
|
export { createDbMemoryStore, asRows } from './memory-store.ts';
|
|
6
|
+
export { createOutcomeStore } from './outcome-store.ts';
|
|
7
|
+
export type { OutcomeStoreOptions } from './outcome-store.ts';
|
|
6
8
|
export type { DbMemoryStoreOptions } from './memory-store.ts';
|
|
7
9
|
export { createRunLog } from './runs.ts';
|
|
8
10
|
export type { RunLog, RunRecord, RunEvent, RunLogOptions, RunIdentityStatus, RunView } from './runs.ts';
|
package/src/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
export { TANGLE_DB_MODEL } from "./model.js";
|
|
3
3
|
export { openTangleDb, pickDriver } from "./db.js";
|
|
4
4
|
export { createDbMemoryStore, asRows } from "./memory-store.js";
|
|
5
|
+
export { createOutcomeStore } from "./outcome-store.js";
|
|
5
6
|
export { createRunLog } from "./runs.js";
|
|
6
7
|
export { createIdentityRepository } from "./identities.js";
|
|
7
8
|
export { createDocumentStore } from "./document-store.js";
|
package/src/mas-jobs.js
CHANGED
|
@@ -102,7 +102,7 @@ export async function ensurePendingMasSegments(db, masStore) {
|
|
|
102
102
|
for (const run of runs) {
|
|
103
103
|
counts.examined += 1;
|
|
104
104
|
const trace = await masStore.readTrace(run.id);
|
|
105
|
-
const responded = trace?.interactions.find((interaction) => interaction.status === 'responded' && interaction.resumeSegment
|
|
105
|
+
const responded = trace?.interactions.find((interaction) => interaction.status === 'responded' && interaction.resumeSegment === run.segment + 1);
|
|
106
106
|
if (responded === undefined || responded.resumeSegment === null) {
|
|
107
107
|
counts.skipped += 1;
|
|
108
108
|
continue;
|
package/src/mas-store.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* worker claim epoch (`TMAS2005` for a zombie's stale commit), and node
|
|
9
9
|
* completion as ONE transaction writing the terminal attempt, outbound
|
|
10
10
|
* messages, next state revision, budget snapshot and artifact rows —
|
|
11
|
-
* all or nothing
|
|
11
|
+
* all or nothing. Every record validates against the
|
|
12
12
|
* generated runtime contracts before it is written; persistence never
|
|
13
13
|
* invents a shape. Semantic idempotency: `beginNodeAttempt` returns the
|
|
14
14
|
* stored completion for a key it has already committed and refuses an
|
package/src/mas-store.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* worker claim epoch (`TMAS2005` for a zombie's stale commit), and node
|
|
9
9
|
* completion as ONE transaction writing the terminal attempt, outbound
|
|
10
10
|
* messages, next state revision, budget snapshot and artifact rows —
|
|
11
|
-
* all or nothing
|
|
11
|
+
* all or nothing. Every record validates against the
|
|
12
12
|
* generated runtime contracts before it is written; persistence never
|
|
13
13
|
* invents a shape. Semantic idempotency: `beginNodeAttempt` returns the
|
|
14
14
|
* stored completion for a key it has already committed and refuses an
|
|
@@ -47,6 +47,27 @@ export function createMasStore(db, options = {}) {
|
|
|
47
47
|
throw error;
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
+
async function readTraceFrom(reader, runId) {
|
|
51
|
+
const run = await reader.collection('mas_runs').get(runId);
|
|
52
|
+
if (run === undefined)
|
|
53
|
+
return undefined;
|
|
54
|
+
const view = {
|
|
55
|
+
run: structuredClone(run),
|
|
56
|
+
attempts: asRows(await reader.collection('mas_node_attempts').execute(matching({ runId }))).map((row) => structuredClone(row)),
|
|
57
|
+
messages: asRows(await reader.collection('mas_messages').execute(matching({ runId }))).map((row) => structuredClone(row)),
|
|
58
|
+
stateRevisions: asRows(await reader.collection('mas_state_revisions').execute(matching({ runId }))).map((row) => structuredClone(row)),
|
|
59
|
+
interactions: asRows(await reader.collection('mas_interactions').execute(matching({ runId }))).map((row) => structuredClone(row)),
|
|
60
|
+
artifacts: asRows(await reader.collection('mas_trace_artifacts').execute(matching({ runId }))).map((row) => structuredClone(row)),
|
|
61
|
+
};
|
|
62
|
+
return view;
|
|
63
|
+
}
|
|
64
|
+
async function enforceTraceLimit(txn, runId) {
|
|
65
|
+
const trace = await readTraceFrom(txn, runId);
|
|
66
|
+
const limit = trace?.run.budget.limits?.traceBytes;
|
|
67
|
+
if (limit !== undefined && new TextEncoder().encode(JSON.stringify(trace)).byteLength > limit) {
|
|
68
|
+
throw new MasRollback(refuse('TMAS2009', '/limits/traceBytes', 'the retained trace byte budget is spent; the completion was rolled back'));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
50
71
|
async function putImmutable(collection, key, value, keyMember) {
|
|
51
72
|
return atomically(async (txn) => {
|
|
52
73
|
const handle = txn.collection(collection);
|
|
@@ -221,13 +242,24 @@ export function createMasStore(db, options = {}) {
|
|
|
221
242
|
if (!transition.ok)
|
|
222
243
|
return { ok: false, issue: transition.issue };
|
|
223
244
|
const next = { ...run, status: transition.status };
|
|
245
|
+
if (command.settlement) {
|
|
246
|
+
const receipt = command.settlement;
|
|
247
|
+
if (receipt.claimSeq !== run.claim.seq)
|
|
248
|
+
return refuse('TMAS2005', '/claim', 'a stale segment cannot settle the budget');
|
|
249
|
+
if (Object.values(receipt.spent).some(n => !Number.isFinite(n) || n < 0))
|
|
250
|
+
return refuse('TMAS2009', '/budget/spent', 'invalid budget settlement');
|
|
251
|
+
next.budget = { limits: run.budget.limits, spent: { turns: Math.max(run.budget.spent.turns, receipt.spent.turns), tokens: Math.max(run.budget.spent.tokens, receipt.spent.tokens), ms: Math.max(run.budget.spent.ms, receipt.spent.ms) } };
|
|
252
|
+
}
|
|
224
253
|
if (command.kind === 'complete')
|
|
225
254
|
next.output = command.output;
|
|
226
255
|
if (command.kind === 'fail')
|
|
227
256
|
next.failure = command.failure;
|
|
228
257
|
if (command.kind === 'queue-segment')
|
|
229
258
|
next.segment = run.segment + 1;
|
|
230
|
-
|
|
259
|
+
const written = await writeRun(txn, next);
|
|
260
|
+
if (written.ok && (command.kind === 'complete' || command.kind === 'wait'))
|
|
261
|
+
await enforceTraceLimit(txn, runId);
|
|
262
|
+
return written;
|
|
231
263
|
});
|
|
232
264
|
},
|
|
233
265
|
async putRunFsm(runId, controlId, snapshot) {
|
|
@@ -235,7 +267,10 @@ export function createMasStore(db, options = {}) {
|
|
|
235
267
|
const run = await readRun(txn, runId);
|
|
236
268
|
if (run === undefined)
|
|
237
269
|
return refuse('TMAS2002', '/id', `run '${runId}' does not exist`);
|
|
238
|
-
|
|
270
|
+
const written = await writeRun(txn, { ...run, fsm: { ...run.fsm, [controlId]: snapshot } });
|
|
271
|
+
if (written.ok)
|
|
272
|
+
await enforceTraceLimit(txn, runId);
|
|
273
|
+
return written;
|
|
239
274
|
});
|
|
240
275
|
},
|
|
241
276
|
async beginNodeAttempt(plan) {
|
|
@@ -351,12 +386,13 @@ export function createMasStore(db, options = {}) {
|
|
|
351
386
|
spent: {
|
|
352
387
|
turns: spent.turns + plan.spend.turns,
|
|
353
388
|
tokens: spent.tokens + plan.spend.tokens,
|
|
354
|
-
ms: spent.ms + plan.spend.ms,
|
|
389
|
+
ms: Math.max(spent.ms + plan.spend.ms, plan.activeMs ?? 0),
|
|
355
390
|
},
|
|
356
391
|
},
|
|
357
392
|
});
|
|
358
393
|
if (!written.ok)
|
|
359
394
|
throw new MasRollback({ ok: false, issue: written.issue });
|
|
395
|
+
await enforceTraceLimit(txn, plan.runId);
|
|
360
396
|
return {
|
|
361
397
|
ok: true,
|
|
362
398
|
value: {
|
|
@@ -382,12 +418,19 @@ export function createMasStore(db, options = {}) {
|
|
|
382
418
|
if (current.status !== 'running') {
|
|
383
419
|
return refuse('TMAS2003', '/status', `only a running attempt can move to '${plan.status}'; '${plan.attemptId}' is '${current.status}'`);
|
|
384
420
|
}
|
|
385
|
-
const next = { ...current, status: plan.status, error: plan.error, finishedAt: now() };
|
|
421
|
+
const next = { ...current, ...plan.receipt, status: plan.status, error: plan.error, finishedAt: now() };
|
|
386
422
|
const outcome = validateRuntimeRecord('masNodeAttempt', next);
|
|
387
423
|
if (!outcome.valid) {
|
|
388
424
|
return refuse('TMAS2004', `/attempt${outcome.issues[0]?.path ?? ''}`, outcome.issues[0]?.detail ?? '');
|
|
389
425
|
}
|
|
390
426
|
await handle.put(next);
|
|
427
|
+
if (plan.receipt !== undefined) {
|
|
428
|
+
const charged = plan.receipt.spend, spent = run.budget.spent;
|
|
429
|
+
const written = await writeRun(txn, { ...run, budget: { limits: run.budget.limits,
|
|
430
|
+
spent: { turns: spent.turns + charged.turns, tokens: spent.tokens + charged.tokens, ms: Math.max(spent.ms + charged.ms, plan.activeMs ?? 0) } } });
|
|
431
|
+
if (!written.ok)
|
|
432
|
+
throw new MasRollback({ ok: false, issue: written.issue });
|
|
433
|
+
}
|
|
391
434
|
return { ok: true, value: structuredClone(next) };
|
|
392
435
|
});
|
|
393
436
|
},
|
|
@@ -396,7 +439,7 @@ export function createMasStore(db, options = {}) {
|
|
|
396
439
|
const run = await readRun(txn, plan.runId);
|
|
397
440
|
if (run === undefined)
|
|
398
441
|
return refuse('TMAS2002', '/id', `run '${plan.runId}' does not exist`);
|
|
399
|
-
const id = `${plan.runId}:i:${plan.
|
|
442
|
+
const id = `${plan.runId}:i:${plan.path}`;
|
|
400
443
|
const handle = txn.collection('mas_interactions');
|
|
401
444
|
const existing = await handle.get(id);
|
|
402
445
|
if (existing !== undefined)
|
|
@@ -423,6 +466,7 @@ export function createMasStore(db, options = {}) {
|
|
|
423
466
|
return refuse('TMAS2004', `/interaction${outcome.issues[0]?.path ?? ''}`, outcome.issues[0]?.detail ?? '');
|
|
424
467
|
}
|
|
425
468
|
await handle.put(interaction);
|
|
469
|
+
await enforceTraceLimit(txn, plan.runId);
|
|
426
470
|
return { ok: true, value: structuredClone(interaction) };
|
|
427
471
|
});
|
|
428
472
|
},
|
|
@@ -437,6 +481,8 @@ export function createMasStore(db, options = {}) {
|
|
|
437
481
|
if (current === undefined)
|
|
438
482
|
return refuse('TMAS2007', '/id', `interaction '${id}' does not exist`);
|
|
439
483
|
if (current.status === 'responded' && current.responseKey === responseKey) {
|
|
484
|
+
if (!equalsJson(current.response, response))
|
|
485
|
+
return refuse('TMAS2007', '/response', 'the response key already identifies different response bytes');
|
|
440
486
|
return { ok: true, value: structuredClone(current) };
|
|
441
487
|
}
|
|
442
488
|
const transition = planInteractionTransition(current.status, 'responded');
|
|
@@ -506,19 +552,6 @@ export function createMasStore(db, options = {}) {
|
|
|
506
552
|
const latest = rows.at(-1);
|
|
507
553
|
return latest === undefined ? undefined : { id: latest.id, value: structuredClone(latest.value) };
|
|
508
554
|
},
|
|
509
|
-
async readTrace(runId) {
|
|
510
|
-
const run = await runs().get(runId);
|
|
511
|
-
if (run === undefined)
|
|
512
|
-
return undefined;
|
|
513
|
-
const view = {
|
|
514
|
-
run: structuredClone(run),
|
|
515
|
-
attempts: asRows(await attempts().execute(matching({ runId }))).map((row) => structuredClone(row)),
|
|
516
|
-
messages: asRows(await db.collection('mas_messages').execute(matching({ runId }))).map((row) => structuredClone(row)),
|
|
517
|
-
stateRevisions: asRows(await db.collection('mas_state_revisions').execute(matching({ runId }))).map((row) => structuredClone(row)),
|
|
518
|
-
interactions: asRows(await db.collection('mas_interactions').execute(matching({ runId }))).map((row) => structuredClone(row)),
|
|
519
|
-
artifacts: asRows(await db.collection('mas_trace_artifacts').execute(matching({ runId }))).map((row) => structuredClone(row)),
|
|
520
|
-
};
|
|
521
|
-
return view;
|
|
522
|
-
},
|
|
555
|
+
async readTrace(runId) { return readTraceFrom(db, runId); },
|
|
523
556
|
};
|
|
524
557
|
}
|
package/src/model.d.ts
CHANGED
|
@@ -29,6 +29,89 @@ import type { JsonSchema } from '@tangleai/core/schemas/memory';
|
|
|
29
29
|
export declare const TANGLE_DB_MODEL: {
|
|
30
30
|
readonly $model: "0.1";
|
|
31
31
|
readonly collections: {
|
|
32
|
+
readonly outcome_records: {
|
|
33
|
+
readonly schema: {
|
|
34
|
+
readonly type: "object";
|
|
35
|
+
readonly required: readonly ["id", "scopeId"];
|
|
36
|
+
readonly properties: {
|
|
37
|
+
readonly id: JsonSchema;
|
|
38
|
+
readonly scopeId: JsonSchema;
|
|
39
|
+
readonly artifactKey: JsonSchema;
|
|
40
|
+
readonly kind: JsonSchema;
|
|
41
|
+
readonly seq: {
|
|
42
|
+
readonly type: "integer";
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
readonly key: "/id";
|
|
47
|
+
readonly indexes: readonly [{
|
|
48
|
+
readonly name: "by_scope_sequence";
|
|
49
|
+
readonly path: readonly ["$.scopeId", "$.seq"];
|
|
50
|
+
}, {
|
|
51
|
+
readonly name: "by_artifact_kind";
|
|
52
|
+
readonly path: readonly ["$.scopeId", "$.artifactKey", "$.kind"];
|
|
53
|
+
}];
|
|
54
|
+
};
|
|
55
|
+
readonly outcome_keys: {
|
|
56
|
+
readonly schema: {
|
|
57
|
+
readonly type: "object";
|
|
58
|
+
readonly required: readonly ["id", "scopeId"];
|
|
59
|
+
readonly properties: {
|
|
60
|
+
readonly id: JsonSchema;
|
|
61
|
+
readonly scopeId: JsonSchema;
|
|
62
|
+
readonly artifactKey: JsonSchema;
|
|
63
|
+
readonly kind: JsonSchema;
|
|
64
|
+
readonly seq: {
|
|
65
|
+
readonly type: "integer";
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
readonly key: "/id";
|
|
70
|
+
readonly indexes: readonly [{
|
|
71
|
+
readonly name: "by_scope";
|
|
72
|
+
readonly path: "$.scopeId";
|
|
73
|
+
}];
|
|
74
|
+
};
|
|
75
|
+
readonly outcome_heads: {
|
|
76
|
+
readonly schema: {
|
|
77
|
+
readonly type: "object";
|
|
78
|
+
readonly required: readonly ["id", "scopeId"];
|
|
79
|
+
readonly properties: {
|
|
80
|
+
readonly id: JsonSchema;
|
|
81
|
+
readonly scopeId: JsonSchema;
|
|
82
|
+
readonly artifactKey: JsonSchema;
|
|
83
|
+
readonly kind: JsonSchema;
|
|
84
|
+
readonly seq: {
|
|
85
|
+
readonly type: "integer";
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
readonly key: "/id";
|
|
90
|
+
readonly indexes: readonly [{
|
|
91
|
+
readonly name: "by_scope";
|
|
92
|
+
readonly path: "$.scopeId";
|
|
93
|
+
}];
|
|
94
|
+
};
|
|
95
|
+
readonly outcome_operations: {
|
|
96
|
+
readonly schema: {
|
|
97
|
+
readonly type: "object";
|
|
98
|
+
readonly required: readonly ["id", "scopeId"];
|
|
99
|
+
readonly properties: {
|
|
100
|
+
readonly id: JsonSchema;
|
|
101
|
+
readonly scopeId: JsonSchema;
|
|
102
|
+
readonly artifactKey: JsonSchema;
|
|
103
|
+
readonly kind: JsonSchema;
|
|
104
|
+
readonly seq: {
|
|
105
|
+
readonly type: "integer";
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
readonly key: "/id";
|
|
110
|
+
readonly indexes: readonly [{
|
|
111
|
+
readonly name: "by_artifact";
|
|
112
|
+
readonly path: readonly ["$.scopeId", "$.artifactKey"];
|
|
113
|
+
}];
|
|
114
|
+
};
|
|
32
115
|
readonly memories: {
|
|
33
116
|
readonly schema: {
|
|
34
117
|
readonly type: "object";
|
package/src/model.js
CHANGED
|
@@ -29,6 +29,26 @@ const ID = { type: 'string', minLength: 1 };
|
|
|
29
29
|
export const TANGLE_DB_MODEL = {
|
|
30
30
|
$model: '0.1',
|
|
31
31
|
collections: {
|
|
32
|
+
outcome_records: {
|
|
33
|
+
schema: { type: 'object', required: ['id', 'scopeId'], properties: { id: ID, scopeId: ID, artifactKey: ID, kind: ID, seq: { type: 'integer' } } },
|
|
34
|
+
key: '/id',
|
|
35
|
+
indexes: [{ name: 'by_scope_sequence', path: ['$.scopeId', '$.seq'] }, { name: 'by_artifact_kind', path: ['$.scopeId', '$.artifactKey', '$.kind'] }],
|
|
36
|
+
},
|
|
37
|
+
outcome_keys: {
|
|
38
|
+
schema: { type: 'object', required: ['id', 'scopeId'], properties: { id: ID, scopeId: ID, artifactKey: ID, kind: ID, seq: { type: 'integer' } } },
|
|
39
|
+
key: '/id',
|
|
40
|
+
indexes: [{ name: 'by_scope', path: '$.scopeId' }],
|
|
41
|
+
},
|
|
42
|
+
outcome_heads: {
|
|
43
|
+
schema: { type: 'object', required: ['id', 'scopeId'], properties: { id: ID, scopeId: ID, artifactKey: ID, kind: ID, seq: { type: 'integer' } } },
|
|
44
|
+
key: '/id',
|
|
45
|
+
indexes: [{ name: 'by_scope', path: '$.scopeId' }],
|
|
46
|
+
},
|
|
47
|
+
outcome_operations: {
|
|
48
|
+
schema: { type: 'object', required: ['id', 'scopeId'], properties: { id: ID, scopeId: ID, artifactKey: ID, kind: ID, seq: { type: 'integer' } } },
|
|
49
|
+
key: '/id',
|
|
50
|
+
indexes: [{ name: 'by_artifact', path: ['$.scopeId', '$.artifactKey'] }],
|
|
51
|
+
},
|
|
32
52
|
memories: {
|
|
33
53
|
schema: { type: 'object', required: ['id'], properties: { id: ID } },
|
|
34
54
|
key: '/id',
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Outcomes use the database's transaction owner, including memory projection. */
|
|
2
|
+
import { type OutcomeStore } from '@tangleai/outcomes';
|
|
3
|
+
import type { TangleDb } from './db.ts';
|
|
4
|
+
export interface OutcomeStoreOptions {
|
|
5
|
+
applyProbe?: (step: string) => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function createOutcomeStore(db: TangleDb, options?: OutcomeStoreOptions): OutcomeStore;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Outcomes use the database's transaction owner, including memory projection. */
|
|
2
|
+
import { createOutcomeStoreAdapter } from '@tangleai/outcomes';
|
|
3
|
+
import { asRows } from "./memory-store.js";
|
|
4
|
+
const names = { records: 'outcome_records', keys: 'outcome_keys', heads: 'outcome_heads', operations: 'outcome_operations', memories: 'memories' };
|
|
5
|
+
export function createOutcomeStore(db, options = {}) {
|
|
6
|
+
const persistence = { transaction: (task) => db.transaction(async (tx) => {
|
|
7
|
+
const view = {
|
|
8
|
+
async get(table, id) { return tx.collection(names[table]).get(id); },
|
|
9
|
+
async put(table, value) { await tx.collection(names[table]).put(value); options.applyProbe?.(`put:${table}`); },
|
|
10
|
+
async delete(table, id) { await tx.collection(names[table]).delete(id); options.applyProbe?.(`delete:${table}`); },
|
|
11
|
+
async query(table, q) {
|
|
12
|
+
const where = [];
|
|
13
|
+
for (const field of ['scopeId', 'artifactKey', 'kind'])
|
|
14
|
+
if (q[field] !== undefined)
|
|
15
|
+
where.push({ $eq: [`$r.${field}`, { $const: q[field] }] });
|
|
16
|
+
if (q.reservedOnly)
|
|
17
|
+
where.push({ $eq: ['$r.capacityReserved', true] });
|
|
18
|
+
if (q.after !== undefined)
|
|
19
|
+
where.push({ $gt: ['$r.seq', q.after] });
|
|
20
|
+
if (q.upper !== undefined)
|
|
21
|
+
where.push({ $le: ['$r.seq', q.upper] });
|
|
22
|
+
const query = { $for: { r: '$[*]' }, ...(where.length ? { $where: { $and: where } } : {}), $orderby: table === 'records' ? ['$r.seq', '$r.id'] : ['$r.id'], $return: '$r' };
|
|
23
|
+
const bounded = q.limit === undefined ? query : { $subsequence: [query, 0, q.limit] };
|
|
24
|
+
return asRows(await tx.collection(names[table]).execute(bounded));
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
const result = await task(view);
|
|
28
|
+
options.applyProbe?.('commit');
|
|
29
|
+
return result;
|
|
30
|
+
}, { mode: 'immediate' }) };
|
|
31
|
+
return createOutcomeStoreAdapter(persistence);
|
|
32
|
+
}
|