@alma-harness/core 0.1.0 → 0.3.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 +42 -6
- package/dist/{chunk-KPNXPUGR.js → chunk-HSKAZW6G.js} +15 -2
- package/dist/chunk-HSKAZW6G.js.map +1 -0
- package/dist/index.d.ts +43 -244
- package/dist/index.js +59 -6
- package/dist/index.js.map +1 -1
- package/dist/{turn-store-uKJ4inz2.d.ts → routines-B7g-8ZwE.d.ts} +780 -62
- package/dist/testing/index.d.ts +49 -2
- package/dist/testing/index.js +68 -18
- package/dist/testing/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-KPNXPUGR.js.map +0 -1
package/dist/testing/index.d.ts
CHANGED
|
@@ -1,5 +1,52 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { af as RoutineRunStore, ad as RoutineRun, S as Scope, ae as RoutineRunOutcome, ag as RoutineStore, aa as Routine, aw as StoredRoutine, am as SessionStore, Q as Msg, x as LoadOpts, aJ as ToolTrafficExpiry, aq as SpendStore, ap as SpendKey, ar as SpendTotals, aR as TurnStore, L as LeaseOpts, aP as TurnLease, aO as TurnKey, aL as TurnClaim, C as CompletedTurn, c as AuditLog, A as AccessEvent, ah as RoutingEvent, m as CostEvent, a9 as RecallEvent, j as ContextEvent } from '../routines-B7g-8ZwE.js';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* In-memory `RoutineRunStore` — the reference implementation, and the
|
|
5
|
+
* runner tests' double (spec: routine-runner). Test/example use only:
|
|
6
|
+
* nothing survives the process.
|
|
7
|
+
*
|
|
8
|
+
* NESTED maps, looked up exactly — the lesson `InMemoryTurnStore` records
|
|
9
|
+
* (spec 033): `routineId` and `runId` are product strings and must never be
|
|
10
|
+
* concatenated into a key another id could extend.
|
|
11
|
+
*/
|
|
12
|
+
declare class InMemoryRoutineRunStore implements RoutineRunStore {
|
|
13
|
+
#private;
|
|
14
|
+
record(run: RoutineRun): Promise<void>;
|
|
15
|
+
get(scope: Scope, routineId: string, runId: string): Promise<RoutineRun | null>;
|
|
16
|
+
list(scope: Scope, routineId: string, opts?: {
|
|
17
|
+
since?: string;
|
|
18
|
+
outcome?: RoutineRunOutcome;
|
|
19
|
+
limit?: number;
|
|
20
|
+
}): Promise<RoutineRun[]>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* In-memory `RoutineStore` — the reference implementation and the schedule
|
|
25
|
+
* tests' double (spec: clock-tick). Nested maps looked up exactly, the
|
|
26
|
+
* lesson every in-memory store here records (spec 033).
|
|
27
|
+
*/
|
|
28
|
+
declare class InMemoryRoutineStore implements RoutineStore {
|
|
29
|
+
#private;
|
|
30
|
+
constructor(opts?: {
|
|
31
|
+
now?: () => string;
|
|
32
|
+
});
|
|
33
|
+
register(routine: Routine & {
|
|
34
|
+
registeredAt?: string;
|
|
35
|
+
}): Promise<void>;
|
|
36
|
+
cancel(scope: Scope, routineId: string): Promise<void>;
|
|
37
|
+
get(scope: Scope, routineId: string): Promise<StoredRoutine | null>;
|
|
38
|
+
list(): Promise<StoredRoutine[]>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* In-memory `SessionStore` — the reference implementation that proves the
|
|
43
|
+
* shared contract suite (§6) is satisfiable. Test/example use only: nothing
|
|
44
|
+
* survives the process.
|
|
45
|
+
*
|
|
46
|
+
* Keys are built with {@link scopePath}, so scope validation applies here
|
|
47
|
+
* exactly as it will in real adapters.
|
|
48
|
+
*/
|
|
49
|
+
/** Rejects an unparseable cutoff rather than silently treating it as the epoch. */
|
|
3
50
|
declare class InMemorySessionStore implements SessionStore {
|
|
4
51
|
#private;
|
|
5
52
|
append(scope: Scope, sessionId: string, entries: Msg[]): Promise<void>;
|
|
@@ -71,4 +118,4 @@ declare class RecordingAuditLog implements AuditLog {
|
|
|
71
118
|
context(e: ContextEvent): void;
|
|
72
119
|
}
|
|
73
120
|
|
|
74
|
-
export { InMemorySessionStore, InMemorySpendStore, InMemoryTurnStore, RecordingAuditLog };
|
|
121
|
+
export { InMemoryRoutineRunStore, InMemoryRoutineStore, InMemorySessionStore, InMemorySpendStore, InMemoryTurnStore, RecordingAuditLog };
|
package/dist/testing/index.js
CHANGED
|
@@ -1,14 +1,71 @@
|
|
|
1
1
|
import {
|
|
2
|
+
assertIso8601,
|
|
2
3
|
assertWellFormed,
|
|
4
|
+
parseIso8601,
|
|
3
5
|
scopePath
|
|
4
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-HSKAZW6G.js";
|
|
7
|
+
|
|
8
|
+
// src/testing/in-memory-routine-run-store.ts
|
|
9
|
+
var InMemoryRoutineRunStore = class {
|
|
10
|
+
/** scopePath → routineId → runId → run. */
|
|
11
|
+
#runs = /* @__PURE__ */ new Map();
|
|
12
|
+
async record(run) {
|
|
13
|
+
const key = scopePath(run.scope);
|
|
14
|
+
parseIso8601(run.startedAt);
|
|
15
|
+
if (run.finishedAt !== void 0) parseIso8601(run.finishedAt);
|
|
16
|
+
let routines = this.#runs.get(key);
|
|
17
|
+
if (!routines) {
|
|
18
|
+
routines = /* @__PURE__ */ new Map();
|
|
19
|
+
this.#runs.set(key, routines);
|
|
20
|
+
}
|
|
21
|
+
let runs = routines.get(run.routineId);
|
|
22
|
+
if (!runs) {
|
|
23
|
+
runs = /* @__PURE__ */ new Map();
|
|
24
|
+
routines.set(run.routineId, runs);
|
|
25
|
+
}
|
|
26
|
+
runs.set(run.id, structuredClone(run));
|
|
27
|
+
}
|
|
28
|
+
async get(scope, routineId, runId) {
|
|
29
|
+
const run = this.#runs.get(scopePath(scope))?.get(routineId)?.get(runId);
|
|
30
|
+
return run ? structuredClone(run) : null;
|
|
31
|
+
}
|
|
32
|
+
async list(scope, routineId, opts = {}) {
|
|
33
|
+
const all = [...this.#runs.get(scopePath(scope))?.get(routineId)?.values() ?? []];
|
|
34
|
+
const since = opts.since === void 0 ? void 0 : parseIso8601(opts.since);
|
|
35
|
+
const hits = all.filter((r) => (since === void 0 || parseIso8601(r.startedAt) >= since) && (opts.outcome === void 0 || r.outcome === opts.outcome)).sort((a, b) => parseIso8601(b.startedAt) - parseIso8601(a.startedAt) || a.id.localeCompare(b.id));
|
|
36
|
+
return structuredClone(opts.limit === void 0 ? hits : hits.slice(0, Math.max(0, opts.limit)));
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// src/testing/in-memory-routine-store.ts
|
|
41
|
+
var InMemoryRoutineStore = class {
|
|
42
|
+
/** scopePath → routine id → routine. */
|
|
43
|
+
#routines = /* @__PURE__ */ new Map();
|
|
44
|
+
#now;
|
|
45
|
+
constructor(opts = {}) {
|
|
46
|
+
this.#now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
47
|
+
}
|
|
48
|
+
async register(routine) {
|
|
49
|
+
const key = scopePath(routine.scope);
|
|
50
|
+
const mine = this.#routines.get(key) ?? /* @__PURE__ */ new Map();
|
|
51
|
+
if (routine.registeredAt !== void 0) assertIso8601(routine.registeredAt, "registeredAt");
|
|
52
|
+
const registeredAt = mine.get(routine.id)?.registeredAt ?? routine.registeredAt ?? this.#now();
|
|
53
|
+
mine.set(routine.id, structuredClone({ ...routine, registeredAt }));
|
|
54
|
+
this.#routines.set(key, mine);
|
|
55
|
+
}
|
|
56
|
+
async cancel(scope, routineId) {
|
|
57
|
+
this.#routines.get(scopePath(scope))?.delete(routineId);
|
|
58
|
+
}
|
|
59
|
+
async get(scope, routineId) {
|
|
60
|
+
const routine = this.#routines.get(scopePath(scope))?.get(routineId);
|
|
61
|
+
return routine ? structuredClone(routine) : null;
|
|
62
|
+
}
|
|
63
|
+
async list() {
|
|
64
|
+
return structuredClone([...this.#routines.values()].flatMap((mine) => [...mine.values()]));
|
|
65
|
+
}
|
|
66
|
+
};
|
|
5
67
|
|
|
6
68
|
// src/testing/in-memory-session-store.ts
|
|
7
|
-
function instant(at) {
|
|
8
|
-
const ms = Date.parse(at);
|
|
9
|
-
if (Number.isNaN(ms)) throw new Error(`invalid ISO 8601 timestamp: ${JSON.stringify(at)}`);
|
|
10
|
-
return ms;
|
|
11
|
-
}
|
|
12
69
|
var InMemorySessionStore = class {
|
|
13
70
|
/** scopePath(scope) → sessionId → chronological log. */
|
|
14
71
|
#scopes = /* @__PURE__ */ new Map();
|
|
@@ -30,7 +87,7 @@ var InMemorySessionStore = class {
|
|
|
30
87
|
return structuredClone(window);
|
|
31
88
|
}
|
|
32
89
|
async expireToolTraffic(scope, sessionId, opts) {
|
|
33
|
-
const cutoff =
|
|
90
|
+
const cutoff = parseIso8601(opts.inactiveSince);
|
|
34
91
|
const log = this.#scopes.get(scopePath(scope))?.get(sessionId) ?? [];
|
|
35
92
|
const active = log.some((m) => {
|
|
36
93
|
const at = m.meta?.at;
|
|
@@ -43,7 +100,7 @@ var InMemorySessionStore = class {
|
|
|
43
100
|
const kept = [];
|
|
44
101
|
for (const msg of log) {
|
|
45
102
|
const survivors = msg.blocks.filter(
|
|
46
|
-
(b) => b.type !== "tool_call" && b.type !== "tool_result"
|
|
103
|
+
(b) => b.type !== "tool_call" && b.type !== "tool_result" && b.type !== "reasoning"
|
|
47
104
|
);
|
|
48
105
|
blocks += msg.blocks.length - survivors.length;
|
|
49
106
|
if (survivors.length > 0) kept.push({ ...msg, blocks: survivors });
|
|
@@ -175,22 +232,13 @@ var InMemoryTurnStore = class {
|
|
|
175
232
|
}
|
|
176
233
|
async erase(scope, sessionId) {
|
|
177
234
|
if (sessionId !== void 0) {
|
|
178
|
-
|
|
179
|
-
this.#claims.delete(session);
|
|
180
|
-
this.#leases.delete(session);
|
|
181
|
-
this.#wake(session);
|
|
235
|
+
this.#claims.delete(sessionKey(scope, sessionId));
|
|
182
236
|
return;
|
|
183
237
|
}
|
|
184
238
|
const prefix = `${scopePath(scope)}/`;
|
|
185
239
|
for (const id of [...this.#claims.keys()]) {
|
|
186
240
|
if (id.startsWith(prefix)) this.#claims.delete(id);
|
|
187
241
|
}
|
|
188
|
-
for (const id of [...this.#leases.keys()]) {
|
|
189
|
-
if (id.startsWith(prefix)) {
|
|
190
|
-
this.#leases.delete(id);
|
|
191
|
-
this.#wake(id);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
242
|
}
|
|
195
243
|
#waitFor(key, ms) {
|
|
196
244
|
return new Promise((resolve) => {
|
|
@@ -260,6 +308,8 @@ var RecordingAuditLog = class {
|
|
|
260
308
|
}
|
|
261
309
|
};
|
|
262
310
|
export {
|
|
311
|
+
InMemoryRoutineRunStore,
|
|
312
|
+
InMemoryRoutineStore,
|
|
263
313
|
InMemorySessionStore,
|
|
264
314
|
InMemorySpendStore,
|
|
265
315
|
InMemoryTurnStore,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/testing/in-memory-session-store.ts","../../src/testing/in-memory-spend-store.ts","../../src/testing/in-memory-turn-store.ts","../../src/testing/recording-audit-log.ts"],"sourcesContent":["import type { Msg } from \"../messages\";\nimport { scopePath, type Scope } from \"../scope\";\nimport type { LoadOpts, SessionStore, ToolTrafficExpiry } from \"../session\";\nimport { assertWellFormed } from \"../text\";\n\n/**\n * In-memory `SessionStore` — the reference implementation that proves the\n * shared contract suite (§6) is satisfiable. Test/example use only: nothing\n * survives the process.\n *\n * Keys are built with {@link scopePath}, so scope validation applies here\n * exactly as it will in real adapters.\n */\n/** Rejects an unparseable cutoff rather than silently treating it as the epoch. */\nfunction instant(at: string): number {\n const ms = Date.parse(at);\n if (Number.isNaN(ms)) throw new Error(`invalid ISO 8601 timestamp: ${JSON.stringify(at)}`);\n return ms;\n}\n\nexport class InMemorySessionStore implements SessionStore {\n /** scopePath(scope) → sessionId → chronological log. */\n readonly #scopes = new Map<string, Map<string, Msg[]>>();\n\n async append(scope: Scope, sessionId: string, entries: Msg[]): Promise<void> {\n const key = scopePath(scope);\n // Refuse malformed text, exactly as Postgres does — spec 040. This store\n // used to KEEP a lone surrogate while the SQL adapter rejected the write,\n // and both contracts documented the divergence instead of closing it.\n // Per entry rather than over the array, so the error names the row: a\n // migration failing on one of fifteen thousand messages needs to know\n // which, and the clean path costs the same walk either way.\n for (const [i, entry] of entries.entries()) assertWellFormed(entry, `entries[${i}]`);\n let sessions = this.#scopes.get(key);\n if (!sessions) {\n sessions = new Map();\n this.#scopes.set(key, sessions);\n }\n const log = sessions.get(sessionId) ?? [];\n log.push(...structuredClone(entries));\n sessions.set(sessionId, log);\n }\n\n async load(scope: Scope, sessionId: string, opts?: LoadOpts): Promise<Msg[]> {\n const log = this.#scopes.get(scopePath(scope))?.get(sessionId) ?? [];\n const window =\n opts?.limit === undefined ? log : opts.limit <= 0 ? [] : log.slice(-opts.limit);\n return structuredClone(window);\n }\n\n async expireToolTraffic(\n scope: Scope,\n sessionId: string,\n opts: { inactiveSince: string },\n ): Promise<ToolTrafficExpiry> {\n const cutoff = instant(opts.inactiveSince);\n const log = this.#scopes.get(scopePath(scope))?.get(sessionId) ?? [];\n // Refused, not thrown — an active session in a sweep is the normal case\n // (spec 039). A message with no `meta.at` counts as ACTIVE: an entry whose\n // age cannot be established must not be assumed old, which is the safe\n // direction when the cost of being wrong is the +27% rewrite.\n const active = log.some((m) => {\n const at = m.meta?.at;\n // A MALFORMED stamp counts as active too, and the first version missed\n // it: Date.parse gives NaN, and NaN > cutoff is false, so an entry whose\n // age could not be established was treated as OLD — the opposite of\n // what the rule says (spec 039 review).\n if (at === undefined) return true;\n const ms = Date.parse(at);\n return Number.isNaN(ms) || ms > cutoff;\n });\n if (active) return { blocks: 0, messages: 0, expired: false };\n\n let blocks = 0;\n const kept: Msg[] = [];\n for (const msg of log) {\n // ALL of them or none — partial expiry would leave a `tool_call` without\n // its `tool_result`, which a provider answers with a 400 (spec 026).\n const survivors = msg.blocks.filter(\n (b) => b.type !== \"tool_call\" && b.type !== \"tool_result\",\n );\n blocks += msg.blocks.length - survivors.length;\n if (survivors.length > 0) kept.push({ ...msg, blocks: survivors });\n }\n const messages = log.length - kept.length;\n this.#scopes.get(scopePath(scope))?.set(sessionId, kept);\n return { blocks, messages, expired: true };\n }\n\n async erase(scope: Scope, sessionId?: string): Promise<void> {\n const key = scopePath(scope);\n if (sessionId === undefined) {\n this.#scopes.delete(key);\n return;\n }\n this.#scopes.get(key)?.delete(sessionId);\n }\n}\n","import type { SpendKey, SpendStore, SpendTotals } from \"../budget\";\nimport { scopePath, type Scope } from \"../scope\";\n\n/**\n * In-memory `SpendStore` — the reference implementation that proves the\n * shared contract suite (spec: spend-store) is satisfiable, and the loop\n * tests' double. Test/example use only: nothing survives the process.\n *\n * `add` is atomic per call by construction — no `await` sits between the\n * read and the write, so single-threaded JS cannot interleave two adds.\n * Keys are built with {@link scopePath}, so scope validation applies here\n * exactly as it will in real adapters.\n */\nexport class InMemorySpendStore implements SpendStore {\n /** scopePath(scope)/sessionId → usd. */\n readonly #sessions = new Map<string, number>();\n /** org/UTC-day → usd — deliberately org-wide, across uids. */\n readonly #tenantDays = new Map<string, number>();\n\n async add(entry: SpendKey & { usd: number }): Promise<SpendTotals> {\n if (!Number.isFinite(entry.usd) || entry.usd < 0) {\n // Mirrors the Postgres adapter (review finding): one admitted NaN makes\n // both counters NaN forever, disabling every cap comparison — and there\n // is deliberately no delete surface to reset them with.\n throw new Error(`spend must be a non-negative finite number, got ${entry.usd}`);\n }\n const { sessionKey, dayKey } = keysOf(entry);\n const sessionUsd = (this.#sessions.get(sessionKey) ?? 0) + entry.usd;\n this.#sessions.set(sessionKey, sessionUsd);\n const tenantDayUsd = (this.#tenantDays.get(dayKey) ?? 0) + entry.usd;\n this.#tenantDays.set(dayKey, tenantDayUsd);\n return { sessionUsd, tenantDayUsd };\n }\n\n async peek(key: SpendKey): Promise<SpendTotals> {\n const { sessionKey, dayKey } = keysOf(key);\n return {\n sessionUsd: this.#sessions.get(sessionKey) ?? 0,\n tenantDayUsd: this.#tenantDays.get(dayKey) ?? 0,\n };\n }\n}\n\nfunction keysOf(key: SpendKey): { sessionKey: string; dayKey: string } {\n return {\n sessionKey: `${scopePath(key.scope)}/${key.sessionId}`,\n dayKey: `tenants/${validatedOrg(key.scope)}/days/${utcDay(key.at)}`,\n };\n}\n\n/** The day counter keys on org alone, but the whole scope is still validated. */\nfunction validatedOrg(scope: Scope): string {\n scopePath(scope);\n return scope.org;\n}\n\n/** ISO 8601 → `YYYY-MM-DD` in UTC — the contract's day-bucket derivation. */\nfunction utcDay(at: string): string {\n const ms = Date.parse(at);\n if (Number.isNaN(ms)) throw new Error(`invalid ISO 8601 timestamp: ${JSON.stringify(at)}`);\n return new Date(ms).toISOString().slice(0, 10);\n}\n","import { scopePath, type Scope } from \"../scope\";\nimport { assertWellFormed } from \"../text\";\nimport type {\n CompletedTurn,\n LeaseOpts,\n TurnClaim,\n TurnKey,\n TurnLease,\n TurnStore,\n} from \"../turn-store\";\n\n/**\n * In-memory `TurnStore` — the reference implementation that proves the shared\n * contract suite (spec 030) is satisfiable, and the loop tests' double.\n * Test/example use only: nothing survives the process, so it serializes one\n * instance and not a deployment.\n *\n * The clock is `Date.now()` and deliberately NOT injectable. A fake clock here\n * would desynchronize from the real `setTimeout` the wait is built on — the\n * test advances one and the other keeps sleeping. Expiry is exercised with\n * small TTLs against real time, which is also the only thing that can work\n * against Postgres, where `now()` is the server's.\n *\n * Keys are built with {@link scopePath}, so scope validation applies here\n * exactly as it does in real adapters.\n */\nexport class InMemoryTurnStore implements TurnStore {\n /** scopePath/sessionId → the live lease. */\n readonly #leases = new Map<string, { token: string; expiresAtMs: number }>();\n /**\n * scopePath/sessionId → idempotencyKey → the completed turn, or null while in\n * flight.\n *\n * NESTED and looked up exactly, never one flat string erased by prefix —\n * spec 033. `scopePath` validates `org` and `uid` against a charset without\n * `/`, but `sessionId` is not validated and never should be: a product using\n * a composite id (`\"platform/thread\"`, a migration's `\"s1/legacy\"`) made one\n * session's prefix match another's keys, so erasing `\"s1\"` deleted\n * `\"s1/legacy\"`'s stored reply. `PostgresTurnStore` compares `session_id` by\n * SQL equality and was immune, so the two adapters DIVERGED on erasure — the\n * one thing the shared contract suite exists to prevent, missed because its\n * sibling case used `\"s1\"`/`\"s2\"`.\n *\n * `InMemorySessionStore` had already solved this by nesting; concatenating\n * and prefix-matching in a new store reintroduced a bug class this codebase\n * knew about.\n */\n readonly #claims = new Map<string, Map<string, CompletedTurn | null>>();\n /** Waiters per session key, woken in FIFO order on release. */\n readonly #waiters = new Map<string, (() => void)[]>();\n #tokens = 0;\n\n async acquire(scope: Scope, sessionId: string, opts: LeaseOpts): Promise<TurnLease | null> {\n assertLeaseOpts(opts);\n const key = sessionKey(scope, sessionId);\n const deadline = Date.now() + opts.waitMs;\n for (;;) {\n const taken = this.#leases.get(key);\n // An EXPIRED lease is not a lease. A holder that crashed must not block\n // its session forever, which is the whole reason `ttlMs` exists.\n if (taken === undefined || taken.expiresAtMs <= Date.now()) {\n const lease = {\n token: `lease-${++this.#tokens}`,\n expiresAtMs: Date.now() + opts.ttlMs,\n };\n this.#leases.set(key, lease);\n return { token: lease.token, expiresAt: new Date(lease.expiresAtMs).toISOString() };\n }\n const remaining = Math.min(deadline, taken.expiresAtMs) - Date.now();\n if (remaining <= 0) return null;\n // Woken by `release`, or by the expiry — whichever comes first. Waiting\n // on the expiry too is what keeps a crashed holder from making every\n // waiter burn its whole `waitMs` before noticing.\n await this.#waitFor(key, remaining);\n }\n }\n\n async release(scope: Scope, sessionId: string, lease: TurnLease): Promise<void> {\n const key = sessionKey(scope, sessionId);\n const held = this.#leases.get(key);\n // A STALE token is a no-op. A holder whose lease already expired must\n // never release the lease the NEXT turn is holding — that serializes\n // nothing while appearing to (spec 030).\n if (held === undefined || held.token !== lease.token) return;\n this.#leases.delete(key);\n this.#wake(key);\n }\n\n async claim(key: TurnKey): Promise<TurnClaim> {\n const session = sessionKey(key.scope, key.sessionId);\n const completed = this.#claims.get(session)?.get(key.idempotencyKey);\n // `null` is an IN-FLIGHT claim, only reachable after a crash: the lease\n // makes concurrency impossible. Re-running is the correct answer — the\n // turn produced no result, so there is nothing to replay — and it is also\n // the crash window this slice does not close (spec 030).\n //\n // `undefined` means no entry at all. Reading `has` and then `get` would\n // have been two lookups agreeing about a map nothing else can touch here,\n // but `get` alone distinguishes all three states.\n if (completed != null) return { status: \"replay\", completed };\n const claims = this.#claims.get(session) ?? new Map<string, CompletedTurn | null>();\n claims.set(key.idempotencyKey, null);\n this.#claims.set(session, claims);\n return { status: \"fresh\" };\n }\n\n async complete(key: TurnKey, completed: CompletedTurn): Promise<void> {\n const claims = this.#claims.get(sessionKey(key.scope, key.sessionId));\n // Same guard as `SessionStore.append`, for the same reason and the same\n // divergence — spec 040. BEFORE the update-only test, not after, because\n // that is where Postgres effectively checks: `$5::jsonb` is parsed whether\n // or not the UPDATE matches a row, so the SQL adapter refuses a malformed\n // reply even for a claim that no longer exists. Guarding after `has` here\n // would have closed one divergence by opening another.\n assertWellFormed(completed, \"completed\");\n // UPDATE-only. A session erased while its turn was still running must not\n // have the reply resurrected by that turn finishing: erasure wins over\n // work that started before it — the rule the memory tier already spells\n // out as the `stale` observe outcome (spec 011).\n //\n // It is also the only shape a SQL adapter can implement without an upsert\n // that re-creates the row, so writing it unconditionally here would put a\n // silent divergence between the reference and Postgres into the one place\n // the contract suite exists to prevent it.\n if (!claims?.has(key.idempotencyKey)) return;\n claims.set(key.idempotencyKey, structuredClone(completed));\n }\n\n async abandon(key: TurnKey): Promise<void> {\n this.#claims.get(sessionKey(key.scope, key.sessionId))?.delete(key.idempotencyKey);\n }\n\n async erase(scope: Scope, sessionId?: string): Promise<void> {\n // One session: exact keys on both maps. Scope-wide: the scope's own prefix,\n // which IS safe — `scopePath` renders `tenants/{org}/users/{uid}` from a\n // charset excluding `/`, so no uid can spoof the delimiter after it. Only\n // the untrusted `sessionId` ever needed exact treatment (spec 033).\n if (sessionId !== undefined) {\n const session = sessionKey(scope, sessionId);\n this.#claims.delete(session);\n this.#leases.delete(session);\n this.#wake(session);\n return;\n }\n const prefix = `${scopePath(scope)}/`;\n for (const id of [...this.#claims.keys()]) {\n if (id.startsWith(prefix)) this.#claims.delete(id);\n }\n for (const id of [...this.#leases.keys()]) {\n if (id.startsWith(prefix)) {\n this.#leases.delete(id);\n this.#wake(id);\n }\n }\n }\n\n #waitFor(key: string, ms: number): Promise<void> {\n return new Promise<void>((resolve) => {\n const queue = this.#waiters.get(key) ?? [];\n let done = false;\n const settle = (): void => {\n if (done) return;\n done = true;\n clearTimeout(timer);\n resolve();\n };\n const timer = setTimeout(() => {\n // Removes ITSELF from the queue — the first draft only guarded against\n // firing twice, so a waiter that timed out unwoken left a dead closure\n // in the array until the next real `release`, which for a crashed\n // holder never comes (spec 033).\n const pending = this.#waiters.get(key);\n if (pending) {\n const at = pending.indexOf(settle);\n if (at !== -1) pending.splice(at, 1);\n if (pending.length === 0) this.#waiters.delete(key);\n }\n settle();\n }, ms);\n // Not `unref`'d and deliberately cleared on both paths: a pending timer\n // outliving the turn that scheduled it is the leak spec 022 closed on\n // the recall deadline.\n queue.push(settle);\n this.#waiters.set(key, queue);\n });\n }\n\n #wake(key: string): void {\n const queue = this.#waiters.get(key);\n if (!queue) return;\n this.#waiters.delete(key);\n for (const wake of queue) wake();\n }\n}\n\nfunction assertLeaseOpts(opts: LeaseOpts): void {\n for (const [name, value] of [\n [\"ttlMs\", opts.ttlMs],\n [\"waitMs\", opts.waitMs],\n ] as const) {\n // A NaN ttl makes every comparison false, so the lease reads as live\n // forever and the session is blocked until the process restarts.\n if (!Number.isFinite(value) || value < 0) {\n throw new Error(`${name} must be a non-negative finite number, got ${value}`);\n }\n }\n}\n\nfunction sessionKey(scope: Scope, sessionId: string): string {\n return `${scopePath(scope)}/${sessionId}`;\n}\n\n\n","import type {\n AccessEvent,\n AuditLog,\n ContextEvent,\n CostEvent,\n RecallEvent,\n RoutingEvent,\n} from \"../audit\";\n\n/**\n * `AuditLog` that keeps what it was given — for tests that must assert a trail\n * exists, not just that an operation succeeded (§6.8: where trails are written\n * is swappable, that they are written is not).\n */\nexport class RecordingAuditLog implements AuditLog {\n readonly accessEvents: AccessEvent[] = [];\n readonly routingEvents: RoutingEvent[] = [];\n readonly costEvents: CostEvent[] = [];\n readonly recallEvents: RecallEvent[] = [];\n readonly contextEvents: ContextEvent[] = [];\n\n access(e: AccessEvent): void {\n this.accessEvents.push(e);\n }\n\n routing(e: RoutingEvent): void {\n this.routingEvents.push(e);\n }\n\n cost(e: CostEvent): void {\n this.costEvents.push(e);\n }\n\n recall(e: RecallEvent): void {\n this.recallEvents.push(e);\n }\n\n context(e: ContextEvent): void {\n this.contextEvents.push(e);\n }\n}\n"],"mappings":"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","names":["sessionKey"]}
|
|
1
|
+
{"version":3,"sources":["../../src/testing/in-memory-routine-run-store.ts","../../src/testing/in-memory-routine-store.ts","../../src/testing/in-memory-session-store.ts","../../src/testing/in-memory-spend-store.ts","../../src/testing/in-memory-turn-store.ts","../../src/testing/recording-audit-log.ts"],"sourcesContent":["import type { RoutineRun, RoutineRunOutcome, RoutineRunStore } from \"../routines\";\nimport { scopePath, type Scope } from \"../scope\";\nimport { parseIso8601 } from \"../time\";\n\n/**\n * In-memory `RoutineRunStore` — the reference implementation, and the\n * runner tests' double (spec: routine-runner). Test/example use only:\n * nothing survives the process.\n *\n * NESTED maps, looked up exactly — the lesson `InMemoryTurnStore` records\n * (spec 033): `routineId` and `runId` are product strings and must never be\n * concatenated into a key another id could extend.\n */\nexport class InMemoryRoutineRunStore implements RoutineRunStore {\n /** scopePath → routineId → runId → run. */\n readonly #runs = new Map<string, Map<string, Map<string, RoutineRun>>>();\n\n async record(run: RoutineRun): Promise<void> {\n const key = scopePath(run.scope);\n // Refused on the way in, like the Postgres adapter: a stamp that cannot be\n // parsed would otherwise fail every later `list` (spec: postgres-routine-runs).\n parseIso8601(run.startedAt);\n if (run.finishedAt !== undefined) parseIso8601(run.finishedAt);\n let routines = this.#runs.get(key);\n if (!routines) {\n routines = new Map();\n this.#runs.set(key, routines);\n }\n let runs = routines.get(run.routineId);\n if (!runs) {\n runs = new Map();\n routines.set(run.routineId, runs);\n }\n runs.set(run.id, structuredClone(run));\n }\n\n async get(scope: Scope, routineId: string, runId: string): Promise<RoutineRun | null> {\n const run = this.#runs.get(scopePath(scope))?.get(routineId)?.get(runId);\n return run ? structuredClone(run) : null;\n }\n\n async list(\n scope: Scope,\n routineId: string,\n opts: { since?: string; outcome?: RoutineRunOutcome; limit?: number } = {},\n ): Promise<RoutineRun[]> {\n const all = [...(this.#runs.get(scopePath(scope))?.get(routineId)?.values() ?? [])];\n const since = opts.since === undefined ? undefined : parseIso8601(opts.since);\n const hits = all\n .filter((r) => (since === undefined || parseIso8601(r.startedAt) >= since) && (opts.outcome === undefined || r.outcome === opts.outcome))\n // Ties by id, ascending — the order Postgres gives (spec: close-060-064-findings).\n .sort((a, b) => parseIso8601(b.startedAt) - parseIso8601(a.startedAt) || a.id.localeCompare(b.id));\n return structuredClone(opts.limit === undefined ? hits : hits.slice(0, Math.max(0, opts.limit)));\n }\n}\n\n","import type { Routine, RoutineStore, StoredRoutine } from \"../routines\";\nimport { scopePath, type Scope } from \"../scope\";\nimport { assertIso8601 } from \"../time\";\n\n/**\n * In-memory `RoutineStore` — the reference implementation and the schedule\n * tests' double (spec: clock-tick). Nested maps looked up exactly, the\n * lesson every in-memory store here records (spec 033).\n */\nexport class InMemoryRoutineStore implements RoutineStore {\n /** scopePath → routine id → routine. */\n readonly #routines = new Map<string, Map<string, StoredRoutine>>();\n readonly #now: () => string;\n\n constructor(opts: { now?: () => string } = {}) {\n this.#now = opts.now ?? (() => new Date().toISOString());\n }\n\n async register(routine: Routine & { registeredAt?: string }): Promise<void> {\n const key = scopePath(routine.scope);\n const mine = this.#routines.get(key) ?? new Map<string, StoredRoutine>();\n if (routine.registeredAt !== undefined) assertIso8601(routine.registeredAt, \"registeredAt\");\n // The anchor stays: editing a goal must not move an `every` routine's fires.\n const registeredAt = mine.get(routine.id)?.registeredAt ?? routine.registeredAt ?? this.#now();\n mine.set(routine.id, structuredClone({ ...routine, registeredAt }));\n this.#routines.set(key, mine);\n }\n\n async cancel(scope: Scope, routineId: string): Promise<void> {\n this.#routines.get(scopePath(scope))?.delete(routineId);\n }\n\n async get(scope: Scope, routineId: string): Promise<StoredRoutine | null> {\n const routine = this.#routines.get(scopePath(scope))?.get(routineId);\n return routine ? structuredClone(routine) : null;\n }\n\n async list(): Promise<StoredRoutine[]> {\n return structuredClone([...this.#routines.values()].flatMap((mine) => [...mine.values()]));\n }\n}\n","import type { Msg } from \"../messages\";\nimport { scopePath, type Scope } from \"../scope\";\nimport { parseIso8601 } from \"../time\";\nimport type { LoadOpts, SessionStore, ToolTrafficExpiry } from \"../session\";\nimport { assertWellFormed } from \"../text\";\n\n/**\n * In-memory `SessionStore` — the reference implementation that proves the\n * shared contract suite (§6) is satisfiable. Test/example use only: nothing\n * survives the process.\n *\n * Keys are built with {@link scopePath}, so scope validation applies here\n * exactly as it will in real adapters.\n */\n/** Rejects an unparseable cutoff rather than silently treating it as the epoch. */\n\nexport class InMemorySessionStore implements SessionStore {\n /** scopePath(scope) → sessionId → chronological log. */\n readonly #scopes = new Map<string, Map<string, Msg[]>>();\n\n async append(scope: Scope, sessionId: string, entries: Msg[]): Promise<void> {\n const key = scopePath(scope);\n // Refuse malformed text, exactly as Postgres does — spec 040. This store\n // used to KEEP a lone surrogate while the SQL adapter rejected the write,\n // and both contracts documented the divergence instead of closing it.\n // Per entry rather than over the array, so the error names the row: a\n // migration failing on one of fifteen thousand messages needs to know\n // which, and the clean path costs the same walk either way.\n for (const [i, entry] of entries.entries()) assertWellFormed(entry, `entries[${i}]`);\n let sessions = this.#scopes.get(key);\n if (!sessions) {\n sessions = new Map();\n this.#scopes.set(key, sessions);\n }\n const log = sessions.get(sessionId) ?? [];\n log.push(...structuredClone(entries));\n sessions.set(sessionId, log);\n }\n\n async load(scope: Scope, sessionId: string, opts?: LoadOpts): Promise<Msg[]> {\n const log = this.#scopes.get(scopePath(scope))?.get(sessionId) ?? [];\n const window =\n opts?.limit === undefined ? log : opts.limit <= 0 ? [] : log.slice(-opts.limit);\n return structuredClone(window);\n }\n\n async expireToolTraffic(\n scope: Scope,\n sessionId: string,\n opts: { inactiveSince: string },\n ): Promise<ToolTrafficExpiry> {\n const cutoff = parseIso8601(opts.inactiveSince);\n const log = this.#scopes.get(scopePath(scope))?.get(sessionId) ?? [];\n // Refused, not thrown — an active session in a sweep is the normal case\n // (spec 039). A message with no `meta.at` counts as ACTIVE: an entry whose\n // age cannot be established must not be assumed old, which is the safe\n // direction when the cost of being wrong is the +27% rewrite.\n const active = log.some((m) => {\n const at = m.meta?.at;\n // A MALFORMED stamp counts as active too, and the first version missed\n // it: Date.parse gives NaN, and NaN > cutoff is false, so an entry whose\n // age could not be established was treated as OLD — the opposite of\n // what the rule says (spec 039 review).\n if (at === undefined) return true;\n const ms = Date.parse(at);\n return Number.isNaN(ms) || ms > cutoff;\n });\n if (active) return { blocks: 0, messages: 0, expired: false };\n\n let blocks = 0;\n const kept: Msg[] = [];\n for (const msg of log) {\n // ALL of them or none — partial expiry would leave a `tool_call` without\n // its `tool_result`, which a provider answers with a 400 (spec 026).\n // Reasoning goes with them: backstage content of the same half (spec:\n // reasoning-blocks).\n const survivors = msg.blocks.filter(\n (b) => b.type !== \"tool_call\" && b.type !== \"tool_result\" && b.type !== \"reasoning\",\n );\n blocks += msg.blocks.length - survivors.length;\n if (survivors.length > 0) kept.push({ ...msg, blocks: survivors });\n }\n const messages = log.length - kept.length;\n this.#scopes.get(scopePath(scope))?.set(sessionId, kept);\n return { blocks, messages, expired: true };\n }\n\n async erase(scope: Scope, sessionId?: string): Promise<void> {\n const key = scopePath(scope);\n if (sessionId === undefined) {\n this.#scopes.delete(key);\n return;\n }\n this.#scopes.get(key)?.delete(sessionId);\n }\n}\n","import type { SpendKey, SpendStore, SpendTotals } from \"../budget\";\nimport { scopePath, type Scope } from \"../scope\";\n\n/**\n * In-memory `SpendStore` — the reference implementation that proves the\n * shared contract suite (spec: spend-store) is satisfiable, and the loop\n * tests' double. Test/example use only: nothing survives the process.\n *\n * `add` is atomic per call by construction — no `await` sits between the\n * read and the write, so single-threaded JS cannot interleave two adds.\n * Keys are built with {@link scopePath}, so scope validation applies here\n * exactly as it will in real adapters.\n */\nexport class InMemorySpendStore implements SpendStore {\n /** scopePath(scope)/sessionId → usd. */\n readonly #sessions = new Map<string, number>();\n /** org/UTC-day → usd — deliberately org-wide, across uids. */\n readonly #tenantDays = new Map<string, number>();\n\n async add(entry: SpendKey & { usd: number }): Promise<SpendTotals> {\n if (!Number.isFinite(entry.usd) || entry.usd < 0) {\n // Mirrors the Postgres adapter (review finding): one admitted NaN makes\n // both counters NaN forever, disabling every cap comparison — and there\n // is deliberately no delete surface to reset them with.\n throw new Error(`spend must be a non-negative finite number, got ${entry.usd}`);\n }\n const { sessionKey, dayKey } = keysOf(entry);\n const sessionUsd = (this.#sessions.get(sessionKey) ?? 0) + entry.usd;\n this.#sessions.set(sessionKey, sessionUsd);\n const tenantDayUsd = (this.#tenantDays.get(dayKey) ?? 0) + entry.usd;\n this.#tenantDays.set(dayKey, tenantDayUsd);\n return { sessionUsd, tenantDayUsd };\n }\n\n async peek(key: SpendKey): Promise<SpendTotals> {\n const { sessionKey, dayKey } = keysOf(key);\n return {\n sessionUsd: this.#sessions.get(sessionKey) ?? 0,\n tenantDayUsd: this.#tenantDays.get(dayKey) ?? 0,\n };\n }\n}\n\nfunction keysOf(key: SpendKey): { sessionKey: string; dayKey: string } {\n return {\n sessionKey: `${scopePath(key.scope)}/${key.sessionId}`,\n dayKey: `tenants/${validatedOrg(key.scope)}/days/${utcDay(key.at)}`,\n };\n}\n\n/** The day counter keys on org alone, but the whole scope is still validated. */\nfunction validatedOrg(scope: Scope): string {\n scopePath(scope);\n return scope.org;\n}\n\n/** ISO 8601 → `YYYY-MM-DD` in UTC — the contract's day-bucket derivation. */\nfunction utcDay(at: string): string {\n const ms = Date.parse(at);\n if (Number.isNaN(ms)) throw new Error(`invalid ISO 8601 timestamp: ${JSON.stringify(at)}`);\n return new Date(ms).toISOString().slice(0, 10);\n}\n","import { scopePath, type Scope } from \"../scope\";\nimport { assertWellFormed } from \"../text\";\nimport type {\n CompletedTurn,\n LeaseOpts,\n TurnClaim,\n TurnKey,\n TurnLease,\n TurnStore,\n} from \"../turn-store\";\n\n/**\n * In-memory `TurnStore` — the reference implementation that proves the shared\n * contract suite (spec 030) is satisfiable, and the loop tests' double.\n * Test/example use only: nothing survives the process, so it serializes one\n * instance and not a deployment.\n *\n * The clock is `Date.now()` and deliberately NOT injectable. A fake clock here\n * would desynchronize from the real `setTimeout` the wait is built on — the\n * test advances one and the other keeps sleeping. Expiry is exercised with\n * small TTLs against real time, which is also the only thing that can work\n * against Postgres, where `now()` is the server's.\n *\n * Keys are built with {@link scopePath}, so scope validation applies here\n * exactly as it does in real adapters.\n */\nexport class InMemoryTurnStore implements TurnStore {\n /** scopePath/sessionId → the live lease. */\n readonly #leases = new Map<string, { token: string; expiresAtMs: number }>();\n /**\n * scopePath/sessionId → idempotencyKey → the completed turn, or null while in\n * flight.\n *\n * NESTED and looked up exactly, never one flat string erased by prefix —\n * spec 033. `scopePath` validates `org` and `uid` against a charset without\n * `/`, but `sessionId` is not validated and never should be: a product using\n * a composite id (`\"platform/thread\"`, a migration's `\"s1/legacy\"`) made one\n * session's prefix match another's keys, so erasing `\"s1\"` deleted\n * `\"s1/legacy\"`'s stored reply. `PostgresTurnStore` compares `session_id` by\n * SQL equality and was immune, so the two adapters DIVERGED on erasure — the\n * one thing the shared contract suite exists to prevent, missed because its\n * sibling case used `\"s1\"`/`\"s2\"`.\n *\n * `InMemorySessionStore` had already solved this by nesting; concatenating\n * and prefix-matching in a new store reintroduced a bug class this codebase\n * knew about.\n */\n readonly #claims = new Map<string, Map<string, CompletedTurn | null>>();\n /** Waiters per session key, woken in FIFO order on release. */\n readonly #waiters = new Map<string, (() => void)[]>();\n #tokens = 0;\n\n async acquire(scope: Scope, sessionId: string, opts: LeaseOpts): Promise<TurnLease | null> {\n assertLeaseOpts(opts);\n const key = sessionKey(scope, sessionId);\n const deadline = Date.now() + opts.waitMs;\n for (;;) {\n const taken = this.#leases.get(key);\n // An EXPIRED lease is not a lease. A holder that crashed must not block\n // its session forever, which is the whole reason `ttlMs` exists.\n if (taken === undefined || taken.expiresAtMs <= Date.now()) {\n const lease = {\n token: `lease-${++this.#tokens}`,\n expiresAtMs: Date.now() + opts.ttlMs,\n };\n this.#leases.set(key, lease);\n return { token: lease.token, expiresAt: new Date(lease.expiresAtMs).toISOString() };\n }\n const remaining = Math.min(deadline, taken.expiresAtMs) - Date.now();\n if (remaining <= 0) return null;\n // Woken by `release`, or by the expiry — whichever comes first. Waiting\n // on the expiry too is what keeps a crashed holder from making every\n // waiter burn its whole `waitMs` before noticing.\n await this.#waitFor(key, remaining);\n }\n }\n\n async release(scope: Scope, sessionId: string, lease: TurnLease): Promise<void> {\n const key = sessionKey(scope, sessionId);\n const held = this.#leases.get(key);\n // A STALE token is a no-op. A holder whose lease already expired must\n // never release the lease the NEXT turn is holding — that serializes\n // nothing while appearing to (spec 030).\n if (held === undefined || held.token !== lease.token) return;\n this.#leases.delete(key);\n this.#wake(key);\n }\n\n async claim(key: TurnKey): Promise<TurnClaim> {\n const session = sessionKey(key.scope, key.sessionId);\n const completed = this.#claims.get(session)?.get(key.idempotencyKey);\n // `null` is an IN-FLIGHT claim, only reachable after a crash: the lease\n // makes concurrency impossible. Re-running is the correct answer — the\n // turn produced no result, so there is nothing to replay — and it is also\n // the crash window this slice does not close (spec 030).\n //\n // `undefined` means no entry at all. Reading `has` and then `get` would\n // have been two lookups agreeing about a map nothing else can touch here,\n // but `get` alone distinguishes all three states.\n if (completed != null) return { status: \"replay\", completed };\n const claims = this.#claims.get(session) ?? new Map<string, CompletedTurn | null>();\n claims.set(key.idempotencyKey, null);\n this.#claims.set(session, claims);\n return { status: \"fresh\" };\n }\n\n async complete(key: TurnKey, completed: CompletedTurn): Promise<void> {\n const claims = this.#claims.get(sessionKey(key.scope, key.sessionId));\n // Same guard as `SessionStore.append`, for the same reason and the same\n // divergence — spec 040. BEFORE the update-only test, not after, because\n // that is where Postgres effectively checks: `$5::jsonb` is parsed whether\n // or not the UPDATE matches a row, so the SQL adapter refuses a malformed\n // reply even for a claim that no longer exists. Guarding after `has` here\n // would have closed one divergence by opening another.\n assertWellFormed(completed, \"completed\");\n // UPDATE-only. A session erased while its turn was still running must not\n // have the reply resurrected by that turn finishing: erasure wins over\n // work that started before it — the rule the memory tier already spells\n // out as the `stale` observe outcome (spec 011).\n //\n // It is also the only shape a SQL adapter can implement without an upsert\n // that re-creates the row, so writing it unconditionally here would put a\n // silent divergence between the reference and Postgres into the one place\n // the contract suite exists to prevent it.\n if (!claims?.has(key.idempotencyKey)) return;\n claims.set(key.idempotencyKey, structuredClone(completed));\n }\n\n async abandon(key: TurnKey): Promise<void> {\n this.#claims.get(sessionKey(key.scope, key.sessionId))?.delete(key.idempotencyKey);\n }\n\n async erase(scope: Scope, sessionId?: string): Promise<void> {\n // One session: exact keys on both maps. Scope-wide: the scope's own prefix,\n // which IS safe — `scopePath` renders `tenants/{org}/users/{uid}` from a\n // charset excluding `/`, so no uid can spoof the delimiter after it. Only\n // the untrusted `sessionId` ever needed exact treatment (spec 033).\n // Claims only: the lease is the guard of a turn that may be in flight,\n // and it expires on its own clock (spec: close-review-part-two).\n if (sessionId !== undefined) {\n this.#claims.delete(sessionKey(scope, sessionId));\n return;\n }\n const prefix = `${scopePath(scope)}/`;\n for (const id of [...this.#claims.keys()]) {\n if (id.startsWith(prefix)) this.#claims.delete(id);\n }\n }\n\n #waitFor(key: string, ms: number): Promise<void> {\n return new Promise<void>((resolve) => {\n const queue = this.#waiters.get(key) ?? [];\n let done = false;\n const settle = (): void => {\n if (done) return;\n done = true;\n clearTimeout(timer);\n resolve();\n };\n const timer = setTimeout(() => {\n // Removes ITSELF from the queue — the first draft only guarded against\n // firing twice, so a waiter that timed out unwoken left a dead closure\n // in the array until the next real `release`, which for a crashed\n // holder never comes (spec 033).\n const pending = this.#waiters.get(key);\n if (pending) {\n const at = pending.indexOf(settle);\n if (at !== -1) pending.splice(at, 1);\n if (pending.length === 0) this.#waiters.delete(key);\n }\n settle();\n }, ms);\n // Not `unref`'d and deliberately cleared on both paths: a pending timer\n // outliving the turn that scheduled it is the leak spec 022 closed on\n // the recall deadline.\n queue.push(settle);\n this.#waiters.set(key, queue);\n });\n }\n\n #wake(key: string): void {\n const queue = this.#waiters.get(key);\n if (!queue) return;\n this.#waiters.delete(key);\n for (const wake of queue) wake();\n }\n}\n\nfunction assertLeaseOpts(opts: LeaseOpts): void {\n for (const [name, value] of [\n [\"ttlMs\", opts.ttlMs],\n [\"waitMs\", opts.waitMs],\n ] as const) {\n // A NaN ttl makes every comparison false, so the lease reads as live\n // forever and the session is blocked until the process restarts.\n if (!Number.isFinite(value) || value < 0) {\n throw new Error(`${name} must be a non-negative finite number, got ${value}`);\n }\n }\n}\n\nfunction sessionKey(scope: Scope, sessionId: string): string {\n return `${scopePath(scope)}/${sessionId}`;\n}\n\n\n","import type {\n AccessEvent,\n AuditLog,\n ContextEvent,\n CostEvent,\n RecallEvent,\n RoutingEvent,\n} from \"../audit\";\n\n/**\n * `AuditLog` that keeps what it was given — for tests that must assert a trail\n * exists, not just that an operation succeeded (§6.8: where trails are written\n * is swappable, that they are written is not).\n */\nexport class RecordingAuditLog implements AuditLog {\n readonly accessEvents: AccessEvent[] = [];\n readonly routingEvents: RoutingEvent[] = [];\n readonly costEvents: CostEvent[] = [];\n readonly recallEvents: RecallEvent[] = [];\n readonly contextEvents: ContextEvent[] = [];\n\n access(e: AccessEvent): void {\n this.accessEvents.push(e);\n }\n\n routing(e: RoutingEvent): void {\n this.routingEvents.push(e);\n }\n\n cost(e: CostEvent): void {\n this.costEvents.push(e);\n }\n\n recall(e: RecallEvent): void {\n this.recallEvents.push(e);\n }\n\n context(e: ContextEvent): void {\n this.contextEvents.push(e);\n }\n}\n"],"mappings":"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","names":["sessionKey"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alma-harness/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Alma core contracts: agentic loop, neutral messages, scoped tools, budget, routing policy, tenancy/audit, lifecycle events, routines.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/scope.ts","../src/text.ts"],"sourcesContent":["/**\n * Tenancy scope — the isolation boundary of the whole harness.\n *\n * `{org, uid}` derives from a server-verified token, never from a client\n * payload, and is bound to tool registries by closure at construction time —\n * the model never passes org/uid as parameters.\n *\n * @see docs/architecture.md §6.1, §6.8\n */\nexport interface Scope {\n /** Organization (tenant) id. */\n readonly org: string;\n /** User id within the organization. */\n readonly uid: string;\n}\n\n/**\n * Thrown by {@link scopePath} when a scope segment could be used for path\n * traversal or key-delimiter injection.\n */\nexport class InvalidScopeError extends Error {\n constructor(segment: \"org\" | \"uid\", value: string) {\n super(`Invalid scope ${segment}: ${JSON.stringify(value)}`);\n this.name = \"InvalidScopeError\";\n }\n}\n\n/**\n * DECISION: allowed charset for scope segments. The doc (§2) records uneven\n * path-sanitization hardening as a production bug class; the harness therefore\n * validates centrally. Alphanumerics plus `_ - .` (no leading dot) covers real\n * Firebase uids and org slugs while excluding `/`, `..` and whitespace.\n */\nconst SCOPE_SEGMENT = /^[A-Za-z0-9_-][A-Za-z0-9._-]*$/;\n\n/**\n * The single place that builds `tenants/{org}/users/{uid}` — §6.8.\n *\n * A *concept*, not a literal storage path: the Firestore adapter renders it as\n * a collection path, the Postgres adapter as row-level-security predicates\n * (§6). Every store keys its data by this concept so scoped purge and export\n * have one canonical addressing scheme.\n */\nexport function scopePath(scope: Scope): string {\n if (!SCOPE_SEGMENT.test(scope.org)) throw new InvalidScopeError(\"org\", scope.org);\n if (!SCOPE_SEGMENT.test(scope.uid)) throw new InvalidScopeError(\"uid\", scope.uid);\n return `tenants/${scope.org}/users/${scope.uid}`;\n}\n","/**\n * Text safety at the persistence boundary — spec: well-formed-text.\n *\n * A JavaScript string is UTF-16 code units, and everything outside the BMP —\n * emoji, the CJK range — is two of them. Any operation that cuts by index can\n * leave a LONE SURROGATE: half a character, which is not text. It is invalid\n * in `jsonb`, so a store that persists `JSON.stringify(msg)::jsonb` rejects\n * the write and the turn loses every message including the user's.\n */\n\n/**\n * Cuts `text` at `end` code units, moving back one when that would split a\n * surrogate pair.\n *\n * PRECONDITION: `text` is well-formed. This helper does not REPAIR — it only\n * declines to break — so a lone surrogate already present survives the cut.\n * A caller that cannot guarantee its input (spec 025 review: the memory\n * validator now REFUSES what this could hand it) runs `toWellFormedDeep`\n * first.\n *\n * Given that precondition, exactly one case can break, so the check is O(1):\n * a HIGH surrogate at the last included index, whose partner sits just past\n * the cut. A low surrogate there already has its partner included.\n *\n * Grapheme clusters are deliberately NOT preserved — an accent can still be\n * separated from its base letter. That is cosmetic in a string the reader is\n * told was truncated; a lone surrogate is a data-integrity failure.\n */\nexport function cutAtCodePoint(text: string, end: number): string {\n if (end <= 0) return \"\";\n const last = text.charCodeAt(end - 1);\n return text.slice(0, last >= 0xd800 && last <= 0xdbff ? end - 1 : end);\n}\n\n/**\n * True when any string in `value` — including any object KEY — is malformed.\n *\n * ITERATIVE on purpose. The first draft recursed, and a recursive walk blew\n * the stack at ~4,000 levels of nesting while `JSON.stringify` handled the\n * same value: a payload the store would have ACCEPTED made the repair throw\n * (spec 025 review). Detection runs on every turn, so it is the half that must\n * never be the thing that fails.\n */\nfunction hasMalformed(root: unknown): boolean {\n const stack: unknown[] = [root];\n while (stack.length > 0) {\n const value = stack.pop();\n if (typeof value === \"string\") {\n if (!value.isWellFormed()) return true;\n } else if (Array.isArray(value)) {\n for (const item of value) stack.push(item);\n } else if (value !== null && typeof value === \"object\") {\n for (const [key, item] of Object.entries(value)) {\n // Keys travel into the JSON too, and a malformed one is refused by\n // `jsonb` exactly like a malformed value — the first draft checked\n // values only, so `{\"bad\\ud83dkey\": \"fine\"}` sailed through.\n if (!key.isWellFormed()) return true;\n stack.push(item);\n }\n }\n }\n return false;\n}\n\n/**\n * Rebuilds `value` with every string — key and leaf — made well-formed.\n *\n * Wholesale, not selectively: identity is the guarantee for a CLEAN value, and\n * a broken one is being rewritten anyway. The untouched branches keep their\n * bytes, and the cached prefix keys off content rather than object identity.\n */\nfunction repair(root: unknown): unknown {\n // ITERATIVE, like `hasMalformed` and for the same reason — spec 040 review.\n // This was the LAST recursive walk over this shape, and it was the one that\n // mattered: detection was made iterative in spec 025 while repair was left\n // recursive behind a best-effort `catch`, so a deeply nested MALFORMED value\n // was detected and then failed to be repaired. The existing depth pin could\n // not see it because its 50,000-level fixture is clean, so `hasMalformed`\n // returns false and repair never runs.\n //\n // An explicit stack of half-built containers, closed bottom-up. Children are\n // produced before their parent, so nothing is rebuilt twice.\n interface Frame {\n /** Repaired keys, in order — `null` marks an array. */\n keys: string[] | null;\n items: readonly unknown[];\n built: unknown[];\n }\n\n const leaf = (v: unknown): unknown => (typeof v === \"string\" ? v.toWellFormed() : v);\n\n const open = (v: unknown): Frame | null => {\n if (Array.isArray(v)) return { keys: null, items: v, built: [] };\n if (v !== null && typeof v === \"object\") {\n const entries = Object.entries(v);\n return {\n keys: entries.map(([key]) => key.toWellFormed()),\n items: entries.map(([, item]) => item),\n built: [],\n };\n }\n return null;\n };\n\n const close = (f: Frame): unknown =>\n // `Object.fromEntries` uses CreateDataProperty, so a literal `__proto__`\n // key becomes an OWN property. Assigning `out[key] = …` instead invoked\n // the `Object.prototype.__proto__` SETTER: the key vanished from the\n // persisted message and the rebuilt object took a tool-controlled\n // prototype (spec 025 review).\n f.keys === null ? f.built : Object.fromEntries(f.keys.map((k, i) => [k, f.built[i]]));\n\n const rootFrame = open(root);\n if (rootFrame === null) return leaf(root);\n\n const frames: Frame[] = [rootFrame];\n for (;;) {\n const frame = frames[frames.length - 1]!;\n if (frame.built.length === frame.items.length) {\n const value = close(frame);\n frames.pop();\n if (frames.length === 0) return value;\n frames[frames.length - 1]!.built.push(value);\n continue;\n }\n const next = frame.items[frame.built.length];\n const child = open(next);\n if (child === null) frame.built.push(leaf(next));\n else frames.push(child);\n }\n}\n\n/**\n * Returns `value` with every string made well-formed, each lone surrogate\n * replaced by U+FFFD — and the SAME REFERENCE when nothing was broken.\n *\n * The identity property is load-bearing rather than an optimization: an\n * under-ceiling tool output must be persisted byte-identical or the cached\n * prefix drifts (spec: tool-output-discipline), and a normalizer that rebuilt\n * every message would drift all of them. Detection allocates nothing beyond a\n * work stack; only an already-broken value is rebuilt.\n *\n * ⚠ Do NOT replace this with a one-pass check on the serialized form.\n * `JSON.stringify` has been well-formed since ES2019: it escapes a lone\n * surrogate as `\\ud83d`, so `JSON.stringify(v).isWellFormed()` is ALWAYS true\n * and detects nothing. Normalizing that output does not help either — the\n * escape survives, and the store still rejects it. Only the source string can\n * be repaired.\n *\n * BOTH halves are iterative as of spec 040. Detection was made so in spec 025;\n * repair was left recursive behind each caller's best-effort `catch`, which\n * meant a deeply nested MALFORMED value was detected and then not repaired —\n * invisible to the depth pin of the day, whose fixture is clean and therefore\n * never reaches repair at all. Callers still treat repair as best-effort, and\n * that is now belt-and-braces rather than the load-bearing mitigation it was.\n */\nexport function toWellFormedDeep<T>(value: T): T {\n return hasMalformed(value) ? (repair(value) as T) : value;\n}\n\n/**\n * A store refused a write because a string in it was not text — spec 040.\n *\n * Its own class so a bulk writer can catch this and nothing else: a migration\n * wants to skip or repair the one bad row, not swallow a connection failure\n * alongside it.\n *\n * The message names the PATH and never the content. A lone surrogate prints\n * as a replacement box and the string around it is, in the case that drove\n * this, clinical text — neither belongs in a log line.\n */\nexport class MalformedTextError extends Error {\n constructor(readonly path: string) {\n super(\n `malformed UTF-16 at ${path}: a lone surrogate is not text and cannot be ` +\n `persisted — run toWellFormedDeep() on the value first`,\n );\n this.name = \"MalformedTextError\";\n }\n}\n\n/**\n * Locates a malformed string, as a readable path. Runs ONLY after\n * {@link hasMalformed} has already said there is one, so the cost of carrying\n * paths is paid on the failing write and never on the hot one.\n *\n * ITERATIVE, for the reason {@link hasMalformed} is — spec 040's own review.\n * The first version recursed one frame per level of nesting and threw\n * `RangeError` instead of {@link MalformedTextError} on a value `JSON.stringify`\n * handles, which is the exact defect spec 025 removed from detection and this\n * function reintroduced beside it. A caller writing\n * `catch (e) { if (e instanceof MalformedTextError) … }` — the usage this class\n * exists to support — would not have caught it.\n *\n * Depth-first, left to right, with an object's KEYS all checked before any of\n * its values are descended into. So the path is the first offender in that\n * order, not an arbitrary one.\n */\nfunction malformedPath(root: unknown, base: string): string {\n const stack: { value: unknown; path: string }[] = [{ value: root, path: base }];\n while (stack.length > 0) {\n const { value, path } = stack.pop()!;\n if (typeof value === \"string\") {\n if (!value.isWellFormed()) return path;\n } else if (Array.isArray(value)) {\n // Pushed in reverse so `pop` yields them in order.\n for (let i = value.length - 1; i >= 0; i--) {\n stack.push({ value: value[i], path: `${path}[${i}]` });\n }\n } else if (value !== null && typeof value === \"object\") {\n const entries = Object.entries(value);\n // A malformed KEY is reported as `<key>` rather than by its own text —\n // the text is what cannot be printed.\n for (const [key] of entries) if (!key.isWellFormed()) return `${path}.<key>`;\n for (let i = entries.length - 1; i >= 0; i--) {\n const entry = entries[i]!;\n stack.push({ value: entry[1], path: `${path}.${entry[0]}` });\n }\n }\n }\n return base;\n}\n\n/**\n * Throws {@link MalformedTextError} when any string in `value` — object keys\n * included — is not well-formed UTF-16.\n *\n * The guard every store calls at its write boundary, so that the adapters\n * AGREE (spec 040). They used to diverge: `jsonb` refuses a lone surrogate so\n * the Postgres adapters failed the write, while the in-memory references kept\n * it — and both contracts said \"do not rely on either behaviour\", which is not\n * a contract. `runTurn` repairs on the way in, but only BEST-EFFORT (its catch\n * keeps the unrepaired message), and a product writing to a store directly —\n * a migration, a backfill, a replay — has no such pass at all.\n *\n * Refusing rather than repairing here is deliberate. A store that silently\n * rewrites the bytes of a record kept for years is worse than one that\n * refuses, and since the repair is best-effort by design an `append` that\n * repaired would still not be a guarantee.\n *\n * `where` labels the value for the message — typically the parameter name and\n * index, e.g. `entries[3]`.\n */\nexport function assertWellFormed(value: unknown, where: string): void {\n // Cheap detection first: the clean path allocates nothing beyond the work\n // stack and never builds a path string.\n if (!hasMalformed(value)) return;\n throw new MalformedTextError(malformedPath(value, where));\n}\n"],"mappings":"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","names":[]}
|