@alma-harness/core 0.1.0 → 0.2.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.
@@ -1,4 +1,24 @@
1
- import { H as SessionStore, a as Scope, x as Msg, p as LoadOpts, a3 as ToolTrafficExpiry, N as SpendStore, K as SpendKey, Q as SpendTotals, a9 as TurnStore, L as LeaseOpts, a7 as TurnLease, a6 as TurnKey, a4 as TurnClaim, C as CompletedTurn, A as AuditLog, c as AccessEvent, E as RoutingEvent, m as CostEvent, R as RecallEvent, j as ContextEvent } from '../turn-store-uKJ4inz2.js';
1
+ import { af as RoutineRunStore, ad as RoutineRun, S as Scope, ae as RoutineRunOutcome, al as SessionStore, Q as Msg, x as LoadOpts, aH as ToolTrafficExpiry, ap as SpendStore, ao as SpendKey, aq as SpendTotals, aP as TurnStore, L as LeaseOpts, aN as TurnLease, aM as TurnKey, aJ as TurnClaim, C as CompletedTurn, c as AuditLog, A as AccessEvent, ag as RoutingEvent, m as CostEvent, a9 as RecallEvent, j as ContextEvent } from '../routines-CgJqyy7A.js';
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
+ }
2
22
 
3
23
  declare class InMemorySessionStore implements SessionStore {
4
24
  #private;
@@ -71,4 +91,4 @@ declare class RecordingAuditLog implements AuditLog {
71
91
  context(e: ContextEvent): void;
72
92
  }
73
93
 
74
- export { InMemorySessionStore, InMemorySpendStore, InMemoryTurnStore, RecordingAuditLog };
94
+ export { InMemoryRoutineRunStore, InMemorySessionStore, InMemorySpendStore, InMemoryTurnStore, RecordingAuditLog };
@@ -3,12 +3,49 @@ import {
3
3
  scopePath
4
4
  } from "../chunk-KPNXPUGR.js";
5
5
 
6
- // src/testing/in-memory-session-store.ts
6
+ // src/testing/in-memory-routine-run-store.ts
7
+ var InMemoryRoutineRunStore = class {
8
+ /** scopePath → routineId → runId → run. */
9
+ #runs = /* @__PURE__ */ new Map();
10
+ async record(run) {
11
+ const key = scopePath(run.scope);
12
+ instant(run.startedAt);
13
+ if (run.finishedAt !== void 0) instant(run.finishedAt);
14
+ let routines = this.#runs.get(key);
15
+ if (!routines) {
16
+ routines = /* @__PURE__ */ new Map();
17
+ this.#runs.set(key, routines);
18
+ }
19
+ let runs = routines.get(run.routineId);
20
+ if (!runs) {
21
+ runs = /* @__PURE__ */ new Map();
22
+ routines.set(run.routineId, runs);
23
+ }
24
+ runs.set(run.id, structuredClone(run));
25
+ }
26
+ async get(scope, routineId, runId) {
27
+ const run = this.#runs.get(scopePath(scope))?.get(routineId)?.get(runId);
28
+ return run ? structuredClone(run) : null;
29
+ }
30
+ async list(scope, routineId, opts = {}) {
31
+ const all = [...this.#runs.get(scopePath(scope))?.get(routineId)?.values() ?? []];
32
+ const since = opts.since === void 0 ? void 0 : instant(opts.since);
33
+ const hits = all.filter((r) => (since === void 0 || instant(r.startedAt) >= since) && (opts.outcome === void 0 || r.outcome === opts.outcome)).sort((a, b) => instant(b.startedAt) - instant(a.startedAt));
34
+ return structuredClone(opts.limit === void 0 ? hits : hits.slice(0, Math.max(0, opts.limit)));
35
+ }
36
+ };
7
37
  function instant(at) {
8
38
  const ms = Date.parse(at);
9
39
  if (Number.isNaN(ms)) throw new Error(`invalid ISO 8601 timestamp: ${JSON.stringify(at)}`);
10
40
  return ms;
11
41
  }
42
+
43
+ // src/testing/in-memory-session-store.ts
44
+ function instant2(at) {
45
+ const ms = Date.parse(at);
46
+ if (Number.isNaN(ms)) throw new Error(`invalid ISO 8601 timestamp: ${JSON.stringify(at)}`);
47
+ return ms;
48
+ }
12
49
  var InMemorySessionStore = class {
13
50
  /** scopePath(scope) → sessionId → chronological log. */
14
51
  #scopes = /* @__PURE__ */ new Map();
@@ -30,7 +67,7 @@ var InMemorySessionStore = class {
30
67
  return structuredClone(window);
31
68
  }
32
69
  async expireToolTraffic(scope, sessionId, opts) {
33
- const cutoff = instant(opts.inactiveSince);
70
+ const cutoff = instant2(opts.inactiveSince);
34
71
  const log = this.#scopes.get(scopePath(scope))?.get(sessionId) ?? [];
35
72
  const active = log.some((m) => {
36
73
  const at = m.meta?.at;
@@ -43,7 +80,7 @@ var InMemorySessionStore = class {
43
80
  const kept = [];
44
81
  for (const msg of log) {
45
82
  const survivors = msg.blocks.filter(
46
- (b) => b.type !== "tool_call" && b.type !== "tool_result"
83
+ (b) => b.type !== "tool_call" && b.type !== "tool_result" && b.type !== "reasoning"
47
84
  );
48
85
  blocks += msg.blocks.length - survivors.length;
49
86
  if (survivors.length > 0) kept.push({ ...msg, blocks: survivors });
@@ -175,22 +212,13 @@ var InMemoryTurnStore = class {
175
212
  }
176
213
  async erase(scope, sessionId) {
177
214
  if (sessionId !== void 0) {
178
- const session = sessionKey(scope, sessionId);
179
- this.#claims.delete(session);
180
- this.#leases.delete(session);
181
- this.#wake(session);
215
+ this.#claims.delete(sessionKey(scope, sessionId));
182
216
  return;
183
217
  }
184
218
  const prefix = `${scopePath(scope)}/`;
185
219
  for (const id of [...this.#claims.keys()]) {
186
220
  if (id.startsWith(prefix)) this.#claims.delete(id);
187
221
  }
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
222
  }
195
223
  #waitFor(key, ms) {
196
224
  return new Promise((resolve) => {
@@ -260,6 +288,7 @@ var RecordingAuditLog = class {
260
288
  }
261
289
  };
262
290
  export {
291
+ InMemoryRoutineRunStore,
263
292
  InMemorySessionStore,
264
293
  InMemorySpendStore,
265
294
  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-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\";\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 instant(run.startedAt);\n if (run.finishedAt !== undefined) instant(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 : instant(opts.since);\n const hits = all\n .filter((r) => (since === undefined || instant(r.startedAt) >= since) && (opts.outcome === undefined || r.outcome === opts.outcome))\n .sort((a, b) => instant(b.startedAt) - instant(a.startedAt));\n return structuredClone(opts.limit === undefined ? hits : hits.slice(0, Math.max(0, opts.limit)));\n }\n}\n\n/** Rejects an unparseable stamp rather than silently sorting it to 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","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 // 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":["instant","sessionKey"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alma-harness/core",
3
- "version": "0.1.0",
3
+ "version": "0.2.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",