@volter/world-core 2.0.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/LICENSE +202 -0
- package/README.md +29 -0
- package/app-route.cjs +154 -0
- package/app-route.d.cts +7 -0
- package/attach.cjs +80 -0
- package/dist/app-route.cjs +154 -0
- package/dist/app-route.d.cts +7 -0
- package/dist/attach.cjs +80 -0
- package/dist/generated/pack-facts.json +4306 -0
- package/dist/inject.cjs +1097 -0
- package/dist/network-policy.cjs +92 -0
- package/dist/network-policy.d.cts +10 -0
- package/dist/src/actions.d.ts +276 -0
- package/dist/src/actions.js +436 -0
- package/dist/src/ancestry.d.ts +22 -0
- package/dist/src/ancestry.js +238 -0
- package/dist/src/args.d.ts +3 -0
- package/dist/src/args.js +12 -0
- package/dist/src/blob-store.d.ts +55 -0
- package/dist/src/blob-store.js +186 -0
- package/dist/src/brand-tokens.d.ts +2 -0
- package/dist/src/brand-tokens.js +17 -0
- package/dist/src/changeset.d.ts +431 -0
- package/dist/src/changeset.js +0 -0
- package/dist/src/client-bundle.d.ts +1 -0
- package/dist/src/client-bundle.js +28 -0
- package/dist/src/credential.d.ts +38 -0
- package/dist/src/credential.js +114 -0
- package/dist/src/derived-core.d.ts +452 -0
- package/dist/src/derived-core.js +782 -0
- package/dist/src/derived.d.ts +84 -0
- package/dist/src/derived.js +122 -0
- package/dist/src/emit.d.ts +106 -0
- package/dist/src/emit.js +157 -0
- package/dist/src/executor.d.ts +120 -0
- package/dist/src/executor.js +387 -0
- package/dist/src/file-response.d.ts +3 -0
- package/dist/src/file-response.js +22 -0
- package/dist/src/fork.d.ts +26 -0
- package/dist/src/fork.js +68 -0
- package/dist/src/git/history.d.ts +36 -0
- package/dist/src/git/history.js +298 -0
- package/dist/src/git/index.d.ts +6 -0
- package/dist/src/git/index.js +6 -0
- package/dist/src/git/inflate.d.ts +11 -0
- package/dist/src/git/inflate.js +194 -0
- package/dist/src/git/objects.d.ts +64 -0
- package/dist/src/git/objects.js +161 -0
- package/dist/src/git/pack.d.ts +14 -0
- package/dist/src/git/pack.js +199 -0
- package/dist/src/git/refs.d.ts +19 -0
- package/dist/src/git/refs.js +35 -0
- package/dist/src/git/smart-http.d.ts +45 -0
- package/dist/src/git/smart-http.js +223 -0
- package/dist/src/hash.d.ts +38 -0
- package/dist/src/hash.js +48 -0
- package/dist/src/head.d.ts +140 -0
- package/dist/src/head.js +313 -0
- package/dist/src/history.d.ts +76 -0
- package/dist/src/history.js +322 -0
- package/dist/src/index.d.ts +73 -0
- package/dist/src/index.js +98 -0
- package/dist/src/lifecycle.d.ts +1 -0
- package/dist/src/lifecycle.js +8 -0
- package/dist/src/log.d.ts +254 -0
- package/dist/src/log.js +801 -0
- package/dist/src/mirror-shell.d.ts +2 -0
- package/dist/src/mirror-shell.js +13 -0
- package/dist/src/observe.d.ts +49 -0
- package/dist/src/observe.js +148 -0
- package/dist/src/pack-assets.d.ts +30 -0
- package/dist/src/pack-assets.js +88 -0
- package/dist/src/packRegistry.d.ts +374 -0
- package/dist/src/packRegistry.js +142 -0
- package/dist/src/placeholder-remote.d.ts +22 -0
- package/dist/src/placeholder-remote.js +86 -0
- package/dist/src/proxy.d.ts +25 -0
- package/dist/src/proxy.js +155 -0
- package/dist/src/rateBudget.d.ts +367 -0
- package/dist/src/rateBudget.js +925 -0
- package/dist/src/references.d.ts +18 -0
- package/dist/src/references.js +27 -0
- package/dist/src/remote-execute.d.ts +22 -0
- package/dist/src/remote-execute.js +1 -0
- package/dist/src/resource-blob.d.ts +10 -0
- package/dist/src/resource-blob.js +56 -0
- package/dist/src/scenario.d.ts +197 -0
- package/dist/src/scenario.js +425 -0
- package/dist/src/schemas.d.ts +78 -0
- package/dist/src/schemas.js +50 -0
- package/dist/src/serve-http.d.ts +48 -0
- package/dist/src/serve-http.js +340 -0
- package/dist/src/serve.d.ts +147 -0
- package/dist/src/serve.js +507 -0
- package/dist/src/shared-blob-index.d.ts +4 -0
- package/dist/src/shared-blob-index.js +126 -0
- package/dist/src/state-system.d.ts +70 -0
- package/dist/src/state-system.js +90 -0
- package/dist/src/storage.d.ts +101 -0
- package/dist/src/storage.js +337 -0
- package/dist/src/twin-fetch.d.ts +64 -0
- package/dist/src/twin-fetch.js +91 -0
- package/dist/src/types.d.ts +40 -0
- package/dist/src/types.js +1 -0
- package/dist/src/v1-removed.d.ts +159 -0
- package/dist/src/v1-removed.js +124 -0
- package/dist/src/volter-home.d.ts +5 -0
- package/dist/src/volter-home.js +10 -0
- package/dist/src/world-clock.d.ts +4 -0
- package/dist/src/world-clock.js +32 -0
- package/dist/src/world-env.d.ts +3 -0
- package/dist/src/world-env.js +22 -0
- package/dist/src/world-store-sql.d.ts +27 -0
- package/dist/src/world-store-sql.js +86 -0
- package/dist/src/world-store.d.ts +168 -0
- package/dist/src/world-store.js +475 -0
- package/dist/src/worldConfig.d.ts +9 -0
- package/dist/src/worldConfig.js +17 -0
- package/dist/stream-bridge.cjs +80 -0
- package/dist/vendor-hosts.cjs +200 -0
- package/generated/pack-facts.json +4306 -0
- package/inject.cjs +1097 -0
- package/network-policy.cjs +92 -0
- package/network-policy.d.cts +10 -0
- package/package.json +103 -0
- package/src/actions.ts +564 -0
- package/src/ancestry.ts +213 -0
- package/src/args.ts +14 -0
- package/src/blob-store.ts +185 -0
- package/src/brand-tokens.ts +17 -0
- package/src/changeset.ts +1032 -0
- package/src/client-bundle.ts +29 -0
- package/src/credential.ts +140 -0
- package/src/derived-core.ts +1004 -0
- package/src/derived.ts +176 -0
- package/src/emit.ts +242 -0
- package/src/executor.ts +431 -0
- package/src/file-response.ts +22 -0
- package/src/fork.ts +89 -0
- package/src/git/history.ts +177 -0
- package/src/git/index.ts +6 -0
- package/src/git/inflate.ts +125 -0
- package/src/git/objects.ts +110 -0
- package/src/git/pack.ts +105 -0
- package/src/git/refs.ts +25 -0
- package/src/git/smart-http.ts +149 -0
- package/src/hash.ts +66 -0
- package/src/head.ts +318 -0
- package/src/history.ts +246 -0
- package/src/index.ts +323 -0
- package/src/lifecycle.ts +8 -0
- package/src/log.ts +793 -0
- package/src/mirror-shell.ts +15 -0
- package/src/observe.ts +130 -0
- package/src/pack-assets.ts +81 -0
- package/src/packRegistry.ts +408 -0
- package/src/placeholder-remote.ts +81 -0
- package/src/proxy.ts +183 -0
- package/src/rateBudget.ts +1115 -0
- package/src/references.ts +46 -0
- package/src/remote-execute.ts +26 -0
- package/src/resource-blob.ts +57 -0
- package/src/scenario.ts +479 -0
- package/src/schemas.ts +56 -0
- package/src/serve-http.ts +299 -0
- package/src/serve.ts +618 -0
- package/src/shared-blob-index.ts +108 -0
- package/src/state-system.ts +115 -0
- package/src/storage.ts +407 -0
- package/src/twin-fetch.ts +147 -0
- package/src/types.ts +50 -0
- package/src/v1-removed.ts +172 -0
- package/src/volter-home.ts +11 -0
- package/src/world-clock.ts +33 -0
- package/src/world-env.ts +18 -0
- package/src/world-store-sql.ts +118 -0
- package/src/world-store.ts +572 -0
- package/src/worldConfig.ts +27 -0
- package/stream-bridge.cjs +80 -0
- package/vendor-hosts.cjs +200 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SubjectFields } from './hash.js';
|
|
2
|
+
export type ReferenceDeclaration = {
|
|
3
|
+
/** the referencing subject type */
|
|
4
|
+
type: string;
|
|
5
|
+
/** the referenced subject type */
|
|
6
|
+
to: string;
|
|
7
|
+
/** the referenced subject's id, read from the referencing subject's fields (undefined: no reference) */
|
|
8
|
+
key: (fields: SubjectFields) => string | undefined;
|
|
9
|
+
/** the fields to overlay once the referenced subject's id is `vendorId` */
|
|
10
|
+
adopt: (fields: SubjectFields, vendorId: string) => SubjectFields;
|
|
11
|
+
};
|
|
12
|
+
/** The common case: one field holds the referenced subject's id as it is. */
|
|
13
|
+
export declare function referenceField(type: string, field: string, to: string): ReferenceDeclaration;
|
|
14
|
+
export declare function registerReferences(vendor: string, references: ReferenceDeclaration[]): void;
|
|
15
|
+
export declare function packReferences(vendor: string): ReferenceDeclaration[];
|
|
16
|
+
/** `fields` of a `type` subject with every declared reference resolved through `aliases`
|
|
17
|
+
* (`${type}:${oldId}` → newId, as `aliasesFrom` builds it). The same object when nothing applies. */
|
|
18
|
+
export declare function resolveReferences(vendor: string, type: string, fields: SubjectFields, aliases: Map<string, string>): SubjectFields;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** The common case: one field holds the referenced subject's id as it is. */
|
|
2
|
+
export function referenceField(type, field, to) {
|
|
3
|
+
return { type, to, key: (f) => (f[field] === undefined || f[field] === null ? undefined : String(f[field])), adopt: (_f, vendorId) => ({ [field]: vendorId }) };
|
|
4
|
+
}
|
|
5
|
+
const REGISTRY = Symbol.for('volter.references');
|
|
6
|
+
const registry = () => (globalThis[REGISTRY] ??= new Map());
|
|
7
|
+
export function registerReferences(vendor, references) { registry().set(vendor, references); }
|
|
8
|
+
export function packReferences(vendor) { return registry().get(vendor) ?? []; }
|
|
9
|
+
/** `fields` of a `type` subject with every declared reference resolved through `aliases`
|
|
10
|
+
* (`${type}:${oldId}` → newId, as `aliasesFrom` builds it). The same object when nothing applies. */
|
|
11
|
+
export function resolveReferences(vendor, type, fields, aliases) {
|
|
12
|
+
if (aliases.size === 0)
|
|
13
|
+
return fields;
|
|
14
|
+
let out = fields;
|
|
15
|
+
for (const ref of packReferences(vendor)) {
|
|
16
|
+
if (ref.type !== type)
|
|
17
|
+
continue;
|
|
18
|
+
const key = ref.key(out);
|
|
19
|
+
if (key === undefined)
|
|
20
|
+
continue;
|
|
21
|
+
const adopted = aliases.get(`${ref.to}:${key}`);
|
|
22
|
+
if (adopted === undefined || adopted === key)
|
|
23
|
+
continue;
|
|
24
|
+
out = { ...out, ...ref.adopt(out, adopted) };
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type RemoteExecuteRequest = {
|
|
2
|
+
method: string;
|
|
3
|
+
path: string;
|
|
4
|
+
headers?: Record<string, string>;
|
|
5
|
+
/** A JSON text, or raw bytes (a multipart upload) — the host hands either to fetch as is. */
|
|
6
|
+
body?: string | Uint8Array;
|
|
7
|
+
/** Opt into exact bytes for object/attachment downloads; JSON clients keep body text. */
|
|
8
|
+
responseType?: 'bytes';
|
|
9
|
+
/** A URL the vendor itself returned for an upload, already carrying its own authorization
|
|
10
|
+
* (LinkedIn's video parts on www.linkedin.com/dms-uploads, Slack's upload_url, TikTok's
|
|
11
|
+
* upload.<region>.tiktokapis.com): `path` is then that absolute URL, often on another host than
|
|
12
|
+
* the API, and the request goes WITHOUT the sealed credential. An upload the vendor authorizes
|
|
13
|
+
* with the API token (LinkedIn's images) is not presigned and cannot use this. */
|
|
14
|
+
presigned?: true;
|
|
15
|
+
};
|
|
16
|
+
export type RemoteExecuteResponse = {
|
|
17
|
+
status: number;
|
|
18
|
+
headers: Record<string, string>;
|
|
19
|
+
body: string;
|
|
20
|
+
bodyBytes?: Uint8Array;
|
|
21
|
+
};
|
|
22
|
+
export type RemoteExecute = (request: RemoteExecuteRequest) => Promise<RemoteExecuteResponse>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Read a resource payload at this branch, then its retained ancestors. */
|
|
2
|
+
export declare function readResourceBlob(service: string, key: string, root?: string): Promise<Uint8Array | null>;
|
|
3
|
+
/** A service's resources dir at this branch, then at each retained ancestor's: where a read resolves a payload, and
|
|
4
|
+
* what a listing that mints ids covers, so a branch serves bytes its parent received and never re-mints a parent's id. */
|
|
5
|
+
export declare function resourceChain(service: string, root?: string): string[];
|
|
6
|
+
/** Size of a resource payload at this branch or its ancestors, or null. */
|
|
7
|
+
export declare function resourceBlobSize(service: string, key: string, root?: string): Promise<number | null>;
|
|
8
|
+
/** A byte range of a resource payload at this branch or its ancestors (a ranged read where the
|
|
9
|
+
* store has one), or null when absent. */
|
|
10
|
+
export declare function readResourceBlobRange(service: string, key: string, start: number, endInclusive: number, root?: string): Promise<Uint8Array | null>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { isAbsolute, join } from 'node:path';
|
|
2
|
+
import { getActiveBlobStore, readBlobRange } from "./blob-store.js";
|
|
3
|
+
import { readBranchMeta } from "./log.js";
|
|
4
|
+
import { worldPaths } from "./storage.js";
|
|
5
|
+
/** Read a resource payload at this branch, then its retained ancestors. */
|
|
6
|
+
export async function readResourceBlob(service, key, root) {
|
|
7
|
+
assertResourceKey(key);
|
|
8
|
+
for (const dir of resourceChain(service, root)) {
|
|
9
|
+
const bytes = await getActiveBlobStore().get(join(dir, key));
|
|
10
|
+
if (bytes !== null)
|
|
11
|
+
return bytes;
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
/** A service's resources dir at this branch, then at each retained ancestor's: where a read resolves a payload, and
|
|
16
|
+
* what a listing that mints ids covers, so a branch serves bytes its parent received and never re-mints a parent's id. */
|
|
17
|
+
export function resourceChain(service, root) {
|
|
18
|
+
const out = [];
|
|
19
|
+
const seen = new Set();
|
|
20
|
+
let current = worldPaths(service, root).root;
|
|
21
|
+
for (;;) {
|
|
22
|
+
if (seen.has(current))
|
|
23
|
+
throw new Error('Cyclic resource blob ancestry');
|
|
24
|
+
seen.add(current);
|
|
25
|
+
out.push(worldPaths(service, current).resources);
|
|
26
|
+
const parent = readBranchMeta(service, current)?.parent;
|
|
27
|
+
if (!parent)
|
|
28
|
+
return out;
|
|
29
|
+
current = parent.at;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function assertResourceKey(key) {
|
|
33
|
+
if (isAbsolute(key) || key.includes('\0') || key.split(/[\\/]/).includes('..'))
|
|
34
|
+
throw new Error('Invalid relative resource blob key');
|
|
35
|
+
}
|
|
36
|
+
/** The key's full path at the first of this branch and its retained ancestors that holds it. */
|
|
37
|
+
async function resolveResourceBlob(service, key, root) {
|
|
38
|
+
assertResourceKey(key);
|
|
39
|
+
for (const dir of resourceChain(service, root)) {
|
|
40
|
+
const path = join(dir, key);
|
|
41
|
+
if (await getActiveBlobStore().exists(path))
|
|
42
|
+
return path;
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
/** Size of a resource payload at this branch or its ancestors, or null. */
|
|
47
|
+
export async function resourceBlobSize(service, key, root) {
|
|
48
|
+
const path = await resolveResourceBlob(service, key, root);
|
|
49
|
+
return path === null ? null : getActiveBlobStore().size(path);
|
|
50
|
+
}
|
|
51
|
+
/** A byte range of a resource payload at this branch or its ancestors (a ranged read where the
|
|
52
|
+
* store has one), or null when absent. */
|
|
53
|
+
export async function readResourceBlobRange(service, key, start, endInclusive, root) {
|
|
54
|
+
const path = await resolveResourceBlob(service, key, root);
|
|
55
|
+
return path === null ? null : readBlobRange(path, start, endInclusive);
|
|
56
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/** A vendor-agnostic bag of facts about one request, produced by the pack's adapter. Powers
|
|
2
|
+
* matching context, miss records (the authoring signal), and the self-teaching stub text. */
|
|
3
|
+
export type ScenarioFeatures = Record<string, string | number | boolean | readonly string[]>;
|
|
4
|
+
/** One matcher: does THIS request satisfy `condition`? Pure — no state, no IO. */
|
|
5
|
+
export type ScenarioMatcher<Req> = (req: Req, condition: unknown) => boolean;
|
|
6
|
+
/** The pack's declaration of its vocabulary — the ONLY vendor-specific surface. */
|
|
7
|
+
export type PackScenarioAdapter<Req> = {
|
|
8
|
+
/** Vendor key, e.g. "anthropic" — used in errors and the manifest. */
|
|
9
|
+
vendor: string;
|
|
10
|
+
/** Extract the feature bag for miss records / stub teaching. Pure. */
|
|
11
|
+
features: (req: Req) => ScenarioFeatures;
|
|
12
|
+
/** The legal `on` keys and their per-request semantics. Pure. */
|
|
13
|
+
matchers: Record<string, ScenarioMatcher<Req>>;
|
|
14
|
+
/** Validate `on` CONDITION VALUES at load (key membership is the kernel's; VALUE typing is
|
|
15
|
+
* the pack's — "a string nthCall silently never matches" is exactly the misfire strict
|
|
16
|
+
* loading exists to prevent). Return an error string to refuse. */
|
|
17
|
+
validateOn?: (on: Record<string, unknown>) => string | null;
|
|
18
|
+
/** Validate a handler's `respond` payload at load; return an error string to refuse.
|
|
19
|
+
* This is ALSO where a pack refuses success-shaped handlers on stateful routes
|
|
20
|
+
* ("seed that through the vendor's API instead"). */
|
|
21
|
+
validateRespond?: (respond: unknown, handler: ScenarioHandler) => string | null;
|
|
22
|
+
/** The request's text corpus for `textPattern` extractors (packs with text requests). */
|
|
23
|
+
text?: (req: Req) => string;
|
|
24
|
+
/** Pack-defined extractor KINDS beyond the builtins (feature, textPattern). A pack kind with
|
|
25
|
+
* a builtin's name OVERRIDES the builtin (e.g. a richer textPattern with flags/group).
|
|
26
|
+
* validate returns an error string to refuse the spec at load; extract runs at serve time
|
|
27
|
+
* and throws ScenarioError when the request cannot supply the value. */
|
|
28
|
+
extractorKinds?: Record<string, {
|
|
29
|
+
validate: (spec: Record<string, unknown>, name: string) => string | null;
|
|
30
|
+
extract: (spec: Record<string, unknown>, req: Req, name: string) => string | number;
|
|
31
|
+
}>;
|
|
32
|
+
/** The per-session scope discriminator; omitted → all requests share one "world" scope. */
|
|
33
|
+
scopeKey?: (req: Req) => string;
|
|
34
|
+
/** The VENDOR'S error envelope for a `status` fault — a 429 from this vendor looks like this
|
|
35
|
+
* vendor's 429. Omitted → the kernel's envelope `{ error: { message, type: "twin_fault" } }`. */
|
|
36
|
+
renderFault?: (fault: ScenarioStatusFault, req: Req) => {
|
|
37
|
+
body: unknown;
|
|
38
|
+
headers?: Record<string, string>;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
/** THE FAULT VOCABULARY (runtime contract R15 — outage rehearsal as a world-level value). One
|
|
42
|
+
* grammar every pack's engine honors, so a 429 or a hung request is authored the same way for
|
|
43
|
+
* every vendor instead of improvised per realizer:
|
|
44
|
+
* slow — hold the answer `ms` before serving it (the response is whatever the handler's
|
|
45
|
+
* `respond` or the twin's own path produces; timing is not content, R9 holds);
|
|
46
|
+
* status — answer this HTTP status instead of serving: a 4xx/5xx the vendor's own envelope
|
|
47
|
+
* (the pack's adapter renders it; a kernel envelope when it declares none), with
|
|
48
|
+
* `Retry-After` when `retryAfterSeconds` is set;
|
|
49
|
+
* drop — never answer: the request hangs until the client's own timeout ends it (what an
|
|
50
|
+
* outage looks like from the client; a TCP reset is not something a fetch can do),
|
|
51
|
+
* released after `holdMs` (default 300 000) so a socket is not held forever. */
|
|
52
|
+
export type ScenarioFault = {
|
|
53
|
+
kind: "slow";
|
|
54
|
+
ms: number;
|
|
55
|
+
} | {
|
|
56
|
+
kind: "status";
|
|
57
|
+
status: number;
|
|
58
|
+
retryAfterSeconds?: number;
|
|
59
|
+
message?: string;
|
|
60
|
+
} | {
|
|
61
|
+
kind: "drop";
|
|
62
|
+
holdMs?: number;
|
|
63
|
+
};
|
|
64
|
+
export type ScenarioStatusFault = Extract<ScenarioFault, {
|
|
65
|
+
kind: "status";
|
|
66
|
+
}>;
|
|
67
|
+
/** What a `status` fault serves: the pack's rendered envelope, or the kernel's. */
|
|
68
|
+
export type ScenarioFaultResult = {
|
|
69
|
+
status: number;
|
|
70
|
+
headers: Record<string, string>;
|
|
71
|
+
body: unknown;
|
|
72
|
+
};
|
|
73
|
+
export type ScenarioHandler = {
|
|
74
|
+
/** Stable id for status/telemetry; defaults to `handler-<1-based index>`. */
|
|
75
|
+
id?: string;
|
|
76
|
+
/** Conditions — ALL must hold. `{}` matches every request (an ordered catch-all). */
|
|
77
|
+
on: Record<string, unknown>;
|
|
78
|
+
/** Pack-realized response content (the pack's realizer builds the faithful envelope).
|
|
79
|
+
* Optional only on a handler that carries a `fault` (a `slow` may still carry one; a
|
|
80
|
+
* `status` or `drop` serves no content, so it must not). */
|
|
81
|
+
respond?: unknown;
|
|
82
|
+
/** A fault to inject when this handler fires (ScenarioFault). Same once/phase/scope
|
|
83
|
+
* semantics as any handler — a mid-story outage is `{on, fault, once}` or `{phase}`. */
|
|
84
|
+
fault?: ScenarioFault;
|
|
85
|
+
/** Fire at most once per scope. */
|
|
86
|
+
once?: boolean;
|
|
87
|
+
/** Reserved: "world" (default) | "session" — with "session", once/phase state is per
|
|
88
|
+
* scopeKey instead of shared. */
|
|
89
|
+
scope?: "world" | "session";
|
|
90
|
+
/** Fires only while the scope's phase equals this. Handlers without `phase` fire in any. */
|
|
91
|
+
phase?: string;
|
|
92
|
+
/** On fire, move the scope's phase — the tiny sequencing primitive that replaces
|
|
93
|
+
* linear scripts and nthCall arithmetic. */
|
|
94
|
+
advancePhase?: string;
|
|
95
|
+
};
|
|
96
|
+
/** Request-derived values a `respond` payload may reference as "{{name}}" placeholders —
|
|
97
|
+
* builtin kinds, or any kind the pack's adapter declares. Payload placeholders support the
|
|
98
|
+
* numeric transforms `{{name|min:N}}` / `{{name|max:N}}`; a whole-string placeholder yields
|
|
99
|
+
* the TYPED value (numbers stay numbers). */
|
|
100
|
+
export type ScenarioExtractorSpec = {
|
|
101
|
+
kind: "feature";
|
|
102
|
+
feature: string;
|
|
103
|
+
} | {
|
|
104
|
+
kind: "textPattern";
|
|
105
|
+
pattern: string;
|
|
106
|
+
as?: "string" | "number";
|
|
107
|
+
} | ({
|
|
108
|
+
kind: string;
|
|
109
|
+
} & Record<string, unknown>);
|
|
110
|
+
export type ScenarioDocument = {
|
|
111
|
+
extractors?: Record<string, ScenarioExtractorSpec>;
|
|
112
|
+
handlers: ScenarioHandler[];
|
|
113
|
+
};
|
|
114
|
+
export type ScenarioMissRecord = {
|
|
115
|
+
features: ScenarioFeatures;
|
|
116
|
+
phase: string | undefined;
|
|
117
|
+
};
|
|
118
|
+
export type ScenarioDecision = {
|
|
119
|
+
kind: "handler";
|
|
120
|
+
handler: ScenarioHandler;
|
|
121
|
+
respond: unknown;
|
|
122
|
+
ruleId: string;
|
|
123
|
+
fault?: ScenarioFault;
|
|
124
|
+
} | {
|
|
125
|
+
kind: "miss";
|
|
126
|
+
miss: ScenarioMissRecord;
|
|
127
|
+
};
|
|
128
|
+
export type ScenarioStatus = {
|
|
129
|
+
vendor: string;
|
|
130
|
+
handlers: Array<{
|
|
131
|
+
id: string;
|
|
132
|
+
phase?: string;
|
|
133
|
+
once?: boolean;
|
|
134
|
+
scope?: string;
|
|
135
|
+
matches: number;
|
|
136
|
+
source: "file" | "use";
|
|
137
|
+
}>;
|
|
138
|
+
misses: number;
|
|
139
|
+
recentMisses: ScenarioMissRecord[];
|
|
140
|
+
};
|
|
141
|
+
export declare class ScenarioError extends Error {
|
|
142
|
+
}
|
|
143
|
+
/** Strict-loud parse of a scenario DOCUMENT (the per-vendor handlers/<vendor>.json content).
|
|
144
|
+
* The caller does file IO; this validates. Every refusal names the valid vocabulary. */
|
|
145
|
+
export declare function parseScenarioDocument<Req>(raw: unknown, adapter: PackScenarioAdapter<Req>): ScenarioDocument;
|
|
146
|
+
/** The engine: pure state machine over registered handlers. One instance per twin server. */
|
|
147
|
+
export declare class ScenarioEngine<Req> {
|
|
148
|
+
private readonly adapter;
|
|
149
|
+
private readonly extractors;
|
|
150
|
+
private baseline;
|
|
151
|
+
private overrides;
|
|
152
|
+
private scopes;
|
|
153
|
+
private missCount;
|
|
154
|
+
private recentMisses;
|
|
155
|
+
constructor(adapter: PackScenarioAdapter<Req>, document?: ScenarioDocument);
|
|
156
|
+
/** In-process test overrides: LIFO over the baseline; returns a remover. NOT a runtime
|
|
157
|
+
* door — nothing outside this process can reach it, by design. */
|
|
158
|
+
use(...handlers: ScenarioHandler[]): () => void;
|
|
159
|
+
/** Decide one request. Mutates scope state (calls, once, phase) exactly like serving. */
|
|
160
|
+
next(req: Req): ScenarioDecision;
|
|
161
|
+
/** Decide one request AND honor its fault: a `slow` is awaited here and the decision is
|
|
162
|
+
* returned for the pack to serve; a `status` returns the rendered refusal to serve as-is; a
|
|
163
|
+
* `drop` never resolves within its hold. The one call a realizer makes so every vendor's
|
|
164
|
+
* outage reads the same. */
|
|
165
|
+
serve(req: Req, signal?: AbortSignal): Promise<ScenarioDecision | {
|
|
166
|
+
kind: "fault";
|
|
167
|
+
result: ScenarioFaultResult;
|
|
168
|
+
ruleId: string;
|
|
169
|
+
}>;
|
|
170
|
+
/** For GET /twin/scenario — active handlers with match counts, and the recent misses. */
|
|
171
|
+
status(): ScenarioStatus;
|
|
172
|
+
private scope;
|
|
173
|
+
private matches;
|
|
174
|
+
private extractValue;
|
|
175
|
+
private substitute;
|
|
176
|
+
}
|
|
177
|
+
export declare function scenarioFaultResult(fault: ScenarioFault, render?: () => {
|
|
178
|
+
body: unknown;
|
|
179
|
+
headers?: Record<string, string>;
|
|
180
|
+
} | undefined, signal?: AbortSignal): Promise<ScenarioFaultResult | null>;
|
|
181
|
+
export declare function statefulTwinManifest(input: {
|
|
182
|
+
vendor: string;
|
|
183
|
+
twinOf: string;
|
|
184
|
+
stores: string;
|
|
185
|
+
identity?: string;
|
|
186
|
+
notes?: string;
|
|
187
|
+
}): Record<string, unknown>;
|
|
188
|
+
/** The GET /twin manifest — the discovery door's body. Education ships INSIDE the twin:
|
|
189
|
+
* in-world agents have no repos or skills, only HTTP. */
|
|
190
|
+
export declare function twinManifest(input: {
|
|
191
|
+
vendor: string;
|
|
192
|
+
twinOf: string;
|
|
193
|
+
stateSentence: string;
|
|
194
|
+
behaviorSentence: string;
|
|
195
|
+
exampleHandler: ScenarioHandler | null;
|
|
196
|
+
engine?: ScenarioEngine<never>;
|
|
197
|
+
}): Record<string, unknown>;
|