@sema-agent/mods-kernel 0.0.0-stage → 7.54.1
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 +106 -0
- package/README.md +62 -2
- package/dist/budget.d.ts +22 -0
- package/dist/budget.js +83 -0
- package/dist/dispatch.d.ts +73 -0
- package/dist/dispatch.js +699 -0
- package/dist/events.d.ts +200 -0
- package/dist/events.js +210 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/receive.d.ts +27 -0
- package/dist/receive.js +193 -0
- package/dist/registration.d.ts +95 -0
- package/dist/registration.js +102 -0
- package/dist/site-rules.d.ts +58 -0
- package/dist/site-rules.js +296 -0
- package/package.json +32 -4
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { type ModBusSide, type ModEventName, type ModRegistrationTier, type ModSeat, type ModTier } from "./events.js";
|
|
2
|
+
import { type ModBudgetReading } from "./budget.js";
|
|
3
|
+
import type { ModEventPayload, ModFailureCause, ModMatcher, ModSkip } from "./site-rules.js";
|
|
4
|
+
export declare const MODS_INVALID_CODE = "config.mods_invalid";
|
|
5
|
+
export declare const MODS_REGISTRATION_REFUSED_CODE = "mods.registration_refused";
|
|
6
|
+
export interface ModUses {
|
|
7
|
+
readonly events: readonly string[];
|
|
8
|
+
readonly calls: readonly string[];
|
|
9
|
+
}
|
|
10
|
+
export interface ModOrigin {
|
|
11
|
+
readonly plugin: string;
|
|
12
|
+
readonly tier: ModTier;
|
|
13
|
+
}
|
|
14
|
+
export declare const ENGINE_ORIGIN: ModOrigin;
|
|
15
|
+
export interface ModPluginFacts {
|
|
16
|
+
readonly name: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ModDollarBase {
|
|
19
|
+
readonly plugin: ModPluginFacts;
|
|
20
|
+
}
|
|
21
|
+
export interface ModTraceEntry {
|
|
22
|
+
readonly index: number;
|
|
23
|
+
readonly plugin: string;
|
|
24
|
+
readonly tier: ModTier;
|
|
25
|
+
readonly outcome: ModTraceOutcome;
|
|
26
|
+
readonly ms: number;
|
|
27
|
+
}
|
|
28
|
+
export declare const MOD_TRACE_OUTCOMES: readonly ["returned", "passed", "rejected", "caught", "expired", "kept", "skipped", "bypassed"];
|
|
29
|
+
export type ModTraceOutcome = (typeof MOD_TRACE_OUTCOMES)[number];
|
|
30
|
+
export interface ModCaughtError {
|
|
31
|
+
readonly kind: ModFailureCause;
|
|
32
|
+
readonly message?: string;
|
|
33
|
+
readonly budget: number;
|
|
34
|
+
}
|
|
35
|
+
export interface ModNext {
|
|
36
|
+
(e: ModEventPayload): Promise<unknown>;
|
|
37
|
+
to(e: ModEventPayload, tier: ModTier): Promise<unknown>;
|
|
38
|
+
readonly origin: ModOrigin;
|
|
39
|
+
readonly signal: AbortSignal;
|
|
40
|
+
readonly trace: readonly ModTraceEntry[];
|
|
41
|
+
readonly budget: ModBudgetReading;
|
|
42
|
+
readonly error?: ModCaughtError;
|
|
43
|
+
readonly called?: boolean;
|
|
44
|
+
}
|
|
45
|
+
export type ModHandler<D extends ModDollarBase = ModDollarBase> = ($: D, e: ModEventPayload, next: ModNext) => unknown;
|
|
46
|
+
export interface ModHandlerOptions<D extends ModDollarBase = ModDollarBase> {
|
|
47
|
+
readonly catch?: ModHandler<D>;
|
|
48
|
+
readonly observe?: boolean;
|
|
49
|
+
}
|
|
50
|
+
export interface On<D extends ModDollarBase = ModDollarBase> {
|
|
51
|
+
(pattern: string, handler: ModHandler<D>, options?: ModHandlerOptions<D>): void;
|
|
52
|
+
(pattern: string, matcher: ModMatcher, handler: ModHandler<D>, options?: ModHandlerOptions<D>): void;
|
|
53
|
+
}
|
|
54
|
+
export interface ModRegistration<D extends ModDollarBase = ModDollarBase> {
|
|
55
|
+
readonly name: string;
|
|
56
|
+
readonly tier?: ModRegistrationTier;
|
|
57
|
+
readonly uses: ModUses;
|
|
58
|
+
readonly provenance?: string;
|
|
59
|
+
readonly budgetMs?: number;
|
|
60
|
+
readonly options?: unknown;
|
|
61
|
+
register(on: On<D>, options: unknown): void;
|
|
62
|
+
}
|
|
63
|
+
export type ModAdmissionRecord = {
|
|
64
|
+
readonly name: string;
|
|
65
|
+
readonly seat: ModSeat;
|
|
66
|
+
readonly tier: ModTier;
|
|
67
|
+
readonly events: readonly string[];
|
|
68
|
+
readonly observe?: readonly string[];
|
|
69
|
+
readonly skipped?: readonly ModSkip[];
|
|
70
|
+
} | {
|
|
71
|
+
readonly name: string;
|
|
72
|
+
readonly seat: ModSeat;
|
|
73
|
+
readonly tier: ModTier;
|
|
74
|
+
readonly refusedBy: string;
|
|
75
|
+
readonly reason: string;
|
|
76
|
+
};
|
|
77
|
+
export interface ModSeats<D extends ModDollarBase = ModDollarBase> {
|
|
78
|
+
readonly deployment?: readonly ModRegistration<D>[];
|
|
79
|
+
readonly task?: readonly ModRegistration<D>[];
|
|
80
|
+
}
|
|
81
|
+
export interface ScreenedRegistration<D extends ModDollarBase = ModDollarBase> {
|
|
82
|
+
readonly registration: ModRegistration<D>;
|
|
83
|
+
readonly name: string;
|
|
84
|
+
readonly seat: ModSeat;
|
|
85
|
+
readonly tier: ModRegistrationTier;
|
|
86
|
+
readonly budgetMs: number;
|
|
87
|
+
readonly uses: ModUses;
|
|
88
|
+
readonly provenance?: string;
|
|
89
|
+
readonly eventsBound: ReadonlySet<ModEventName>;
|
|
90
|
+
readonly callsBound: ReadonlySet<ModEventName>;
|
|
91
|
+
}
|
|
92
|
+
export declare function modsInvalid(message: string): Error & {
|
|
93
|
+
readonly code: string;
|
|
94
|
+
};
|
|
95
|
+
export declare function screenModSeats<D extends ModDollarBase>(seats: ModSeats<D>, side: ModBusSide, report: (err: Error) => void): ScreenedRegistration<D>[];
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { describe, expandModPattern, MOD_SEATS, readOnSide, SEAT_TIER_TABLE, } from "./events.js";
|
|
2
|
+
import { resolveModBudgetMs } from "./budget.js";
|
|
3
|
+
export const MODS_INVALID_CODE = "config.mods_invalid";
|
|
4
|
+
export const MODS_REGISTRATION_REFUSED_CODE = "mods.registration_refused";
|
|
5
|
+
export const ENGINE_ORIGIN = Object.freeze({ plugin: "engine", tier: "core" });
|
|
6
|
+
export const MOD_TRACE_OUTCOMES = Object.freeze(["returned", "passed", "rejected", "caught", "expired", "kept", "skipped", "bypassed"]);
|
|
7
|
+
export function modsInvalid(message) {
|
|
8
|
+
return Object.assign(new Error(message), { code: MODS_INVALID_CODE });
|
|
9
|
+
}
|
|
10
|
+
const TIER_ORDER = ["prepend", "user", "append"];
|
|
11
|
+
export function screenModSeats(seats, side, report) {
|
|
12
|
+
const out = [];
|
|
13
|
+
const names = new Set();
|
|
14
|
+
for (const seat of MOD_SEATS) {
|
|
15
|
+
const list = seats[seat];
|
|
16
|
+
if (list === undefined)
|
|
17
|
+
continue;
|
|
18
|
+
if (!Array.isArray(list))
|
|
19
|
+
throw modsInvalid(`the ${seat} seat's mods must be a list of registrations, not ${describe(list)}`);
|
|
20
|
+
for (const item of list) {
|
|
21
|
+
const screened = screenOne(item, seat, side, report);
|
|
22
|
+
if (names.has(screened.name))
|
|
23
|
+
throw modsInvalid(`two registrations are named "${screened.name}" — a registration name is unique in the run`);
|
|
24
|
+
names.add(screened.name);
|
|
25
|
+
out.push(screened);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return out.sort((a, b) => TIER_ORDER.indexOf(a.tier) - TIER_ORDER.indexOf(b.tier));
|
|
29
|
+
}
|
|
30
|
+
function isRegistration(v) {
|
|
31
|
+
return typeof v === "object" && v !== null && typeof Reflect.get(v, "name") === "string" && typeof Reflect.get(v, "register") === "function";
|
|
32
|
+
}
|
|
33
|
+
function screenOne(item, seat, side, report) {
|
|
34
|
+
if (!isRegistration(item))
|
|
35
|
+
throw modsInvalid(`a ${seat} mods entry is ${describe(item)}; a registration has a name and a register function`);
|
|
36
|
+
const name = item.name;
|
|
37
|
+
if (name === "")
|
|
38
|
+
throw modsInvalid(`a ${seat} registration has an empty name`);
|
|
39
|
+
if (name === ENGINE_ORIGIN.plugin)
|
|
40
|
+
throw modsInvalid(`a ${seat} registration is named "${name}", the engine's own origin word — choose another name`);
|
|
41
|
+
const tier = tierOf(item.tier, seat, name);
|
|
42
|
+
const uses = usesOf(item.uses, name);
|
|
43
|
+
if (item.provenance !== undefined && typeof item.provenance !== "string")
|
|
44
|
+
throw modsInvalid(`${name}: provenance is ${describe(item.provenance)}; it is a label (a string)`);
|
|
45
|
+
const eventsBound = new Set();
|
|
46
|
+
for (const p of uses.events)
|
|
47
|
+
for (const n of namesOf(p, name, "uses.events"))
|
|
48
|
+
eventsBound.add(n);
|
|
49
|
+
const callsBound = new Set();
|
|
50
|
+
for (const c of uses.calls)
|
|
51
|
+
for (const n of namesOf(c, name, "uses.calls"))
|
|
52
|
+
if (readOnSide(n, side))
|
|
53
|
+
callsBound.add(n);
|
|
54
|
+
return {
|
|
55
|
+
registration: item,
|
|
56
|
+
name,
|
|
57
|
+
seat,
|
|
58
|
+
tier,
|
|
59
|
+
budgetMs: resolveModBudgetMs(item.budgetMs, report, name),
|
|
60
|
+
uses,
|
|
61
|
+
...(item.provenance !== undefined ? { provenance: item.provenance } : {}),
|
|
62
|
+
eventsBound,
|
|
63
|
+
callsBound,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function tierOf(declared, seat, name) {
|
|
67
|
+
const row = SEAT_TIER_TABLE[seat];
|
|
68
|
+
const allowed = row.tiers;
|
|
69
|
+
if (declared === undefined) {
|
|
70
|
+
if (row.declared === "required")
|
|
71
|
+
throw modsInvalid(`${name}: a ${seat} registration declares its tier (${allowed.join(", ")})`);
|
|
72
|
+
return allowed[0];
|
|
73
|
+
}
|
|
74
|
+
const tier = allowed.find((t) => t === declared);
|
|
75
|
+
if (tier === undefined)
|
|
76
|
+
throw modsInvalid(`${name}: a ${seat} registration's tier is ${allowed.join(" or ")}, not ${describe(declared)}`);
|
|
77
|
+
return tier;
|
|
78
|
+
}
|
|
79
|
+
function usesOf(uses, name) {
|
|
80
|
+
if (typeof uses !== "object" || uses === null)
|
|
81
|
+
throw modsInvalid(`${name}: uses is ${describe(uses)}; it declares { events, calls }`);
|
|
82
|
+
const events = stringList(Reflect.get(uses, "events"), name, "uses.events");
|
|
83
|
+
const calls = stringList(Reflect.get(uses, "calls"), name, "uses.calls");
|
|
84
|
+
return Object.freeze({ events, calls });
|
|
85
|
+
}
|
|
86
|
+
function stringList(v, name, field) {
|
|
87
|
+
if (!Array.isArray(v))
|
|
88
|
+
throw modsInvalid(`${name}: ${field} is ${describe(v)}; it is a list of event names`);
|
|
89
|
+
const out = [];
|
|
90
|
+
for (const x of v) {
|
|
91
|
+
if (typeof x !== "string")
|
|
92
|
+
throw modsInvalid(`${name}: ${field} holds ${describe(x)}; it is a list of event names`);
|
|
93
|
+
out.push(x);
|
|
94
|
+
}
|
|
95
|
+
return Object.freeze(out);
|
|
96
|
+
}
|
|
97
|
+
function namesOf(pattern, name, field) {
|
|
98
|
+
const expanded = expandModPattern(pattern);
|
|
99
|
+
if ("refusal" in expanded)
|
|
100
|
+
throw modsInvalid(`${name}: ${field}: ${expanded.refusal}`);
|
|
101
|
+
return expanded.names;
|
|
102
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type ModSeat, type ModTier } from "./events.js";
|
|
2
|
+
export type ProxyProbe = (value: object) => boolean;
|
|
3
|
+
export declare const MODS_VALUE_DEPTH = 1000;
|
|
4
|
+
export declare const MOD_FAILURE_CAUSES: readonly ["threw", "timeout", "violation", "reentry"];
|
|
5
|
+
export type ModFailureCause = (typeof MOD_FAILURE_CAUSES)[number];
|
|
6
|
+
export interface ModSkip {
|
|
7
|
+
readonly registration: string;
|
|
8
|
+
readonly cause: ModFailureCause;
|
|
9
|
+
readonly reason: string;
|
|
10
|
+
}
|
|
11
|
+
export type ModEventPayload = Readonly<Record<string, unknown>>;
|
|
12
|
+
export interface ModAnswerer {
|
|
13
|
+
readonly plugin: string;
|
|
14
|
+
readonly seat: ModSeat;
|
|
15
|
+
readonly tier: ModTier;
|
|
16
|
+
}
|
|
17
|
+
export declare const NEXT_IN_FLIGHT: Readonly<{
|
|
18
|
+
inFlight: true;
|
|
19
|
+
}>;
|
|
20
|
+
export type ModAnswerCheck = (answer: unknown, e: ModEventPayload, by: ModAnswerer, below: readonly unknown[]) => string | undefined;
|
|
21
|
+
export type ModMatcher = Readonly<Record<string, string | number | boolean>>;
|
|
22
|
+
export interface PinnedKeyBreak {
|
|
23
|
+
readonly key: string;
|
|
24
|
+
readonly unproven?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function pinnedKeyChanged(pinned: readonly string[], received: ModEventPayload, passed: ModEventPayload): PinnedKeyBreak | undefined;
|
|
27
|
+
export declare function engineObjectsOnce(held: ModEventPayload): () => ReadonlySet<object>;
|
|
28
|
+
export declare function handedOn(pinned: readonly string[], held: ModEventPayload, passed: unknown, engines: ReadonlySet<object>, isProxy?: ProxyProbe): {
|
|
29
|
+
readonly payload: ModEventPayload;
|
|
30
|
+
} | {
|
|
31
|
+
readonly violation: string;
|
|
32
|
+
};
|
|
33
|
+
export declare function pinnedHandOn(pinned: readonly string[], held: ModEventPayload, passed: ModEventPayload, engines?: ReadonlySet<object>): {
|
|
34
|
+
readonly payload: ModEventPayload;
|
|
35
|
+
} | {
|
|
36
|
+
readonly violation: string;
|
|
37
|
+
};
|
|
38
|
+
export declare function answerViolation(answer: unknown, check: ModAnswerCheck | undefined, e: ModEventPayload, by: ModAnswerer, below: readonly unknown[]): string | undefined;
|
|
39
|
+
export declare function methodAnswerViolation(answer: unknown, _e?: ModEventPayload, _by?: ModAnswerer, below?: readonly unknown[]): string | undefined;
|
|
40
|
+
export declare function matcherViolation(m: unknown): string | undefined;
|
|
41
|
+
export declare function matcherMatches(m: ModMatcher | undefined, e: ModEventPayload): boolean;
|
|
42
|
+
export declare function receivePayload(v: unknown, isProxy?: ProxyProbe, held?: ReadonlySet<object>): {
|
|
43
|
+
readonly payload: ModEventPayload;
|
|
44
|
+
} | {
|
|
45
|
+
readonly refusal: string;
|
|
46
|
+
};
|
|
47
|
+
export declare function receiveEvent(e: unknown): {
|
|
48
|
+
readonly payload: ModEventPayload;
|
|
49
|
+
} | {
|
|
50
|
+
readonly refusal: string;
|
|
51
|
+
};
|
|
52
|
+
export declare function answerUpward(v: unknown): unknown;
|
|
53
|
+
export declare function receiveAnswer(answer: unknown, engines: ReadonlySet<object>, isProxy?: ProxyProbe): {
|
|
54
|
+
readonly answer: unknown;
|
|
55
|
+
} | {
|
|
56
|
+
readonly refusal: string;
|
|
57
|
+
};
|
|
58
|
+
export declare function detachedCopy(e: ModEventPayload): Record<string, unknown>;
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { describe } from "./events.js";
|
|
2
|
+
import { describeFault, isPlainRecord, MODS_PAYLOAD_MODE, receive } from "./receive.js";
|
|
3
|
+
export const MODS_VALUE_DEPTH = 1_000;
|
|
4
|
+
const HANDED_MODE = Object.freeze({ ...MODS_PAYLOAD_MODE, depth: MODS_VALUE_DEPTH + 1, past: "refuse" });
|
|
5
|
+
function payloadMode(isProxy) {
|
|
6
|
+
return isProxy === undefined ? HANDED_MODE : Object.freeze({ ...HANDED_MODE, isProxy });
|
|
7
|
+
}
|
|
8
|
+
const TOO_DEEP_TO_COMPARE = `too deep to compare (nested past ${MODS_VALUE_DEPTH} levels)`;
|
|
9
|
+
function faultClause(fault) {
|
|
10
|
+
if (fault.kind !== "too-deep")
|
|
11
|
+
return describeFault(fault);
|
|
12
|
+
return `a value nested past ${MODS_VALUE_DEPTH} levels${fault.path.length > 0 ? ` under "${fault.path[0]}"` : ""}`;
|
|
13
|
+
}
|
|
14
|
+
export const MOD_FAILURE_CAUSES = Object.freeze(["threw", "timeout", "violation", "reentry"]);
|
|
15
|
+
export const NEXT_IN_FLIGHT = Object.freeze({ inFlight: true });
|
|
16
|
+
const TOO_DEEP = Object.freeze({ tooDeep: true });
|
|
17
|
+
const OPEN = "open";
|
|
18
|
+
function sameModValue(a0, b0, paired, depth0, homes) {
|
|
19
|
+
const stack = [];
|
|
20
|
+
const named = (r) => Object.keys(r).filter((k) => r[k] !== undefined);
|
|
21
|
+
const meet = (a, b, depth) => {
|
|
22
|
+
if (a === b || (Number.isNaN(a) && Number.isNaN(b)))
|
|
23
|
+
return 0;
|
|
24
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null)
|
|
25
|
+
return false;
|
|
26
|
+
let row = paired.get(a);
|
|
27
|
+
const met = row?.get(b);
|
|
28
|
+
if (met !== undefined) {
|
|
29
|
+
const height = met === OPEN ? 1 : met;
|
|
30
|
+
if (depth + height - 1 > MODS_VALUE_DEPTH)
|
|
31
|
+
throw TOO_DEEP;
|
|
32
|
+
return height;
|
|
33
|
+
}
|
|
34
|
+
if (depth > MODS_VALUE_DEPTH)
|
|
35
|
+
throw TOO_DEEP;
|
|
36
|
+
if (row === undefined)
|
|
37
|
+
paired.set(a, (row = new Map()));
|
|
38
|
+
row.set(b, OPEN);
|
|
39
|
+
if (!homes.has(b))
|
|
40
|
+
homes.set(b, a);
|
|
41
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
42
|
+
if (!Array.isArray(a) || !Array.isArray(b) || Object.getPrototypeOf(a) !== Array.prototype || Object.getPrototypeOf(b) !== Array.prototype || a.length !== b.length)
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
else if (!isPlainRecord(a) || !isPlainRecord(b))
|
|
46
|
+
return false;
|
|
47
|
+
const ra = a;
|
|
48
|
+
const rb = b;
|
|
49
|
+
const names = named(ra);
|
|
50
|
+
if (names.length !== named(rb).length)
|
|
51
|
+
return false;
|
|
52
|
+
stack.push({ a: ra, b: rb, row, names, depth, next: 0, height: 1 });
|
|
53
|
+
return undefined;
|
|
54
|
+
};
|
|
55
|
+
const top = meet(a0, b0, depth0);
|
|
56
|
+
if (top !== undefined)
|
|
57
|
+
return top;
|
|
58
|
+
for (;;) {
|
|
59
|
+
const f = stack[stack.length - 1];
|
|
60
|
+
if (f.next < f.names.length) {
|
|
61
|
+
const k = f.names[f.next++];
|
|
62
|
+
if (!Object.hasOwn(f.b, k))
|
|
63
|
+
return false;
|
|
64
|
+
const below = meet(f.a[k], f.b[k], f.depth + 1);
|
|
65
|
+
if (below === false)
|
|
66
|
+
return false;
|
|
67
|
+
if (below !== undefined)
|
|
68
|
+
f.height = Math.max(f.height, below + 1);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
f.row.set(f.b, f.height);
|
|
72
|
+
stack.pop();
|
|
73
|
+
const holder = stack[stack.length - 1];
|
|
74
|
+
if (holder === undefined)
|
|
75
|
+
return f.height;
|
|
76
|
+
holder.height = Math.max(holder.height, f.height + 1);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export function pinnedKeyChanged(pinned, received, passed) {
|
|
80
|
+
const compared = comparePinned(pinned, received, passed);
|
|
81
|
+
return "broke" in compared ? compared.broke : undefined;
|
|
82
|
+
}
|
|
83
|
+
function comparePinned(pinned, received, passed) {
|
|
84
|
+
const paired = new Map();
|
|
85
|
+
const homes = new Map();
|
|
86
|
+
for (const key of pinned) {
|
|
87
|
+
try {
|
|
88
|
+
if (sameModValue(received[key], passed[key], paired, 1, homes) === false)
|
|
89
|
+
return { broke: { key } };
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
return { broke: { key, unproven: err === TOO_DEEP ? TOO_DEEP_TO_COMPARE : "the comparison could not walk it to the end" } };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { homes };
|
|
96
|
+
}
|
|
97
|
+
function engineObjects(held) {
|
|
98
|
+
const found = new Set();
|
|
99
|
+
const stack = Object.values(held);
|
|
100
|
+
while (stack.length > 0) {
|
|
101
|
+
const v = stack.pop();
|
|
102
|
+
if (typeof v !== "object" || v === null || found.has(v) || !Object.isFrozen(v))
|
|
103
|
+
continue;
|
|
104
|
+
if (!isPlainRecord(v) && !(Array.isArray(v) && Object.getPrototypeOf(v) === Array.prototype))
|
|
105
|
+
continue;
|
|
106
|
+
found.add(v);
|
|
107
|
+
for (const k of Object.keys(v))
|
|
108
|
+
stack.push(v[k]);
|
|
109
|
+
}
|
|
110
|
+
return found;
|
|
111
|
+
}
|
|
112
|
+
export function engineObjectsOnce(held) {
|
|
113
|
+
let found;
|
|
114
|
+
return () => (found ??= engineObjects(held));
|
|
115
|
+
}
|
|
116
|
+
const unprovenCopy = (key, why) => `next() handed on a copy of the pinned key "${key}" that is ${why}; a copy is taken only when proven the same`;
|
|
117
|
+
export function handedOn(pinned, held, passed, engines, isProxy) {
|
|
118
|
+
const received = receiveRecord(passed, payloadMode(isProxy), engines);
|
|
119
|
+
if ("refusal" in received) {
|
|
120
|
+
const key = received.fault?.kind === "too-deep" ? received.fault.path[0] : undefined;
|
|
121
|
+
return { violation: key !== undefined && pinned.includes(key) ? unprovenCopy(key, TOO_DEEP_TO_COMPARE) : `next() takes the event object: ${received.refusal}` };
|
|
122
|
+
}
|
|
123
|
+
return pinnedHandOn(pinned, held, received.payload, engines);
|
|
124
|
+
}
|
|
125
|
+
export function pinnedHandOn(pinned, held, passed, engines) {
|
|
126
|
+
const compared = comparePinned(pinned, held, passed);
|
|
127
|
+
if ("broke" in compared) {
|
|
128
|
+
return { violation: compared.broke.unproven === undefined ? `next() changed the pinned key "${compared.broke.key}"` : unprovenCopy(compared.broke.key, compared.broke.unproven) };
|
|
129
|
+
}
|
|
130
|
+
if (pinned.every((k) => Object.hasOwn(passed, k) === Object.hasOwn(held, k) && Object.is(passed[k], held[k])))
|
|
131
|
+
return { payload: passed };
|
|
132
|
+
return { payload: rehomed(pinned, held, passed, compared.homes, engines ?? engineObjects(held)) };
|
|
133
|
+
}
|
|
134
|
+
function rehomed(pinned, held, passed, homes, engines) {
|
|
135
|
+
const swap = new Map();
|
|
136
|
+
for (const [copy, home] of homes)
|
|
137
|
+
if (copy !== passed && !engines.has(copy))
|
|
138
|
+
swap.set(copy, home);
|
|
139
|
+
const isContainer = (v) => typeof v === "object" && v !== null && !engines.has(v) && !swap.has(v) && (isPlainRecord(v) || (Array.isArray(v) && Object.getPrototypeOf(v) === Array.prototype));
|
|
140
|
+
const holders = new Map();
|
|
141
|
+
const walked = new Set([passed]);
|
|
142
|
+
const stack = [passed];
|
|
143
|
+
while (stack.length > 0) {
|
|
144
|
+
const node = stack.pop();
|
|
145
|
+
for (const k of Object.keys(node)) {
|
|
146
|
+
if (node === passed && pinned.includes(k))
|
|
147
|
+
continue;
|
|
148
|
+
const v = node[k];
|
|
149
|
+
if (v !== passed && !swap.has(v) && !isContainer(v))
|
|
150
|
+
continue;
|
|
151
|
+
const by = holders.get(v);
|
|
152
|
+
if (by === undefined)
|
|
153
|
+
holders.set(v, [node]);
|
|
154
|
+
else
|
|
155
|
+
by.push(node);
|
|
156
|
+
if (isContainer(v) && !walked.has(v)) {
|
|
157
|
+
walked.add(v);
|
|
158
|
+
stack.push(v);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const twins = new Map([[passed, {}]]);
|
|
163
|
+
const reach = [passed, ...swap.keys()];
|
|
164
|
+
while (reach.length > 0) {
|
|
165
|
+
for (const h of holders.get(reach.pop()) ?? []) {
|
|
166
|
+
if (twins.has(h))
|
|
167
|
+
continue;
|
|
168
|
+
twins.set(h, Array.isArray(h) ? [] : {});
|
|
169
|
+
reach.push(h);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const put = (to, k, v) => void Object.defineProperty(to, k, { value: v, enumerable: true, writable: true, configurable: true });
|
|
173
|
+
const moved = (v) => (typeof v !== "object" || v === null ? v : swap.has(v) ? swap.get(v) : (twins.get(v) ?? v));
|
|
174
|
+
for (const [from, to] of twins) {
|
|
175
|
+
const r = from;
|
|
176
|
+
for (const k of Object.keys(r)) {
|
|
177
|
+
if (from !== passed || !pinned.includes(k))
|
|
178
|
+
put(to, k, moved(r[k]));
|
|
179
|
+
else if (Object.hasOwn(held, k))
|
|
180
|
+
put(to, k, held[k]);
|
|
181
|
+
}
|
|
182
|
+
if (from === passed)
|
|
183
|
+
for (const k of pinned)
|
|
184
|
+
if (Object.hasOwn(held, k) && !Object.hasOwn(to, k))
|
|
185
|
+
put(to, k, held[k]);
|
|
186
|
+
if (Array.isArray(from) && Array.isArray(to))
|
|
187
|
+
to.length = from.length;
|
|
188
|
+
}
|
|
189
|
+
for (const to of twins.values())
|
|
190
|
+
Object.freeze(to);
|
|
191
|
+
return twins.get(passed);
|
|
192
|
+
}
|
|
193
|
+
export function answerViolation(answer, check, e, by, below) {
|
|
194
|
+
if (answer === undefined)
|
|
195
|
+
return "returned no answer (return next(e) to pass the event on)";
|
|
196
|
+
if (check === undefined)
|
|
197
|
+
return undefined;
|
|
198
|
+
try {
|
|
199
|
+
return check(answer, e, by, below);
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return "returned an answer the station cannot read";
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const denyOf = (a) => (typeof a === "object" && a !== null && Object.prototype.hasOwnProperty.call(a, "deny") ? Reflect.get(a, "deny") : undefined);
|
|
206
|
+
export function methodAnswerViolation(answer, _e, _by, below = []) {
|
|
207
|
+
if (!isPlainRecord(answer))
|
|
208
|
+
return "takes { value } or { deny }";
|
|
209
|
+
const hasValue = Object.prototype.hasOwnProperty.call(answer, "value");
|
|
210
|
+
const deny = denyOf(answer);
|
|
211
|
+
if (deny !== undefined) {
|
|
212
|
+
if (hasValue || typeof deny !== "string" || deny === "")
|
|
213
|
+
return "takes { value } or { deny: <reason> }";
|
|
214
|
+
return below.some((b) => denyOf(b) === undefined) ? "a deny after its next() was answered or while it still runs (deny in place of next)" : undefined;
|
|
215
|
+
}
|
|
216
|
+
if (!hasValue)
|
|
217
|
+
return "takes { value } or { deny }";
|
|
218
|
+
return below.includes(NEXT_IN_FLIGHT) ? "a value while its next() still runs (value in place of next)" : undefined;
|
|
219
|
+
}
|
|
220
|
+
export function matcherViolation(m) {
|
|
221
|
+
if (typeof m !== "object" || m === null || Array.isArray(m))
|
|
222
|
+
return `a matcher is an object of top-level fields, not ${describe(m)}`;
|
|
223
|
+
for (const [k, v] of Object.entries(m)) {
|
|
224
|
+
if (typeof v !== "string" && typeof v !== "number" && typeof v !== "boolean")
|
|
225
|
+
return `matcher field "${k}" is ${describe(v)}; a matcher value is a string, a number or a boolean`;
|
|
226
|
+
}
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
export function matcherMatches(m, e) {
|
|
230
|
+
if (m === undefined)
|
|
231
|
+
return true;
|
|
232
|
+
return Object.entries(m).every(([k, v]) => Object.prototype.hasOwnProperty.call(e, k) && e[k] === v);
|
|
233
|
+
}
|
|
234
|
+
function receiveRecord(v, mode, held) {
|
|
235
|
+
if (mode.isProxy !== undefined && typeof v === "object" && v !== null && mode.isProxy(v))
|
|
236
|
+
return { refusal: "the payload is a Proxy (refused before any of its traps runs)" };
|
|
237
|
+
if (!isPlainRecord(v))
|
|
238
|
+
return { refusal: `an event payload is a plain object, not ${describe(v)}` };
|
|
239
|
+
const r = receive(v, mode, held);
|
|
240
|
+
return r.ok ? { payload: Object.freeze(r.value) } : { refusal: `the payload holds ${faultClause(r.fault)}`, fault: r.fault };
|
|
241
|
+
}
|
|
242
|
+
export function receivePayload(v, isProxy, held) {
|
|
243
|
+
const r = receiveRecord(v, payloadMode(isProxy), held);
|
|
244
|
+
return "refusal" in r ? { refusal: r.refusal } : r;
|
|
245
|
+
}
|
|
246
|
+
export function receiveEvent(e) {
|
|
247
|
+
const r = receiveRecord(e, MODS_PAYLOAD_MODE);
|
|
248
|
+
return "refusal" in r ? { refusal: r.refusal } : r;
|
|
249
|
+
}
|
|
250
|
+
export function answerUpward(v) {
|
|
251
|
+
if (typeof v !== "object" || v === null || Object.isFrozen(v))
|
|
252
|
+
return v;
|
|
253
|
+
if (Array.isArray(v) && Object.getPrototypeOf(v) === Array.prototype)
|
|
254
|
+
return Object.freeze([...v]);
|
|
255
|
+
return isPlainRecord(v) ? Object.freeze({ ...v }) : v;
|
|
256
|
+
}
|
|
257
|
+
export function receiveAnswer(answer, engines, isProxy) {
|
|
258
|
+
const r = receive(answer, payloadMode(isProxy), engines);
|
|
259
|
+
return r.ok ? { answer: r.value } : { refusal: `answered ${faultClause(r.fault)}` };
|
|
260
|
+
}
|
|
261
|
+
const DETACHED_MODE = Object.freeze({ accessors: "refuse", depth: Infinity, others: "as-is" });
|
|
262
|
+
export function detachedCopy(e) {
|
|
263
|
+
const r = receive(e, DETACHED_MODE);
|
|
264
|
+
if (!r.ok)
|
|
265
|
+
throw new Error(`the observer's copy of the event could not be made: ${describeFault(r.fault)}`);
|
|
266
|
+
const copy = r.value;
|
|
267
|
+
const clones = new Map();
|
|
268
|
+
const walked = new Set([copy]);
|
|
269
|
+
const stack = [copy];
|
|
270
|
+
while (stack.length > 0) {
|
|
271
|
+
const node = stack.pop();
|
|
272
|
+
for (const k of Object.keys(node)) {
|
|
273
|
+
const v = node[k];
|
|
274
|
+
if (typeof v !== "object" || v === null)
|
|
275
|
+
continue;
|
|
276
|
+
if (isPlainRecord(v) || (Array.isArray(v) && Object.getPrototypeOf(v) === Array.prototype)) {
|
|
277
|
+
if (!walked.has(v)) {
|
|
278
|
+
walked.add(v);
|
|
279
|
+
stack.push(v);
|
|
280
|
+
}
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (!clones.has(v)) {
|
|
284
|
+
let own = v;
|
|
285
|
+
try {
|
|
286
|
+
own = structuredClone(v);
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
}
|
|
290
|
+
clones.set(v, own);
|
|
291
|
+
}
|
|
292
|
+
Object.defineProperty(node, k, { value: clones.get(v), enumerable: true, writable: true, configurable: true });
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return copy;
|
|
296
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/mods-kernel",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "7.54.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "The sema mods kernel: one extension bus (registration, admission, chain, failure and budget law) shared by the engine and the shell. Zero dependencies.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"license": "BUSL-1.1",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/sema-agent/sema-core.git",
|
|
11
|
+
"directory": "packages/mods-kernel"
|
|
12
|
+
},
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=20.18.1"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc -p tsconfig.json"
|
|
33
|
+
}
|
|
34
|
+
}
|