@volter/world-core 3.0.166 → 3.0.167
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/src/changeset.d.ts +9 -0
- package/dist/src/changeset.js +5 -2
- package/dist/src/completeness.d.ts +36 -0
- package/dist/src/completeness.js +46 -0
- package/dist/src/derived-core.d.ts +27 -0
- package/dist/src/derived-core.js +31 -2
- package/dist/src/events.d.ts +6 -1
- package/dist/src/events.js +58 -13
- package/dist/src/index.d.ts +4 -2
- package/dist/src/index.js +3 -2
- package/dist/src/openai-wire.js +0 -1
- package/dist/src/pack-fetch.d.ts +1 -3
- package/dist/src/pack-fetch.js +103 -43
- package/dist/src/runtime.d.ts +5 -2
- package/dist/src/runtime.js +3 -1
- package/dist/src/scenario-consume.d.ts +43 -0
- package/dist/src/scenario-consume.js +111 -0
- package/dist/src/scenario-serve.d.ts +16 -0
- package/dist/src/scenario-serve.js +47 -0
- package/dist/src/scenario-timeline.d.ts +131 -0
- package/dist/src/scenario-timeline.js +242 -0
- package/dist/src/scenario.d.ts +15 -93
- package/dist/src/scenario.js +34 -323
- package/dist/src/sockets.d.ts +8 -4
- package/dist/src/sockets.js +1 -1
- package/dist/src/world-clock.d.ts +3 -0
- package/dist/src/world-clock.js +14 -9
- package/package.json +1 -1
- package/src/changeset.ts +13 -3
- package/src/completeness.ts +57 -0
- package/src/derived-core.ts +41 -2
- package/src/events.ts +58 -11
- package/src/index.ts +5 -3
- package/src/openai-wire.ts +0 -1
- package/src/pack-fetch.ts +81 -39
- package/src/runtime.ts +5 -2
- package/src/scenario-consume.ts +123 -0
- package/src/scenario-serve.ts +47 -0
- package/src/scenario-timeline.ts +269 -0
- package/src/scenario.ts +43 -341
- package/src/sockets.ts +5 -5
- package/src/world-clock.ts +16 -9
package/dist/src/changeset.d.ts
CHANGED
|
@@ -21,6 +21,9 @@ export type LedgerPosition = {
|
|
|
21
21
|
stateService: string;
|
|
22
22
|
count: number;
|
|
23
23
|
lastActionId: string | null;
|
|
24
|
+
/** the twin's history view and its position in it: what a branch at this mark starts from (ADR 0036 §5) */
|
|
25
|
+
view?: string;
|
|
26
|
+
position?: number;
|
|
24
27
|
};
|
|
25
28
|
export declare const MARKER_KIND = "volter.world.marker.v1";
|
|
26
29
|
export declare const CHANGESET_KIND = "volter.world.changeset.v1";
|
|
@@ -33,6 +36,10 @@ export type WorldMarker = {
|
|
|
33
36
|
/** Every ledger that existed at capture time. A ledger absent here is treated as position 0
|
|
34
37
|
* (a twin that recorded its first action AFTER the mark contributes its whole ledger). */
|
|
35
38
|
ledgers: LedgerPosition[];
|
|
39
|
+
/** the World clock's instant at the mark, or null when it ran on the machine's time (a run's position, ADR 0036 §5) */
|
|
40
|
+
clock?: string | null;
|
|
41
|
+
/** whether the machine's fault door took the app's disk with the mark */
|
|
42
|
+
disk?: boolean;
|
|
36
43
|
};
|
|
37
44
|
/** The id reserved for the synthetic "everything this world has ever recorded" base. */
|
|
38
45
|
export declare const WORLD_BOOT_MARKER_ID = "world-boot";
|
|
@@ -176,6 +183,8 @@ export declare function captureMarker(opts: {
|
|
|
176
183
|
world: string;
|
|
177
184
|
ledgers: LedgerRef[];
|
|
178
185
|
createdAt?: string;
|
|
186
|
+
clock?: string | null;
|
|
187
|
+
disk?: boolean;
|
|
179
188
|
}): WorldMarker;
|
|
180
189
|
/** A marker taken in world A says nothing about world B's ledgers — comparing them would
|
|
181
190
|
* produce a confident, wrong delta. Refuse. */
|
package/dist/src/changeset.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseExactJson } from "./exact-json.js";
|
|
2
2
|
import { resolveReferences } from "./references.js";
|
|
3
3
|
import { withHistoryLocks } from "./ancestry.js";
|
|
4
|
-
import { captureParentHistory, historyChanges, historyEntries, historyPrefix, readHistoryView } from "./history.js";
|
|
4
|
+
import { captureHistory, captureParentHistory, historyChanges, historyEntries, historyPrefix, readHistoryView } from "./history.js";
|
|
5
5
|
import { worldPaths } from "./storage.js";
|
|
6
6
|
// The changeset primitive — "commit" for operational reality (docs/concepts/the-model.md, v0).
|
|
7
7
|
//
|
|
@@ -84,14 +84,17 @@ export function worldBootMarker(world, createdAt) {
|
|
|
84
84
|
export function captureMarker(opts) {
|
|
85
85
|
const ledgers = opts.ledgers.map((ledger) => {
|
|
86
86
|
const rows = listActions(ledger.stateService, ledger.controlRoot);
|
|
87
|
+
const { view, position } = captureHistory(ledger.stateService, ledger.controlRoot);
|
|
87
88
|
return {
|
|
88
89
|
service: ledger.service,
|
|
89
90
|
stateService: ledger.stateService,
|
|
90
91
|
count: rows.length,
|
|
91
92
|
lastActionId: rows.length ? rows[rows.length - 1].id : null,
|
|
93
|
+
view,
|
|
94
|
+
position,
|
|
92
95
|
};
|
|
93
96
|
});
|
|
94
|
-
return { kind: MARKER_KIND, id: opts.id, world: opts.world, createdAt: opts.createdAt ?? new Date().toISOString(), ledgers };
|
|
97
|
+
return { kind: MARKER_KIND, id: opts.id, world: opts.world, createdAt: opts.createdAt ?? new Date().toISOString(), ledgers, ...(opts.clock !== undefined ? { clock: opts.clock } : {}), ...(opts.disk ? { disk: true } : {}) };
|
|
95
98
|
}
|
|
96
99
|
function positionKey(service, stateService) {
|
|
97
100
|
return `${service}\u0000${stateService}`;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** What a relay or a twin notes; the World's time is added as it is appended. */
|
|
2
|
+
type CompletenessNote = {
|
|
3
|
+
kind: 'egress';
|
|
4
|
+
destination: string;
|
|
5
|
+
outcome: 'refused' | 'passed';
|
|
6
|
+
rule?: string;
|
|
7
|
+
} | {
|
|
8
|
+
kind: 'unimplemented';
|
|
9
|
+
vendor: string;
|
|
10
|
+
method: string;
|
|
11
|
+
path: string;
|
|
12
|
+
operation?: string;
|
|
13
|
+
} | {
|
|
14
|
+
kind: 'machine';
|
|
15
|
+
layer: string;
|
|
16
|
+
fault: string;
|
|
17
|
+
target?: string;
|
|
18
|
+
};
|
|
19
|
+
/** Append one record to the World's completeness file named in `env`; a process outside a World notes nothing. */
|
|
20
|
+
export declare function noteCompleteness(env: Record<string, string | undefined>, record: CompletenessNote): void;
|
|
21
|
+
export type Completeness = {
|
|
22
|
+
/** zero when the World was the whole internet the run reached */
|
|
23
|
+
total: number;
|
|
24
|
+
egress: Record<string, {
|
|
25
|
+
refused: number;
|
|
26
|
+
passed: number;
|
|
27
|
+
}>;
|
|
28
|
+
unimplemented: Record<string, Record<string, number>>;
|
|
29
|
+
/** machine faults the run placed and no fault door carried, by layer and kind (ADR 0036 §4) */
|
|
30
|
+
machine: Record<string, number>;
|
|
31
|
+
};
|
|
32
|
+
/** How many records the completeness file holds: where a run starts counting. */
|
|
33
|
+
export declare function completenessMark(file: string): number;
|
|
34
|
+
/** What reached past the World after the file's `mark`, counted per destination and per vendor's operation. */
|
|
35
|
+
export declare function completenessSince(file: string, mark: number): Completeness;
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// A run's completeness (ADR 0036 §9; architecture, "Scenarios"): the World is the application's internet closure, so
|
|
2
|
+
// what reached past it is what the World is missing. The relay notes every destination no twin serves (refused, or
|
|
3
|
+
// passed to the real internet where the World lets it), a twin every request to an operation it does not implement
|
|
4
|
+
// (answered as the vendor answers an unknown request), and a run every machine fault no fault door carried. All are
|
|
5
|
+
// appended, at World time, to the instance's completeness file (TWIN_WORLD_COMPLETENESS_FILE), and a run counts them.
|
|
6
|
+
import { getActiveWorldStore } from "./world-store.js";
|
|
7
|
+
import clockForm from '../world-clock.cjs';
|
|
8
|
+
const WORLD_COMPLETENESS_ENV = 'TWIN_WORLD_COMPLETENESS_FILE';
|
|
9
|
+
/** The World's time in `env` (its clock file's, else the machine's). */
|
|
10
|
+
function worldTimeIn(env) {
|
|
11
|
+
const file = env.TWIN_WORLD_CLOCK_FILE;
|
|
12
|
+
const held = file ? getActiveWorldStore().read(file) : null;
|
|
13
|
+
return new Date(held ? clockForm.clockNowMs(clockForm.parseClock(held), Date.now()) : Date.now()).toISOString();
|
|
14
|
+
}
|
|
15
|
+
/** Append one record to the World's completeness file named in `env`; a process outside a World notes nothing. */
|
|
16
|
+
export function noteCompleteness(env, record) {
|
|
17
|
+
const file = env[WORLD_COMPLETENESS_ENV];
|
|
18
|
+
if (!file)
|
|
19
|
+
return;
|
|
20
|
+
try {
|
|
21
|
+
getActiveWorldStore().append(file, `${JSON.stringify({ at: worldTimeIn(env), ...record })}\n`);
|
|
22
|
+
}
|
|
23
|
+
catch { /* the answer still goes out */ }
|
|
24
|
+
}
|
|
25
|
+
/** How many records the completeness file holds: where a run starts counting. */
|
|
26
|
+
export function completenessMark(file) {
|
|
27
|
+
return (getActiveWorldStore().read(file) ?? '').split('\n').filter((line) => line.trim()).length;
|
|
28
|
+
}
|
|
29
|
+
/** What reached past the World after the file's `mark`, counted per destination and per vendor's operation. */
|
|
30
|
+
export function completenessSince(file, mark) {
|
|
31
|
+
const out = { total: 0, egress: {}, unimplemented: {}, machine: {} };
|
|
32
|
+
for (const line of (getActiveWorldStore().read(file) ?? '').split('\n').filter((l) => l.trim()).slice(mark)) {
|
|
33
|
+
const r = JSON.parse(line);
|
|
34
|
+
out.total += 1;
|
|
35
|
+
if (r.kind === 'egress')
|
|
36
|
+
(out.egress[r.destination] ??= { refused: 0, passed: 0 })[r.outcome] += 1;
|
|
37
|
+
else if (r.kind === 'machine')
|
|
38
|
+
out.machine[`${r.layer} ${r.fault}`] = (out.machine[`${r.layer} ${r.fault}`] ?? 0) + 1;
|
|
39
|
+
else {
|
|
40
|
+
const ops = out.unimplemented[r.vendor] ??= {};
|
|
41
|
+
const key = r.operation ?? `${r.method} ${r.path}`;
|
|
42
|
+
ops[key] = (ops[key] ?? 0) + 1;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
@@ -17,6 +17,7 @@ import { type TwinAction } from './actions.js';
|
|
|
17
17
|
import type { DerivedCall, DerivedCoreOutcome, DerivedHandler, DerivedOperation } from './derived.js';
|
|
18
18
|
import { type PackDescriptor } from './packRegistry.js';
|
|
19
19
|
import type { ScenarioDecision, ScenarioHandler } from './scenario.js';
|
|
20
|
+
import { type ScenarioLedger } from './scenario-consume.js';
|
|
20
21
|
/** How a stored field gets its value when the server assigns it. `now` is the world clock in the
|
|
21
22
|
* manifest's time format; `id` the subject's id; a `value` is stored as given. */
|
|
22
23
|
/** A field the server assigns: the World clock's now, the subject's id, a fixed value, or a template over the stored
|
|
@@ -1018,6 +1019,21 @@ export type SemanticsContext = {
|
|
|
1018
1019
|
/** What the World's scenario decided for this turn (a model vendor's answer: the handler matched, or the miss), on an
|
|
1019
1020
|
* operation the pack's `semantics/scenario.ts` names; undefined elsewhere, or when the World carries no scenario. */
|
|
1020
1021
|
scenario: ScenarioDecision | undefined;
|
|
1022
|
+
/** The vendor's judgement at one of its decision points (a fraud score, an identity check, deliverability), for the
|
|
1023
|
+
* subject it is keyed to: what the World's scenario decided, else `byDefault`, the vendor's normal outcome; recorded in
|
|
1024
|
+
* the twin's log either way (architecture, "Scenarios"). The handler follows it with what the vendor does next. */
|
|
1025
|
+
decide<T>(point: string, subject: string, byDefault: T): Promise<T>;
|
|
1026
|
+
/** A fault the World's scenario placed at a point of the pack's own (one item of a batch) for `subject`, recorded as
|
|
1027
|
+
* landed; undefined when none is placed. */
|
|
1028
|
+
fault(point: string, subject: string): Promise<{
|
|
1029
|
+
kind: string;
|
|
1030
|
+
status?: number;
|
|
1031
|
+
message?: string;
|
|
1032
|
+
errorType?: string;
|
|
1033
|
+
} | undefined>;
|
|
1034
|
+
/** A World fact (an exchange rate, a holiday, a place) the twin answers with in its own shape, recorded as answered;
|
|
1035
|
+
* undefined when the World holds none. */
|
|
1036
|
+
fact(kind: string, key: string): Promise<unknown>;
|
|
1021
1037
|
/** A message the vendor sends the application's server and decides by its answer, waited on for at most `within`
|
|
1022
1038
|
* milliseconds (a real-time authorization request): its status and body, or why there was none. */
|
|
1023
1039
|
ask(url: string, init: RequestInit, within: number): Promise<ApplicationAnswer>;
|
|
@@ -1295,6 +1311,17 @@ export type HandlerContext = Omit<SemanticsContext, 'now' | 'core' | 'atomically
|
|
|
1295
1311
|
export type WriteHookContext = Omit<HandlerContext, 'write' | 'batch' | 'create' | 'createUnique' | 'change' | 'issue' | 'record' | 'accumulate' | 'tenant' | 'deploymentObject' | 'accountKey' | 'at' | 'over'>;
|
|
1296
1312
|
/** A derived pack's handler: one operation, by its operationId, over the contract's context. */
|
|
1297
1313
|
export type Handler = (ctx: HandlerContext) => Promise<Response>;
|
|
1314
|
+
/** What a context consumes the World's scenario through (scenario-consume.ts): its twin's bookkeeping, at its time. */
|
|
1315
|
+
export declare function scenarioLedgerOf(ctx: object): ScenarioLedger;
|
|
1316
|
+
/** `GET /twin/scenario`: what the World's scenario placed on this vendor, and what the twin consumed of it. */
|
|
1317
|
+
export declare function scenarioRecords(m: DerivedManifest, root?: string): {
|
|
1318
|
+
vendor: string;
|
|
1319
|
+
placed: unknown[];
|
|
1320
|
+
consumed: {
|
|
1321
|
+
decisions: unknown[];
|
|
1322
|
+
faults: unknown[];
|
|
1323
|
+
};
|
|
1324
|
+
};
|
|
1298
1325
|
export declare function contextFor(m: DerivedManifest, call: DerivedCall, scope: CoreScope): Promise<SemanticsContext>;
|
|
1299
1326
|
/** A pack's semantics handlers as the dispatch's handlers. */
|
|
1300
1327
|
export declare function bindSemantics(m: DerivedManifest, handlers: Record<string, Semantics>, scope?: CoreScope): Record<string, DerivedHandler>;
|
package/dist/src/derived-core.js
CHANGED
|
@@ -38,6 +38,8 @@ import { packReferences } from "./references.js";
|
|
|
38
38
|
import { twinPublicBase, withRequestScopes } from "./twin-fetch.js";
|
|
39
39
|
import { inVendorMove, isReadOnlyRequest, runAsVendorMove, writesRefused } from "./request-scope.js";
|
|
40
40
|
import { getPack } from "./packRegistry.js";
|
|
41
|
+
import { decide, fact, faultAt, listLag, recordFault, serviceLedger } from "./scenario-consume.js";
|
|
42
|
+
import { durationMs, placedOn } from "./scenario-timeline.js";
|
|
41
43
|
import { bodyTextOf, checkJsonBody, takeParsedBody } from "./derived.js";
|
|
42
44
|
/** What a move to `to` stores in the field: a boolean field's value is a boolean, and a derived field's
|
|
43
45
|
* value is written by the move's effects (a state name is not a timestamp), so it stores nothing itself. */
|
|
@@ -998,7 +1000,7 @@ async function finishWrite(m, call, resource, row, result, operation, params, ro
|
|
|
998
1000
|
const endpoint = (kind) => ({ ...kind, storedAs: storedType(m, kind.storedAs), ...(kind.joins ? { joins: Object.fromEntries(Object.entries(kind.joins).map(([name, join]) => [name, { ...join, storedAs: storedType(m, join.storedAs) }])) } : {}) });
|
|
999
1001
|
const events = { ...m.events, ...(m.events.store ? { store: storedType(m, m.events.store) } : {}), endpoints: 'storedAs' in m.events.endpoints ? endpoint(m.events.endpoints) : m.events.endpoints.map(endpoint) };
|
|
1000
1002
|
const write = { operation, storedType: m.resources[resource]?.storedAs ?? resource, body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone() };
|
|
1001
|
-
await deliverEvents(m.service, events, write, async (type) => (events.render ? events.render(await context(), write, type) : body), undefined, events.values ? async (type) => events.values(await context(), write, type) : undefined, scopeOfCall.get(call)?.application);
|
|
1003
|
+
await deliverEvents(m.service, events, write, async (type) => (events.render ? events.render(await context(), write, type) : body), undefined, events.values ? async (type) => events.values(await context(), write, type) : undefined, scopeOfCall.get(call)?.application, m.vendor);
|
|
1002
1004
|
}
|
|
1003
1005
|
if (m.sockets?.length)
|
|
1004
1006
|
socketWrite(m.service, root, { operation, storedType: m.resources[resource]?.storedAs ?? resource, body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone() });
|
|
@@ -1439,7 +1441,15 @@ export async function serveCore(m, call, scope = {}) {
|
|
|
1439
1441
|
case 'list': {
|
|
1440
1442
|
if (!m.list)
|
|
1441
1443
|
return { unmodeled: 'no manifest list configuration' };
|
|
1442
|
-
|
|
1444
|
+
// a `lag` the World's scenario placed: a write younger than it is missing from lists, as an eventually consistent
|
|
1445
|
+
// index misses it (architecture, "Scenarios"); recorded when it withholds one
|
|
1446
|
+
const ledger = serviceLedger({ vendor: m.vendor, service: m.service, root, at, recording: !(scope.readOnly === true || isReadOnlyRequest(call.request)) });
|
|
1447
|
+
const lag = listLag(ledger, op.id);
|
|
1448
|
+
const settled = (r) => !lag || Date.parse(r.updatedAt) <= Date.parse(at) - durationMs(lag.by);
|
|
1449
|
+
const listed = stored(m, resource, root).filter(underParent);
|
|
1450
|
+
if (lag && listed.some((r) => !settled(r)))
|
|
1451
|
+
await recordFault(ledger, lag, { operation: op.id, withheld: listed.filter((r) => !settled(r)).map((r) => r.id) });
|
|
1452
|
+
const rows = listed.filter(settled).map((r) => view(m, resource, r));
|
|
1443
1453
|
const arranged = arrangeRows(m, resource, params, rows);
|
|
1444
1454
|
if (!Array.isArray(arranged))
|
|
1445
1455
|
return { served: vendorError(m, arranged) };
|
|
@@ -1542,6 +1552,20 @@ const coreReadsBody = (type) => {
|
|
|
1542
1552
|
const media = (type ?? '').split(';')[0].trim().toLowerCase();
|
|
1543
1553
|
return media === '' || media === 'application/json' || media.endsWith('+json') || media === 'application/x-www-form-urlencoded' || media.startsWith('multipart/') || media.startsWith('text/');
|
|
1544
1554
|
};
|
|
1555
|
+
const ledgers = new WeakMap();
|
|
1556
|
+
/** What a context consumes the World's scenario through (scenario-consume.ts): its twin's bookkeeping, at its time. */
|
|
1557
|
+
export function scenarioLedgerOf(ctx) {
|
|
1558
|
+
const ledger = ledgers.get(ctx);
|
|
1559
|
+
if (!ledger)
|
|
1560
|
+
throw new Error('a scenario ledger is a context the kernel opened');
|
|
1561
|
+
return ledger();
|
|
1562
|
+
}
|
|
1563
|
+
/** `GET /twin/scenario`: what the World's scenario placed on this vendor, and what the twin consumed of it. */
|
|
1564
|
+
export function scenarioRecords(m, root) {
|
|
1565
|
+
const placed = placedOn(m.vendor);
|
|
1566
|
+
const kept = (type) => resourcesOfType(m.service, storedType(m, type), root).map((r) => ownFields(r));
|
|
1567
|
+
return { vendor: m.vendor, placed, consumed: { decisions: kept('_decision'), faults: kept('_fault') } };
|
|
1568
|
+
}
|
|
1545
1569
|
export async function contextFor(m, call, scope) {
|
|
1546
1570
|
const root = scope.root;
|
|
1547
1571
|
const asAsked = call;
|
|
@@ -1611,6 +1635,7 @@ export async function contextFor(m, call, scope) {
|
|
|
1611
1635
|
const row = context.row(link.resource, link.id);
|
|
1612
1636
|
return row && row.deleted !== true ? row : undefined;
|
|
1613
1637
|
};
|
|
1638
|
+
const ledger = () => ({ vendor: m.vendor, service: m.service, root, at, recording: !(scope.readOnly === true || isReadOnlyRequest(call.request)), rows: (type) => context.rowsRaw(type), record: (type, fields, id) => context.record(type, fields, id) });
|
|
1614
1639
|
const context = {
|
|
1615
1640
|
call,
|
|
1616
1641
|
params,
|
|
@@ -1620,6 +1645,9 @@ export async function contextFor(m, call, scope) {
|
|
|
1620
1645
|
root,
|
|
1621
1646
|
publicBase: twinPublicBase(call.request),
|
|
1622
1647
|
scenario: scenarioDecisionOf(call.request),
|
|
1648
|
+
decide: (point, subject, byDefault) => decide(ledger(), point, subject, byDefault),
|
|
1649
|
+
fault: (point, subject) => faultAt(ledger(), point, subject),
|
|
1650
|
+
fact: (kind, key) => fact(ledger(), kind, key),
|
|
1623
1651
|
ask: (url, init, within) => askApplication(url, init, within, scope.application),
|
|
1624
1652
|
worldEnv: (name) => worldEnvValue(name),
|
|
1625
1653
|
asVendor: (fn) => runAsVendorMove(fn),
|
|
@@ -1903,6 +1931,7 @@ export async function contextFor(m, call, scope) {
|
|
|
1903
1931
|
};
|
|
1904
1932
|
if (batchable(m, root))
|
|
1905
1933
|
bindBatch(m, call, context, params, root, at, rawRow);
|
|
1934
|
+
ledgers.set(context, ledger);
|
|
1906
1935
|
return context;
|
|
1907
1936
|
}
|
|
1908
1937
|
/**
|
package/dist/src/events.d.ts
CHANGED
|
@@ -201,4 +201,9 @@ export type ApplicationStandIn = (url: string, init: RequestInit) => Promise<{
|
|
|
201
201
|
export declare function askApplication(url: string, init: RequestInit, within: number, standIn?: ApplicationStandIn): Promise<ApplicationAnswer>;
|
|
202
202
|
/** Every event a write sends, rendered, stored when the vendor keeps its events, signed, delivered to each endpoint
|
|
203
203
|
* that takes it, and recorded. */
|
|
204
|
-
export declare function deliverEvents(service: string, decl: EventsDecl, write: EventWrite, data: (type: string) => Promise<Record<string, unknown>>, transport?: EventTransport, values?: (type: string) => Promise<Record<string, unknown>>, application?: ApplicationStandIn): Promise<EventDelivery[]>;
|
|
204
|
+
export declare function deliverEvents(service: string, decl: EventsDecl, write: EventWrite, data: (type: string) => Promise<Record<string, unknown>>, transport?: EventTransport, values?: (type: string) => Promise<Record<string, unknown>>, application?: ApplicationStandIn, vendor?: string): Promise<EventDelivery[]>;
|
|
205
|
+
/** Who hears that a twin held a packet for later (its pack's fetch, which sets the timer a running clock needs). */
|
|
206
|
+
export declare function onHeldDelivery(service: string, root: string | undefined, listener: () => void): void;
|
|
207
|
+
/** Send what a `webhook-delay` held that the World's clock has reached, in the order it fell due, and wait until it is
|
|
208
|
+
* sent; answer the instant the next held packet falls due, if any (ADR 0036 §4: the mover of the clock waits on it). */
|
|
209
|
+
export declare function releaseHeldDeliveries(service: string, root: string | undefined, record?: string, transport?: EventTransport): Promise<string | null>;
|
package/dist/src/events.js
CHANGED
|
@@ -14,6 +14,9 @@ import { deliveryTraceHeaders } from "./trace-context.js";
|
|
|
14
14
|
import { sendRecordedEvent } from "./event-delivery.js";
|
|
15
15
|
import { getActiveWorldStore, withWorldStore } from "./world-store.js";
|
|
16
16
|
import { worldPaths } from "./storage.js";
|
|
17
|
+
import { worldNow } from "./world-clock.js";
|
|
18
|
+
import { recordFault, serviceLedger, webhookFault } from "./scenario-consume.js";
|
|
19
|
+
import { durationMs } from "./scenario-timeline.js";
|
|
17
20
|
/** Whether an endpoint takes an event by its `match`: every field of one match (any, when several) equal to the event's
|
|
18
21
|
* value, or, a list, holding it. */
|
|
19
22
|
function matches(row, match, filled) {
|
|
@@ -289,7 +292,7 @@ export async function askApplication(url, init, within, standIn) {
|
|
|
289
292
|
}
|
|
290
293
|
/** Every event a write sends, rendered, stored when the vendor keeps its events, signed, delivered to each endpoint
|
|
291
294
|
* that takes it, and recorded. */
|
|
292
|
-
export async function deliverEvents(service, decl, write, data, transport = worldTransport, values, application) {
|
|
295
|
+
export async function deliverEvents(service, decl, write, data, transport = worldTransport, values, application, vendor) {
|
|
293
296
|
if (write.storedType === decl.store)
|
|
294
297
|
return [];
|
|
295
298
|
const dispatch = application ? async (url, body, headers) => {
|
|
@@ -298,11 +301,11 @@ export async function deliverEvents(service, decl, write, data, transport = worl
|
|
|
298
301
|
} : transport;
|
|
299
302
|
const sent = [];
|
|
300
303
|
for (const type of eventTypesOf(decl, write.operation))
|
|
301
|
-
sent.push(...(await deliverEventType(service, decl, write, data, dispatch, values, type)));
|
|
304
|
+
sent.push(...(await deliverEventType(service, decl, write, data, dispatch, values, type, vendor)));
|
|
302
305
|
return sent;
|
|
303
306
|
}
|
|
304
307
|
/** One event type a write sends: to the live endpoints that take it, stored when the vendor keeps its events. */
|
|
305
|
-
async function deliverEventType(service, decl, write, data, transport, values, type) {
|
|
308
|
+
async function deliverEventType(service, decl, write, data, transport, values, type, vendor) {
|
|
306
309
|
const all = twinResources(service, write.root);
|
|
307
310
|
const kinds = Array.isArray(decl.endpoints) ? decl.endpoints : [decl.endpoints];
|
|
308
311
|
// an endpoint's row with its joined rows under their names (`app.hook_url` reads the installation's App)
|
|
@@ -371,16 +374,35 @@ async function deliverEventType(service, decl, write, data, transport, values, t
|
|
|
371
374
|
// kept with the write, by an id of the delivery itself (two made at once never take the same one)
|
|
372
375
|
const record = decl.record ?? '_world_event_delivery';
|
|
373
376
|
const id = `${record.slice(1)}_v1_${seq}`;
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
377
|
+
const packet = { url, body, headers, event_type: type, sent_at: millis,
|
|
378
|
+
delivery_protocol: 1, event_id: String(filled.$id),
|
|
379
|
+
endpoint_type: kind.storedAs, endpoint_id: String(row.id),
|
|
380
|
+
delivery_state: 'queued', attempt_count: 0, current_attempt: null,
|
|
381
|
+
transport_status: null, finished_at: null, accepted_at: null,
|
|
382
|
+
};
|
|
383
|
+
await applyTwinWrite(service, { operation: `${record.slice(1)}.record`, subjectType: record, subjectId: id, fields: packet, occurredAt: write.occurredAt, actor: { kind: 'system' } }, write.root);
|
|
384
|
+
// a webhook fault the World's scenario placed (architecture, "Scenarios"): dropped, held until the World's clock
|
|
385
|
+
// reaches its delay (sent when the clock's mover reaches it, or by a running clock's timer: releaseHeldDeliveries),
|
|
386
|
+
// or sent twice, recorded as landed
|
|
387
|
+
const ledger = vendor ? serviceLedger({ vendor, service, root: write.root, at: write.occurredAt, recording: true }) : undefined;
|
|
388
|
+
const fault = ledger ? webhookFault(ledger, type) : undefined;
|
|
389
|
+
if (ledger && fault)
|
|
390
|
+
await recordFault(ledger, fault, { event: type, delivery: id });
|
|
391
|
+
if (fault?.kind === 'webhook-drop' || fault?.kind === 'webhook-delay') {
|
|
392
|
+
const held = fault.kind === 'webhook-delay' ? { delivery_state: 'held', due_at: millis + durationMs(fault.by) } : { delivery_state: 'dropped' };
|
|
393
|
+
await applyTwinWrite(service, { operation: `${record.slice(1)}.record`, subjectType: record, subjectId: id, fields: held, occurredAt: write.occurredAt, actor: { kind: 'system' } }, write.root);
|
|
394
|
+
sent.pop();
|
|
395
|
+
if (fault.kind === 'webhook-delay')
|
|
396
|
+
heldListeners.get(worldPaths(service, write.root).root)?.();
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
383
399
|
recorded.push({ type: record, id });
|
|
400
|
+
if (fault?.kind === 'webhook-duplicate') {
|
|
401
|
+
const twin = `${id}_duplicate`;
|
|
402
|
+
await applyTwinWrite(service, { operation: `${record.slice(1)}.record`, subjectType: record, subjectId: twin, fields: packet, occurredAt: write.occurredAt, actor: { kind: 'system' } }, write.root);
|
|
403
|
+
sent.push(sent.at(-1));
|
|
404
|
+
recorded.push({ type: record, id: twin });
|
|
405
|
+
}
|
|
384
406
|
}
|
|
385
407
|
// sent after the write's answer, as a vendor sends it: the caller never waits on its receiver, and a receiver that is
|
|
386
408
|
// slow or down fails no request
|
|
@@ -391,6 +413,11 @@ async function deliverEventType(service, decl, write, data, transport, values, t
|
|
|
391
413
|
const store = getActiveWorldStore(), deliveryRoot = worldPaths(service, write.root).root;
|
|
392
414
|
for (const d of sent)
|
|
393
415
|
deliveryObserver?.({ ...d, service });
|
|
416
|
+
dispatchRecorded(service, store, deliveryRoot, recorded, transport, trace);
|
|
417
|
+
return sent;
|
|
418
|
+
}
|
|
419
|
+
/** Each recorded packet sent after the write's answer, keeping its request's store and root. */
|
|
420
|
+
function dispatchRecorded(service, store, deliveryRoot, recorded, transport, trace) {
|
|
394
421
|
const roots = pendingDeliveries.get(store) ?? new Map();
|
|
395
422
|
pendingDeliveries.set(store, roots);
|
|
396
423
|
const pending = roots.get(deliveryRoot) ?? new Set();
|
|
@@ -415,5 +442,23 @@ async function deliverEventType(service, decl, write, data, transport, values, t
|
|
|
415
442
|
}
|
|
416
443
|
}, 0);
|
|
417
444
|
}
|
|
418
|
-
|
|
445
|
+
}
|
|
446
|
+
const heldListeners = new Map();
|
|
447
|
+
/** Who hears that a twin held a packet for later (its pack's fetch, which sets the timer a running clock needs). */
|
|
448
|
+
export function onHeldDelivery(service, root, listener) {
|
|
449
|
+
heldListeners.set(worldPaths(service, root).root, listener);
|
|
450
|
+
}
|
|
451
|
+
/** Send what a `webhook-delay` held that the World's clock has reached, in the order it fell due, and wait until it is
|
|
452
|
+
* sent; answer the instant the next held packet falls due, if any (ADR 0036 §4: the mover of the clock waits on it). */
|
|
453
|
+
export async function releaseHeldDeliveries(service, root, record = '_world_event_delivery', transport = worldTransport) {
|
|
454
|
+
const now = Date.parse(worldNow());
|
|
455
|
+
const held = twinResources(service, root).filter((r) => r.type === record && r.deleted !== true && r.delivery_state === 'held').sort((a, b) => Number(a.due_at) - Number(b.due_at));
|
|
456
|
+
const due = held.filter((r) => Number(r.due_at) <= now);
|
|
457
|
+
for (const r of due)
|
|
458
|
+
await applyTwinWrite(service, { operation: `${record.slice(1)}.release`, subjectType: record, subjectId: r.id, fields: { delivery_state: 'queued' }, occurredAt: worldNow(), actor: { kind: 'system' } }, root);
|
|
459
|
+
const deliveryRoot = worldPaths(service, root).root;
|
|
460
|
+
dispatchRecorded(service, getActiveWorldStore(), deliveryRoot, due.map((r) => ({ type: record, id: r.id })), transport, {});
|
|
461
|
+
await settleEventDeliveries(deliveryRoot);
|
|
462
|
+
const next = held.find((r) => Number(r.due_at) > now);
|
|
463
|
+
return next ? new Date(Number(next.due_at)).toISOString() : null;
|
|
419
464
|
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -76,8 +76,10 @@ export * as mysql from './mysql/index.js';
|
|
|
76
76
|
export * as live from './live/index.js';
|
|
77
77
|
export * as mongodb from './mongodb/index.js';
|
|
78
78
|
export * as git from './git/index.js';
|
|
79
|
-
export {
|
|
80
|
-
export
|
|
79
|
+
export { scenarioFaultResult, ScenarioError } from './scenario.js';
|
|
80
|
+
export { parseScenario, resolveScenario, WORLD_SCENARIO_ENV } from './scenario-timeline.js';
|
|
81
|
+
export type { ResolvedScenario, Scenario } from './scenario-timeline.js';
|
|
82
|
+
export type { PackScenarioAdapter, ScenarioDecision, ScenarioFault, ScenarioFaultResult, ScenarioFeatures, ScenarioHandler, ScenarioMatcher, ScenarioMissRecord } from './scenario.js';
|
|
81
83
|
export { verifyAttestationBundle } from './attestation.js';
|
|
82
84
|
export { cookieOf, signedIn, browserSessionToken, startBrowserSession, endBrowserSession } from './browser-session.js';
|
|
83
85
|
export { readHostPort } from './host-port.js';
|
package/dist/src/index.js
CHANGED
|
@@ -69,8 +69,9 @@ export * as mysql from "./mysql/index.js";
|
|
|
69
69
|
export * as live from "./live/index.js";
|
|
70
70
|
export * as mongodb from "./mongodb/index.js";
|
|
71
71
|
export * as git from "./git/index.js";
|
|
72
|
-
// ── the scenario grammar (architecture, "
|
|
73
|
-
export {
|
|
72
|
+
// ── the scenario grammar (architecture, "Scenarios") ───────────────────────────────────────────
|
|
73
|
+
export { scenarioFaultResult, ScenarioError } from "./scenario.js";
|
|
74
|
+
export { parseScenario, resolveScenario, WORLD_SCENARIO_ENV } from "./scenario-timeline.js";
|
|
74
75
|
export { verifyAttestationBundle } from "./attestation.js";
|
|
75
76
|
export { cookieOf, signedIn, browserSessionToken, startBrowserSession, endBrowserSession } from "./browser-session.js";
|
|
76
77
|
export { readHostPort } from "./host-port.js";
|
package/dist/src/openai-wire.js
CHANGED
|
@@ -303,7 +303,6 @@ export function scenario(vendor, operations, faultBody, readers = {}) {
|
|
|
303
303
|
toolResultFor: (r, c) => nonEmpty(c) && r.results.includes(c),
|
|
304
304
|
hasTool: (r, c) => nonEmpty(c) && r.tools.includes(c),
|
|
305
305
|
},
|
|
306
|
-
text: (r) => r.text,
|
|
307
306
|
// source: https://platform.openai.com/docs/api-reference/chat/create "none means the model will not call any tool and instead generates a message"
|
|
308
307
|
admits: (r, respond) => !(r.toolsForbidden && obj(respond).toolCalls !== undefined),
|
|
309
308
|
validateOn: (on) => {
|
package/dist/src/pack-fetch.d.ts
CHANGED
|
@@ -75,14 +75,12 @@ export type LanePart = {
|
|
|
75
75
|
};
|
|
76
76
|
};
|
|
77
77
|
/** Where a pack serves: its World's root, whether it was started read-only, the World instant when a caller pins it, the
|
|
78
|
-
* World's managed Postgres the runtime binds a pack declaring `managedDatabase` to (a handler's `ctx.engine`)
|
|
79
|
-
* World's scenario file for a model vendor's turns (`scenarioPath`, the runtime's `--scenario`). */
|
|
78
|
+
* and the World's managed Postgres the runtime binds a pack declaring `managedDatabase` to (a handler's `ctx.engine`). */
|
|
80
79
|
export type PackFetchOptions = {
|
|
81
80
|
root?: string;
|
|
82
81
|
readOnly?: boolean;
|
|
83
82
|
clock?: () => string;
|
|
84
83
|
database?: string;
|
|
85
|
-
scenarioPath?: string;
|
|
86
84
|
/** the application a walk stands in for: this fetch's requests and declared events are answered by it */
|
|
87
85
|
application?: ApplicationStandIn;
|
|
88
86
|
};
|