@cotal-ai/lang 0.23.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -0
- package/dist/effects.d.ts +19 -0
- package/dist/effects.d.ts.map +1 -1
- package/dist/effects.js +23 -0
- package/dist/effects.js.map +1 -1
- package/dist/errors.d.ts +48 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +90 -2
- package/dist/errors.js.map +1 -1
- package/dist/grammar.d.ts.map +1 -1
- package/dist/grammar.js +561 -128
- package/dist/grammar.js.map +1 -1
- package/dist/index.d.ts +10 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -6
- package/dist/index.js.map +1 -1
- package/dist/interpret.d.ts +81 -3
- package/dist/interpret.d.ts.map +1 -1
- package/dist/interpret.js +1446 -270
- package/dist/interpret.js.map +1 -1
- package/dist/journal.d.ts +218 -9
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +232 -12
- package/dist/journal.js.map +1 -1
- package/dist/keys.d.ts +39 -1
- package/dist/keys.d.ts.map +1 -1
- package/dist/keys.js +61 -0
- package/dist/keys.js.map +1 -1
- package/dist/library.d.ts +65 -0
- package/dist/library.d.ts.map +1 -0
- package/dist/library.js +525 -0
- package/dist/library.js.map +1 -0
- package/dist/notify-fact.d.ts +8 -0
- package/dist/notify-fact.d.ts.map +1 -0
- package/dist/notify-fact.js +69 -0
- package/dist/notify-fact.js.map +1 -0
- package/dist/pins.d.ts +82 -0
- package/dist/pins.d.ts.map +1 -0
- package/dist/pins.js +87 -0
- package/dist/pins.js.map +1 -0
- package/dist/primitives.d.ts +14 -0
- package/dist/primitives.d.ts.map +1 -1
- package/dist/primitives.js +45 -5
- package/dist/primitives.js.map +1 -1
- package/dist/sim.d.ts +9 -1
- package/dist/sim.d.ts.map +1 -1
- package/dist/sim.js +9 -1
- package/dist/sim.js.map +1 -1
- package/dist/syntax.d.ts +34 -0
- package/dist/syntax.d.ts.map +1 -0
- package/dist/syntax.js +178 -0
- package/dist/syntax.js.map +1 -0
- package/dist/values.d.ts +20 -1
- package/dist/values.d.ts.map +1 -1
- package/dist/values.js +0 -0
- package/dist/values.js.map +1 -1
- package/package.json +2 -2
package/dist/journal.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"journal.d.ts","sourceRoot":"","sources":["../src/journal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;
|
|
1
|
+
{"version":3,"file":"journal.d.ts","sourceRoot":"","sources":["../src/journal.ts"],"names":[],"mappings":"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"}
|
package/dist/journal.js
CHANGED
|
@@ -11,29 +11,119 @@
|
|
|
11
11
|
* found, so a changed input is a diagnosable divergence naming the step, rather than a silent
|
|
12
12
|
* miss that quietly re-runs the effect and lets two versions of the truth coexist.
|
|
13
13
|
*/
|
|
14
|
+
import { deepFreeze } from "./values.js";
|
|
14
15
|
import { stepKeyString } from "./keys.js";
|
|
16
|
+
import { EFFECT_KINDS } from "./primitives.js";
|
|
17
|
+
/**
|
|
18
|
+
* A durable append the store REFUSED.
|
|
19
|
+
*
|
|
20
|
+
* This is not an effect failure, and conflating the two is not cosmetic. A handler that completed
|
|
21
|
+
* plus a log that would not accept the completion, recorded as one fact, is an entry saying the
|
|
22
|
+
* work FAILED: a later replay then reports failure for work the world actually did, which is the
|
|
23
|
+
* journal lying about the one thing it exists to remember. The domains are separate: "the world
|
|
24
|
+
* said no" is the run's result, "the log said no" is the run losing its ability to have one. A
|
|
25
|
+
* driver reads the second as
|
|
26
|
+
* "stop, and do not record anything else", never as an outcome.
|
|
27
|
+
*
|
|
28
|
+
* Nothing was written and nothing in memory moved, so a caller holding this error knows exactly as
|
|
29
|
+
* much as it did before the call.
|
|
30
|
+
*/
|
|
31
|
+
export class JournalAppendRejected extends Error {
|
|
32
|
+
stepKey;
|
|
33
|
+
state;
|
|
34
|
+
reason;
|
|
35
|
+
code = "L5010";
|
|
36
|
+
/** True when the store could not tell whether the entry landed. See {@link IndeterminateAppend}. */
|
|
37
|
+
indeterminate;
|
|
38
|
+
constructor(stepKey, state, reason) {
|
|
39
|
+
const indeterminate = reason?.indeterminate === true;
|
|
40
|
+
super(`L5010 Journal append rejected\n\n step ${stepKey} ${state}\n\n${reason.message}\n\n${indeterminate
|
|
41
|
+
? "Whether the entry was recorded is UNKNOWN — the store could not tell — and the in-memory journal was left as it was."
|
|
42
|
+
: "The entry was not recorded and the in-memory journal was left as it was."} This is a durability failure, not an effect failure: whatever the effect did, it stands, and this run can no longer say so.`);
|
|
43
|
+
this.stepKey = stepKey;
|
|
44
|
+
this.state = state;
|
|
45
|
+
this.reason = reason;
|
|
46
|
+
this.indeterminate = indeterminate;
|
|
47
|
+
this.name = "JournalAppendRejected";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
15
50
|
export class JournalReadOnlyError extends Error {
|
|
16
51
|
constructor(key) {
|
|
17
52
|
super(`journal is read-only; ${stepKeyString(key)} would have been appended`);
|
|
18
53
|
this.name = "JournalReadOnlyError";
|
|
19
54
|
}
|
|
20
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* The step-key string an entry was written under, rebuilt from the entry itself.
|
|
58
|
+
*
|
|
59
|
+
* A recorded entry keeps the scope path as a STRING and its own `(kind, name, occurrence)` beside
|
|
60
|
+
* it, so the key it was filed under is recoverable — but only by re-applying the grammar that
|
|
61
|
+
* `stepKeyString` owns. That grammar lives in exactly one place and this is how anything outside
|
|
62
|
+
* the language addresses a recorded step: a caller that re-joined the parts by hand would be
|
|
63
|
+
* maintaining a second copy of a rule, and the first edit to the naming would silently address a
|
|
64
|
+
* different step rather than fail.
|
|
65
|
+
*/
|
|
66
|
+
export function journalEntryKeyString(entry) {
|
|
67
|
+
const named = entry.name === "" ? entry.kind : `${entry.kind}:${entry.name}`;
|
|
68
|
+
return `${entry.scope}/${named}#${entry.occurrence}`;
|
|
69
|
+
}
|
|
21
70
|
export class Journal {
|
|
22
71
|
run;
|
|
23
72
|
readOnly;
|
|
73
|
+
store;
|
|
24
74
|
byKey = new Map();
|
|
25
75
|
order = [];
|
|
76
|
+
/**
|
|
77
|
+
* Append order, allocated where the entry is BUILT rather than read off `order.length`.
|
|
78
|
+
*
|
|
79
|
+
* `begin` awaits a durable append before the key joins `order`, so two concurrent branches — which
|
|
80
|
+
* is the normal shape, not an edge case — both read the same length and both claimed the same
|
|
81
|
+
* `seq`. Rendering then showed two "first" steps, and any tooling ordering by it saw a tie it had
|
|
82
|
+
* no way to break. A counter is allocated synchronously, so the two branches cannot observe the
|
|
83
|
+
* same value however their awaits interleave.
|
|
84
|
+
*/
|
|
85
|
+
nextSeq = 0;
|
|
26
86
|
/** Every key the current replay has looked up. What is left over is an orphan. */
|
|
27
87
|
consumed = new Set();
|
|
28
88
|
constructor(init) {
|
|
29
89
|
this.run = init.run;
|
|
30
90
|
this.readOnly = init.readOnly === true;
|
|
91
|
+
if (init.store !== undefined)
|
|
92
|
+
this.store = init.store;
|
|
31
93
|
for (const e of init.entries ?? []) {
|
|
94
|
+
// A journal is ONE run's. Seeding it with another run's entry would make this run resume
|
|
95
|
+
// against a history it never had — the keys are structural, so a foreign entry with the same
|
|
96
|
+
// scope and name matches, and its recorded result is returned as if this run had produced it.
|
|
97
|
+
if (e.run !== this.run)
|
|
98
|
+
throw new Error(`journal for run ${this.run} was seeded with an entry from run ${e.run}; a run resumes only from its own journal`);
|
|
32
99
|
// The stored `scope` string is authoritative: it is what makes a journal readable back
|
|
33
100
|
// without re-running the program that produced it.
|
|
34
101
|
const full = `${e.scope}/${e.name === "" ? e.kind : `${e.kind}:${e.name}`}#${e.occurrence}`;
|
|
35
|
-
|
|
36
|
-
|
|
102
|
+
// FROZEN ON THE WAY IN, because replay returns `entry.result` to the program as a value that
|
|
103
|
+
// already crossed an effect boundary. The LIVE path freezes the result before it settles, but
|
|
104
|
+
// a journal read back from a durable store is fresh deserialized data — measured before this
|
|
105
|
+
// line: a program mutated a replayed spawn handle, and pushed into a replayed scope result,
|
|
106
|
+
// with no L2031, on exactly the values whose recorded form is the one thing a resume trusts.
|
|
107
|
+
// The journal never mutates an entry in place (every transition builds a new record), so
|
|
108
|
+
// freezing the seed changes nothing else.
|
|
109
|
+
this.byKey.set(full, deepFreeze(e));
|
|
110
|
+
// FOLD THE LOG. A seed is an APPEND LOG, not a keyed view: the stream carries the pending
|
|
111
|
+
// record and the settled one as two records (`journal-store.smoke`: "settling appends a second
|
|
112
|
+
// record rather than editing the first"), `RunJournalAppender.steps()` replays both, and the
|
|
113
|
+
// driver seeds a journal straight from that. `byKey` already folds — last write wins, which is
|
|
114
|
+
// the settled row — so pushing the key again put the SAME entry in `order` twice and left the
|
|
115
|
+
// journal's two views disagreeing: the ceiling counted one step and `entries()` reported two,
|
|
116
|
+
// both resolving through `byKey`, so the pending row came back as a second copy of the settled
|
|
117
|
+
// one. `orphans()` reads `order`, so an unconsumed step reached the migrate table twice — one
|
|
118
|
+
// decision presented to an operator as two, about a step that happened once.
|
|
119
|
+
//
|
|
120
|
+
// FIRST occurrence, not last: `order` is the order the run PERFORMED its steps in, and a step
|
|
121
|
+
// begins when its pending row is written. Re-pushing on settle would reorder history by
|
|
122
|
+
// completion, which is a different sequence and not the one a reader is looking for.
|
|
123
|
+
if (!this.order.includes(full))
|
|
124
|
+
this.order.push(full);
|
|
125
|
+
if (e.seq >= this.nextSeq)
|
|
126
|
+
this.nextSeq = e.seq + 1;
|
|
37
127
|
}
|
|
38
128
|
}
|
|
39
129
|
static keyOf(key) {
|
|
@@ -65,14 +155,41 @@ export class Journal {
|
|
|
65
155
|
return { verdict: "replay-cancelled", entry };
|
|
66
156
|
return { verdict: "replay", entry };
|
|
67
157
|
}
|
|
68
|
-
/**
|
|
69
|
-
|
|
158
|
+
/**
|
|
159
|
+
* Durably record one entry, BEFORE the in-memory map is allowed to hold it.
|
|
160
|
+
*
|
|
161
|
+
* The order is the whole point. Mutating first leaves a volatile transition behind when the store
|
|
162
|
+
* refuses: a `begin` whose append was rejected still reads as `pending`, and a `settle` whose
|
|
163
|
+
* append was rejected still reads as settled, so the in-memory journal claims a durability the
|
|
164
|
+
* store explicitly declined to provide. Persisting first means a rejected append changes nothing
|
|
165
|
+
* at all, which is the only state a caller can reason about.
|
|
166
|
+
*
|
|
167
|
+
* With no store there is nothing to refuse: `persist` returns, the map is updated, and nothing is
|
|
168
|
+
* awaited that could fail.
|
|
169
|
+
*/
|
|
170
|
+
async persist(k, entry) {
|
|
171
|
+
if (this.store === undefined)
|
|
172
|
+
return;
|
|
173
|
+
try {
|
|
174
|
+
await this.store.append(entry);
|
|
175
|
+
}
|
|
176
|
+
catch (e) {
|
|
177
|
+
throw new JournalAppendRejected(k, entry.state, e);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Append the `pending` half: the effect is about to be performed.
|
|
182
|
+
*
|
|
183
|
+
* AWAIT THIS BEFORE DISPATCHING. The entry carries the request id the handler will submit under,
|
|
184
|
+
* and an identity that is not durable when the work is issued names nothing a resume can find.
|
|
185
|
+
*/
|
|
186
|
+
async begin(key, inputHash, startedAt, requestId) {
|
|
70
187
|
if (this.readOnly)
|
|
71
188
|
throw new JournalReadOnlyError(key);
|
|
72
189
|
const k = Journal.keyOf(key);
|
|
73
190
|
const entry = {
|
|
74
191
|
v: 1,
|
|
75
|
-
seq: this.
|
|
192
|
+
seq: this.nextSeq++,
|
|
76
193
|
run: this.run,
|
|
77
194
|
scope: k.slice(0, k.lastIndexOf("/")),
|
|
78
195
|
kind: key.kind,
|
|
@@ -83,6 +200,7 @@ export class Journal {
|
|
|
83
200
|
state: "pending",
|
|
84
201
|
startedAt,
|
|
85
202
|
};
|
|
203
|
+
await this.persist(k, entry);
|
|
86
204
|
this.byKey.set(k, entry);
|
|
87
205
|
this.order.push(k);
|
|
88
206
|
this.consumed.add(k);
|
|
@@ -100,26 +218,38 @@ export class Journal {
|
|
|
100
218
|
* The INDEX moves with the id, and it is the half that recovery reads: an id says what to submit
|
|
101
219
|
* under, the index says how much of the chain is already spent.
|
|
102
220
|
*/
|
|
103
|
-
reissueAs(key, requestId, attempt) {
|
|
221
|
+
async reissueAs(key, requestId, attempt) {
|
|
104
222
|
if (this.readOnly)
|
|
105
223
|
throw new JournalReadOnlyError(key);
|
|
106
224
|
const k = Journal.keyOf(key);
|
|
107
225
|
const entry = this.byKey.get(k);
|
|
108
226
|
if (entry === undefined)
|
|
109
227
|
throw new Error(`reissueAs before begin for ${k}`);
|
|
110
|
-
|
|
228
|
+
const next = { ...entry, requestId, attempt };
|
|
229
|
+
await this.persist(k, next);
|
|
230
|
+
this.byKey.set(k, next);
|
|
111
231
|
}
|
|
112
|
-
bind(key, external) {
|
|
232
|
+
async bind(key, external) {
|
|
113
233
|
if (this.readOnly)
|
|
114
234
|
throw new JournalReadOnlyError(key);
|
|
115
235
|
const k = Journal.keyOf(key);
|
|
116
236
|
const entry = this.byKey.get(k);
|
|
117
237
|
if (entry === undefined)
|
|
118
238
|
throw new Error(`bind before begin for ${k}`);
|
|
119
|
-
|
|
239
|
+
const next = { ...entry, external };
|
|
240
|
+
await this.persist(k, next);
|
|
241
|
+
this.byKey.set(k, next);
|
|
120
242
|
}
|
|
121
|
-
/**
|
|
122
|
-
|
|
243
|
+
/**
|
|
244
|
+
* Append the `settled` half.
|
|
245
|
+
*
|
|
246
|
+
* `facts.cancel` is for a scope that cancels siblings: the intent is recorded WITH the outcome
|
|
247
|
+
* rather than after it, because a process dies between instructions and two appends are two
|
|
248
|
+
* network operations however few keywords separate them. `facts.closed` is a `conclave`'s
|
|
249
|
+
* membership disposition, for the same reason and with the same rule: it goes down with the
|
|
250
|
+
* outcome or not at all.
|
|
251
|
+
*/
|
|
252
|
+
async settle(key, outcome, endedAt, facts = {}) {
|
|
123
253
|
if (this.readOnly)
|
|
124
254
|
throw new JournalReadOnlyError(key);
|
|
125
255
|
const k = Journal.keyOf(key);
|
|
@@ -133,10 +263,58 @@ export class Journal {
|
|
|
133
263
|
endedAt,
|
|
134
264
|
...(outcome.status === "ok" ? { result: outcome.result } : {}),
|
|
135
265
|
...(outcome.status === "failed" ? { error: outcome.error } : {}),
|
|
266
|
+
...(facts.cancel !== undefined ? { cancel: facts.cancel } : {}),
|
|
267
|
+
...(facts.closed !== undefined ? { closed: facts.closed } : {}),
|
|
268
|
+
...(facts.branchDigest !== undefined ? { branchDigest: facts.branchDigest } : {}),
|
|
269
|
+
// Only where `result` is not carrying them, so the two can never disagree.
|
|
270
|
+
...(outcome.status !== "ok" && facts.branches !== undefined ? { branches: facts.branches } : {}),
|
|
136
271
|
};
|
|
272
|
+
await this.persist(k, settled);
|
|
137
273
|
this.byKey.set(k, settled);
|
|
138
274
|
return settled;
|
|
139
275
|
}
|
|
276
|
+
/**
|
|
277
|
+
* Account for everything under a settled scope WITHOUT entering a branch.
|
|
278
|
+
*
|
|
279
|
+
* A settled scope is delivered from its own entry, so the branches are never walked — and an
|
|
280
|
+
* entry nothing walks is an orphan, which on a migration means "the edit removed this step". The
|
|
281
|
+
* branches were not removed; they were decided. So the subtree is marked consumed explicitly.
|
|
282
|
+
*
|
|
283
|
+
* A loser still sitting `pending` is settled `cancelled` in the same pass, because that is what
|
|
284
|
+
* it is: the scope resolved without it. Settling the record is NOT the cancellation — the world
|
|
285
|
+
* has to be told separately, which is what `cancel.issued` tracks — but leaving it pending would
|
|
286
|
+
* make a resumed run try to recover work the scope already decided to abandon.
|
|
287
|
+
*/
|
|
288
|
+
async consumeScope(scopeKeyString, endedAt,
|
|
289
|
+
/**
|
|
290
|
+
* Restrict the accounting to these branch keys.
|
|
291
|
+
*
|
|
292
|
+
* A resume passes nothing and takes the whole subtree, which is correct because the program
|
|
293
|
+
* hash is unchanged and nothing under the scope can have been removed. A MIGRATION passes the
|
|
294
|
+
* losers alone: the branches it does not name are walked instead, so that an effect the edited
|
|
295
|
+
* source removed still shows up as an orphan rather than being accounted for by a scope that
|
|
296
|
+
* never entered it.
|
|
297
|
+
*/
|
|
298
|
+
only) {
|
|
299
|
+
const prefix = `${scopeKeyString}/b:`;
|
|
300
|
+
const wanted = only === undefined ? undefined : new Set([...only].map((b) => `${prefix}${b}/`));
|
|
301
|
+
const touched = [];
|
|
302
|
+
for (const k of this.order) {
|
|
303
|
+
if (!k.startsWith(prefix))
|
|
304
|
+
continue;
|
|
305
|
+
if (wanted !== undefined && ![...wanted].some((w) => k.startsWith(w)))
|
|
306
|
+
continue;
|
|
307
|
+
this.consumed.add(k);
|
|
308
|
+
touched.push(k);
|
|
309
|
+
const entry = this.byKey.get(k);
|
|
310
|
+
if (entry === undefined || entry.state !== "pending")
|
|
311
|
+
continue;
|
|
312
|
+
const cancelled = { ...entry, state: "settled", status: "cancelled", endedAt };
|
|
313
|
+
await this.persist(k, cancelled);
|
|
314
|
+
this.byKey.set(k, cancelled);
|
|
315
|
+
}
|
|
316
|
+
return touched;
|
|
317
|
+
}
|
|
140
318
|
/** Every entry, in append order. The journal is the prompt context for repair. */
|
|
141
319
|
entries() {
|
|
142
320
|
return this.order.map((k) => this.byKey.get(k)).filter((e) => e !== undefined);
|
|
@@ -155,6 +333,33 @@ export class Journal {
|
|
|
155
333
|
.map((k) => this.byKey.get(k))
|
|
156
334
|
.filter((e) => e !== undefined);
|
|
157
335
|
}
|
|
336
|
+
/**
|
|
337
|
+
* How many EFFECTS this run has already dispatched, counted from what is recorded.
|
|
338
|
+
*
|
|
339
|
+
* The effect ceiling is a RUN bound (L4009, "Run effect ceiling reached") and the
|
|
340
|
+
* interpreter's counter is per-instance, so without this a run got a fresh allowance at every
|
|
341
|
+
* activation and a runaway loop that crashed periodically could never reach the ceiling. The
|
|
342
|
+
* journal is the only thing that survives an activation, and it already holds the answer.
|
|
343
|
+
*
|
|
344
|
+
* Distinct KEYS, not entries: a dispatch writes a pending entry and then a settled one under the
|
|
345
|
+
* same key, and the interpreter counts the dispatch once. A pending entry still counts — the
|
|
346
|
+
* effect was performed, which is exactly why it was written before the handler was called.
|
|
347
|
+
*
|
|
348
|
+
* `conclave` is excluded because the interpreter does not count it either: it dispatches
|
|
349
|
+
* `openConclave` from the scope walker rather than through `performEffect`, so counting it here
|
|
350
|
+
* would make a resumed run's tally disagree with a fresh one's. That asymmetry is a real gap in
|
|
351
|
+
* what the ceiling sees and it is reported separately; this method mirrors the counter rather
|
|
352
|
+
* than quietly repairing it, because a seed that does not match the thing it seeds is worse than
|
|
353
|
+
* the gap it would paper over.
|
|
354
|
+
*/
|
|
355
|
+
dispatchedEffects() {
|
|
356
|
+
let n = 0;
|
|
357
|
+
for (const e of this.byKey.values()) {
|
|
358
|
+
if (e.kind !== "conclave" && EFFECT_KINDS.includes(e.kind))
|
|
359
|
+
n += 1;
|
|
360
|
+
}
|
|
361
|
+
return n;
|
|
362
|
+
}
|
|
158
363
|
/** Start a fresh replay pass. */
|
|
159
364
|
resetConsumed() {
|
|
160
365
|
this.consumed.clear();
|
|
@@ -174,16 +379,31 @@ export class Journal {
|
|
|
174
379
|
*/
|
|
175
380
|
export class RunClock {
|
|
176
381
|
value;
|
|
382
|
+
listeners;
|
|
177
383
|
constructor(startedAt) {
|
|
178
384
|
this.value = startedAt;
|
|
179
385
|
}
|
|
180
386
|
now() {
|
|
181
387
|
return this.value;
|
|
182
388
|
}
|
|
389
|
+
/**
|
|
390
|
+
* Observe every forward move of THIS clock. A `race` subscribes to each arm's clock, because an
|
|
391
|
+
* in-flight effect that lands after the arm was cancelled advances the arm past the frontier,
|
|
392
|
+
* and the cut has to be re-decided on the fact rather than left where the settle put it. A fork
|
|
393
|
+
* does not inherit listeners: the subscriber asked about one branch, not its descendants — a
|
|
394
|
+
* nested branch's history reaches the arm's own clock at the join, which fires this then.
|
|
395
|
+
*/
|
|
396
|
+
onAdvance(fn) {
|
|
397
|
+
(this.listeners ??= []).push(fn);
|
|
398
|
+
}
|
|
183
399
|
/** Advance past an effect this branch awaited. Monotone: an out-of-order settle cannot rewind. */
|
|
184
400
|
advance(endedAt) {
|
|
185
|
-
if (endedAt > this.value)
|
|
401
|
+
if (endedAt > this.value) {
|
|
186
402
|
this.value = endedAt;
|
|
403
|
+
if (this.listeners !== undefined)
|
|
404
|
+
for (const l of this.listeners)
|
|
405
|
+
l(this.value);
|
|
406
|
+
}
|
|
187
407
|
}
|
|
188
408
|
/** A branch inherits its parent's clock at the moment it forks. */
|
|
189
409
|
fork() {
|
package/dist/journal.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"journal.js","sourceRoot":"","sources":["../src/journal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;
|
|
1
|
+
{"version":3,"file":"journal.js","sourceRoot":"","sources":["../src/journal.ts"],"names":[],"mappings":"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"}
|
package/dist/keys.d.ts
CHANGED
|
@@ -16,6 +16,15 @@
|
|
|
16
16
|
import type { EffectKind } from "./primitives.js";
|
|
17
17
|
/** The concurrency combinators that open a scope. */
|
|
18
18
|
export type ScopeKind = "parallel" | "race" | "fanOut" | "conclave";
|
|
19
|
+
/**
|
|
20
|
+
* What a journal entry can be keyed as: an effect, or a concurrency SCOPE.
|
|
21
|
+
*
|
|
22
|
+
* The scope kinds are here because a combinator writes an entry for itself, and without them that
|
|
23
|
+
* entry has no legal key and no legal kind — which is the state this package was in: scopes pushed
|
|
24
|
+
* a frame, allocated an occurrence, and journalled nothing, so a replayed `race` re-raced and could
|
|
25
|
+
* resolve a different arm with nothing recorded saying which one had won.
|
|
26
|
+
*/
|
|
27
|
+
export type JournalKind = EffectKind | ScopeKind;
|
|
19
28
|
export interface ScopeFrame {
|
|
20
29
|
readonly kind: ScopeKind;
|
|
21
30
|
/** The combinator's step name, or null when it was not named. */
|
|
@@ -27,7 +36,7 @@ export interface ScopeFrame {
|
|
|
27
36
|
}
|
|
28
37
|
export interface StepKey {
|
|
29
38
|
readonly scope: readonly ScopeFrame[];
|
|
30
|
-
readonly kind:
|
|
39
|
+
readonly kind: JournalKind;
|
|
31
40
|
/** The literal step name, or "" when the effect was not named. */
|
|
32
41
|
readonly name: string;
|
|
33
42
|
readonly occurrence: number;
|
|
@@ -51,6 +60,27 @@ export declare const DIGEST_PREFIX: "sha256:";
|
|
|
51
60
|
*/
|
|
52
61
|
export declare function requestId(runId: string, key: StepKey, inputHash: string, attempt?: number): string;
|
|
53
62
|
export declare function digest(value: unknown): string;
|
|
63
|
+
/**
|
|
64
|
+
* A program's identity, as one function so there is only one answer.
|
|
65
|
+
*
|
|
66
|
+
* The interpreter pins a run to this and a migration records the source it moved TO, and those two
|
|
67
|
+
* must be the same hash of the same thing — a second copy of `digest({ source })` in another package
|
|
68
|
+
* is a different function's answer wearing this one's name the moment either side changes.
|
|
69
|
+
*/
|
|
70
|
+
export declare function programHashOf(source: string): string;
|
|
71
|
+
/**
|
|
72
|
+
* The characters the key grammar reserves as STRUCTURE.
|
|
73
|
+
*
|
|
74
|
+
* `frameString` and `stepKeyString` below build a key by concatenation and escape nothing, so any
|
|
75
|
+
* of these appearing inside a name or a branch key forges a path segment: a single branch keyed
|
|
76
|
+
* `a/parallel:inner#0/b:b` prints exactly what a genuinely nested `a` → `inner` → `b` prints. The
|
|
77
|
+
* validator refuses them at the source rather than escaping them here, because escaping would
|
|
78
|
+
* change every key already written to a journal.
|
|
79
|
+
*
|
|
80
|
+
* It lives beside the grammar that reserves them: a key format that gains a delimiter and leaves
|
|
81
|
+
* this set behind gives a validator that guards the wrong characters and still passes its tests.
|
|
82
|
+
*/
|
|
83
|
+
export declare const KEY_RESERVED_RE: RegExp;
|
|
54
84
|
/** The canonical string form, used in the journal, the trace, and error messages. */
|
|
55
85
|
export declare function scopePathString(scope: readonly ScopeFrame[]): string;
|
|
56
86
|
export declare function stepKeyString(key: StepKey): string;
|
|
@@ -75,6 +105,14 @@ export declare class KeyScope {
|
|
|
75
105
|
* per combinator call, then {@link branch} once per branch.
|
|
76
106
|
*/
|
|
77
107
|
nextScope(kind: ScopeKind, name?: string | null): number;
|
|
108
|
+
/**
|
|
109
|
+
* The scope's OWN key, in the namespace that opened it.
|
|
110
|
+
*
|
|
111
|
+
* Allocation-free on purpose: {@link KeyScope.nextScope} already allocated the occurrence, and
|
|
112
|
+
* calling this must not consume a second one. The scope entry therefore sits beside the effects
|
|
113
|
+
* of the namespace that opened it (`/race:first-answer#0`), while its branches live under it.
|
|
114
|
+
*/
|
|
115
|
+
scopeKey(kind: ScopeKind, name: string | null, occurrence: number): StepKey;
|
|
78
116
|
/** The child namespace for one branch of a scope opened from here. */
|
|
79
117
|
branch(kind: ScopeKind, name: string | null, occurrence: number, branchKey: string): KeyScope;
|
|
80
118
|
}
|
package/dist/keys.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAGlD,qDAAqD;AACrD,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEpE;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,SAAS,CAAC;AAEjD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,iEAAiE;IACjE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,wEAAwE;IACxE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,iEAAiE;IACjE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;IACtC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED;gDACgD;AAChD,eAAO,MAAM,aAAa,EAAG,SAAkB,CAAC;AAEhD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,SAAI,GAAG,MAAM,CAI7F;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAE7C;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,eAAe,QAAU,CAAC;AA0BvC,qFAAqF;AACrF,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,GAAG,MAAM,CAEpE;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAGlD;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,CAE7D;AAED;;;;;;;GAOG;AACH,qBAAa,QAAQ;IAIP,QAAQ,CAAC,IAAI,EAAE,SAAS,UAAU,EAAE;IAHhD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6B;IAC1D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;gBAEpC,IAAI,GAAE,SAAS,UAAU,EAAO;IAErD,uFAAuF;IACvF,UAAU,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,SAAK,GAAG,OAAO;IAchD;;;OAGG;IACH,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,GAAE,MAAM,GAAG,IAAW,GAAG,MAAM;IAO9D;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO;IAI3E,sEAAsE;IACtE,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,QAAQ;CAO9F;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,MAAM,EAAE,CAIrG"}
|
package/dist/keys.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { createHash } from "node:crypto";
|
|
17
17
|
import { canonicalize } from "json-canonicalize";
|
|
18
|
+
import { RuntimeFault } from "./errors.js";
|
|
18
19
|
/** `sha256:<hex>` over the RFC 8785 canonical form, matching the digest discipline used for
|
|
19
20
|
* contract artifacts elsewhere in the repo. */
|
|
20
21
|
export const DIGEST_PREFIX = "sha256:";
|
|
@@ -40,6 +41,45 @@ export function requestId(runId, key, inputHash, attempt = 0) {
|
|
|
40
41
|
export function digest(value) {
|
|
41
42
|
return DIGEST_PREFIX + createHash("sha256").update(canonicalize(value), "utf8").digest("hex");
|
|
42
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* A program's identity, as one function so there is only one answer.
|
|
46
|
+
*
|
|
47
|
+
* The interpreter pins a run to this and a migration records the source it moved TO, and those two
|
|
48
|
+
* must be the same hash of the same thing — a second copy of `digest({ source })` in another package
|
|
49
|
+
* is a different function's answer wearing this one's name the moment either side changes.
|
|
50
|
+
*/
|
|
51
|
+
export function programHashOf(source) {
|
|
52
|
+
return digest({ source });
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The characters the key grammar reserves as STRUCTURE.
|
|
56
|
+
*
|
|
57
|
+
* `frameString` and `stepKeyString` below build a key by concatenation and escape nothing, so any
|
|
58
|
+
* of these appearing inside a name or a branch key forges a path segment: a single branch keyed
|
|
59
|
+
* `a/parallel:inner#0/b:b` prints exactly what a genuinely nested `a` → `inner` → `b` prints. The
|
|
60
|
+
* validator refuses them at the source rather than escaping them here, because escaping would
|
|
61
|
+
* change every key already written to a journal.
|
|
62
|
+
*
|
|
63
|
+
* It lives beside the grammar that reserves them: a key format that gains a delimiter and leaves
|
|
64
|
+
* this set behind gives a validator that guards the wrong characters and still passes its tests.
|
|
65
|
+
*/
|
|
66
|
+
export const KEY_RESERVED_RE = /[/#:]/;
|
|
67
|
+
/**
|
|
68
|
+
* Refuse a name or branch key that would forge structure, at the point the key is MINTED.
|
|
69
|
+
*
|
|
70
|
+
* The validator closes the static case, and it is the better place to close it — an author gets the
|
|
71
|
+
* refusal before the run starts. But it can only see a LITERAL. `fanOut(items, (i) => sleep("1m",
|
|
72
|
+
* { name: i }))` names each step after its item, which is idiomatic and is how a fan-out gets
|
|
73
|
+
* distinct keys at all, and those values are usually data from outside the program. **A forged key
|
|
74
|
+
* built from data is strictly easier to reach than one written in the source, and it is exactly the
|
|
75
|
+
* case static analysis cannot see.** So the guard exists in both places on purpose: this is not a
|
|
76
|
+
* belt on the validator's braces, it covers inputs the validator provably cannot.
|
|
77
|
+
*/
|
|
78
|
+
function assertKeySafe(value, what) {
|
|
79
|
+
if (!KEY_RESERVED_RE.test(value))
|
|
80
|
+
return;
|
|
81
|
+
throw new RuntimeFault("L3025", `${what} is ${JSON.stringify(value)}, which contains a character the step-key grammar reserves (\`/\`, \`#\` or \`:\`). Keys are built by concatenation and nothing is escaped, so this value would forge a scope path that a genuinely nested scope also produces — and two locations sharing a key share a durable journal row, silently when their inputs match.`);
|
|
82
|
+
}
|
|
43
83
|
function frameString(f) {
|
|
44
84
|
const named = f.name === null ? f.kind : `${f.kind}:${f.name}`;
|
|
45
85
|
return `/${named}#${f.occurrence}/b:${f.branch}`;
|
|
@@ -72,6 +112,13 @@ export class KeyScope {
|
|
|
72
112
|
}
|
|
73
113
|
/** Allocate the next occurrence for an effect in this namespace, and build its key. */
|
|
74
114
|
nextEffect(kind, name = "") {
|
|
115
|
+
// The validator refuses a reserved character in a LITERAL name, but a name can be computed —
|
|
116
|
+
// `fanOut(items, (lens) => sleep("1m", { name: lens }))` is idiomatic and its name is only
|
|
117
|
+
// known here. A static check cannot see that value, so the refusal has to exist at the point
|
|
118
|
+
// where it does. Without this, a fan-out over items containing a `/` forges scope structure
|
|
119
|
+
// from data, which is the same collision the validator closes for the static case and is
|
|
120
|
+
// strictly easier to hit, because the items usually come from outside the program.
|
|
121
|
+
assertKeySafe(name, `the name of this \`${kind}\``);
|
|
75
122
|
const tag = `${kind}:${name}`;
|
|
76
123
|
const occurrence = this.effectCounts.get(tag) ?? 0;
|
|
77
124
|
this.effectCounts.set(tag, occurrence + 1);
|
|
@@ -87,8 +134,22 @@ export class KeyScope {
|
|
|
87
134
|
this.scopeCounts.set(tag, occurrence + 1);
|
|
88
135
|
return occurrence;
|
|
89
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* The scope's OWN key, in the namespace that opened it.
|
|
139
|
+
*
|
|
140
|
+
* Allocation-free on purpose: {@link KeyScope.nextScope} already allocated the occurrence, and
|
|
141
|
+
* calling this must not consume a second one. The scope entry therefore sits beside the effects
|
|
142
|
+
* of the namespace that opened it (`/race:first-answer#0`), while its branches live under it.
|
|
143
|
+
*/
|
|
144
|
+
scopeKey(kind, name, occurrence) {
|
|
145
|
+
return { scope: this.path, kind, name: name ?? "", occurrence };
|
|
146
|
+
}
|
|
90
147
|
/** The child namespace for one branch of a scope opened from here. */
|
|
91
148
|
branch(kind, name, occurrence, branchKey) {
|
|
149
|
+
// The other half of the same hazard, and the one the review actually found. A `fanOut` keyed by
|
|
150
|
+
// a function over its items produces branch keys from DATA, so this is reachable without anyone
|
|
151
|
+
// writing a suspicious literal anywhere in the program.
|
|
152
|
+
assertKeySafe(branchKey, `the branch key of this \`${kind}\``);
|
|
92
153
|
return new KeyScope([...this.path, { kind, name, occurrence, branch: branchKey }]);
|
|
93
154
|
}
|
|
94
155
|
}
|
package/dist/keys.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"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"}
|