@vostride/agent-qa-agent-local 0.0.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,3875 @@
1
+ // src/composition/run-composition.ts
2
+ import { createHash } from "node:crypto";
3
+ import { dirname, isAbsolute, resolve } from "node:path";
4
+ import { detectExecutionContext } from "@vostride/agent-qa-safety-local";
5
+ import { guardToolOutput } from "@vostride/agent-qa-model";
6
+ import {
7
+ FsEventLog,
8
+ FsEvidenceArtifactStore,
9
+ FsHandoffStore,
10
+ FsRunArtifactStore,
11
+ nextBuildOrdinal,
12
+ recordBuildTag,
13
+ RUNS_SUBDIR,
14
+ slugBranch,
15
+ SharpScreenshotCompressor
16
+ } from "@vostride/agent-qa-runner-local";
17
+ import {
18
+ coerceRunJSResult,
19
+ createMemoryTools,
20
+ createRecordLedger,
21
+ liveStreamFor,
22
+ resolveSecretEnv,
23
+ resolveSecretTemplatesInValue
24
+ } from "@vostride/agent-qa-runner";
25
+
26
+ // src/composition/knowledge-budgets.ts
27
+ var BRIEF_FRACTION = 0.05;
28
+ var CAUTION_FRACTION = 0.01;
29
+ var MIN_BRIEF = 256;
30
+ var MIN_CAUTION = 64;
31
+ var BRIEF_CEILING = 2048;
32
+ var READ_BODY_FRACTION = 0.1;
33
+ var GLOBAL_RECORD_CAP = 24;
34
+ var WEAK_MODEL_CAP_SCALE = 0.5;
35
+ var MIN_MEMORY_TOOL_CONTEXT_WINDOW = 16384;
36
+ function floorFinite(value) {
37
+ if (Number.isNaN(value)) return 0;
38
+ if (value === Number.POSITIVE_INFINITY) return Number.MAX_SAFE_INTEGER;
39
+ if (value === Number.NEGATIVE_INFINITY) return Number.MIN_SAFE_INTEGER;
40
+ return Math.floor(value);
41
+ }
42
+ function clamp(value, minimum, maximum) {
43
+ return Math.min(maximum, Math.max(minimum, value));
44
+ }
45
+ function computeKnowledgeBudgets(input) {
46
+ const usableContext = input.contextWindow - input.reservedOutput - input.qaRulesTokens - input.workingContextTokens;
47
+ const brief = clamp(
48
+ floorFinite(usableContext * BRIEF_FRACTION),
49
+ MIN_BRIEF,
50
+ BRIEF_CEILING
51
+ );
52
+ const caution = Math.max(MIN_CAUTION, floorFinite(usableContext * CAUTION_FRACTION));
53
+ const readBody = Math.max(MIN_BRIEF, floorFinite(usableContext * READ_BODY_FRACTION));
54
+ const searchResults = brief;
55
+ return { brief, searchResults, caution, readBody, recordCap: GLOBAL_RECORD_CAP };
56
+ }
57
+ function memoryToolGate(capabilities) {
58
+ return capabilities.toolCalling && capabilities.vision && capabilities.contextWindow >= MIN_MEMORY_TOOL_CONTEXT_WINDOW;
59
+ }
60
+ function scaledRecordCap(capabilities) {
61
+ if (memoryToolGate(capabilities)) return GLOBAL_RECORD_CAP;
62
+ return Math.max(1, Math.floor(GLOBAL_RECORD_CAP * WEAK_MODEL_CAP_SCALE));
63
+ }
64
+
65
+ // src/composition/knowledge-reader.ts
66
+ import { openKnowledgeReader } from "@vostride/agent-qa-knowledge-local/reader";
67
+ function createFrozenKnowledgeReader(reader) {
68
+ const briefs = /* @__PURE__ */ new Map();
69
+ const scopeKey = (scope) => JSON.stringify([scope.tier, scope.key]);
70
+ return {
71
+ getRunBrief(scope, budget) {
72
+ const key = scopeKey(scope);
73
+ const existing = briefs.get(key);
74
+ if (existing !== void 0) return existing;
75
+ const brief = reader.getRunBrief(scope, budget);
76
+ briefs.set(key, brief);
77
+ return brief;
78
+ },
79
+ search: (query, options) => reader.search(query, options),
80
+ read: (id, options) => reader.read(id, options),
81
+ getCautions: (anchors, budget) => reader.getCautions(anchors, budget),
82
+ getProcedure: (intent, screenState, availability) => reader.getProcedure(intent, screenState, availability),
83
+ emitCanaryOutcome: (payload) => reader.emitCanaryOutcome(payload)
84
+ };
85
+ }
86
+ function constructKnowledgeReader(input) {
87
+ return createFrozenKnowledgeReader(openKnowledgeReader({
88
+ ...input.resolveTrustPriors === void 0 ? {} : { priors: input.resolveTrustPriors() },
89
+ knowledgeRoot: input.knowledgeRoot,
90
+ ...input.product === void 0 ? {} : { product: input.product },
91
+ scopeDefaults: {
92
+ asOfBuild: input.asOfBuild,
93
+ ...input.parentScopes === void 0 ? {} : { parentScopes: input.parentScopes }
94
+ },
95
+ budgets: { recordCap: input.recordCap }
96
+ }));
97
+ }
98
+
99
+ // src/composition/run-composition.ts
100
+ function openRunKnowledgeComposition(input) {
101
+ const ceilings = input.accounting.getCeilings();
102
+ const qaRulesTokens = input.accounting.estimate([
103
+ { role: "system", content: input.qaRulesText }
104
+ ]);
105
+ const workingContextTokens = input.accounting.estimate([
106
+ { role: "user", content: input.workingContextText }
107
+ ]);
108
+ const computed = computeKnowledgeBudgets({
109
+ contextWindow: ceilings.contextWindow,
110
+ reservedOutput: ceilings.maxOutput,
111
+ qaRulesTokens,
112
+ workingContextTokens
113
+ });
114
+ const budgets = {
115
+ ...computed,
116
+ recordCap: scaledRecordCap(input.capabilities)
117
+ };
118
+ const reader = constructKnowledgeReader({
119
+ ...input.resolveTrustPriors === void 0 ? {} : { resolveTrustPriors: input.resolveTrustPriors },
120
+ knowledgeRoot: input.knowledgeRoot,
121
+ ...input.product === void 0 ? {} : { product: input.product },
122
+ ...input.parentScopes === void 0 ? {} : { parentScopes: input.parentScopes },
123
+ asOfBuild: input.asOfBuild,
124
+ recordCap: budgets.recordCap
125
+ });
126
+ const retrievalRung = input.resolveRetrievalRung({
127
+ knowledgeRoot: input.knowledgeRoot,
128
+ ...input.agentQaRoot === void 0 ? {} : { agentQaRoot: input.agentQaRoot }
129
+ });
130
+ retrievalRung.catch(() => void 0);
131
+ const channel2Enabled = memoryToolGate(input.capabilities);
132
+ const recordLedger = createRecordLedger({
133
+ cap: budgets.recordCap,
134
+ budget: budgets.brief + budgets.caution + (channel2Enabled ? budgets.searchResults : 0),
135
+ seedRecords: []
136
+ });
137
+ const memory = channel2Enabled ? createMemoryTools({
138
+ reader,
139
+ scope: input.scope,
140
+ asOfBuild: input.asOfBuild,
141
+ searchResultBudget: budgets.searchResults,
142
+ readBodyBudget: budgets.readBody,
143
+ recordLedger,
144
+ estimateTokens: (text) => input.accounting.estimate([
145
+ { role: "user", content: text }
146
+ ])
147
+ }) : void 0;
148
+ return {
149
+ reader,
150
+ budgets,
151
+ retrievalRung,
152
+ recordLedger,
153
+ nonActionTools: memory?.tools,
154
+ beginMemoryTurn: memory?.beginTurn,
155
+ drainMemoryReadTraces: memory?.drainReadTraces
156
+ };
157
+ }
158
+ function digest16(input) {
159
+ return createHash("sha256").update(input, "utf8").digest("hex").slice(0, 16);
160
+ }
161
+ function computeRunConfigDigests(input) {
162
+ const perMember = input.members.map((m) => ({
163
+ suiteConfigDigest: digest16(input.configContent + "\n" + (m.suiteFileContent ?? "")),
164
+ testConfigDigest: digest16(m.testFileContent)
165
+ }));
166
+ if (perMember.length === 1) {
167
+ return { runLevel: { ...perMember[0] }, perMember };
168
+ }
169
+ const distinctSuiteDigests = [];
170
+ const seen = /* @__PURE__ */ new Set();
171
+ for (const pair of perMember) {
172
+ if (!seen.has(pair.suiteConfigDigest)) {
173
+ seen.add(pair.suiteConfigDigest);
174
+ distinctSuiteDigests.push(pair.suiteConfigDigest);
175
+ }
176
+ }
177
+ const runLevel = {
178
+ suiteConfigDigest: digest16(distinctSuiteDigests.join("\n")),
179
+ testConfigDigest: digest16(perMember.map((p) => p.testConfigDigest).join("\n"))
180
+ };
181
+ return { runLevel, perMember };
182
+ }
183
+ function resolveRunExecutionContext(override) {
184
+ return detectExecutionContext(override);
185
+ }
186
+ function buildRunPorts(input) {
187
+ const root = resolve(input.configDir, ".agent-qa");
188
+ return {
189
+ eventLog: new FsEventLog({ root, runId: input.runId }),
190
+ handoffStore: new FsHandoffStore({ root, runId: input.runId }),
191
+ runArtifactStore: new FsRunArtifactStore({ root, runId: input.runId }),
192
+ evidenceArtifacts: new FsEvidenceArtifactStore({ root, runId: input.runId }),
193
+ // A NAMED, observable, non-durable destination: greppable by code, visible to a test through
194
+ // `process.once('warning', …)`, and no `console.` call in a package that makes none.
195
+ onSubscriberFault: (code, message) => {
196
+ process.emitWarning(message, { code });
197
+ }
198
+ };
199
+ }
200
+ function buildScreenshotCompressor(logger) {
201
+ return new SharpScreenshotCompressor(logger);
202
+ }
203
+ function resolveBuildTagSetting(input) {
204
+ for (const value of [input.flag, input.env, input.use?.buildTag]) {
205
+ const trimmed = value?.trim();
206
+ if (trimmed) return trimmed;
207
+ }
208
+ return void 0;
209
+ }
210
+ async function resolveRunBuildOrdinal(input) {
211
+ const root = resolve(input.configDir, ".agent-qa");
212
+ const ordinal = await nextBuildOrdinal({ root, product: input.product });
213
+ if (input.buildTag !== void 0) {
214
+ await recordBuildTag({
215
+ root,
216
+ product: input.product,
217
+ tag: input.buildTag,
218
+ ordinal
219
+ });
220
+ }
221
+ return ordinal;
222
+ }
223
+ var SUITE_LIFECYCLE_LANE = "suite-lifecycle";
224
+ var SuiteBoundaryWhileLiveError = class extends Error {
225
+ constructor(activeSuiteKey, requestedSuiteKey, liveMembers) {
226
+ super(
227
+ `Cannot enter suite "${requestedSuiteKey}": suite "${activeSuiteKey}" still has ${liveMembers} live member(s). Suite boundaries are serialized.`
228
+ );
229
+ this.activeSuiteKey = activeSuiteKey;
230
+ this.requestedSuiteKey = requestedSuiteKey;
231
+ this.liveMembers = liveMembers;
232
+ this.name = "SuiteBoundaryWhileLiveError";
233
+ }
234
+ };
235
+ function createSuiteLifecycle() {
236
+ let activeKey;
237
+ let activeSetup;
238
+ let activeTeardown;
239
+ let liveMembers = 0;
240
+ const tearDownActive = async () => {
241
+ const teardown = activeTeardown;
242
+ activeKey = void 0;
243
+ activeSetup = void 0;
244
+ activeTeardown = void 0;
245
+ liveMembers = 0;
246
+ if (teardown) await teardown();
247
+ };
248
+ return {
249
+ async ensureReady(suiteKey, hooks) {
250
+ if (activeKey !== void 0 && activeKey !== suiteKey) {
251
+ if (liveMembers > 0) {
252
+ throw new SuiteBoundaryWhileLiveError(activeKey, suiteKey, liveMembers);
253
+ }
254
+ await tearDownActive();
255
+ }
256
+ if (activeKey !== suiteKey) {
257
+ activeKey = suiteKey;
258
+ activeTeardown = hooks.teardown;
259
+ activeSetup = (async () => {
260
+ try {
261
+ await hooks.setup();
262
+ } catch (error) {
263
+ activeTeardown = void 0;
264
+ throw error;
265
+ }
266
+ })();
267
+ }
268
+ const setup = activeSetup;
269
+ liveMembers += 1;
270
+ let released = false;
271
+ const lease = {
272
+ release() {
273
+ if (released) return;
274
+ released = true;
275
+ liveMembers -= 1;
276
+ }
277
+ };
278
+ try {
279
+ await setup;
280
+ } catch (error) {
281
+ lease.release();
282
+ throw error;
283
+ }
284
+ return lease;
285
+ },
286
+ async finalize() {
287
+ await tearDownActive();
288
+ }
289
+ };
290
+ }
291
+ function registerAdapterLiveStream(ctx, adapter) {
292
+ const registration = liveStreamFor(adapter);
293
+ ctx.registerLiveStream?.(registration?.capability, registration?.fallback);
294
+ }
295
+ function buildSuiteMemberExecutor(input) {
296
+ return async (member, ctx) => {
297
+ const binding = input.bindings[ctx.testIndex];
298
+ if (!binding) {
299
+ throw new Error(`No suite-member binding for testIndex ${ctx.testIndex} (testId ${member.testId})`);
300
+ }
301
+ const lease = await input.lifecycle.ensureReady(binding.suiteKey, binding.hooks);
302
+ try {
303
+ registerAdapterLiveStream(ctx, binding.adapter);
304
+ return await binding.run(ctx);
305
+ } finally {
306
+ lease.release();
307
+ }
308
+ };
309
+ }
310
+ function runDirFor(input) {
311
+ return resolve(input.configDir, ".agent-qa", RUNS_SUBDIR, slugBranch(input.runId));
312
+ }
313
+ async function captureFailureScreenshotFromAdapter(adapter) {
314
+ try {
315
+ const shot = adapter.screenshot;
316
+ if (typeof shot !== "function") return void 0;
317
+ return await shot.call(adapter);
318
+ } catch {
319
+ return void 0;
320
+ }
321
+ }
322
+ function buildRunJsHandler(adapter) {
323
+ return async (code, ctx) => {
324
+ if (adapter.platform !== "web") {
325
+ return { value: "[runJS error: runJS is only supported on web platform]", error: "runJS is only supported on web platform" };
326
+ }
327
+ const page = "getPage" in adapter ? adapter.getPage() : void 0;
328
+ if (!page) {
329
+ return { value: "[runJS error: no browser page available]", error: "no browser page available" };
330
+ }
331
+ void ctx;
332
+ const raw = await page.evaluate(code);
333
+ return { value: coerceRunJSResult(raw) };
334
+ };
335
+ }
336
+ function buildAppiumScriptHandler(adapter, secretStore) {
337
+ return async (action) => adapter.execute(resolveSecretTemplatesInValue(action, secretStore));
338
+ }
339
+ function buildAccessibilityCheckHandler(adapter) {
340
+ return async (opts) => {
341
+ const page = "getPage" in adapter ? adapter.getPage() : void 0;
342
+ if (!page) throw new Error("Accessibility capture requires an active browser page");
343
+ const web = await import("@vostride/agent-qa-web");
344
+ return web.runAccessibilityCheck(page, opts);
345
+ };
346
+ }
347
+ function buildFailureScreenshotWriter(adapter) {
348
+ return async () => captureFailureScreenshotFromAdapter(adapter);
349
+ }
350
+ function toMemberTest(test, filePath) {
351
+ return {
352
+ testId: test["test-id"] ?? test.name,
353
+ name: test.name,
354
+ filePath,
355
+ url: test.url,
356
+ context: test.context,
357
+ steps: test.steps,
358
+ meta: test.meta
359
+ };
360
+ }
361
+ function buildMemberRunConfig(input) {
362
+ const sandbox = input.sandboxOptions;
363
+ return {
364
+ adapter: input.adapter,
365
+ planner: input.planner,
366
+ verifier: input.verifier,
367
+ cache: input.cache,
368
+ workingMemory: input.workingMemory,
369
+ storageState: input.storageState,
370
+ // CTXB-06: model's canonical compaction (compactLogs for log bursts, guardBlob
371
+ // otherwise) replaces the runner-local ceiling fallback on every tool-output value.
372
+ guardBlob: guardToolOutput,
373
+ compressor: input.compressor,
374
+ knowledgeReader: input.knowledgeReader,
375
+ knowledgeRetrievalRung: input.knowledgeRetrievalRung,
376
+ knowledgeBudgets: input.knowledgeBudgets,
377
+ knowledgeAsOfBuild: input.knowledgeAsOfBuild,
378
+ recordLedger: input.recordLedger,
379
+ beginMemoryTurn: input.beginMemoryTurn,
380
+ drainMemoryReadTraces: input.drainMemoryReadTraces,
381
+ configDigests: input.configDigests,
382
+ plannerModel: input.plannerModel,
383
+ verifierModel: input.verifierModel,
384
+ stepModelScope: input.stepModelScope,
385
+ envFileVars: input.envFileVars,
386
+ inlineVars: input.inlineVars,
387
+ suiteVars: input.suiteVars,
388
+ cliVars: input.cliVars,
389
+ hookSetupVars: input.hookSetupVars,
390
+ // Inline hooks: the runner-local DockerHookExecutor + sandbox env/authState come off the resolved
391
+ // sandbox options; the per-run secret env is resolved store-side (never crosses into the runner).
392
+ hookExecutor: sandbox?.executor,
393
+ inlineHookDefs: input.inlineHookDefs,
394
+ hookEnvVars: sandbox?.envVars,
395
+ hookSecretEnv: resolveSecretEnv(input.secretStore),
396
+ hookAuthState: sandbox?.authState,
397
+ runJs: buildRunJsHandler(input.adapter),
398
+ appiumScript: buildAppiumScriptHandler(input.adapter, input.secretStore),
399
+ accessibilityCheck: "getPage" in input.adapter && typeof input.adapter.getPage === "function" ? buildAccessibilityCheckHandler(input.adapter) : void 0,
400
+ persistFailureScreenshot: buildFailureScreenshotWriter(input.adapter),
401
+ resolveSecrets: (text) => resolveSecretTemplatesInValue(text, input.secretStore),
402
+ secretStore: input.secretStore,
403
+ secretRedactor: input.secretRedactor,
404
+ sink: input.sink,
405
+ testIndex: input.testIndex,
406
+ abortSignal: input.abortSignal,
407
+ evidenceArtifacts: input.evidenceArtifacts,
408
+ captureScreenshots: input.captureScreenshots,
409
+ accessibility: input.accessibility,
410
+ timeouts: input.timeouts,
411
+ plannerConfig: input.plannerConfig,
412
+ logger: input.logger,
413
+ logCapture: input.logCapture,
414
+ vision: input.vision,
415
+ visionGate: input.visionGate,
416
+ contextWindow: input.contextWindow,
417
+ suiteContext: input.suiteContext,
418
+ resolveUploadPath: (rawPath) => isAbsolute(rawPath) ? rawPath : resolve(dirname(input.filePath), rawPath)
419
+ };
420
+ }
421
+
422
+ // src/composition/run-invocation-core.ts
423
+ import { existsSync } from "node:fs";
424
+ import { dirname as dirname2 } from "node:path";
425
+ import { startRun } from "@vostride/agent-qa-runner";
426
+
427
+ // src/composition/run-risk.ts
428
+ import {
429
+ ALLOW_PRODUCTION_OPT_IN,
430
+ buildProductionRefusal,
431
+ distinctInFirstOccurrenceOrder
432
+ } from "@vostride/agent-qa-schema";
433
+
434
+ // src/composition/run-invocation-core.ts
435
+ var REFUSED_RUN_REQUEST_FIELDS = [
436
+ "scope",
437
+ "executionContext"
438
+ ];
439
+ var RunRequestFieldRefusedError = class extends Error {
440
+ constructor(refusedFields) {
441
+ super(
442
+ `agent-qa cannot start a run with: ${refusedFields.join(", ")}. scope resolves from the config the run is started against, and executionContext is supplied by the composition that owns the door \u2014 a caller-named execution context would be a caller relabelling where its run came from.`
443
+ );
444
+ this.refusedFields = refusedFields;
445
+ this.name = "RunRequestFieldRefusedError";
446
+ }
447
+ };
448
+ var RunIdNotMintedError = class extends Error {
449
+ constructor(runId) {
450
+ super(
451
+ `agent-qa cannot start a run under ${JSON.stringify(runId)}: a caller-named run id must be one this system minted. Start the run without naming an id and read the id back off the acknowledgment, or name an id \`agent-qa\` handed you.`
452
+ );
453
+ this.runId = runId;
454
+ this.name = "RunIdNotMintedError";
455
+ }
456
+ };
457
+ var RunIdAlreadyInUseError = class extends Error {
458
+ constructor(runId) {
459
+ super(
460
+ `agent-qa cannot start a run under ${JSON.stringify(runId)}: that id already names a run in this project. Starting a second run under it would append to the first run's event log and overwrite its handoff. Start the run without naming an id.`
461
+ );
462
+ this.runId = runId;
463
+ this.name = "RunIdAlreadyInUseError";
464
+ }
465
+ };
466
+ var RunDeniedError = class extends Error {
467
+ constructor(runId, reason) {
468
+ super(reason);
469
+ this.runId = runId;
470
+ this.reason = reason;
471
+ this.name = "RunDeniedError";
472
+ this.neutralReason = reason;
473
+ }
474
+ neutralReason;
475
+ };
476
+ function refuseProductionWithoutOptIn(runId, declaration) {
477
+ const production = declaration.productionTargetNames.length > 0 || declaration.targetClass === "production";
478
+ if (!production || declaration.allowProduction === true) return;
479
+ const reason = buildProductionRefusal(declaration.productionTargetNames);
480
+ throw new RunDeniedError(runId, reason);
481
+ }
482
+ function refuseUnavailableRunId(input) {
483
+ if (!input.isMintedRunId(input.runId)) throw new RunIdNotMintedError(input.runId);
484
+ if (existsSync(runDirFor({ configDir: input.configDir, runId: input.runId }))) {
485
+ throw new RunIdAlreadyInUseError(input.runId);
486
+ }
487
+ }
488
+ function createRunInvocationCore(options) {
489
+ const configDir = dirname2(options.agentQaRoot);
490
+ return {
491
+ async execute(request) {
492
+ const refused = REFUSED_RUN_REQUEST_FIELDS.filter((field) => request[field] !== void 0);
493
+ if (refused.length > 0) throw new RunRequestFieldRefusedError(refused);
494
+ if (request.runId !== void 0) {
495
+ refuseUnavailableRunId({
496
+ configDir,
497
+ runId: request.runId,
498
+ isMintedRunId: options.isMintedRunId
499
+ });
500
+ }
501
+ const runId = request.runId ?? options.mintRunId();
502
+ const declaration = await options.resolveTargetDeclaration(request);
503
+ const targetClass = declaration.targetClass;
504
+ refuseProductionWithoutOptIn(runId, declaration);
505
+ await options.afterRefusal?.();
506
+ const ports = buildRunPorts({ configDir, runId });
507
+ const postVerdict = options.postVerdict?.({ runId, configDir });
508
+ const assembled = await options.assembleRun({
509
+ runId,
510
+ request,
511
+ executionContext: options.executionContext,
512
+ ports: {
513
+ ...ports,
514
+ ...postVerdict === void 0 ? {} : { postVerdict }
515
+ },
516
+ configDir,
517
+ configPath: request.configPath ?? options.configPath,
518
+ targetClass
519
+ });
520
+ const session = startRun({
521
+ ...assembled,
522
+ runId,
523
+ ...request.gateRun === true ? { gateRun: true } : {},
524
+ ...request.excludedFromOutcomes === true ? { excludedFromOutcomes: true } : {}
525
+ });
526
+ options.onSession?.(session);
527
+ await session.done;
528
+ return { runId };
529
+ }
530
+ };
531
+ }
532
+
533
+ // src/composition/run-drive-vocabulary.ts
534
+ import { runSafetyScope } from "@vostride/agent-qa-safety-local";
535
+ import { mapVerdictToExitCode } from "@vostride/agent-qa-runner";
536
+
537
+ // src/composition/evidence-blob-reader.ts
538
+ import { existsSync as existsSync2 } from "node:fs";
539
+ import { join } from "node:path";
540
+ import { FsEvidenceArtifactStore as FsEvidenceArtifactStore2, RUNS_SUBDIR as RUNS_SUBDIR2, slugBranch as slugBranch2 } from "@vostride/agent-qa-runner-local";
541
+ var SHA256_HEX_RE = /^[0-9a-f]{64}$/;
542
+ function runDirectoryExists(input, runId) {
543
+ return existsSync2(join(input.agentQaRoot, RUNS_SUBDIR2, slugBranch2(runId)));
544
+ }
545
+ function lookupEvidenceBlob(input, runId, sha) {
546
+ if (!SHA256_HEX_RE.test(sha)) return { status: "blob_absent" };
547
+ if (!runDirectoryExists(input, runId)) return { status: "unknown_run" };
548
+ const bytes = new FsEvidenceArtifactStore2({ root: input.agentQaRoot, runId }).getBlob(sha);
549
+ return bytes === void 0 ? { status: "blob_absent" } : { status: "found", bytes };
550
+ }
551
+ function statEvidenceBlob(input, runId, sha) {
552
+ if (!SHA256_HEX_RE.test(sha)) return "blob_absent";
553
+ if (!runDirectoryExists(input, runId)) return "unknown_run";
554
+ return new FsEvidenceArtifactStore2({ root: input.agentQaRoot, runId }).hasBlob(sha) ? "found" : "blob_absent";
555
+ }
556
+ function statEvidenceBlobSize(input, runId, sha) {
557
+ if (!SHA256_HEX_RE.test(sha)) return { status: "blob_absent" };
558
+ if (!runDirectoryExists(input, runId)) return { status: "unknown_run" };
559
+ const byteLength = new FsEvidenceArtifactStore2({ root: input.agentQaRoot, runId }).blobSize(sha);
560
+ return byteLength === void 0 ? { status: "blob_absent" } : { status: "found", byteLength };
561
+ }
562
+ function readEvidenceBlobRange(input, runId, sha, start, end) {
563
+ if (!SHA256_HEX_RE.test(sha)) return { status: "blob_absent" };
564
+ if (!runDirectoryExists(input, runId)) return { status: "unknown_run" };
565
+ const bytes = new FsEvidenceArtifactStore2({ root: input.agentQaRoot, runId }).readBlobRange(sha, start, end);
566
+ return bytes === void 0 ? { status: "blob_absent" } : { status: "found", bytes };
567
+ }
568
+ function createEvidenceBlobReader(input) {
569
+ return {
570
+ read: (runId, sha) => Promise.resolve(lookupEvidenceBlob(input, String(runId), sha)),
571
+ has: (runId, sha) => Promise.resolve(statEvidenceBlob(input, String(runId), sha)),
572
+ stat: (runId, sha) => Promise.resolve(statEvidenceBlobSize(input, String(runId), sha)),
573
+ readRange: (runId, sha, start, end) => Promise.resolve(readEvidenceBlobRange(input, String(runId), sha, start, end))
574
+ };
575
+ }
576
+ function readEvidenceBlobBytes(input, runId, sha) {
577
+ const lookup = lookupEvidenceBlob(input, runId, sha);
578
+ return lookup.status === "found" ? lookup.bytes : void 0;
579
+ }
580
+
581
+ // src/composition/quarantine-snapshot.ts
582
+ import { slugBranch as slugBranch3 } from "@vostride/agent-qa-schema";
583
+ import {
584
+ QUARANTINE_LEDGER_FILENAME,
585
+ foldQuarantineLedgerState,
586
+ readQuarantineLedger
587
+ } from "@vostride/agent-qa-knowledge-local/reader";
588
+ function resolveRunQuarantineSnapshot(input) {
589
+ const warn = input.warn ?? (() => {
590
+ });
591
+ const product = slugBranch3(input.product);
592
+ let read;
593
+ try {
594
+ read = readQuarantineLedger(input.knowledgeRoot);
595
+ } catch (error) {
596
+ warn(
597
+ `${QUARANTINE_LEDGER_FILENAME} could not be read (${error instanceof Error ? error.message : String(error)}); no test is quarantined for this run`
598
+ );
599
+ return [];
600
+ }
601
+ if (read.refusedLines.length > 0) {
602
+ warn(
603
+ `${QUARANTINE_LEDGER_FILENAME} has ${read.refusedLines.length} unreadable line(s) \u2014 ${read.refusedLines.map((refused) => `line ${refused.lineNumber}: ${refused.reason}`).join("; ")}`
604
+ );
605
+ }
606
+ return foldQuarantineLedgerState(read.events, input.now).filter((entry) => entry.state === "quarantined" && entry.product === product).map((entry) => ({
607
+ product: entry.product,
608
+ filePath: entry.filePath,
609
+ testName: entry.testName,
610
+ expiresAt: entry.expiresAt
611
+ }));
612
+ }
613
+
614
+ // src/composition/quarantine-vocabulary.ts
615
+ import {
616
+ QUARANTINE_CLASS_DISPOSITIONS,
617
+ QUARANTINE_FLOOR_ELIGIBILITY
618
+ } from "@vostride/agent-qa-knowledge-local/reader";
619
+
620
+ // src/composition/compose-local-agent.ts
621
+ import { randomUUID as randomUUID3 } from "node:crypto";
622
+ import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync4 } from "node:fs";
623
+ import { createRequire } from "node:module";
624
+ import { dirname as dirname6, join as join7 } from "node:path";
625
+ import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
626
+ import { canaryOutcomeObservationId, planMarkEvidence, projectMarkEvidenceOutcome } from "@vostride/agent-qa-knowledge-local/reader";
627
+ import {
628
+ AUTHORING_DRAFT_VERIFY_FUNCTION_ID,
629
+ CHAT_HELP_TEXT,
630
+ CHAT_RESOLVER_TABLE,
631
+ createAgentJobQueue,
632
+ createChatCompactionHook,
633
+ createChatState,
634
+ createFunctionRegistry,
635
+ createPlannerStage,
636
+ createRealJobKindRegistry,
637
+ createTurnEngine,
638
+ deterministicOverrideStrategy,
639
+ foldFunctionResult,
640
+ invokeFunction,
641
+ parseProposeGate1Allowance,
642
+ projectPlannerTools,
643
+ PROPOSE_GATE1_ALLOWANCE_CHECKPOINT,
644
+ RUNS_START_FUNCTION_ID,
645
+ splitClauses
646
+ } from "@vostride/agent-qa-agent";
647
+ import {
648
+ ADDRESSING_LEDGER_DB_FILE,
649
+ acquireEmbedder,
650
+ acquireWriterLease,
651
+ classifyIndexFace,
652
+ countPendingCurationJobs,
653
+ createMemorySightingSource,
654
+ createLocalKnowledgeStore,
655
+ createQuarantineOperations,
656
+ QUARANTINE_FLAG_FLOOR,
657
+ describeAllEntities,
658
+ embedPendingEntities,
659
+ countRemainingCurationWork,
660
+ deriveWriterLeaseFreshness,
661
+ drainQueue,
662
+ DRAIN_BUDGET_MS,
663
+ EMBEDDER_WORKER_REGISTRATION_FILE,
664
+ EMBEDDER_WORKER_START_TIMEOUT_MS,
665
+ EMBEDDER_WORKER_TERM_GRACE_MS,
666
+ EmbedderWorkerTimeoutError,
667
+ embedderWorkerProcessIsAlive,
668
+ embedderWorkerSocketPath,
669
+ enqueueCurationCandidateProjection,
670
+ failureRecurrence,
671
+ fingerprintRecurrence,
672
+ listTestsInFile,
673
+ foldQuarantineLedgerState as foldQuarantineLedgerState2,
674
+ foldRetrievalRung,
675
+ foldRunOutcomes,
676
+ createDraftReceiptsStore,
677
+ createSuiteFingerprintIndex,
678
+ indexDbPath,
679
+ readQuarantineLedger as readQuarantineLedger2,
680
+ readTestOutcomeHistory,
681
+ getEntityDescriptor,
682
+ heartbeatLease,
683
+ inspectDrainableCurationWork,
684
+ isLeaseStale,
685
+ listQueuedCandidates,
686
+ openAddressingLedger,
687
+ openEntityIndex,
688
+ openKnowledgeReader as openKnowledgeReader2,
689
+ purgeAffectedSetFingerprint,
690
+ probeEmbedderWorkerSocket,
691
+ purgeRecord,
692
+ readCanonicalBundleAnchors,
693
+ readCanonicalBundleStateDigests,
694
+ canaryPostureFactsFor,
695
+ standingReceiptDigestsFor,
696
+ lowerTrustSharesFor,
697
+ readEmbedderWorkerRegistration,
698
+ readWriterLease,
699
+ rebuildIndex,
700
+ reconcileRunOutcomesProjection,
701
+ entityIndexDbPath,
702
+ resolveEntityRef,
703
+ sightEntity,
704
+ refreshWriterLeaseAuthority,
705
+ releaseLease,
706
+ removeEmbedderWorkerRegistration,
707
+ resetEmbedderCapability,
708
+ resolveEmbedderCapability,
709
+ resolveProcessIncarnation,
710
+ verifyEmbedderWorkerIncarnation,
711
+ resolvePurgeAffectedSet,
712
+ resolveRollbackAffectedSet,
713
+ rollbackAffectedSetFingerprint,
714
+ rollbackCampaign,
715
+ rollbackCurationJob,
716
+ runMaintenanceTail,
717
+ sweepEntityIndex,
718
+ RUN_OUTCOMES_PROJECTION_VERSION,
719
+ ENTITY_INDEX_SWEEP_INTERVAL_MS,
720
+ applyExplicitReview,
721
+ appendMarkEvidence,
722
+ triageMark,
723
+ servedIdsOn,
724
+ procedureReadOn,
725
+ applyFeedback,
726
+ enumerateBundleDirs,
727
+ loadBundle,
728
+ verifyBundleIntegrity,
729
+ createAuthoringReader,
730
+ openAddressingLedger as openAddressingLedgerForReview,
731
+ resolveRetrievalRung
732
+ } from "@vostride/agent-qa-knowledge-local";
733
+ import { ReplayIncompleteError } from "@vostride/agent-qa-runner";
734
+ import {
735
+ getActiveExecutionContext,
736
+ setActiveExecutionContextResolver
737
+ } from "@vostride/agent-qa-safety";
738
+ import {
739
+ ARTIFACT_FILE,
740
+ FsEventLog as FsEventLog2,
741
+ HANDOFF_FILE,
742
+ RUNS_SUBDIR as RUNS_SUBDIR3,
743
+ loadCompletedRunHandoff,
744
+ slugBranch as slugBranch4
745
+ } from "@vostride/agent-qa-runner-local";
746
+ import {
747
+ admitAnalysisArtifactForRun,
748
+ ANALYSIS_FILE,
749
+ asJobId as asJobId2,
750
+ DEFAULT_EMBEDDING_PROFILE,
751
+ guardRunAnalysisArtifact,
752
+ maskPayloadTree,
753
+ sha256hex16
754
+ } from "@vostride/agent-qa-schema";
755
+
756
+ // src/composition/knowledge-review-reader.ts
757
+ import {
758
+ canaryAttributionsFor,
759
+ candidateInjectionFor,
760
+ gateReceiptsFor,
761
+ diffSince,
762
+ inspectRecords,
763
+ listBundles,
764
+ showRecord,
765
+ showRecords
766
+ } from "@vostride/agent-qa-knowledge-local/reader";
767
+ function createKnowledgeReaderPort(input) {
768
+ async function sight(bundleIds) {
769
+ if (input.sightMemoryEntities === void 0 || bundleIds.length === 0) return;
770
+ try {
771
+ await input.sightMemoryEntities(bundleIds);
772
+ } catch {
773
+ }
774
+ }
775
+ return {
776
+ // 2026-09-07, 417 D-01: forward the candidate DATA verbatim. Since 441-41 (D-06) nothing here
777
+ // scans it; the chat and MCP doors mask declared values in every op result.
778
+ async candidateInjection({ candidateId }) {
779
+ return candidateInjectionFor({
780
+ ...input.resolveTrustPriors === void 0 ? {} : { priors: input.resolveTrustPriors() },
781
+ knowledgeRoot: await input.resolveKnowledgeRoot(),
782
+ candidateId
783
+ });
784
+ },
785
+ // 416 D-17: the caller supplied the identity; forward the reader's answer verbatim.
786
+ async gateReceipts({ candidateId }) {
787
+ return gateReceiptsFor({
788
+ knowledgeRoot: await input.resolveKnowledgeRoot(),
789
+ candidateId,
790
+ resolvePromotionPosture: await input.resolvePromotionPosture?.()
791
+ });
792
+ },
793
+ async listBundles() {
794
+ const rows = listBundles(await input.resolveKnowledgeRoot(), input.resolveTrustPriors?.());
795
+ await sight(rows.map((row) => row.bundleId));
796
+ return rows;
797
+ },
798
+ async inspectRecords({ filter }) {
799
+ const records = inspectRecords({
800
+ ...input.resolveTrustPriors === void 0 ? {} : { priors: input.resolveTrustPriors() },
801
+ knowledgeRoot: await input.resolveKnowledgeRoot(),
802
+ filter
803
+ });
804
+ await sight(records.map((record) => record.recordId));
805
+ return records;
806
+ },
807
+ async diffSince({ sinceOrdinal }) {
808
+ const changes = diffSince({
809
+ knowledgeRoot: await input.resolveKnowledgeRoot(),
810
+ sinceOrdinal
811
+ });
812
+ await sight(changes.map((change) => change.recordId));
813
+ return changes;
814
+ },
815
+ async showRecord({ recordId }) {
816
+ const result = showRecord({
817
+ ...input.resolveTrustPriors === void 0 ? {} : { priors: input.resolveTrustPriors() },
818
+ knowledgeRoot: await input.resolveKnowledgeRoot(),
819
+ recordId
820
+ });
821
+ await sight(result.kind === "found" ? [result.record.recordId] : []);
822
+ return result;
823
+ },
824
+ // Caller-supplied union IDs are already known, like canary tuples below. Forward
825
+ // the maintenance batch without address-ledger sightings or per-record scans.
826
+ async showRecords({ recordIds }) {
827
+ return showRecords({ knowledgeRoot: await input.resolveKnowledgeRoot(), recordIds });
828
+ },
829
+ /**
830
+ * 407 D-08: what became of the canary lines one run served.
831
+ *
832
+ * Resolved per call like its four siblings, and NOT sighted. The caller already holds every
833
+ * candidate id in the tuples it passes — it read them off its own run's evidence — so a
834
+ * sighting here would record that the reader was handed ids rather than that an entity was
835
+ * surfaced to anyone, which is the distinction the ledger exists to keep.
836
+ *
837
+ * The reader entry's answer is forwarded VERBATIM, `skippedBundles` included (407-19, REVIEW
838
+ * WR-03). This binding neither counts nor interprets: how much of the store went unread is a
839
+ * fact only the walk knows, and a composition that dropped it here would hand the op a
840
+ * clean-looking answer over a half-read store.
841
+ */
842
+ async canaryAttributions({ runId, tuples }) {
843
+ return canaryAttributionsFor({
844
+ knowledgeRoot: await input.resolveKnowledgeRoot(),
845
+ runId,
846
+ tuples
847
+ });
848
+ }
849
+ };
850
+ }
851
+
852
+ // src/analysis/persist-analysis.ts
853
+ import { closeSync, fsyncSync, openSync, renameSync, rmSync, writeFileSync } from "node:fs";
854
+ import { randomUUID } from "node:crypto";
855
+ import { dirname as dirname3, join as join2 } from "node:path";
856
+ import { canonicalJson } from "@vostride/agent-qa-schema";
857
+ var ANALYSIS_STAGE_PREFIX = "analysis.json";
858
+ function fsyncFile(file) {
859
+ const fd = openSync(file, "r");
860
+ try {
861
+ fsyncSync(fd);
862
+ } finally {
863
+ closeSync(fd);
864
+ }
865
+ }
866
+ function writeAnalysisArtifact(path, artifact) {
867
+ const dir = dirname3(path);
868
+ const staged = join2(dir, `.${ANALYSIS_STAGE_PREFIX}.${randomUUID()}.tmp`);
869
+ try {
870
+ writeFileSync(staged, `${canonicalJson(artifact)}
871
+ `, { encoding: "utf8", mode: 384 });
872
+ fsyncFile(staged);
873
+ renameSync(staged, path);
874
+ fsyncFile(dir);
875
+ } finally {
876
+ rmSync(staged, { force: true });
877
+ }
878
+ }
879
+
880
+ // src/session/jsonl-session-log.ts
881
+ import {
882
+ closeSync as closeSync3,
883
+ fsyncSync as fsyncSync3,
884
+ mkdirSync as mkdirSync2,
885
+ openSync as openSync3,
886
+ readSync,
887
+ readFileSync as readFileSync2,
888
+ readdirSync,
889
+ writeFileSync as writeFileSync3
890
+ } from "node:fs";
891
+ import { isAbsolute as isAbsolute3, join as join4 } from "node:path";
892
+ import { repairSession } from "@vostride/agent-qa-agent";
893
+ import { CURRENT_CHAT_SESSION_SCHEMA_VERSION } from "@vostride/agent-qa-schema";
894
+
895
+ // src/store/job-checkpoint-store.ts
896
+ import { randomUUID as randomUUID2 } from "node:crypto";
897
+ import {
898
+ closeSync as closeSync2,
899
+ fsyncSync as fsyncSync2,
900
+ mkdirSync,
901
+ openSync as openSync2,
902
+ readFileSync,
903
+ renameSync as renameSync2,
904
+ rmSync as rmSync2,
905
+ writeFileSync as writeFileSync2
906
+ } from "node:fs";
907
+ import { dirname as dirname4, isAbsolute as isAbsolute2, join as join3 } from "node:path";
908
+ import {
909
+ canonicalJson as canonicalJson2,
910
+ asJobId,
911
+ JOB_CHECKPOINT_SCHEMA_VERSION as JOB_CHECKPOINT_SCHEMA_VERSION2,
912
+ jobCheckpointEnvelopeSchema
913
+ } from "@vostride/agent-qa-schema";
914
+
915
+ // src/store/store-errors.ts
916
+ import { JOB_CHECKPOINT_SCHEMA_VERSION } from "@vostride/agent-qa-schema";
917
+ import { QuarantinedJobRequeueError, StaleJobStoreEpochError } from "@vostride/agent-qa-agent";
918
+ var JobStoreSchemaVersionError = class extends Error {
919
+ constructor(path, expectedVersion, observedVersion) {
920
+ super(
921
+ `The agent job store at ${path} is stamped at schema version ${observedVersion}, but this build reads version ${expectedVersion}. There is no migration machinery by design \u2014 clear agent job queue state (delete the agent job database and its checkpoint directory) and re-enqueue.`
922
+ );
923
+ this.path = path;
924
+ this.expectedVersion = expectedVersion;
925
+ this.observedVersion = observedVersion;
926
+ }
927
+ name = "JobStoreSchemaVersionError";
928
+ };
929
+ var UnknownJobRowError = class extends Error {
930
+ constructor(jobId) {
931
+ super(`No agent job row with id ${jobId}`);
932
+ this.jobId = jobId;
933
+ }
934
+ name = "UnknownJobRowError";
935
+ };
936
+ var InvalidJobCheckpointIdError = class extends Error {
937
+ constructor(field, value) {
938
+ super(
939
+ `Unusable ${field} for a checkpoint path: ${JSON.stringify(value)}. A checkpoint id becomes a path segment, so it is validated before the path is built rather than after.`
940
+ );
941
+ this.field = field;
942
+ this.value = value;
943
+ }
944
+ name = "InvalidJobCheckpointIdError";
945
+ };
946
+ var JobCheckpointSchemaVersionError = class extends Error {
947
+ constructor(path, observedVersion) {
948
+ super(
949
+ `Agent job checkpoint ${path} was written at schema version ${String(observedVersion)}, but this build reads version ${JOB_CHECKPOINT_SCHEMA_VERSION}. There is no migration machinery by design \u2014 clear agent job queue state and re-enqueue.`
950
+ );
951
+ this.path = path;
952
+ this.observedVersion = observedVersion;
953
+ }
954
+ name = "JobCheckpointSchemaVersionError";
955
+ };
956
+
957
+ // src/store/job-checkpoint-store.ts
958
+ var JOB_CHECKPOINT_SUBDIR = ["agent-jobs", "checkpoints"];
959
+ var SAFE_PATH_SEGMENT = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
960
+ var DIRECTORY_FSYNC_SWALLOWED_CODES = [
961
+ "ENOENT",
962
+ "EINVAL",
963
+ "EISDIR",
964
+ "EPERM",
965
+ "ENOTSUP"
966
+ ];
967
+ var REAL_JOB_CHECKPOINT_FS = Object.freeze({
968
+ mkdirSync,
969
+ openSync: openSync2,
970
+ writeFileSync: writeFileSync2,
971
+ fsyncSync: fsyncSync2,
972
+ closeSync: closeSync2,
973
+ renameSync: renameSync2,
974
+ rmSync: rmSync2,
975
+ readFileSync
976
+ });
977
+ function requireSegment(field, value) {
978
+ if (!SAFE_PATH_SEGMENT.test(value)) {
979
+ throw new InvalidJobCheckpointIdError(field, value);
980
+ }
981
+ return value;
982
+ }
983
+ function checkpointPath(root, jobId, checkpointId) {
984
+ if (!isAbsolute2(root)) throw new TypeError("agent job checkpoint root must be absolute");
985
+ return join3(
986
+ root,
987
+ ...JOB_CHECKPOINT_SUBDIR,
988
+ requireSegment("jobId", jobId),
989
+ `${requireSegment("checkpointId", checkpointId)}.json`
990
+ );
991
+ }
992
+ function readEnvelopeOn(fs, root, jobId, checkpointId) {
993
+ const file = checkpointPath(root, jobId, checkpointId);
994
+ let text;
995
+ try {
996
+ text = fs.readFileSync(file, "utf8");
997
+ } catch (error) {
998
+ if (error.code === "ENOENT") return { found: false };
999
+ throw error;
1000
+ }
1001
+ const raw = JSON.parse(text);
1002
+ if (raw?.schemaVersion !== JOB_CHECKPOINT_SCHEMA_VERSION2) {
1003
+ throw new JobCheckpointSchemaVersionError(file, raw?.schemaVersion);
1004
+ }
1005
+ const envelope = jobCheckpointEnvelopeSchema.parse(raw);
1006
+ if (envelope.jobId !== jobId || envelope.checkpointId !== checkpointId) {
1007
+ throw new TypeError("Agent job checkpoint envelope does not match its address");
1008
+ }
1009
+ return { found: true, envelope };
1010
+ }
1011
+ function readJobCheckpointEnvelope(options, jobId, checkpointId) {
1012
+ return readEnvelopeOn(REAL_JOB_CHECKPOINT_FS, options.root, jobId, checkpointId);
1013
+ }
1014
+ function isSwallowableDirectoryFsyncError(error) {
1015
+ const code = error.code;
1016
+ return DIRECTORY_FSYNC_SWALLOWED_CODES.some((allowed) => allowed === code);
1017
+ }
1018
+ function createJobCheckpointStore(options) {
1019
+ return createJobCheckpointStoreOn(REAL_JOB_CHECKPOINT_FS, options);
1020
+ }
1021
+ function createJobCheckpointStoreOn(fs, options) {
1022
+ if (!isAbsolute2(options.root)) {
1023
+ throw new TypeError("agent job checkpoint root must be absolute");
1024
+ }
1025
+ const checkpointsRoot = join3(options.root, ...JOB_CHECKPOINT_SUBDIR);
1026
+ function pathFor(jobId, checkpointId) {
1027
+ return checkpointPath(options.root, jobId, checkpointId);
1028
+ }
1029
+ function bestEffortFsyncDirectory(dir) {
1030
+ let fd;
1031
+ try {
1032
+ fd = fs.openSync(dir, "r");
1033
+ fs.fsyncSync(fd);
1034
+ } catch (error) {
1035
+ if (!isSwallowableDirectoryFsyncError(error)) {
1036
+ throw error;
1037
+ }
1038
+ } finally {
1039
+ if (fd !== void 0) fs.closeSync(fd);
1040
+ }
1041
+ }
1042
+ return {
1043
+ checkpointsRoot,
1044
+ checkpointPath: pathFor,
1045
+ publish(jobId, checkpointId, payload) {
1046
+ const file = pathFor(jobId, checkpointId);
1047
+ const envelope = {
1048
+ schemaVersion: JOB_CHECKPOINT_SCHEMA_VERSION2,
1049
+ jobId: asJobId(jobId),
1050
+ checkpointId,
1051
+ payload
1052
+ };
1053
+ fs.mkdirSync(dirname4(file), { recursive: true });
1054
+ const temporary = `${file}.tmp-${randomUUID2()}`;
1055
+ let fd;
1056
+ try {
1057
+ fd = fs.openSync(temporary, "wx");
1058
+ fs.writeFileSync(fd, `${canonicalJson2(envelope)}
1059
+ `, "utf8");
1060
+ fs.fsyncSync(fd);
1061
+ fs.closeSync(fd);
1062
+ fd = void 0;
1063
+ fs.renameSync(temporary, file);
1064
+ bestEffortFsyncDirectory(dirname4(file));
1065
+ } finally {
1066
+ if (fd !== void 0) fs.closeSync(fd);
1067
+ fs.rmSync(temporary, { force: true });
1068
+ }
1069
+ },
1070
+ discard(jobId, checkpointId) {
1071
+ fs.rmSync(pathFor(jobId, checkpointId), { force: true });
1072
+ },
1073
+ read(jobId, checkpointId) {
1074
+ const read = readEnvelopeOn(fs, options.root, jobId, checkpointId);
1075
+ return read.found ? { found: true, payload: read.envelope.payload } : read;
1076
+ }
1077
+ };
1078
+ }
1079
+
1080
+ // src/session/jsonl-session-log.ts
1081
+ var CHAT_SESSIONS_SUBDIR = "sessions";
1082
+ var SESSION_FILE_SUFFIX = ".jsonl";
1083
+ var CHAT_SESSION_READ_CHUNK_BYTES = 64 * 1024;
1084
+ var SAFE_PATH_SEGMENT2 = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
1085
+ var InvalidChatSessionIdError = class extends Error {
1086
+ constructor(sessionId) {
1087
+ super(
1088
+ `${JSON.stringify(sessionId)} is not a usable chat session id. Ids are minted by this adapter and are limited to letters, digits, dot, dash and underscore.`
1089
+ );
1090
+ this.sessionId = sessionId;
1091
+ }
1092
+ name = "InvalidChatSessionIdError";
1093
+ };
1094
+ var UnknownChatSessionError = class extends Error {
1095
+ constructor(sessionId) {
1096
+ super(`No chat session ${sessionId} exists under this project's runtime root.`);
1097
+ this.sessionId = sessionId;
1098
+ }
1099
+ name = "UnknownChatSessionError";
1100
+ };
1101
+ var ChatSessionBusyError = class extends Error {
1102
+ constructor(sessionId) {
1103
+ super(
1104
+ `Chat session ${sessionId} is already open in this process. One writer at a time: a second handle would interleave two turns in one conversation.`
1105
+ );
1106
+ this.sessionId = sessionId;
1107
+ }
1108
+ name = "ChatSessionBusyError";
1109
+ };
1110
+ var ChatSessionHeaderError = class extends Error {
1111
+ constructor(sessionId, detail) {
1112
+ super(`The session file for ${sessionId} cannot be read: ${detail}.`);
1113
+ this.sessionId = sessionId;
1114
+ this.detail = detail;
1115
+ }
1116
+ name = "ChatSessionHeaderError";
1117
+ };
1118
+ var REAL_CHAT_SESSION_FS = Object.freeze({
1119
+ mkdirSync: mkdirSync2,
1120
+ openSync: openSync3,
1121
+ writeFileSync: writeFileSync3,
1122
+ fsyncSync: fsyncSync3,
1123
+ closeSync: closeSync3,
1124
+ readSync,
1125
+ readFileSync: readFileSync2,
1126
+ readdirSync
1127
+ });
1128
+ function mintChatSessionId(nowIso, random) {
1129
+ const stamp = nowIso.replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z").replace("T", "-").replace("Z", "");
1130
+ const suffix = Math.floor(random() * 36 ** 4).toString(36).padStart(4, "0");
1131
+ return `chat-${stamp}-${suffix}`;
1132
+ }
1133
+ function requireSessionId(sessionId) {
1134
+ if (!SAFE_PATH_SEGMENT2.test(sessionId)) throw new InvalidChatSessionIdError(sessionId);
1135
+ return sessionId;
1136
+ }
1137
+ function isSwallowableDirectoryFsyncError2(error) {
1138
+ const code = error.code;
1139
+ return DIRECTORY_FSYNC_SWALLOWED_CODES.some((allowed) => allowed === code);
1140
+ }
1141
+ function guardHeader(sessionId, line) {
1142
+ let parsed;
1143
+ try {
1144
+ parsed = JSON.parse(line);
1145
+ } catch {
1146
+ throw new ChatSessionHeaderError(sessionId, "its first line is not JSON");
1147
+ }
1148
+ if (parsed === null || typeof parsed !== "object") {
1149
+ throw new ChatSessionHeaderError(sessionId, "its header line is not an object");
1150
+ }
1151
+ const header = parsed;
1152
+ if (header.schemaVersion !== CURRENT_CHAT_SESSION_SCHEMA_VERSION) {
1153
+ throw new ChatSessionHeaderError(
1154
+ sessionId,
1155
+ `it declares schemaVersion ${String(header.schemaVersion)} and this build reads ` + String(CURRENT_CHAT_SESSION_SCHEMA_VERSION)
1156
+ );
1157
+ }
1158
+ if (header.sessionId !== sessionId) {
1159
+ throw new ChatSessionHeaderError(
1160
+ sessionId,
1161
+ `its header names session ${String(header.sessionId)}`
1162
+ );
1163
+ }
1164
+ if (typeof header.createdAt !== "string" || typeof header.model !== "string") {
1165
+ throw new ChatSessionHeaderError(sessionId, "its header names no creation instant or model");
1166
+ }
1167
+ if (header.surface !== void 0 && typeof header.surface !== "string") {
1168
+ throw new ChatSessionHeaderError(
1169
+ sessionId,
1170
+ `its header names a surface of type ${typeof header.surface} and this build reads a string`
1171
+ );
1172
+ }
1173
+ return header;
1174
+ }
1175
+ function listSessions(root) {
1176
+ if (!isAbsolute3(root)) throw new TypeError("chat session log root must be absolute");
1177
+ const fs = REAL_CHAT_SESSION_FS;
1178
+ const sessionsRoot = join4(root, CHAT_SESSIONS_SUBDIR);
1179
+ let files;
1180
+ try {
1181
+ files = fs.readdirSync(sessionsRoot);
1182
+ } catch (error) {
1183
+ if (error.code === "ENOENT") return { sessions: [], refused: [] };
1184
+ throw error;
1185
+ }
1186
+ const sessions = [];
1187
+ const refused = [];
1188
+ for (const file of [...files].sort()) {
1189
+ if (!file.endsWith(SESSION_FILE_SUFFIX)) continue;
1190
+ const sessionId = file.slice(0, -SESSION_FILE_SUFFIX.length);
1191
+ try {
1192
+ const scanned = scanSessionFile(fs, join4(sessionsRoot, file));
1193
+ if (scanned.headerLine === void 0) {
1194
+ refused.push({ file, reason: "it carries no complete header line" });
1195
+ continue;
1196
+ }
1197
+ const header = guardHeader(sessionId, scanned.headerLine);
1198
+ sessions.push({
1199
+ sessionId: header.sessionId,
1200
+ createdAt: header.createdAt,
1201
+ model: header.model,
1202
+ eventCount: Math.max(scanned.lineCount - 1, 0)
1203
+ });
1204
+ } catch (error) {
1205
+ refused.push({ file, reason: error instanceof Error ? error.message : String(error) });
1206
+ }
1207
+ }
1208
+ return { sessions, refused };
1209
+ }
1210
+ function scanSessionFile(fs, path) {
1211
+ const fd = fs.openSync(path, "r");
1212
+ try {
1213
+ const buffer = Buffer.allocUnsafe(CHAT_SESSION_READ_CHUNK_BYTES);
1214
+ let headerLine;
1215
+ let headerBytes = "";
1216
+ let lineCount = 0;
1217
+ let read = fs.readSync(fd, buffer, 0, buffer.length, null);
1218
+ while (read > 0) {
1219
+ const chunk = buffer.subarray(0, read);
1220
+ for (let index = 0; index < chunk.length; index += 1) {
1221
+ if (chunk[index] === 10) lineCount += 1;
1222
+ }
1223
+ if (headerLine === void 0) {
1224
+ headerBytes += chunk.toString("utf8");
1225
+ const newline = headerBytes.indexOf("\n");
1226
+ if (newline >= 0) headerLine = headerBytes.slice(0, newline);
1227
+ else if (headerBytes.length > CHAT_SESSION_READ_CHUNK_BYTES) break;
1228
+ }
1229
+ read = fs.readSync(fd, buffer, 0, buffer.length, null);
1230
+ }
1231
+ return { headerLine, lineCount };
1232
+ } finally {
1233
+ fs.closeSync(fd);
1234
+ }
1235
+ }
1236
+ function createJsonlSessionLog(options) {
1237
+ return createJsonlSessionLogOn(REAL_CHAT_SESSION_FS, options);
1238
+ }
1239
+ function createJsonlSessionLogOn(fs, options) {
1240
+ if (!isAbsolute3(options.root)) {
1241
+ throw new TypeError("chat session log root must be absolute");
1242
+ }
1243
+ const sessionsRoot = join4(options.root, CHAT_SESSIONS_SUBDIR);
1244
+ const random = options.random ?? Math.random;
1245
+ const liveDescriptors = /* @__PURE__ */ new Map();
1246
+ function pathFor(sessionId) {
1247
+ return join4(sessionsRoot, `${sessionId}.jsonl`);
1248
+ }
1249
+ function bestEffortFsyncDirectory(dir) {
1250
+ let directoryFd;
1251
+ try {
1252
+ directoryFd = fs.openSync(dir, "r");
1253
+ fs.fsyncSync(directoryFd);
1254
+ } catch (error) {
1255
+ if (!isSwallowableDirectoryFsyncError2(error)) throw error;
1256
+ } finally {
1257
+ if (directoryFd !== void 0) fs.closeSync(directoryFd);
1258
+ }
1259
+ }
1260
+ function readLines(sessionId) {
1261
+ let text;
1262
+ try {
1263
+ text = fs.readFileSync(pathFor(sessionId), "utf8");
1264
+ } catch (error) {
1265
+ if (error.code === "ENOENT") return void 0;
1266
+ throw error;
1267
+ }
1268
+ return {
1269
+ // Line numbers are 1-based and count the header, so a refusal names what an editor shows the
1270
+ // person who has to go and look at the file.
1271
+ lines: text.split("\n").map((line, index) => ({ lineNumber: index + 1, text: line })).filter((line) => line.text !== ""),
1272
+ // Every line this adapter writes ends in a newline, so a file that does not is a file whose
1273
+ // last write was cut short. That fact cannot be recovered once the lines are split, and the
1274
+ // append path needs it BEFORE it writes anything (see `terminateTornTail`).
1275
+ terminated: text === "" || text.endsWith("\n")
1276
+ };
1277
+ }
1278
+ function writeEvent(target, event) {
1279
+ fs.writeFileSync(target, `${JSON.stringify(event)}
1280
+ `, "utf8");
1281
+ }
1282
+ function handleFor(id, fd, opened) {
1283
+ let live = true;
1284
+ const requireLive = (verb) => {
1285
+ if (!live) {
1286
+ throw new TypeError(
1287
+ `a chat session ${verb} cannot go through a closed handle for ${id}. Open the session again to write to it.`
1288
+ );
1289
+ }
1290
+ };
1291
+ const handle = {
1292
+ header: opened.header,
1293
+ events: opened.events,
1294
+ refusedLines: opened.refusedLines,
1295
+ append(event) {
1296
+ requireLive("event");
1297
+ writeEvent(fd, event);
1298
+ return Promise.resolve();
1299
+ },
1300
+ turnBoundary() {
1301
+ requireLive("turn boundary");
1302
+ fs.fsyncSync(fd);
1303
+ return Promise.resolve();
1304
+ },
1305
+ close() {
1306
+ if (live) {
1307
+ live = false;
1308
+ liveDescriptors.delete(id);
1309
+ fs.closeSync(fd);
1310
+ }
1311
+ return Promise.resolve();
1312
+ }
1313
+ };
1314
+ liveDescriptors.set(id, fd);
1315
+ return handle;
1316
+ }
1317
+ return {
1318
+ open(sessionId) {
1319
+ fs.mkdirSync(sessionsRoot, { recursive: true });
1320
+ if (sessionId !== void 0) {
1321
+ const id2 = requireSessionId(sessionId);
1322
+ if (liveDescriptors.has(id2)) throw new ChatSessionBusyError(id2);
1323
+ const read = readLines(id2);
1324
+ if (read === void 0 || read.lines.length === 0) throw new UnknownChatSessionError(id2);
1325
+ const header2 = guardHeader(id2, read.lines[0].text);
1326
+ const repair = repairSession(read.lines.slice(1), {
1327
+ declaredVersion: header2.schemaVersion,
1328
+ now: options.now
1329
+ });
1330
+ const resumed = fs.openSync(pathFor(id2), "a");
1331
+ let wroteDuringRepair = false;
1332
+ if (!read.terminated) {
1333
+ fs.writeFileSync(resumed, "\n", "utf8");
1334
+ wroteDuringRepair = true;
1335
+ }
1336
+ for (const event of repair.synthesized) {
1337
+ writeEvent(resumed, event);
1338
+ wroteDuringRepair = true;
1339
+ }
1340
+ if (wroteDuringRepair) {
1341
+ fs.fsyncSync(resumed);
1342
+ }
1343
+ return Promise.resolve(
1344
+ handleFor(id2, resumed, {
1345
+ header: header2,
1346
+ events: repair.repaired,
1347
+ refusedLines: repair.refusedLines
1348
+ })
1349
+ );
1350
+ }
1351
+ const createdAt = options.now();
1352
+ const id = mintChatSessionId(createdAt, random);
1353
+ const header = {
1354
+ schemaVersion: CURRENT_CHAT_SESSION_SCHEMA_VERSION,
1355
+ sessionId: id,
1356
+ createdAt,
1357
+ model: options.model(),
1358
+ toolsetHash: options.toolsetHash(),
1359
+ // CONDITIONALLY SPREAD, on the composition's own idiom (400-01) and for its reason: an
1360
+ // unconditional `surface: options.surface` would put `"surface":undefined` through
1361
+ // `JSON.stringify` — which drops it — but would also make an explicit `undefined` and an
1362
+ // unsupplied option two spellings of one state at every call site above. Absent here means
1363
+ // the option was never given, and that is exactly the pre-400 population's meaning.
1364
+ ...options.surface === void 0 ? {} : { surface: options.surface }
1365
+ };
1366
+ const created = fs.openSync(pathFor(id), "wx");
1367
+ try {
1368
+ fs.writeFileSync(created, `${JSON.stringify(header)}
1369
+ `, "utf8");
1370
+ fs.fsyncSync(created);
1371
+ } finally {
1372
+ fs.closeSync(created);
1373
+ }
1374
+ bestEffortFsyncDirectory(sessionsRoot);
1375
+ const appendFd = fs.openSync(pathFor(id), "a");
1376
+ return Promise.resolve(handleFor(id, appendFd, { header, events: [], refusedLines: [] }));
1377
+ }
1378
+ };
1379
+ }
1380
+
1381
+ // src/store/post-processing-reader.ts
1382
+ import { existsSync as existsSync3 } from "node:fs";
1383
+ import { join as join6 } from "node:path";
1384
+ import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
1385
+ import { canonicalJson as canonicalJson5, CurationSnapshotSchema, guardRunNotification as guardRunNotification2, isJobKind as isJobKind2, sha256hex as sha256hex3 } from "@vostride/agent-qa-schema";
1386
+
1387
+ // src/store/sqlite-job-store.ts
1388
+ import { mkdirSync as mkdirSync3 } from "node:fs";
1389
+ import { dirname as dirname5 } from "node:path";
1390
+ import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
1391
+ import {
1392
+ JobCancellationNotRequestedError,
1393
+ MissingIdempotencyKeyError,
1394
+ QuarantinedJobRequeueError as QuarantinedJobRequeueError2,
1395
+ StaleJobStoreEpochError as StaleJobStoreEpochError2
1396
+ } from "@vostride/agent-qa-agent";
1397
+ import {
1398
+ canonicalJson as canonicalJson4,
1399
+ guardRunNotification,
1400
+ isJobKind,
1401
+ jobSchema,
1402
+ sha256hex as sha256hex2
1403
+ } from "@vostride/agent-qa-schema";
1404
+
1405
+ // src/store/logical-dump.ts
1406
+ import { readdirSync as readdirSync2, readFileSync as readFileSync3 } from "node:fs";
1407
+ import { join as join5, relative, sep } from "node:path";
1408
+ import "node:sqlite";
1409
+ import { canonicalJson as canonicalJson3, sha256hex } from "@vostride/agent-qa-schema";
1410
+ var SAFE_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
1411
+ function requireIdentifier(value) {
1412
+ if (!SAFE_IDENTIFIER.test(value)) {
1413
+ throw new TypeError(`unusable sqlite identifier in a logical dump: ${JSON.stringify(value)}`);
1414
+ }
1415
+ return value;
1416
+ }
1417
+ function orderedSelectSql(table, columns) {
1418
+ if (columns.length === 0) {
1419
+ throw new TypeError(`table ${table} reports no columns, so no order can be stated`);
1420
+ }
1421
+ const order = columns.map((column) => `"${requireIdentifier(column)}" ASC`).join(", ");
1422
+ return `SELECT * FROM "${requireIdentifier(table)}" ORDER BY ${order}`;
1423
+ }
1424
+ function listStoreTables(db) {
1425
+ return db.prepare(
1426
+ `SELECT name FROM sqlite_master
1427
+ WHERE type = 'table' AND name NOT LIKE 'sqlite_%'
1428
+ ORDER BY name ASC`
1429
+ ).all().map((row) => String(row.name));
1430
+ }
1431
+ function tableColumns(db, table) {
1432
+ return db.prepare(`PRAGMA table_info("${requireIdentifier(table)}")`).all().map((row) => String(row.name));
1433
+ }
1434
+ function normalize(value) {
1435
+ if (typeof value === "bigint") return Number(value);
1436
+ if (value instanceof Uint8Array) return { base16: Buffer.from(value).toString("hex") };
1437
+ return value;
1438
+ }
1439
+ function selectOrderedRows(db, table) {
1440
+ const columns = tableColumns(db, table);
1441
+ const rows = db.prepare(orderedSelectSql(table, columns)).all().map((row) => {
1442
+ const normalized = {};
1443
+ for (const column of columns) normalized[column] = normalize(row[column]);
1444
+ return normalized;
1445
+ });
1446
+ return { table, orderBy: columns, rows };
1447
+ }
1448
+ function checkpointDigests(checkpointsRoot) {
1449
+ const found = [];
1450
+ function walk(dir) {
1451
+ let entries;
1452
+ try {
1453
+ entries = readdirSync2(dir, { withFileTypes: true });
1454
+ } catch (error) {
1455
+ if (error.code === "ENOENT") return;
1456
+ throw error;
1457
+ }
1458
+ for (const entry of entries) {
1459
+ const path = join5(dir, entry.name);
1460
+ if (entry.isDirectory()) {
1461
+ walk(path);
1462
+ continue;
1463
+ }
1464
+ found.push({
1465
+ path: relative(checkpointsRoot, path).split(sep).join("/"),
1466
+ digest: sha256hex(readFileSync3(path, "utf8"))
1467
+ });
1468
+ }
1469
+ }
1470
+ walk(checkpointsRoot);
1471
+ return found.sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
1472
+ }
1473
+ function createLogicalDump(input) {
1474
+ return {
1475
+ schemaVersion: input.schemaVersion,
1476
+ tables: listStoreTables(input.db).map((table) => selectOrderedRows(input.db, table)),
1477
+ checkpoints: checkpointDigests(input.checkpointsRoot)
1478
+ };
1479
+ }
1480
+ function canonicalLogicalDump(dump) {
1481
+ return canonicalJson3(dump);
1482
+ }
1483
+
1484
+ // src/store/sqlite-job-store.ts
1485
+ var AGENT_JOB_STORE_USER_VERSION = 2;
1486
+ var AGENT_JOB_STORE_BUSY_TIMEOUT_MS = 2e3;
1487
+ var AGENT_JOB_STORE_DB_FILE = "agent-jobs.db";
1488
+ var SCHEMA_STATEMENTS = [
1489
+ `CREATE TABLE IF NOT EXISTS jobs (
1490
+ id TEXT PRIMARY KEY,
1491
+ kind TEXT NOT NULL,
1492
+ idempotency_key TEXT NOT NULL,
1493
+ status TEXT NOT NULL,
1494
+ summary TEXT NOT NULL,
1495
+ payload TEXT NOT NULL,
1496
+ attempts INTEGER NOT NULL,
1497
+ claimed_at TEXT,
1498
+ created_at TEXT NOT NULL,
1499
+ updated_at TEXT NOT NULL,
1500
+ -- The disposition the engine DECIDED, recorded so a failed job says why it failed.
1501
+ -- Nullable because most rows never fail; written only by recordFailure.
1502
+ last_failure_kind TEXT,
1503
+ last_failure_reason TEXT,
1504
+ -- When somebody asked this job to stop (D-19). A column rather than a status, for the
1505
+ -- same reason the claim is one: the job status vocabulary describes lifecycle stages,
1506
+ -- and "still queued, but asked to stop" is a fact about a request, not a stage.
1507
+ -- Appended LAST deliberately - a column inserted in the middle would move every
1508
+ -- positional read of this table for no gain.
1509
+ cancel_requested_at TEXT,
1510
+ -- The instant this row was FIRST claimed, kept after the lease is released (425). The
1511
+ -- claim column answers who holds the row now; no column answered how long the work took.
1512
+ -- Written once by the claim, cleared only by the operator requeue. Appended last, as above.
1513
+ started_at TEXT,
1514
+ -- 444.1 D-23: a retrying row is not claimable before this instant. Appended last, as above.
1515
+ not_before TEXT
1516
+ )`,
1517
+ // D-08's dedupe is THIS line. Making it a store-level constraint rather than a check the
1518
+ // caller performs is what turns "two enqueues of the same work produce one job" from a
1519
+ // convention into something no caller, in any process, can get wrong.
1520
+ `CREATE UNIQUE INDEX IF NOT EXISTS jobs_kind_idempotency_key
1521
+ ON jobs (kind, idempotency_key)`,
1522
+ `CREATE TABLE IF NOT EXISTS job_store_fence (
1523
+ id INTEGER PRIMARY KEY CHECK (id = 1),
1524
+ epoch INTEGER NOT NULL
1525
+ )`,
1526
+ // The row is the authoritative checkpoint. The file remains a durable projection for
1527
+ // cross-process observability, but recovery accepts its bytes only when their digest agrees
1528
+ // with the latest fenced row for this job/checkpoint. Attempt stays in the key so retries
1529
+ // preserve their recorded history while a same-attempt takeover replaces only that attempt.
1530
+ `CREATE TABLE IF NOT EXISTS job_checkpoints (
1531
+ job_id TEXT NOT NULL,
1532
+ checkpoint_id TEXT NOT NULL,
1533
+ attempt INTEGER NOT NULL,
1534
+ payload_json TEXT NOT NULL,
1535
+ payload_digest TEXT NOT NULL,
1536
+ epoch INTEGER NOT NULL,
1537
+ PRIMARY KEY (job_id, attempt, checkpoint_id)
1538
+ )`,
1539
+ // 388 D-09: the chain-terminal record, beside the jobs it summarizes rather than in a
1540
+ // second durable medium. `created_at` is written from the injected clock like every other
1541
+ // timestamp in this file — a SQL-side default would leak host time into the D-03 dump and
1542
+ // make two identical histories compare unequal.
1543
+ `CREATE TABLE IF NOT EXISTS notifications (
1544
+ run_id TEXT NOT NULL,
1545
+ payload TEXT NOT NULL,
1546
+ created_at TEXT NOT NULL
1547
+ )`,
1548
+ // D-10's dedupe is THIS line, and it is the same idiom as the jobs index above for the same
1549
+ // reason: a crash-replayed chain terminal re-attempts the insert, and "one notification per
1550
+ // run, ever" has to be something no caller — in this process or the next — can get wrong.
1551
+ `CREATE UNIQUE INDEX IF NOT EXISTS notifications_run_id
1552
+ ON notifications (run_id)`
1553
+ ];
1554
+ var TERMINAL_JOB_STATUSES = ["done", "failed", "cancelled"];
1555
+ var UNREADABLE_KIND_CONCLUSION = "unreadable_kind";
1556
+ var QUARANTINED_UNREADABLE_KIND_REASON = "agent job quarantined at claim: this build does not read the kind recorded for this row, so the row was landed permanently without being parsed. The original kind, id and payload are preserved in the row for inspection.";
1557
+ var QUARANTINE_SCAN_BOUND = 1e3;
1558
+ function isUnreadableKindRow(row) {
1559
+ return !isJobKind(String(row.kind));
1560
+ }
1561
+ function quarantineConclusionOf(row) {
1562
+ if (!isUnreadableKindRow(row)) return void 0;
1563
+ if (String(row.status) !== "failed") return void 0;
1564
+ if (String(row.last_failure_kind) !== "permanent") return void 0;
1565
+ if (String(row.last_failure_reason) !== QUARANTINED_UNREADABLE_KIND_REASON) return void 0;
1566
+ return UNREADABLE_KIND_CONCLUSION;
1567
+ }
1568
+ function runIdOfPayloadColumn(payload) {
1569
+ const record = JSON.parse(String(payload));
1570
+ if (typeof record !== "object" || record === null || Array.isArray(record)) return void 0;
1571
+ const runId = record.runId;
1572
+ return typeof runId === "string" && runId !== "" ? runId : void 0;
1573
+ }
1574
+ function toNumber(value) {
1575
+ return typeof value === "bigint" ? Number(value) : Number(value);
1576
+ }
1577
+ function createSqliteJobStore(options) {
1578
+ const { clock, checkpoints, hooks } = options;
1579
+ mkdirSync3(dirname5(options.path), { recursive: true });
1580
+ const db = new DatabaseSync2(options.path, {
1581
+ timeout: options.timeoutMs ?? AGENT_JOB_STORE_BUSY_TIMEOUT_MS
1582
+ });
1583
+ db.exec("PRAGMA journal_mode=WAL");
1584
+ const stampedVersion = toNumber(db.prepare("PRAGMA user_version").get()?.user_version);
1585
+ if (stampedVersion !== 0 && stampedVersion !== AGENT_JOB_STORE_USER_VERSION) {
1586
+ db.close();
1587
+ throw new JobStoreSchemaVersionError(
1588
+ options.path,
1589
+ AGENT_JOB_STORE_USER_VERSION,
1590
+ stampedVersion
1591
+ );
1592
+ }
1593
+ for (const statement of SCHEMA_STATEMENTS) db.exec(statement);
1594
+ if (stampedVersion === 0) {
1595
+ db.exec(`PRAGMA user_version=${AGENT_JOB_STORE_USER_VERSION}`);
1596
+ }
1597
+ const insertJob = db.prepare(
1598
+ `INSERT INTO jobs
1599
+ (id, kind, idempotency_key, status, summary, payload, attempts, claimed_at,
1600
+ created_at, updated_at, last_failure_kind, last_failure_reason,
1601
+ cancel_requested_at, started_at, not_before)
1602
+ VALUES (?, ?, ?, 'queued', ?, ?, 0, NULL, ?, ?, NULL, NULL, NULL, NULL, NULL)
1603
+ ON CONFLICT (kind, idempotency_key) DO NOTHING`
1604
+ );
1605
+ const selectByKey = db.prepare("SELECT * FROM jobs WHERE kind = ? AND idempotency_key = ?");
1606
+ const selectById = db.prepare("SELECT * FROM jobs WHERE id = ?");
1607
+ const selectClaimable = db.prepare(
1608
+ `SELECT * FROM jobs
1609
+ WHERE status = 'queued' AND claimed_at IS NULL
1610
+ AND (not_before IS NULL OR not_before <= ?)
1611
+ ORDER BY created_at ASC, id ASC
1612
+ LIMIT 1`
1613
+ );
1614
+ const selectClaimableExcluding = /* @__PURE__ */ new Map();
1615
+ function claimableStatementFor(excludedCount) {
1616
+ const cached = selectClaimableExcluding.get(excludedCount);
1617
+ if (cached !== void 0) return cached;
1618
+ const placeholders = Array.from({ length: excludedCount }, () => "?").join(", ");
1619
+ const prepared = db.prepare(
1620
+ `SELECT * FROM jobs
1621
+ WHERE status = 'queued' AND claimed_at IS NULL
1622
+ AND (not_before IS NULL OR not_before <= ?) AND kind NOT IN (${placeholders})
1623
+ ORDER BY created_at ASC, id ASC
1624
+ LIMIT 1`
1625
+ );
1626
+ selectClaimableExcluding.set(excludedCount, prepared);
1627
+ return prepared;
1628
+ }
1629
+ const markClaimed = db.prepare(
1630
+ `UPDATE jobs
1631
+ SET claimed_at = ?, started_at = COALESCE(started_at, ?), attempts = attempts + 1,
1632
+ updated_at = ?
1633
+ WHERE id = ?`
1634
+ );
1635
+ const markDone = db.prepare(
1636
+ "UPDATE jobs SET status = 'done', claimed_at = NULL, updated_at = ? WHERE id = ?"
1637
+ );
1638
+ const markQuarantined = db.prepare(
1639
+ `UPDATE jobs
1640
+ SET status = 'failed', claimed_at = NULL, last_failure_kind = 'permanent',
1641
+ last_failure_reason = ?, updated_at = ?
1642
+ WHERE id = ?`
1643
+ );
1644
+ const touchJob = db.prepare("UPDATE jobs SET updated_at = ? WHERE id = ?");
1645
+ const markRetryable = db.prepare(
1646
+ `UPDATE jobs
1647
+ SET claimed_at = NULL, last_failure_kind = 'retry', last_failure_reason = ?,
1648
+ updated_at = ?, not_before = ?
1649
+ WHERE id = ?`
1650
+ );
1651
+ const markInterrupted = db.prepare(
1652
+ `UPDATE jobs
1653
+ SET claimed_at = NULL, attempts = MAX(attempts - 1, 0), updated_at = ?
1654
+ WHERE id = ?`
1655
+ );
1656
+ const markFailed = db.prepare(
1657
+ `UPDATE jobs
1658
+ SET status = 'failed', claimed_at = NULL, last_failure_kind = 'permanent',
1659
+ last_failure_reason = ?, updated_at = ?
1660
+ WHERE id = ?`
1661
+ );
1662
+ const markCancelledNow = db.prepare(
1663
+ `UPDATE jobs
1664
+ SET status = 'cancelled', claimed_at = NULL, cancel_requested_at = ?, updated_at = ?
1665
+ WHERE id = ?`
1666
+ );
1667
+ const markCancelRequested = db.prepare(
1668
+ "UPDATE jobs SET cancel_requested_at = ?, updated_at = ? WHERE id = ?"
1669
+ );
1670
+ const markCancelHonoured = db.prepare(
1671
+ `UPDATE jobs
1672
+ SET status = 'cancelled', claimed_at = NULL, updated_at = ?
1673
+ WHERE id = ?`
1674
+ );
1675
+ const markRequeued = db.prepare(
1676
+ `UPDATE jobs
1677
+ SET status = 'queued', claimed_at = NULL, started_at = NULL, attempts = 0,
1678
+ last_failure_kind = NULL, last_failure_reason = NULL, cancel_requested_at = NULL,
1679
+ not_before = NULL, updated_at = ?
1680
+ WHERE id = ?`
1681
+ );
1682
+ const selectCheckpointIdsForJob = db.prepare(
1683
+ "SELECT DISTINCT checkpoint_id FROM job_checkpoints WHERE job_id = ?"
1684
+ );
1685
+ const deleteCheckpointsForJob = db.prepare("DELETE FROM job_checkpoints WHERE job_id = ?");
1686
+ const selectFence = db.prepare("SELECT epoch FROM job_store_fence WHERE id = 1");
1687
+ const upsertFence = db.prepare(
1688
+ `INSERT INTO job_store_fence (id, epoch) VALUES (1, ?)
1689
+ ON CONFLICT (id) DO UPDATE SET epoch = excluded.epoch`
1690
+ );
1691
+ const upsertCheckpoint = db.prepare(
1692
+ `INSERT INTO job_checkpoints
1693
+ (job_id, checkpoint_id, attempt, payload_json, payload_digest, epoch)
1694
+ VALUES (?, ?, ?, ?, ?, ?)
1695
+ ON CONFLICT (job_id, attempt, checkpoint_id) DO UPDATE SET
1696
+ payload_json = excluded.payload_json,
1697
+ payload_digest = excluded.payload_digest,
1698
+ epoch = excluded.epoch`
1699
+ );
1700
+ const selectLatestCheckpoint = db.prepare(
1701
+ `SELECT payload_json, payload_digest, attempt, epoch
1702
+ FROM job_checkpoints
1703
+ WHERE job_id = ? AND checkpoint_id = ?
1704
+ ORDER BY attempt DESC
1705
+ LIMIT 1`
1706
+ );
1707
+ const insertNotification = db.prepare(
1708
+ `INSERT INTO notifications (run_id, payload, created_at) VALUES (?, ?, ?)
1709
+ ON CONFLICT (run_id) DO NOTHING`
1710
+ );
1711
+ const selectJobStatus = db.prepare("SELECT status FROM jobs WHERE id = ?");
1712
+ const selectNotifications = db.prepare(
1713
+ "SELECT run_id, payload, created_at FROM notifications ORDER BY run_id ASC"
1714
+ );
1715
+ const deleteNotificationForRun = db.prepare("DELETE FROM notifications WHERE run_id = ?");
1716
+ function inTransaction(body) {
1717
+ db.exec("BEGIN IMMEDIATE");
1718
+ try {
1719
+ const result = body();
1720
+ db.exec("COMMIT");
1721
+ return result;
1722
+ } catch (error) {
1723
+ if (db.isTransaction) db.exec("ROLLBACK");
1724
+ throw error;
1725
+ }
1726
+ }
1727
+ function currentEpoch() {
1728
+ const row = selectFence.get();
1729
+ return row === void 0 ? 0 : toNumber(row.epoch);
1730
+ }
1731
+ function requireFence(fence) {
1732
+ const observed = currentEpoch();
1733
+ if (observed !== fence.epoch) {
1734
+ throw new StaleJobStoreEpochError2(fence.epoch, observed);
1735
+ }
1736
+ }
1737
+ function rowToJob(row) {
1738
+ if (row === void 0) {
1739
+ throw new UnknownJobRowError("<missing>");
1740
+ }
1741
+ return jobSchema.parse({
1742
+ id: String(row.id),
1743
+ kind: String(row.kind),
1744
+ status: String(row.status),
1745
+ summary: String(row.summary),
1746
+ attempts: toNumber(row.attempts),
1747
+ createdAt: String(row.created_at),
1748
+ updatedAt: String(row.updated_at)
1749
+ });
1750
+ }
1751
+ function requireRow(jobId) {
1752
+ const row = selectById.get(jobId);
1753
+ if (row === void 0) {
1754
+ throw new UnknownJobRowError(jobId);
1755
+ }
1756
+ return row;
1757
+ }
1758
+ function insertOrGet(request, now) {
1759
+ if (request.idempotencyKey.trim() === "") {
1760
+ throw new MissingIdempotencyKeyError(request.kind);
1761
+ }
1762
+ const changes = insertJob.run(
1763
+ String(request.id),
1764
+ request.kind,
1765
+ request.idempotencyKey,
1766
+ request.summary,
1767
+ JSON.stringify(request.payload ?? null),
1768
+ now,
1769
+ now
1770
+ ).changes;
1771
+ return {
1772
+ job: rowToJob(selectByKey.get(request.kind, request.idempotencyKey)),
1773
+ deduped: toNumber(changes) === 0
1774
+ };
1775
+ }
1776
+ function recordChainTerminal(chainTerminal, now) {
1777
+ let headExists = false;
1778
+ for (const candidate of chainTerminal.candidateJobIds) {
1779
+ const row = selectJobStatus.get(String(candidate));
1780
+ if (row === void 0) continue;
1781
+ headExists ||= String(candidate) === String(chainTerminal.candidateJobIds[0]);
1782
+ if (!TERMINAL_JOB_STATUSES.includes(String(row.status))) return;
1783
+ }
1784
+ if (!headExists) return;
1785
+ insertNotification.run(chainTerminal.dedupeKey, chainTerminal.payloadJson, now);
1786
+ }
1787
+ function dumpRow(row) {
1788
+ const conclusionKind = quarantineConclusionOf(row);
1789
+ return {
1790
+ id: String(row.id),
1791
+ kind: String(row.kind),
1792
+ idempotencyKey: String(row.idempotency_key),
1793
+ status: String(row.status),
1794
+ summary: String(row.summary),
1795
+ payload: JSON.parse(String(row.payload)),
1796
+ attempts: toNumber(row.attempts),
1797
+ claimed: row.claimed_at !== null,
1798
+ cancelRequested: row.cancel_requested_at !== null,
1799
+ // 422 D-16: OMITTED rather than `undefined` when there is nothing to say, so a reader that
1800
+ // serializes this row does not publish an empty conclusion key for every ordinary job.
1801
+ ...conclusionKind === void 0 ? {} : { conclusionKind },
1802
+ createdAt: String(row.created_at),
1803
+ updatedAt: String(row.updated_at)
1804
+ };
1805
+ }
1806
+ return {
1807
+ close() {
1808
+ db.close();
1809
+ },
1810
+ async adoptEpoch(epoch) {
1811
+ return inTransaction(() => {
1812
+ const observed = currentEpoch();
1813
+ if (epoch < observed) {
1814
+ throw new StaleJobStoreEpochError2(epoch, observed);
1815
+ }
1816
+ upsertFence.run(epoch);
1817
+ return { epoch };
1818
+ });
1819
+ },
1820
+ async enqueue(request) {
1821
+ const now = clock.now();
1822
+ return inTransaction(() => insertOrGet(request, now));
1823
+ },
1824
+ async claimNext(fence, options2) {
1825
+ const now = clock.now();
1826
+ const excludeKinds = options2?.excludeKinds ?? [];
1827
+ return inTransaction(() => {
1828
+ requireFence(fence);
1829
+ for (let scanned = 0; scanned <= QUARANTINE_SCAN_BOUND; scanned += 1) {
1830
+ const row = excludeKinds.length === 0 ? selectClaimable.get(now) : claimableStatementFor(excludeKinds.length).get(now, ...excludeKinds);
1831
+ if (row === void 0) {
1832
+ return { claimed: false };
1833
+ }
1834
+ markClaimed.run(now, now, now, String(row.id));
1835
+ const claimed = requireRow(String(row.id));
1836
+ if (isUnreadableKindRow(claimed)) {
1837
+ markQuarantined.run(QUARANTINED_UNREADABLE_KIND_REASON, now, String(row.id));
1838
+ continue;
1839
+ }
1840
+ return {
1841
+ claimed: true,
1842
+ job: rowToJob(claimed),
1843
+ payload: JSON.parse(String(claimed.payload))
1844
+ };
1845
+ }
1846
+ throw new Error(
1847
+ `agent job claim scanned ${QUARANTINE_SCAN_BOUND} rows without reaching a claimable or exhausted queue; the claimable set is not shrinking, so no row was claimed and the transaction is rolled back rather than reporting an empty queue`
1848
+ );
1849
+ });
1850
+ },
1851
+ async completeAndChain(jobId, next, fence, chainTerminal) {
1852
+ const now = clock.now();
1853
+ return inTransaction(() => {
1854
+ requireFence(fence);
1855
+ requireRow(String(jobId));
1856
+ markDone.run(now, String(jobId));
1857
+ const chained = next.map((request) => insertOrGet(request, now));
1858
+ if (chainTerminal !== void 0) recordChainTerminal(chainTerminal, now);
1859
+ return { job: rowToJob(requireRow(String(jobId))), chained };
1860
+ });
1861
+ },
1862
+ async failAndChain(jobId, disposition, next, fence, chainTerminal) {
1863
+ const now = clock.now();
1864
+ return inTransaction(() => {
1865
+ requireFence(fence);
1866
+ requireRow(String(jobId));
1867
+ markFailed.run(disposition.reason, now, String(jobId));
1868
+ const chained = next.map((request) => insertOrGet(request, now));
1869
+ if (chainTerminal !== void 0) recordChainTerminal(chainTerminal, now);
1870
+ return { job: rowToJob(requireRow(String(jobId))), chained };
1871
+ });
1872
+ },
1873
+ async recordFailure(jobId, disposition, fence) {
1874
+ const now = clock.now();
1875
+ inTransaction(() => {
1876
+ requireFence(fence);
1877
+ requireRow(String(jobId));
1878
+ if (disposition.kind === "retry") {
1879
+ const notBefore = disposition.retryAfterMs === void 0 ? null : new Date(Date.parse(now) + disposition.retryAfterMs).toISOString();
1880
+ markRetryable.run(disposition.reason, now, notBefore, String(jobId));
1881
+ } else {
1882
+ markFailed.run(disposition.reason, now, String(jobId));
1883
+ }
1884
+ });
1885
+ },
1886
+ async releaseInterrupted(jobId, fence) {
1887
+ const now = clock.now();
1888
+ inTransaction(() => {
1889
+ requireFence(fence);
1890
+ requireRow(String(jobId));
1891
+ markInterrupted.run(now, String(jobId));
1892
+ });
1893
+ },
1894
+ async requestCancel(jobId, fence) {
1895
+ const now = clock.now();
1896
+ return inTransaction(() => {
1897
+ requireFence(fence);
1898
+ const row = requireRow(String(jobId));
1899
+ const status = String(row.status);
1900
+ if (TERMINAL_JOB_STATUSES.includes(status)) {
1901
+ return { outcome: "already-terminal", status };
1902
+ }
1903
+ if (row.claimed_at !== null) {
1904
+ markCancelRequested.run(now, now, String(jobId));
1905
+ return { outcome: "cancel-requested" };
1906
+ }
1907
+ markCancelledNow.run(now, now, String(jobId));
1908
+ return { outcome: "cancelled" };
1909
+ });
1910
+ },
1911
+ async recordCancellation(jobId, fence) {
1912
+ const now = clock.now();
1913
+ inTransaction(() => {
1914
+ requireFence(fence);
1915
+ const row = requireRow(String(jobId));
1916
+ if (row.cancel_requested_at === null) {
1917
+ throw new JobCancellationNotRequestedError(jobId);
1918
+ }
1919
+ markCancelHonoured.run(now, String(jobId));
1920
+ });
1921
+ },
1922
+ // The 413-13 gap closure, and the checkpoint forget is its load-bearing half.
1923
+ //
1924
+ // Phase 413's pump composed with no post-run capabilities, so the kind registry selected
1925
+ // the STAND-IN analyze-run handler: nineteen rows recorded a `verdicts-authored`
1926
+ // checkpoint and reached `done` with nothing authored. Resetting the status alone would
1927
+ // not undo that, it would REPEAT it — `selectLatestCheckpoint` reads
1928
+ // `ORDER BY attempt DESC`, so the real handler's next attempt would resume on the
1929
+ // stand-in's recorded value, author nothing again, and report completion again. So the
1930
+ // rows go with the status, inside the same transaction, and the files follow the rows.
1931
+ async requeueTerminal(jobId, fence) {
1932
+ const now = clock.now();
1933
+ const decided = inTransaction(() => {
1934
+ requireFence(fence);
1935
+ const row = requireRow(String(jobId));
1936
+ const status = String(row.status);
1937
+ if (isUnreadableKindRow(row)) {
1938
+ throw new QuarantinedJobRequeueError2(String(jobId), String(row.kind));
1939
+ }
1940
+ if (!TERMINAL_JOB_STATUSES.includes(status)) {
1941
+ return { outcome: "not-terminal", status };
1942
+ }
1943
+ const discard = selectCheckpointIdsForJob.all(String(jobId)).map((checkpoint) => String(checkpoint.checkpoint_id));
1944
+ deleteCheckpointsForJob.run(String(jobId));
1945
+ const requeuedRun = runIdOfPayloadColumn(row.payload);
1946
+ if (requeuedRun !== void 0) deleteNotificationForRun.run(requeuedRun);
1947
+ markRequeued.run(now, String(jobId));
1948
+ return { outcome: "requeued", discard };
1949
+ });
1950
+ if (decided.outcome === "requeued") {
1951
+ for (const checkpointId of decided.discard ?? []) {
1952
+ checkpoints.discard(String(jobId), checkpointId);
1953
+ }
1954
+ return { outcome: "requeued" };
1955
+ }
1956
+ return decided;
1957
+ },
1958
+ async publishCheckpoint(jobId, checkpointId, payload, fence) {
1959
+ const now = clock.now();
1960
+ const payloadJson = canonicalJson4(payload);
1961
+ const payloadDigest = sha256hex2(payloadJson);
1962
+ inTransaction(() => {
1963
+ requireFence(fence);
1964
+ requireRow(String(jobId));
1965
+ touchJob.run(now, String(jobId));
1966
+ upsertCheckpoint.run(
1967
+ String(jobId),
1968
+ checkpointId,
1969
+ payload.attempt,
1970
+ payloadJson,
1971
+ payloadDigest,
1972
+ fence.epoch
1973
+ );
1974
+ });
1975
+ if (hooks?.beforeCheckpointFilePublish !== void 0) {
1976
+ await hooks.beforeCheckpointFilePublish();
1977
+ }
1978
+ checkpoints.publish(String(jobId), checkpointId, payload);
1979
+ },
1980
+ async readCheckpoint(jobId, checkpointId) {
1981
+ const row = selectLatestCheckpoint.get(String(jobId), checkpointId);
1982
+ if (row === void 0) return { found: false };
1983
+ const authoritative = {
1984
+ found: true,
1985
+ payload: JSON.parse(String(row.payload_json))
1986
+ };
1987
+ try {
1988
+ const projection = checkpoints.read(String(jobId), checkpointId);
1989
+ if (!projection.found) return authoritative;
1990
+ const projectionDigest = sha256hex2(canonicalJson4(projection.payload));
1991
+ return projectionDigest === String(row.payload_digest) ? projection : authoritative;
1992
+ } catch {
1993
+ return authoritative;
1994
+ }
1995
+ },
1996
+ async dumpState() {
1997
+ return {
1998
+ schemaVersion: AGENT_JOB_STORE_USER_VERSION,
1999
+ epoch: currentEpoch(),
2000
+ // The same ordered read the logical dump uses, so the typed projection and the
2001
+ // equality instrument cannot disagree about which rows exist or in what order.
2002
+ jobs: selectOrderedRows(db, "jobs").rows.map(dumpRow)
2003
+ };
2004
+ },
2005
+ async listNotifications() {
2006
+ return selectNotifications.all().map((row) => ({
2007
+ runId: String(row.run_id),
2008
+ payload: guardRunNotification(JSON.parse(String(row.payload))),
2009
+ createdAt: String(row.created_at)
2010
+ }));
2011
+ },
2012
+ async logicalDump() {
2013
+ return createLogicalDump({
2014
+ db,
2015
+ schemaVersion: AGENT_JOB_STORE_USER_VERSION,
2016
+ checkpointsRoot: checkpoints.checkpointsRoot
2017
+ });
2018
+ },
2019
+ async dumpLogicalState() {
2020
+ return canonicalLogicalDump(
2021
+ createLogicalDump({
2022
+ db,
2023
+ schemaVersion: AGENT_JOB_STORE_USER_VERSION,
2024
+ checkpointsRoot: checkpoints.checkpointsRoot
2025
+ })
2026
+ );
2027
+ }
2028
+ };
2029
+ }
2030
+
2031
+ // src/store/post-processing-reader.ts
2032
+ var CurationSnapshotReadError = class extends Error {
2033
+ name = "CurationSnapshotReadError";
2034
+ constructor() {
2035
+ super("The recorded curation checkpoint is invalid; its snapshot cannot be read.");
2036
+ }
2037
+ };
2038
+ function recordOf(value) {
2039
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
2040
+ throw new CurationSnapshotReadError();
2041
+ }
2042
+ return value;
2043
+ }
2044
+ function readCurationSnapshot(db, root, ids, runId) {
2045
+ const checkpointId = "bundle-committed";
2046
+ const row = db.prepare(`SELECT payload_json, payload_digest, attempt FROM job_checkpoints
2047
+ WHERE job_id = ? AND checkpoint_id = ? ORDER BY attempt DESC LIMIT 1`).get(String(ids.curateMemory), checkpointId);
2048
+ if (row === void 0) return void 0;
2049
+ try {
2050
+ const authoritative = JSON.parse(String(row.payload_json));
2051
+ if (sha256hex3(canonicalJson5(authoritative)) !== row.payload_digest) {
2052
+ throw new CurationSnapshotReadError();
2053
+ }
2054
+ let payload = authoritative;
2055
+ try {
2056
+ const cached = readJobCheckpointEnvelope({ root }, String(ids.curateMemory), checkpointId);
2057
+ if (cached.found && sha256hex3(canonicalJson5(cached.envelope.payload)) === row.payload_digest) {
2058
+ payload = cached.envelope.payload;
2059
+ }
2060
+ } catch {
2061
+ }
2062
+ const checkpoint = recordOf(payload);
2063
+ if (checkpoint.jobId !== ids.curateMemory || checkpoint.checkpointId !== checkpointId || checkpoint.attempt !== Number(row.attempt) || !Number.isInteger(checkpoint.attempt) || Number(checkpoint.attempt) < 1 || typeof checkpoint.recordedAt !== "string" || checkpoint.recordedAt.length === 0) throw new CurationSnapshotReadError();
2064
+ const value = recordOf(checkpoint.value);
2065
+ if (value.curationSnapshot === void 0) return void 0;
2066
+ const job = db.prepare("SELECT kind, idempotency_key, payload, attempts FROM jobs WHERE id = ?").get(String(ids.curateMemory));
2067
+ if (job === void 0 || job.kind !== "curate-memory" || job.idempotency_key !== `${ids.analyzeRun}->curate-memory` || Number(job.attempts) < Number(checkpoint.attempt) || recordOf(JSON.parse(String(job.payload))).runId !== runId || value.runId !== runId) throw new CurationSnapshotReadError();
2068
+ return CurationSnapshotSchema.parse(value.curationSnapshot);
2069
+ } catch {
2070
+ throw new CurationSnapshotReadError();
2071
+ }
2072
+ }
2073
+ function toNumber2(value) {
2074
+ return typeof value === "bigint" ? Number(value) : Number(value);
2075
+ }
2076
+ function toRow(row) {
2077
+ if (row === void 0) return void 0;
2078
+ const conclusionKind = !isJobKind2(String(row.kind)) && row.status === "failed" && row.last_failure_kind === "permanent" && row.last_failure_reason === QUARANTINED_UNREADABLE_KIND_REASON ? "unreadable_kind" : void 0;
2079
+ return {
2080
+ status: String(row.status),
2081
+ claimed: row.claimed_at !== null,
2082
+ attempts: toNumber2(row.attempts),
2083
+ createdAt: String(row.created_at),
2084
+ updatedAt: String(row.updated_at),
2085
+ ...row.claimed_at === null ? {} : { claimedAt: String(row.claimed_at) },
2086
+ ...row.started_at === null ? {} : { startedAt: String(row.started_at) },
2087
+ ...conclusionKind === void 0 ? {} : { conclusionKind },
2088
+ ...row.last_failure_kind === null ? {} : { lastFailureKind: String(row.last_failure_kind) },
2089
+ ...row.last_failure_reason === null ? {} : { lastFailureReason: String(row.last_failure_reason) }
2090
+ };
2091
+ }
2092
+ function createPostProcessingReader(options) {
2093
+ const path = join6(options.agentQaRoot, AGENT_JOB_STORE_DB_FILE);
2094
+ return {
2095
+ idSource: options.idSource,
2096
+ async readChain(ids, runId) {
2097
+ if (!existsSync3(path)) return void 0;
2098
+ const db = new DatabaseSync3(path, {
2099
+ readOnly: true,
2100
+ timeout: AGENT_JOB_STORE_BUSY_TIMEOUT_MS
2101
+ });
2102
+ try {
2103
+ const stamped = toNumber2(db.prepare("PRAGMA user_version").get()?.user_version);
2104
+ if (stamped !== 0 && stamped !== AGENT_JOB_STORE_USER_VERSION) {
2105
+ throw new JobStoreSchemaVersionError(path, AGENT_JOB_STORE_USER_VERSION, stamped);
2106
+ }
2107
+ const selectJob = db.prepare(`
2108
+ SELECT kind, status, attempts, created_at, claimed_at, started_at, updated_at,
2109
+ last_failure_kind, last_failure_reason
2110
+ FROM jobs WHERE id = ?
2111
+ `);
2112
+ const notificationRow = db.prepare("SELECT payload FROM notifications WHERE run_id = ?").get(runId);
2113
+ const analyzeRun = toRow(
2114
+ selectJob.get(String(ids.analyzeRun))
2115
+ );
2116
+ const curateMemory = toRow(
2117
+ selectJob.get(String(ids.curateMemory))
2118
+ );
2119
+ const proposeImprovements = toRow(
2120
+ selectJob.get(String(ids.proposeImprovements))
2121
+ );
2122
+ const notification = notificationRow === void 0 ? void 0 : guardRunNotification2(JSON.parse(String(notificationRow.payload)));
2123
+ const curationSnapshot = readCurationSnapshot(db, options.agentQaRoot, ids, runId);
2124
+ return {
2125
+ ...analyzeRun === void 0 ? {} : { analyzeRun },
2126
+ ...curateMemory === void 0 ? {} : { curateMemory },
2127
+ ...proposeImprovements === void 0 ? {} : { proposeImprovements },
2128
+ ...notification === void 0 ? {} : { notification },
2129
+ ...curationSnapshot === void 0 ? {} : { curationSnapshot }
2130
+ };
2131
+ } finally {
2132
+ db.close();
2133
+ }
2134
+ }
2135
+ };
2136
+ }
2137
+
2138
+ // src/composition/compose-local-agent.ts
2139
+ var resolveHostRetrievalRung = resolveRetrievalRung;
2140
+ var LOCAL_KNOWLEDGE_PROBES = Object.freeze({
2141
+ classifyIndexFace,
2142
+ foldRetrievalRung,
2143
+ indexDbPath,
2144
+ readEmbedderWorkerRegistration,
2145
+ readWriterLease,
2146
+ resolveEmbedderCapability
2147
+ });
2148
+ function resolveEmbedderWorkerEntryPath() {
2149
+ const requireBase = typeof __filename === "string" ? __filename : import.meta.url;
2150
+ const knowledgeEntry = createRequire(requireBase).resolve("@vostride/agent-qa-knowledge-local");
2151
+ return join7(dirname6(knowledgeEntry), "embedder-worker", "worker-entry.js");
2152
+ }
2153
+ var LOCAL_DAEMON_CAPABILITIES = Object.freeze({
2154
+ acquireWriterLease,
2155
+ heartbeatLease,
2156
+ releaseLease,
2157
+ readWriterLease,
2158
+ deriveWriterLeaseFreshness,
2159
+ isLeaseStale,
2160
+ countPendingCurationJobs,
2161
+ countRemainingCurationWork,
2162
+ inspectDrainableCurationWork,
2163
+ listQueuedCandidates,
2164
+ acquireEmbedder,
2165
+ drainQueue,
2166
+ runMaintenanceTail,
2167
+ resolveEmbedderWorkerEntryPath,
2168
+ readEmbedderWorkerRegistration,
2169
+ embedderWorkerProcessIsAlive,
2170
+ embedderWorkerSocketPath,
2171
+ probeEmbedderWorkerSocket,
2172
+ verifyEmbedderWorkerIncarnation,
2173
+ resolveProcessIncarnation,
2174
+ removeEmbedderWorkerRegistration,
2175
+ resetEmbedderCapability: () => resetEmbedderCapability(DEFAULT_EMBEDDING_PROFILE),
2176
+ pinLocalHeadlessExecutionContext: () => {
2177
+ setActiveExecutionContextResolver(() => "local-headless");
2178
+ }
2179
+ });
2180
+ function receiptWriter(input) {
2181
+ return createLocalKnowledgeStore({
2182
+ knowledgeRoot: input.knowledgeRoot,
2183
+ executionContext: input.executionContext,
2184
+ writerLeaseId: input.lease.leaseId,
2185
+ epoch: input.lease.epoch,
2186
+ ...input.resolveTrustPriors === void 0 ? {} : { resolveTrustPriors: input.resolveTrustPriors },
2187
+ ...input.resolvePromotionPosture === void 0 ? {} : { resolvePromotionPosture: input.resolvePromotionPosture },
2188
+ ...input.sourceBuildOrdinal === void 0 ? {} : { sourceBuildOrdinal: input.sourceBuildOrdinal },
2189
+ ...input.sourceRunId === void 0 ? {} : { sourceRunId: input.sourceRunId }
2190
+ });
2191
+ }
2192
+ function recordGateReceipt(input) {
2193
+ return receiptWriter(input).recordGateReceipt(input.receipt);
2194
+ }
2195
+ function promoteWithReceipts(input) {
2196
+ return receiptWriter(input).promote(input.candidateId, input.receipts);
2197
+ }
2198
+ function readFeedbackRecord(input) {
2199
+ const matches = enumerateBundleDirs(input.knowledgeRoot).flatMap((entry) => {
2200
+ const result = loadBundle(entry.dir);
2201
+ return result.kind === "loaded" && result.bundle.manifest.id === input.recordId && verifyBundleIntegrity(entry.dir).ok ? [result.bundle] : [];
2202
+ });
2203
+ if (matches.length !== 1) return void 0;
2204
+ const bundle = matches[0];
2205
+ const artifact = input.evidenceRef ?? bundle.artifactsIndex.artifacts[0];
2206
+ if (artifact === void 0) return void 0;
2207
+ return {
2208
+ recordId: input.recordId,
2209
+ kind: bundle.frontmatter.kind,
2210
+ ...bundle.frontmatter.subtype === void 0 ? {} : { subtype: bundle.frontmatter.subtype },
2211
+ anchors: bundle.frontmatter.anchors.map((anchor) => anchor.raw),
2212
+ scope: bundle.frontmatter.scope,
2213
+ summary: bundle.summary,
2214
+ distilledEvidenceRef: { id: artifact.id, sha: artifact.sha },
2215
+ // This is the only admitted draft construction stage; user origin belongs on the delta.
2216
+ originStage: "run-curation"
2217
+ };
2218
+ }
2219
+ var LOCAL_CURATION_CAPABILITIES = Object.freeze({
2220
+ readFeedbackRecord,
2221
+ appendMarkEvidence,
2222
+ planMarkEvidence,
2223
+ projectMarkEvidenceOutcome,
2224
+ triageMark,
2225
+ servedIdsOn,
2226
+ procedureReadOn,
2227
+ applyFeedback,
2228
+ recordGateReceipt,
2229
+ promoteWithReceipts,
2230
+ openKnowledgeReader: openKnowledgeReader2,
2231
+ drainQueue,
2232
+ foldRunOutcomes,
2233
+ reconcileRunOutcomesProjection,
2234
+ enqueueCurationCandidateProjection,
2235
+ readCanonicalBundleAnchors,
2236
+ readCanonicalBundleStateDigests,
2237
+ readCanaryPostureFacts: canaryPostureFactsFor,
2238
+ readStandingGateReceipts: standingReceiptDigestsFor,
2239
+ readLowerTrustShares: lowerTrustSharesFor,
2240
+ runOutcomesProjectionVersion: RUN_OUTCOMES_PROJECTION_VERSION,
2241
+ canaryOutcomeObservationId,
2242
+ maintenance: Object.freeze({
2243
+ acquireWriterLease,
2244
+ refreshWriterLeaseAuthority,
2245
+ releaseLease,
2246
+ rebuildIndex,
2247
+ purgeRecord,
2248
+ resolvePurgeAffectedSet,
2249
+ purgeAffectedSetFingerprint,
2250
+ rollbackCampaign,
2251
+ rollbackCurationJob,
2252
+ resolveRollbackAffectedSet,
2253
+ rollbackAffectedSetFingerprint
2254
+ })
2255
+ });
2256
+ function isRecord(value) {
2257
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2258
+ }
2259
+ function isTargetName(value) {
2260
+ return typeof value === "string" && value.trim().length > 0;
2261
+ }
2262
+ function recordedTargetsOf(artifact) {
2263
+ const source = isRecord(artifact) ? artifact.source : void 0;
2264
+ if (!isRecord(source)) return { status: "unknown" };
2265
+ if (source.kind === "test") {
2266
+ const target = isRecord(source.resolvedDefinition) ? source.resolvedDefinition.target : void 0;
2267
+ return isTargetName(target) ? { status: "recorded", names: [target] } : { status: "unknown" };
2268
+ }
2269
+ if (source.kind !== "suite" || !Array.isArray(source.members)) return { status: "unknown" };
2270
+ const names = /* @__PURE__ */ new Set();
2271
+ for (const member of source.members) {
2272
+ if (!isRecord(member)) return { status: "unknown" };
2273
+ if (member.target === void 0 || member.target === null) continue;
2274
+ if (!isTargetName(member.target)) return { status: "unknown" };
2275
+ names.add(member.target);
2276
+ }
2277
+ return names.size === 0 ? { status: "unknown" } : { status: "recorded", names: [...names].sort() };
2278
+ }
2279
+ function createRunsReader(agentQaRoot) {
2280
+ function enumerate() {
2281
+ const runsDir = join7(agentQaRoot, RUNS_SUBDIR3);
2282
+ if (!existsSync4(runsDir)) return [];
2283
+ const documents = [];
2284
+ for (const entry of readdirSync3(runsDir, { withFileTypes: true })) {
2285
+ if (!entry.isDirectory()) continue;
2286
+ const handoffPath = join7(runsDir, entry.name, HANDOFF_FILE);
2287
+ if (!existsSync4(handoffPath)) continue;
2288
+ try {
2289
+ documents.push(loadCompletedRunHandoff(handoffPath));
2290
+ } catch {
2291
+ continue;
2292
+ }
2293
+ }
2294
+ return documents;
2295
+ }
2296
+ return {
2297
+ list() {
2298
+ return Promise.resolve(enumerate());
2299
+ },
2300
+ /**
2301
+ * Resolution runs over the ENUMERATED run documents and matches on the recorded `runId`,
2302
+ * never by joining the caller's string onto a path (T-384-08): a `runId` of `../../etc` can
2303
+ * therefore address nothing outside the runs directory, because it is never used to build a
2304
+ * path at all.
2305
+ */
2306
+ get(runId) {
2307
+ return Promise.resolve(enumerate().find((document) => document.runId === runId));
2308
+ },
2309
+ /**
2310
+ * Deep replay of one run's `events.jsonl` through the SAME concrete store the v49 bus appends
2311
+ * with, so a replayed stream is byte-for-byte what the run emitted.
2312
+ *
2313
+ * Two cases are deliberately distinct. An absent or empty log is an ordinary empty replay —
2314
+ * nothing was recorded, so nothing was lost. A log whose earliest surviving seq is ABOVE the
2315
+ * requested resume point means events the caller asked for existed and are gone; that is
2316
+ * reported with the bus's own `ReplayIncompleteError` carrying the floor, never papered over
2317
+ * by returning the surviving suffix (D-07/D-08).
2318
+ */
2319
+ readEvents(runId, fromSeq) {
2320
+ const recorded = [...new FsEventLog2({ root: agentQaRoot, runId }).read(0)].sort((left, right) => left.seq - right.seq);
2321
+ if (recorded.length === 0) return Promise.resolve([]);
2322
+ const earliestAvailableSeq = recorded[0].seq;
2323
+ if (fromSeq < earliestAvailableSeq) {
2324
+ return Promise.reject(new ReplayIncompleteError(fromSeq, earliestAvailableSeq));
2325
+ }
2326
+ return Promise.resolve(recorded.filter((event) => event.seq >= fromSeq));
2327
+ },
2328
+ /**
2329
+ * One step's persisted evidence, read back out of the run's own handoff document
2330
+ * (387 KREV-02 / D-15).
2331
+ *
2332
+ * Through `loadCompletedRunHandoff` — the version guard — and NOT through a raw JSON read,
2333
+ * which is the load-bearing half. 387-02 gave the guard a present-implies-valid clause for
2334
+ * the retrieval trace, so a document whose trace is present but malformed is refused here
2335
+ * rather than reaching the why-verb looking well-formed.
2336
+ *
2337
+ * The ambiguity check is the never-guess law at this seam. `stepIndex` numbers steps per
2338
+ * MEMBER TEST, so a run with two tests has two step 0s; when the address does not name a
2339
+ * test and more than one candidate answers to it, this reports the candidates rather than
2340
+ * picking whichever test the document happened to list first. Picking would have produced a
2341
+ * confident, wrong answer to a question that was never asked — and a why-verb that does
2342
+ * that is worse than one that refuses, because its answer looks exactly like a right one.
2343
+ */
2344
+ readStepEvidence(runId, address) {
2345
+ const document = enumerate().find((candidate) => candidate.runId === runId);
2346
+ if (document === void 0) return Promise.resolve({ kind: "run-not-recorded" });
2347
+ const candidates = document.tests.filter((test) => (address.testId === void 0 || test.testId === address.testId) && test.steps.some((step) => step.stepIndex === address.stepIndex));
2348
+ if (candidates.length === 0) return Promise.resolve({ kind: "step-not-found" });
2349
+ if (candidates.length > 1) {
2350
+ return Promise.resolve({
2351
+ kind: "ambiguous-step-address",
2352
+ // Sorted, so the refusal a surface prints is the same list twice running.
2353
+ testIds: candidates.map((test) => test.testId).sort()
2354
+ });
2355
+ }
2356
+ const evidence = candidates[0].steps.find((step) => step.stepIndex === address.stepIndex);
2357
+ return Promise.resolve(
2358
+ evidence === void 0 ? { kind: "step-not-found" } : { kind: "found", evidence }
2359
+ );
2360
+ },
2361
+ /**
2362
+ * Resolved over the ENUMERATED run directories (T-384-08, T-431-01): the caller's id is only
2363
+ * ever compared, never joined onto a path, and the directory must hold a completed handoff
2364
+ * recording that same id, so one run's snapshot can never answer for another. A snapshot that
2365
+ * is absent, unreadable or gone since `list()` answered is `unknown`, never an error.
2366
+ */
2367
+ getRecordedTargets(runId) {
2368
+ const runsDir = join7(agentQaRoot, RUNS_SUBDIR3);
2369
+ if (!existsSync4(runsDir)) return Promise.resolve({ status: "unknown" });
2370
+ const entry = readdirSync3(runsDir, { withFileTypes: true }).find((candidate) => candidate.isDirectory() && candidate.name === runId);
2371
+ if (entry === void 0) return Promise.resolve({ status: "unknown" });
2372
+ try {
2373
+ const runDir = join7(runsDir, entry.name);
2374
+ if (loadCompletedRunHandoff(join7(runDir, HANDOFF_FILE)).runId !== runId) {
2375
+ return Promise.resolve({ status: "unknown" });
2376
+ }
2377
+ return Promise.resolve(
2378
+ recordedTargetsOf(JSON.parse(readFileSync4(join7(runDir, ARTIFACT_FILE), "utf8")))
2379
+ );
2380
+ } catch {
2381
+ return Promise.resolve({ status: "unknown" });
2382
+ }
2383
+ }
2384
+ };
2385
+ }
2386
+ function createKnowledgeReader(resolveKnowledgeRoot, sightMemoryEntities, resolvePromotionPosture, resolveTrustPriors) {
2387
+ return createKnowledgeReaderPort({
2388
+ resolveKnowledgeRoot,
2389
+ ...resolveTrustPriors === void 0 ? {} : { resolveTrustPriors },
2390
+ ...resolvePromotionPosture === void 0 ? {} : { resolvePromotionPosture },
2391
+ ...sightMemoryEntities === void 0 ? {} : { sightMemoryEntities }
2392
+ });
2393
+ }
2394
+ function knowledgeChangeOutput(verb, operationId, result) {
2395
+ switch (result.outcome) {
2396
+ case "applied":
2397
+ return {
2398
+ outcome: "applied",
2399
+ verb,
2400
+ operationId,
2401
+ recordIds: [...result.recordIds],
2402
+ ...result.forgetReceipt === void 0 ? {} : { forgetReceipt: result.forgetReceipt },
2403
+ ...result.commitReceipt === void 0 ? {} : { commitReceipt: result.commitReceipt }
2404
+ };
2405
+ case "target-not-found":
2406
+ return { outcome: "target-not-found", verb, operationId, target: result.target };
2407
+ case "policy-materialize-refused":
2408
+ return { outcome: "refused", verb, operationId, reason: "policy-materialize-refused" };
2409
+ default:
2410
+ return result;
2411
+ }
2412
+ }
2413
+ function createKnowledgeReview(input) {
2414
+ async function roots() {
2415
+ return { knowledgeRoot: await input.resolveKnowledgeRoot() };
2416
+ }
2417
+ let changeChain = Promise.resolve();
2418
+ function serialized(work) {
2419
+ const next = changeChain.then(work, work);
2420
+ changeChain = next.then(() => void 0, () => void 0);
2421
+ return next;
2422
+ }
2423
+ return {
2424
+ // 432-12 (D-04/D-05/D-09): one explicit change, applied in the call through the direct writer
2425
+ // (432-06). Nothing is filed: the change is committed under the lease, handed to a live
2426
+ // holder, or refused by type. A missing identity is minted here, once per call, and returned on
2427
+ // the receipt so the caller can retry the same change.
2428
+ change: (request) => serialized(async () => {
2429
+ const { knowledgeRoot } = await roots();
2430
+ const operationId = request.operationId ?? randomUUID3();
2431
+ const authority = input.resolveWriterAuthority?.();
2432
+ const result = await applyKnowledgeChange({
2433
+ agentQaRoot: input.agentQaRoot,
2434
+ knowledgeRoot,
2435
+ request: { verb: request.verb, input: request.input },
2436
+ operationId,
2437
+ // The op that made the change, stamped on Gate-3 receipts: `knowledge.accept`,
2438
+ // `knowledge.reject` or `knowledge.forget`, which is the verb's own op id.
2439
+ opId: `knowledge.${request.verb}`,
2440
+ executionContext: (input.resolveExecutionContext ?? getActiveExecutionContext)(),
2441
+ ...input.resolveTrustPriors === void 0 ? {} : { resolveTrustPriors: input.resolveTrustPriors },
2442
+ ...input.resolvePromotionPosture === void 0 ? {} : { resolvePromotionPosture: input.resolvePromotionPosture },
2443
+ ...input.knowledgeChangeDelegate === void 0 ? {} : { delegate: input.knowledgeChangeDelegate },
2444
+ ...authority === void 0 ? {} : { writerAuthority: authority }
2445
+ });
2446
+ return knowledgeChangeOutput(request.verb, operationId, result);
2447
+ })
2448
+ // 2026-10-01 (432-17, D-05/D-22): `propose` and `apply` are deleted with the proposal inbox.
2449
+ };
2450
+ }
2451
+ async function applyKnowledgeChange(input) {
2452
+ const posture = await input.resolvePromotionPosture?.();
2453
+ const ledger = openAddressingLedgerForReview({
2454
+ path: join7(input.agentQaRoot, ADDRESSING_LEDGER_DB_FILE)
2455
+ });
2456
+ try {
2457
+ return await applyExplicitReview({
2458
+ agentQaRoot: input.agentQaRoot,
2459
+ knowledgeRoot: input.knowledgeRoot,
2460
+ request: input.request,
2461
+ operationId: input.operationId,
2462
+ opId: input.opId,
2463
+ executionContext: input.executionContext,
2464
+ resolveBundleShortId: (shortId) => ledger.lookupByShortId(shortId)?.machineId,
2465
+ ...input.resolveTrustPriors === void 0 ? {} : { resolveTrustPriors: input.resolveTrustPriors },
2466
+ ...posture === void 0 ? {} : { resolvePromotionPosture: posture },
2467
+ ...input.delegate === void 0 ? {} : { delegate: input.delegate },
2468
+ ...input.writerAuthority === void 0 ? {} : { writerAuthority: input.writerAuthority }
2469
+ });
2470
+ } finally {
2471
+ ledger.close();
2472
+ }
2473
+ }
2474
+ function createEntityDirectory(input) {
2475
+ return {
2476
+ resolve(ref) {
2477
+ return withEntityResolutionDeps(input, (deps) => resolveEntityRef(ref, deps));
2478
+ },
2479
+ get(ref) {
2480
+ return withEntityResolutionDeps(input, (deps) => getEntityDescriptor(ref, deps));
2481
+ }
2482
+ };
2483
+ }
2484
+ function createMemorySightingHook(input) {
2485
+ return (bundleIds) => withEntityResolutionDeps(input, (deps) => {
2486
+ for (const bundleId of bundleIds) sightEntity(bundleId, deps);
2487
+ });
2488
+ }
2489
+ async function withEntityResolutionDeps(input, body) {
2490
+ const knowledgeRoot = await input.resolveKnowledgeRoot();
2491
+ const ledger = openAddressingLedger({
2492
+ path: join7(input.agentQaRoot, ADDRESSING_LEDGER_DB_FILE)
2493
+ });
2494
+ const entityIndex = openEntityIndex({ path: entityIndexDbPath(knowledgeRoot), knowledgeRoot });
2495
+ try {
2496
+ return await body({
2497
+ ledger,
2498
+ entityIndex,
2499
+ // One kind is wired by the tracer. 387-03 registers the remaining sources here and
2500
+ // neither the resolver nor the ops change — which is the property the tracer exists
2501
+ // to establish before anything expands.
2502
+ sources: [createMemorySightingSource(() => knowledgeRoot)]
2503
+ });
2504
+ } finally {
2505
+ entityIndex.close();
2506
+ ledger.close();
2507
+ }
2508
+ }
2509
+ async function sweepEntityIndexProjection(input) {
2510
+ const knowledgeRoot = await input.resolveKnowledgeRoot();
2511
+ const ledger = openAddressingLedger({
2512
+ path: join7(input.agentQaRoot, ADDRESSING_LEDGER_DB_FILE)
2513
+ });
2514
+ const entityIndex = openEntityIndex({ path: entityIndexDbPath(knowledgeRoot), knowledgeRoot });
2515
+ try {
2516
+ const report = sweepEntityIndex(entityIndex, describeAllEntities({
2517
+ ledger,
2518
+ sources: [createMemorySightingSource(() => knowledgeRoot)]
2519
+ }));
2520
+ let embedded = 0;
2521
+ const acquire = input.acquireEmbedderCapability ?? acquireEmbedder;
2522
+ const capability = await acquire({
2523
+ preferredProfile: DEFAULT_EMBEDDING_PROFILE,
2524
+ allowDownload: false,
2525
+ agentQaRoot: input.agentQaRoot
2526
+ });
2527
+ if (capability.kind === "ready") {
2528
+ embedded = await embedPendingEntities(
2529
+ entityIndex,
2530
+ capability.embedder,
2531
+ capability.profile
2532
+ );
2533
+ }
2534
+ return { ...report, embedded };
2535
+ } finally {
2536
+ entityIndex.close();
2537
+ ledger.close();
2538
+ }
2539
+ }
2540
+ var RunProducedNoEventsError = class extends Error {
2541
+ constructor(runId) {
2542
+ super(
2543
+ `The host reported run ${runId}, but no run events were recorded for it. A run that started at all writes runStarted before anything else, so an empty event log means no run took place under that id.`
2544
+ );
2545
+ this.runId = runId;
2546
+ this.name = "RunProducedNoEventsError";
2547
+ }
2548
+ };
2549
+ function createLocalRunInvocation(input) {
2550
+ return {
2551
+ async start(request) {
2552
+ const started = await input.execution.execute(request);
2553
+ const { eventLog } = buildRunPorts({
2554
+ configDir: dirname6(input.agentQaRoot),
2555
+ runId: String(started.runId)
2556
+ });
2557
+ if (eventLog.read(0).length === 0) {
2558
+ throw new RunProducedNoEventsError(String(started.runId));
2559
+ }
2560
+ return started;
2561
+ }
2562
+ };
2563
+ }
2564
+ var EMPTY_FINGERPRINT_SNAPSHOT = {
2565
+ union: /* @__PURE__ */ new Set(),
2566
+ covered: [],
2567
+ elements: /* @__PURE__ */ new Map()
2568
+ };
2569
+ function draftIdentityKey(identity) {
2570
+ return `${identity.filePath}\0${identity.testName}`;
2571
+ }
2572
+ function readSuiteFingerprints(knowledgeRoot, product, draft) {
2573
+ const path = indexDbPath(knowledgeRoot);
2574
+ if (!existsSync4(path)) return EMPTY_FINGERPRINT_SNAPSHOT;
2575
+ const db = new DatabaseSync4(path, { readOnly: true });
2576
+ try {
2577
+ const index = createSuiteFingerprintIndex(db, { product });
2578
+ const draftKey = draftIdentityKey(draft);
2579
+ const covered = index.coveredTests().filter((identity) => draftIdentityKey(identity) !== draftKey);
2580
+ const elements = /* @__PURE__ */ new Map();
2581
+ const union = /* @__PURE__ */ new Set();
2582
+ for (const identity of covered) {
2583
+ const own = index.elementsForTest(identity.filePath, identity.testName);
2584
+ elements.set(draftIdentityKey(identity), own);
2585
+ for (const element of own) union.add(element);
2586
+ }
2587
+ return { union, covered, elements };
2588
+ } catch (error) {
2589
+ if (error instanceof Error && error.message.includes("no such table")) {
2590
+ return EMPTY_FINGERPRINT_SNAPSHOT;
2591
+ }
2592
+ throw error;
2593
+ } finally {
2594
+ db.close();
2595
+ }
2596
+ }
2597
+ function createDraftEvaluation(input) {
2598
+ return {
2599
+ resolveDraft(draftPath) {
2600
+ return input.authoring.parseDraft(draftPath);
2601
+ },
2602
+ async openEvaluation(draft) {
2603
+ const knowledgeRoot = await input.knowledgeRoot();
2604
+ const draftIdentity = {
2605
+ filePath: draft.filePath,
2606
+ testName: draft.testName
2607
+ };
2608
+ const draftKey = draftIdentityKey(draftIdentity);
2609
+ const textCorpus = (await input.authoring.suiteTextCorpus()).filter(
2610
+ (entry) => draftIdentityKey(entry) !== draftKey
2611
+ );
2612
+ let observedProduct;
2613
+ let snapshot;
2614
+ const fingerprints = () => {
2615
+ if (snapshot !== void 0) return snapshot;
2616
+ snapshot = observedProduct === void 0 ? EMPTY_FINGERPRINT_SNAPSHOT : readSuiteFingerprints(knowledgeRoot, observedProduct, draftIdentity);
2617
+ return snapshot;
2618
+ };
2619
+ return {
2620
+ runs: {
2621
+ async start(request) {
2622
+ const acknowledgement = await input.startGateRun(request);
2623
+ observedProduct ??= slugBranch4(acknowledgement.scope.key);
2624
+ return acknowledgement;
2625
+ },
2626
+ get: (runId) => input.readRun(runId)
2627
+ },
2628
+ fingerprints: {
2629
+ elementUnion: () => fingerprints().union,
2630
+ coveredTests: () => fingerprints().covered,
2631
+ elementsForTest: (filePath, testName) => fingerprints().elements.get(draftIdentityKey({ filePath, testName })) ?? []
2632
+ },
2633
+ textCorpus,
2634
+ // D-19: the gitignored per-draft receipt store, bound over `agentQaRoot` beside the
2635
+ // proposals inbox and for its reason — a receipt is machine-local working state under the
2636
+ // runtime root, not a committed artifact a pull request has any business showing.
2637
+ receipts: createDraftReceiptsStore({ resolveAgentQaRoot: () => input.agentQaRoot }),
2638
+ // D-14's live arm (394-17, step c). `product` is the SAME `observedProduct` gate 3's index
2639
+ // is opened against — read off the first gate-run acknowledgment, never derived from
2640
+ // configuration — so the stability read and the suite index can never be asking about two
2641
+ // different applications. Before any run is acknowledged it is `undefined` and the
2642
+ // consultation is skipped; D-04 forbids short-circuiting, so a flip cannot be observed
2643
+ // until at least one run has been.
2644
+ suiteStability: {
2645
+ port: input.authoringFunnel,
2646
+ product: () => observedProduct
2647
+ }
2648
+ };
2649
+ }
2650
+ };
2651
+ }
2652
+ var AUTHORING_GATE_RUN_CALLER = Object.freeze({
2653
+ surface: "authoring",
2654
+ origin: "agent"
2655
+ });
2656
+ var AUTHORING_LOOP_VERIFY_CALLER = Object.freeze({
2657
+ surface: "authoring",
2658
+ origin: "agent"
2659
+ });
2660
+ var SYSTEM_CLOCK = Object.freeze({
2661
+ now: () => (/* @__PURE__ */ new Date()).toISOString()
2662
+ });
2663
+ var digestJobIdSource = {
2664
+ jobIdFor: (kind, idempotencyKey) => asJobId2(`job_${sha256hex16(`${kind} ${idempotencyKey}`)}`)
2665
+ };
2666
+ var DIGEST_JOB_ID_SOURCE = Object.freeze(digestJobIdSource);
2667
+ var LocalAgentClosedError = class extends Error {
2668
+ constructor(agentQaRoot) {
2669
+ super(
2670
+ `The local agent composition for ${agentQaRoot} is closed. A closed composition cannot reopen its job store; create a new composition with fresh lifecycle ownership instead.`
2671
+ );
2672
+ this.agentQaRoot = agentQaRoot;
2673
+ }
2674
+ name = "LocalAgentClosedError";
2675
+ };
2676
+ var UNCLAIMABLE_BY_PROSE_POSTURE = Object.freeze({
2677
+ // Frozen to the same depth it is read at (WR-11). The shallow freeze every sibling table
2678
+ // here uses is not enough for this one: the VALUES are the arrays the queue claims
2679
+ // against, so an unfrozen inner array is a writable disclosure guard reachable from any
2680
+ // module that can import the table.
2681
+ "masker-active": Object.freeze([]),
2682
+ "no-masker": Object.freeze(["curate-memory"])
2683
+ });
2684
+ function readProjectedTestHistory(knowledgeRoot, query) {
2685
+ const path = indexDbPath(knowledgeRoot);
2686
+ if (!existsSync4(path)) return void 0;
2687
+ const db = new DatabaseSync4(path, { readOnly: true });
2688
+ try {
2689
+ return readTestOutcomeHistory(db, {
2690
+ product: query.product,
2691
+ filePath: query.filePath,
2692
+ testName: query.testName
2693
+ });
2694
+ } catch (error) {
2695
+ if (error instanceof Error && error.message.includes("no such table")) return void 0;
2696
+ throw error;
2697
+ } finally {
2698
+ db.close();
2699
+ }
2700
+ }
2701
+ function readProjectedRecurrence(knowledgeRoot, query) {
2702
+ const path = indexDbPath(knowledgeRoot);
2703
+ if (!existsSync4(path)) return void 0;
2704
+ const db = new DatabaseSync4(path, { readOnly: true });
2705
+ try {
2706
+ const history = readTestOutcomeHistory(db, {
2707
+ product: query.product,
2708
+ filePath: query.filePath,
2709
+ testName: query.testName
2710
+ });
2711
+ if (history === void 0) return void 0;
2712
+ const currentBuildOrdinal = history.lastBuildOrdinal ?? history.lastOutcomes.reduce((max, entry) => Math.max(max, entry.buildOrdinal), 0);
2713
+ if (query.fingerprint === void 0) return failureRecurrence(history, currentBuildOrdinal);
2714
+ const windowed = fingerprintRecurrence(
2715
+ db,
2716
+ { product: query.product, fingerprint: query.fingerprint },
2717
+ currentBuildOrdinal
2718
+ );
2719
+ return { kth: windowed.occurrenceOrdinals, windowBuilds: windowed.windowOrdinals };
2720
+ } catch (error) {
2721
+ if (error instanceof Error && error.message.includes("no such table")) return void 0;
2722
+ throw error;
2723
+ } finally {
2724
+ db.close();
2725
+ }
2726
+ }
2727
+ var LADDER_STATE_FOR_ENTRY = {
2728
+ quarantined: "quarantined",
2729
+ expired: "expired",
2730
+ released: void 0
2731
+ };
2732
+ function readQuarantineLadder(knowledgeRoot, identity, now, recurrence) {
2733
+ const entries = foldQuarantineLedgerState2(readQuarantineLedger2(knowledgeRoot).events, now);
2734
+ const entry = entries.find((candidate) => candidate.product === identity.product && candidate.filePath === identity.filePath && candidate.testName === identity.testName);
2735
+ const state = entry === void 0 ? void 0 : LADDER_STATE_FOR_ENTRY[entry.state];
2736
+ if (entry !== void 0 && state !== void 0) return { state, expiresAt: entry.expiresAt };
2737
+ if (recurrence.kth >= QUARANTINE_FLAG_FLOOR) return { state: "flagged" };
2738
+ return void 0;
2739
+ }
2740
+ function createProjectionRecurrenceProvider(resolveKnowledgeRoot, now) {
2741
+ let root;
2742
+ let resolving;
2743
+ function beginResolve() {
2744
+ if (resolving !== void 0) return resolving;
2745
+ let answer;
2746
+ try {
2747
+ answer = resolveKnowledgeRoot();
2748
+ } catch {
2749
+ return void 0;
2750
+ }
2751
+ if (typeof answer === "string") {
2752
+ root = answer;
2753
+ return void 0;
2754
+ }
2755
+ resolving = answer.then(
2756
+ (value) => {
2757
+ root = value;
2758
+ },
2759
+ () => {
2760
+ resolving = void 0;
2761
+ }
2762
+ );
2763
+ return resolving;
2764
+ }
2765
+ function rootNow() {
2766
+ if (root !== void 0) return root;
2767
+ beginResolve();
2768
+ return root;
2769
+ }
2770
+ return {
2771
+ prime() {
2772
+ if (root !== void 0) return Promise.resolve();
2773
+ return beginResolve() ?? Promise.resolve();
2774
+ },
2775
+ provider: {
2776
+ failureRecurrence(query) {
2777
+ const knowledgeRoot = rootNow();
2778
+ if (knowledgeRoot === void 0) return void 0;
2779
+ const recurrence = readProjectedRecurrence(knowledgeRoot, query);
2780
+ if (recurrence === void 0) return void 0;
2781
+ const ladder = readQuarantineLadder(knowledgeRoot, query, now(), recurrence);
2782
+ return ladder === void 0 ? recurrence : { ...recurrence, ladder };
2783
+ }
2784
+ }
2785
+ };
2786
+ }
2787
+ function createAnalysisArtifactReader(agentQaRoot) {
2788
+ return {
2789
+ getArtifact(runId) {
2790
+ const path = join7(agentQaRoot, RUNS_SUBDIR3, slugBranch4(runId), ANALYSIS_FILE);
2791
+ let bytes;
2792
+ try {
2793
+ bytes = readFileSync4(path, "utf8");
2794
+ } catch (error) {
2795
+ if (error.code === "ENOENT") return void 0;
2796
+ throw error;
2797
+ }
2798
+ const admission = admitAnalysisArtifactForRun(
2799
+ guardRunAnalysisArtifact(JSON.parse(bytes)),
2800
+ runId
2801
+ );
2802
+ return admission.kind === "admitted" ? admission.artifact : void 0;
2803
+ }
2804
+ };
2805
+ }
2806
+ function postRunCapabilities(input, knowledgeRoot, analysisTokenBudget, jobStore, proposeGate1MaxPerJob) {
2807
+ const postRun = input.postRun;
2808
+ if (postRun === void 0) return void 0;
2809
+ return {
2810
+ // 422 D-14: ONE narrow read — this job's own durable Gate-1 charge, addressed by its own id,
2811
+ // through the authoritative `readCheckpoint` the store already publishes. The handler is
2812
+ // handed no store, no checkpoint reader and no path; the shape check is `agent`'s, beside
2813
+ // the shape it checks, so two hosts cannot disagree about what a readable charge is.
2814
+ ...jobStore === void 0 ? {} : {
2815
+ readProposeGate1Allowance: async (jobId) => parseProposeGate1Allowance(
2816
+ await jobStore.readCheckpoint(asJobId2(jobId), PROPOSE_GATE1_ALLOWANCE_CHECKPOINT)
2817
+ )
2818
+ },
2819
+ loadCompletedRunHandoff: (runId) => loadCompletedRunHandoff(
2820
+ join7(input.agentQaRoot, RUNS_SUBDIR3, slugBranch4(runId), HANDOFF_FILE)
2821
+ ),
2822
+ curateRun: postRun.curateRun,
2823
+ // 422 D-13: spread on the conditional idiom this file uses throughout, so an unbound port
2824
+ // is an ABSENT member rather than a present `undefined` — the handler decides on presence,
2825
+ // in one place, and a host that declined to bind cannot be mistaken for one that bound a
2826
+ // hole.
2827
+ ...postRun.stageAdversarialProposal === void 0 ? {} : { stageAdversarialProposal: postRun.stageAdversarialProposal },
2828
+ // 422-09 (D-14): the Gate-1 leg's four ports and its bound, forwarded on the same
2829
+ // presence-spread idiom one line up. The handler's own `screenGate1` requires ALL FOUR before
2830
+ // it screens anything, so a host that binds three of them is a host that screens nothing —
2831
+ // which is why they are forwarded one by one rather than as a bundle a caller could
2832
+ // half-populate without noticing.
2833
+ ...postRun.runReplayGate === void 0 ? {} : { runReplayGate: postRun.runReplayGate },
2834
+ ...postRun.listBundles === void 0 ? {} : { listBundles: postRun.listBundles },
2835
+ ...postRun.gateReceiptsFor === void 0 ? {} : { gateReceiptsFor: postRun.gateReceiptsFor },
2836
+ ...postRun.readProposeCorpusPosture === void 0 ? {} : { readProposeCorpusPosture: postRun.readProposeCorpusPosture },
2837
+ ...postRun.readMisdirectionRootPosture === void 0 ? {} : { readMisdirectionRootPosture: postRun.readMisdirectionRootPosture },
2838
+ ...proposeGate1MaxPerJob === void 0 ? {} : { proposeGate1MaxPerJob },
2839
+ // 389 D-09: the artifact's path is DERIVED from the artifact's own runId through the
2840
+ // same `runs/<slug>/` law as the handoff read above — `slugBranch` is the traversal
2841
+ // guard (T-389-01), and a job payload never carries a path.
2842
+ persistAnalysis: (artifact) => writeAnalysisArtifact(
2843
+ join7(input.agentQaRoot, RUNS_SUBDIR3, slugBranch4(artifact.runId), ANALYSIS_FILE),
2844
+ artifact
2845
+ ),
2846
+ // Plan 02's read side over the derived projection, keyed the projection's way —
2847
+ // (product, filePath, testName), the stable identity, NEVER a per-run explicit id.
2848
+ // The query arrives already keyed by the author callback; this binding only reads.
2849
+ readTestHistory: (query) => readProjectedTestHistory(knowledgeRoot, query),
2850
+ // D-02's sha-addressed screenshot bytes: the SAME CASS-02 store the runner writes.
2851
+ // `FsEvidenceArtifactStore.getBlob` validates the lowercase 64-hex shape BEFORE any
2852
+ // path use (T-389-14) and answers ENOENT as honest absence; the runId becomes a path
2853
+ // segment only through `slugBranch` inside the store's own constructor.
2854
+ //
2855
+ // 405-05: THROUGH THE SHARED RULE, not a lambda of its own. `runs.artifact` binds a port over
2856
+ // the same `lookupEvidenceBlob` below, and two lambdas over one store would be two answers to
2857
+ // where a run's blobs live — agreeing on the day the second is written and diverging the first
2858
+ // time either side changes a root. This job wants only the found bytes, so the three-arm
2859
+ // lookup collapses here; the SPLIT is what the op needs and what this caller does not.
2860
+ readEvidenceBlob: (runId, sha) => readEvidenceBlobBytes({ agentQaRoot: input.agentQaRoot }, runId, sha),
2861
+ // Host-resolved and passed through untouched (386 D-15): the budget derives from the
2862
+ // same profile resolution as the job model, and this composition adds no knob of its own.
2863
+ ...analysisTokenBudget === void 0 ? {} : { analysisTokenBudget }
2864
+ };
2865
+ }
2866
+ var CHAT_RESOLVER_CALLER = Object.freeze({
2867
+ surface: "chat",
2868
+ origin: "human"
2869
+ });
2870
+ var CHAT_PLANNER_CALLER = Object.freeze({
2871
+ surface: "chat",
2872
+ origin: "agent"
2873
+ });
2874
+ function chatToolsetIds(registry) {
2875
+ if (registry === void 0) return [];
2876
+ return registry.list(CHAT_RESOLVER_CALLER).map((declaration) => declaration.id).sort();
2877
+ }
2878
+ function chatToolsetHash(registry) {
2879
+ if (registry === void 0) return "unbound";
2880
+ return sha256hex16(chatToolsetIds(registry).join("\n"));
2881
+ }
2882
+ function projectChatMembers(value, rules, mask) {
2883
+ return Object.fromEntries(Object.entries(value).map(([key, member]) => {
2884
+ const rule = Object.hasOwn(rules, key) ? rules[key] : "tree";
2885
+ if (rule === "as-written") return [key, member];
2886
+ if (typeof rule === "function") return [key, rule(member, mask)];
2887
+ if (rule === "text" && typeof member === "string") return [key, mask(member)];
2888
+ return [key, maskPayloadTree(member, mask)];
2889
+ }));
2890
+ }
2891
+ var eachChatRecord = (rules) => (value, mask) => {
2892
+ const project = (entry) => isRecord(entry) ? projectChatMembers(entry, rules, mask) : maskPayloadTree(entry, mask);
2893
+ return Array.isArray(value) ? value.map(project) : project(value);
2894
+ };
2895
+ var CHAT_EVENT_BASE = {
2896
+ kind: "as-written",
2897
+ turnId: "as-written",
2898
+ at: "as-written"
2899
+ };
2900
+ var CHAT_CLAUSE_RECEIPT_RULES = {
2901
+ clause: "text",
2902
+ functionId: "as-written",
2903
+ origin: "as-written",
2904
+ status: "as-written",
2905
+ outcome: "text"
2906
+ };
2907
+ var CHAT_EVENT_RULES = {
2908
+ user_message: { ...CHAT_EVENT_BASE, text: "text" },
2909
+ stage_outcome: { ...CHAT_EVENT_BASE, stage: "as-written", outcome: "as-written" },
2910
+ clause_receipt: { ...CHAT_EVENT_BASE, ...CHAT_CLAUSE_RECEIPT_RULES },
2911
+ clause_result: { ...CHAT_EVENT_BASE, functionId: "as-written", payload: "tree" },
2912
+ approval_decision: {
2913
+ ...CHAT_EVENT_BASE,
2914
+ actionClass: "as-written",
2915
+ decision: "as-written",
2916
+ source: "as-written"
2917
+ },
2918
+ mark_unhandled: { ...CHAT_EVENT_BASE, clause: "text", reason: "text" },
2919
+ model_change: { ...CHAT_EVENT_BASE, from: "text", to: "text", source: "as-written" },
2920
+ active_tools_change: {
2921
+ ...CHAT_EVENT_BASE,
2922
+ addedIds: "as-written",
2923
+ removedIds: "as-written",
2924
+ toolsetHash: "as-written"
2925
+ },
2926
+ compaction: {
2927
+ ...CHAT_EVENT_BASE,
2928
+ beforeTokens: "as-written",
2929
+ afterTokens: "as-written",
2930
+ outcome: "as-written"
2931
+ },
2932
+ aborted: { ...CHAT_EVENT_BASE, source: "as-written" },
2933
+ turn_complete: { ...CHAT_EVENT_BASE, status: "as-written" }
2934
+ };
2935
+ var CHAT_INVOKE_ANSWER_RULES = {
2936
+ payload: "tree",
2937
+ verdict: eachChatRecord({ status: "as-written", outcome: "text" }),
2938
+ foldLines: "tree"
2939
+ };
2940
+ var CHAT_TURN_RESULT_RULES = {
2941
+ sessionId: "as-written",
2942
+ turnId: "as-written",
2943
+ status: "as-written",
2944
+ answer: "text",
2945
+ receipts: eachChatRecord(CHAT_CLAUSE_RECEIPT_RULES),
2946
+ results: eachChatRecord({ functionId: "as-written", payload: "tree" }),
2947
+ unhandled: eachChatRecord({ clause: "text", reason: "text" }),
2948
+ refusedLineCount: "as-written"
2949
+ };
2950
+ function projectChatSessionEvent(event, mask) {
2951
+ const rules = Object.hasOwn(CHAT_EVENT_RULES, event.kind) ? CHAT_EVENT_RULES[event.kind] : CHAT_EVENT_BASE;
2952
+ return projectChatMembers(event, rules, mask);
2953
+ }
2954
+ function composeLocalAgent(input) {
2955
+ const clock = input.clock ?? SYSTEM_CLOCK;
2956
+ const entityResolutionInput = {
2957
+ agentQaRoot: input.agentQaRoot,
2958
+ resolveKnowledgeRoot: input.knowledgeRoot
2959
+ };
2960
+ const recurrence = createProjectionRecurrenceProvider(input.knowledgeRoot, () => clock.now());
2961
+ const runsReader = createRunsReader(input.agentQaRoot);
2962
+ let composedRegistry;
2963
+ const composedChatModelName = input.chatModel === void 0 ? "unbound" : input.chatModel.modelId;
2964
+ const chatScopes = /* @__PURE__ */ new Map();
2965
+ const chatScopeFor = (sessionId) => {
2966
+ const existing = chatScopes.get(sessionId);
2967
+ if (existing !== void 0) return existing;
2968
+ if (composedRegistry === void 0) {
2969
+ throw new TypeError("a chat session opened before its registry finished composing");
2970
+ }
2971
+ const scope = {
2972
+ // An UNBORN session has run no `/model`, so it opens under the model this host composed —
2973
+ // which is also what its header will say. A container seeded from another session's override
2974
+ // would make one conversation's verb the next conversation's opening posture.
2975
+ state: createChatState({ activeModel: composedChatModelName })
2976
+ // 432-13 (D-14): no pin holder. `/pin` left chat resolution with the op gate it tightened.
2977
+ };
2978
+ chatScopes.set(sessionId, scope);
2979
+ return scope;
2980
+ };
2981
+ const chatModelNameFor = (sessionId) => chatScopeFor(sessionId).state.read().activeModel;
2982
+ const chatModelsByName = /* @__PURE__ */ new Map();
2983
+ if (input.chatModel !== void 0) chatModelsByName.set(composedChatModelName, input.chatModel);
2984
+ const chatSessionLog = createJsonlSessionLog({
2985
+ root: input.agentQaRoot,
2986
+ now: () => clock.now(),
2987
+ // THE COMPOSED MODEL, not a session's override, and the difference is a leak this keying closes.
2988
+ // This thunk is read when a header is WRITTEN, which happens exactly once per session and only
2989
+ // for a session that has not existed until now — so no `/model` can have run in it. Answering
2990
+ // with a live override would stamp one conversation's switch onto the opening header of every
2991
+ // conversation opened after it. 441 GAP3-D4: written and listed through the declared mask.
2992
+ model: () => input.mask(composedChatModelName),
2993
+ toolsetHash: () => chatToolsetHash(composedRegistry)
2994
+ });
2995
+ const liveChatSessions = /* @__PURE__ */ new Map();
2996
+ const trackedChatSessionLog = {
2997
+ async open(sessionId) {
2998
+ const opened2 = await chatSessionLog.open(sessionId);
2999
+ const id = opened2.header.sessionId;
3000
+ const turnIds = /* @__PURE__ */ new Set();
3001
+ const tracked = {
3002
+ header: opened2.header,
3003
+ events: opened2.events,
3004
+ refusedLines: opened2.refusedLines,
3005
+ append: async (event) => {
3006
+ const projected = projectChatSessionEvent(event, input.mask);
3007
+ turnIds.add(projected.turnId);
3008
+ await opened2.append(projected);
3009
+ try {
3010
+ input.observeSessionEvent?.(id, projected);
3011
+ } catch {
3012
+ }
3013
+ },
3014
+ turnBoundary: () => opened2.turnBoundary(),
3015
+ close: async () => {
3016
+ liveChatSessions.delete(id);
3017
+ await opened2.close();
3018
+ }
3019
+ };
3020
+ liveChatSessions.set(id, { handle: tracked, turnIds });
3021
+ return tracked;
3022
+ }
3023
+ };
3024
+ const chatSessionHoldingTurn = (turnId) => {
3025
+ for (const entry of liveChatSessions.values()) {
3026
+ if (entry.turnIds.has(turnId)) return entry.handle;
3027
+ }
3028
+ return void 0;
3029
+ };
3030
+ const plannerStages = /* @__PURE__ */ new Map();
3031
+ const chatPlanner = input.chatModel === void 0 ? void 0 : {
3032
+ plan: (request) => {
3033
+ if (composedRegistry === void 0) {
3034
+ throw new TypeError("a chat turn ran before its registry finished composing");
3035
+ }
3036
+ const activeName = chatModelNameFor(request.sessionId);
3037
+ let stage = plannerStages.get(activeName);
3038
+ if (stage === void 0) {
3039
+ stage = createPlannerStage({
3040
+ model: chatModelsByName.get(activeName) ?? input.chatModel,
3041
+ tools: projectPlannerTools(composedRegistry, CHAT_PLANNER_CALLER),
3042
+ // Read PER REQUEST — not per turn and not per stage (392 plan 13, CR-02). A stage
3043
+ // is shared by every session planning under the same model name, so a context
3044
+ // thunk taking no argument had exactly one conversation to answer with, and on
3045
+ // two interleaved turns it answered with whichever one published last. Taking the
3046
+ // requesting session's id makes "the conversation this request replays" a fact
3047
+ // about the call. The read is still LIVE, so a `/model` override still moves what
3048
+ // the prompt states without the stage being rebuilt, and the conversation block
3049
+ // still carries what compaction left rather than a second replay of the log.
3050
+ context: (contextRequest) => {
3051
+ const scope = chatScopeFor(contextRequest.sessionId).state.read();
3052
+ return { activeModel: scope.activeModel, conversation: scope.conversation };
3053
+ }
3054
+ });
3055
+ plannerStages.set(activeName, stage);
3056
+ }
3057
+ return stage.plan(request);
3058
+ }
3059
+ };
3060
+ const chatCompaction = input.chatAccounting === void 0 ? void 0 : createChatCompactionHook({
3061
+ accounting: input.chatAccounting,
3062
+ now: () => clock.now(),
3063
+ // 392-06's flagged consumer, wired: what compaction leaves IS the conversation the
3064
+ // prompt renders, and it lands in the live state on EVERY turn rather than only on a
3065
+ // triggered pass — a sink that fired only when a pass ran would leave the prompt holding
3066
+ // a stale conversation on every quiet turn. Into the SESSION'S OWN container (plan 13):
3067
+ // the id is the compacted turn's, so a pruned history cannot land in another
3068
+ // conversation however many turns are in flight.
3069
+ onConversation: (messages, sessionId) => {
3070
+ chatScopeFor(sessionId).state.setConversation(messages);
3071
+ }
3072
+ });
3073
+ const chatTurn = createTurnEngine({
3074
+ table: CHAT_RESOLVER_TABLE,
3075
+ // 392 plan 02, D-09: the real conjunction grammar, quote-aware. The tracer's
3076
+ // whole-utterance placeholder is gone; this is the seam filled, not widened.
3077
+ split: splitClauses,
3078
+ reconcile: input.chatReconcile ?? deterministicOverrideStrategy,
3079
+ sessionLog: trackedChatSessionLog,
3080
+ // RESEARCH Pitfall 1: per-STAGE minting, because origin is a per-call fact. A deterministic
3081
+ // table hit is a person typing and mints `human`; a planner-planned call mints `agent`, and the
3082
+ // turn's receipts record which.
3083
+ // 2026-10-01 (432-21, D-12): NOT STAMPED WITH THE SESSION ANY MORE. The session id rode the
3084
+ // caller for the gate's deny overlay (plan 13, W-03) and the overlay is deleted (432-13/18/19);
3085
+ // nothing reads `FunctionCaller.sessionId`. The session still reaches the ops that record it
3086
+ // through the turn's own session facts, not through the caller.
3087
+ mint: {
3088
+ forStage: (stage) => stage === "resolver" ? CHAT_RESOLVER_CALLER : CHAT_PLANNER_CALLER
3089
+ },
3090
+ // 399.1 D-01/D-03/D-05: the classified envelope, built HERE because this closure is the one
3091
+ // place in the system holding both the registry and the invoke call. The chat subtree cannot
3092
+ // reach a spec by construction (384 FUNC-03), so the verdict and the render fold have to arrive
3093
+ // as data on this return — which is what `ChatInvokeAnswer` is. `foldFunctionResult` owns the
3094
+ // fail-closed rule so both composition roots get the same one rather than a copy each.
3095
+ //
3096
+ // invokeFunction validates canonical output; it does not promise masking. Classify those
3097
+ // original bytes before projecting the answer into results[] and the durable clause_result.
3098
+ invoke: async (functionId, invokeInput, caller) => {
3099
+ if (composedRegistry === void 0) {
3100
+ throw new TypeError("a chat turn ran before its registry finished composing");
3101
+ }
3102
+ const payload = await invokeFunction(composedRegistry, functionId, invokeInput, caller);
3103
+ const fold = foldFunctionResult(composedRegistry, functionId, payload);
3104
+ return projectChatMembers({ payload, ...fold }, CHAT_INVOKE_ANSWER_RULES, input.mask);
3105
+ },
3106
+ ...chatPlanner === void 0 ? {} : { planner: chatPlanner },
3107
+ ...chatCompaction === void 0 ? {} : { compaction: chatCompaction },
3108
+ // 392 plan 08: the directive handlers, which is what turns the engine's
3109
+ // `directive recognised but no handler` refusal from the shipped behaviour into an
3110
+ // unreachable arm on this host. `/help` and `/quit` are REPL chrome and answer in prose;
3111
+ // `/model` changes what the session is and returns the EFFECT it had. (432-13: `/pin` is gone.)
3112
+ //
3113
+ // RESOLVED PER SESSION (plan 13, CR-02 / W-03). Each one acts on a conversation rather than the
3114
+ // project, so the handler set is built over the scope of the session running the turn:
3115
+ // `/model` moves that conversation's model and `/quit` asks that loop to end.
3116
+ directives: (sessionId) => ({
3117
+ // THE BATCH PRE-FLIGHT (392-14, E3/E3b). The engine asks this of EVERY directive of a turn
3118
+ // before it applies the first one, so a refused later directive leaves an earlier one
3119
+ // unapplied.
3120
+ //
3121
+ // IT APPLIES NOTHING. `/model` RESOLVES the model and caches it under its name — a cache is
3122
+ // not the session's state, and the session's own `activeModel` is left exactly where it was.
3123
+ // The resolution is also the only part of `/model` that can fail, so caching it here is what
3124
+ // makes the later apply unable to throw after an earlier directive has already been carried
3125
+ // out.
3126
+ validate: async (directive, argument) => {
3127
+ if (directive === "model" && argument !== void 0 && input.chatModelFor !== void 0) {
3128
+ chatModelsByName.set(argument, await input.chatModelFor(argument));
3129
+ }
3130
+ },
3131
+ model: async (name) => {
3132
+ const scope = chatScopeFor(sessionId);
3133
+ const from = scope.state.read().activeModel;
3134
+ if (input.chatModelFor !== void 0 && !chatModelsByName.has(name)) {
3135
+ chatModelsByName.set(name, await input.chatModelFor(name));
3136
+ }
3137
+ scope.state.setActiveModel(name);
3138
+ return { from, to: name, line: `model: ${from} \u2192 ${name}` };
3139
+ },
3140
+ help: () => CHAT_HELP_TEXT,
3141
+ quit: () => {
3142
+ chatScopeFor(sessionId).state.requestClose();
3143
+ return "closing this session";
3144
+ }
3145
+ }),
3146
+ state: (sessionId) => chatScopeFor(sessionId).state,
3147
+ // D-04: the configuration RE-READ, per turn and PER SESSION. A thunk rather than a captured
3148
+ // value, because the drift worth detecting is precisely the one that happens underneath a
3149
+ // running session — a model resolved differently by the host, or a build whose registry
3150
+ // reaches further than the one this session opened against. The engine folds the log for what
3151
+ // was last recorded and emits `model_change` / `active_tools_change` on the difference.
3152
+ //
3153
+ // THE MODEL IT REPORTS IS THE ASKING SESSION'S (plan 13). A composition-wide read would answer
3154
+ // one conversation's `/model` override for every conversation, and the per-turn diff would
3155
+ // then write a `model_change` into sessions nobody had switched.
3156
+ //
3157
+ // 441 GAP3-D4: masked, because the log it is compared with is masked on append.
3158
+ config: (sessionId) => {
3159
+ const ids = chatToolsetIds(composedRegistry);
3160
+ return {
3161
+ model: input.mask(chatModelNameFor(sessionId)),
3162
+ toolset: { hash: chatToolsetHash(composedRegistry), ids }
3163
+ };
3164
+ },
3165
+ // 395-08 D-14/D-15 · CFGM-05: the target vocabulary the PRE-DISPATCH write_test route reads.
3166
+ // Bound off the SAME host verb `authoringRead.configuredTargetNames` uses, so the seam that
3167
+ // decides whether to offer a scaffold and the loop that later checks what a model emitted are
3168
+ // reading one answer rather than two. A host that cannot name its targets binds nothing and the
3169
+ // route never runs, which leaves the shipped dispatch and the loop's own backstop exactly as
3170
+ // they were.
3171
+ // BOUND ONLY WHERE THE LOOP IS. A host that cannot author has nothing for this route to route,
3172
+ // and binding it anyway would answer an empty vocabulary — which the route reads as the
3173
+ // from-zero case and refuses, reporting the wrong cause for a host whose real problem is that
3174
+ // `authoring.draft.write` is unbound. Absent leaves the shipped dispatch untouched.
3175
+ ...input.draftAuthoring !== void 0 && input.runExecution !== void 0 ? { configuredTargets: () => input.draftAuthoring.configuredTargetNames() } : {},
3176
+ // 395-13 D-14: where the scaffold proposal is rendered before the in-turn apply is invoked.
3177
+ // Conditionally spread on the exact idiom above, so a host that binds no surface leaves the
3178
+ // field ABSENT and the engine's own "renders nothing" posture is what answers — in one place.
3179
+ ...input.chatPresent === void 0 ? {} : { present: input.chatPresent },
3180
+ now: () => clock.now(),
3181
+ mintTurnId: () => `turn-${randomUUID3()}`
3182
+ });
3183
+ const authoringLoopHost = input.draftAuthoring !== void 0 && input.runExecution !== void 0 ? input.draftAuthoring : void 0;
3184
+ const authoringFunnel = {
3185
+ receipts: createDraftReceiptsStore({ resolveAgentQaRoot: () => input.agentQaRoot }),
3186
+ quarantine: {
3187
+ list: async () => await createQuarantineOperations({
3188
+ resolveKnowledgeRoot: input.knowledgeRoot,
3189
+ now: () => clock.now(),
3190
+ mintIdentityPath: input.mintQuarantineIdentityPath,
3191
+ ...input.resolveWriterAuthority === void 0 ? {} : { resolveWriterAuthority: input.resolveWriterAuthority }
3192
+ }).list()
3193
+ },
3194
+ // D-14's history half, over the run-outcome projection's own read side (394-17, step b).
3195
+ //
3196
+ // ONE CONNECTION PER VERB, opened read-only and closed in `finally`, on the shape
3197
+ // `readProjectedTestHistory` above already takes. An absent database, a store predating the
3198
+ // projection tables and an identity nothing has counted all answer EMPTY — which
3199
+ // `assessSuiteStability` turns into the honest `no-history` posture rather than into a clean
3200
+ // bill of health. Any other read failure propagates: a corrupt store is not "no history".
3201
+ //
3202
+ // THE OUTCOME WORDS. The durable window keeps one boolean per build (`last_outcomes_json`),
3203
+ // which is what the fold reduced each member's outcome to, so this spells it back with the two
3204
+ // words it was reduced FROM. Nothing downstream compares those strings against a member list —
3205
+ // `assessSuiteStability` counts DISTINCT values — so a flip in the window is a flip here, and
3206
+ // no vocabulary is invented beyond what the projection actually kept.
3207
+ outcomes: {
3208
+ testsIn: async (product, filePath) => {
3209
+ const knowledgeRoot = await input.knowledgeRoot();
3210
+ const path = indexDbPath(knowledgeRoot);
3211
+ if (!existsSync4(path)) return [];
3212
+ const db = new DatabaseSync4(path, { readOnly: true });
3213
+ try {
3214
+ return listTestsInFile(db, { product, filePath }).map((testName) => ({ filePath, testName }));
3215
+ } catch (error) {
3216
+ if (error instanceof Error && error.message.includes("no such table")) return [];
3217
+ throw error;
3218
+ } finally {
3219
+ db.close();
3220
+ }
3221
+ },
3222
+ outcomesFor: async (product, filePath, testName) => {
3223
+ const knowledgeRoot = await input.knowledgeRoot();
3224
+ const history = readProjectedTestHistory(knowledgeRoot, { product, filePath, testName });
3225
+ if (history === void 0) return [];
3226
+ return history.lastOutcomes.map((entry) => entry.passed ? "passed" : "failed");
3227
+ }
3228
+ }
3229
+ };
3230
+ const registry = createFunctionRegistry({
3231
+ deps: {
3232
+ secretMask: input.mask,
3233
+ runs: {
3234
+ ...runsReader,
3235
+ get: async (runId) => {
3236
+ await recurrence.prime();
3237
+ return runsReader.get(runId);
3238
+ }
3239
+ },
3240
+ // 387 D-01: this host's list verbs are live sighting sites. Every entity a listing,
3241
+ // an inspect, a diff or a show surfaces gets its permanent address the first time it
3242
+ // is surfaced — which is what "pre-existing entities get IDs the day they are first
3243
+ // referenced" means in practice, and it costs the creation paths nothing.
3244
+ knowledge: createKnowledgeReader(
3245
+ input.knowledgeRoot,
3246
+ createMemorySightingHook(entityResolutionInput),
3247
+ input.resolvePromotionPosture,
3248
+ input.resolveTrustPriors
3249
+ ),
3250
+ config: input.config,
3251
+ entities: createEntityDirectory(entityResolutionInput),
3252
+ // 387-08 KREV-01: the WRITE half, and it is present in every local composition because a
3253
+ // local host owns the knowledge root it serves from. It stays an OPTIONAL dep so a host
3254
+ // that is genuinely read-only — a listing, a remote surface — reports the four mutating
3255
+ // verbs as `unbound` BY NAME rather than as a hole in the catalogue.
3256
+ knowledgeReview: createKnowledgeReview({
3257
+ ...input.resolveTrustPriors === void 0 ? {} : { resolveTrustPriors: input.resolveTrustPriors },
3258
+ ...input.resolvePromotionPosture === void 0 ? {} : { resolvePromotionPosture: input.resolvePromotionPosture },
3259
+ agentQaRoot: input.agentQaRoot,
3260
+ resolveKnowledgeRoot: input.knowledgeRoot,
3261
+ ...input.knowledgeChangeDelegate === void 0 ? {} : { knowledgeChangeDelegate: input.knowledgeChangeDelegate },
3262
+ ...input.resolveWriterAuthority === void 0 ? {} : { resolveWriterAuthority: input.resolveWriterAuthority },
3263
+ // 432-12: the host's own context, so a direct change is recorded under the context it was
3264
+ // actually made in. (432-39: the registry no longer receives it; this is its one reader.)
3265
+ ...input.resolveExecutionContext === void 0 ? {} : { resolveExecutionContext: input.resolveExecutionContext }
3266
+ }),
3267
+ // 388 D-14: chip truth, on EVERY local host and not gated on `postRun`. The two are
3268
+ // different questions and conflating them would be the bug: `postRun` says whether this
3269
+ // host can DO the chain's work, while this says whether it can SEE what the chain did —
3270
+ // and a host that never drains anything (a status query, a listing, the CLI after a
3271
+ // handed-off tail) is exactly the caller that most needs to read durable state.
3272
+ //
3273
+ // Given the SAME id source the queue below is built with, so the rows projected are the
3274
+ // rows enqueued. It opens nothing here: the reader constructs no handle until it is asked
3275
+ // a question, and never one that could create the database (Pitfall 4).
3276
+ postProcessing: createPostProcessingReader({
3277
+ agentQaRoot: input.agentQaRoot,
3278
+ idSource: input.idSource ?? DIGEST_JOB_ID_SOURCE
3279
+ }),
3280
+ // 405 D-03: the run-detail projection read, conditionally spread on the house rule — an
3281
+ // absent port must be an ABSENT field and not a present `undefined` one, because the
3282
+ // registry reads exactly this record to decide what the capability LISTS as. Unlike
3283
+ // `postProcessing` above it is NOT present on every local host: chip truth is readable from
3284
+ // `agentQaRoot` alone, but a run's projected step tree lives in a dashboard database that
3285
+ // only the dashboard process holds.
3286
+ // 423 D-04: runs.evidence shares this reader with runs.detail and the blob reader
3287
+ // below. The registry requires both for evidence without narrowing detail availability.
3288
+ ...input.runDetail === void 0 ? {} : { runDetail: input.runDetail },
3289
+ // 405-05 D-12/D-13: the run-scoped evidence-blob read, on EVERY local host and NOT
3290
+ // conditionally spread — unlike `runDetail` one line up, which needs a dashboard database
3291
+ // this host may not hold, every local composition has a runtime root and therefore has the
3292
+ // run directories these blobs live in. It is the SAME reader the analysis job's
3293
+ // `readEvidenceBlob` is built over, so "resolve this ref" and "diet this evidence" cannot
3294
+ // disagree about where a blob is.
3295
+ evidenceBlobs: createEvidenceBlobReader({ agentQaRoot: input.agentQaRoot }),
3296
+ // Present iff this host can execute a run. The registry reads the SAME record to
3297
+ // decide what `runs.start` lists as, so the declaration and the dispatch cannot
3298
+ // disagree about whether this composition can run it (D-16).
3299
+ ...input.runExecution === void 0 ? {} : {
3300
+ runInvocation: createLocalRunInvocation({
3301
+ agentQaRoot: input.agentQaRoot,
3302
+ execution: input.runExecution
3303
+ })
3304
+ },
3305
+ // 389 D-10/D-11: the artifact read `analysis.get` binds through, and the D-08
3306
+ // recurrence join the one renderer consults. Both are bindable from `agentQaRoot`
3307
+ // and the knowledge root alone, so every local composition carries them — a host
3308
+ // with no artifact on disk serves the floor, and a host with no recorded history
3309
+ // renders the explicit no-history clause; neither is a refusal (plan 05's law).
3310
+ analysis: createAnalysisArtifactReader(input.agentQaRoot),
3311
+ recurrence: recurrence.provider,
3312
+ // 398 D-09 (dated 2026-08-17): the catalog port `agent.explain` binds through, handed
3313
+ // straight through from the host. Conditionally spread rather than assigned, on the
3314
+ // `runInvocation` precedent: an absent port must be an ABSENT field and not a present
3315
+ // `undefined` one, because the registry reads exactly this record to decide what the
3316
+ // capability LISTS as — so the declaration and the dispatch cannot disagree about whether
3317
+ // this composition can explain itself (D-16).
3318
+ ...input.capabilityCatalog === void 0 ? {} : { capabilityCatalog: input.capabilityCatalog },
3319
+ // 403 D-03: the live-view mint seam, conditionally spread on the same rule — an absent port
3320
+ // must be an ABSENT field, so `runs.liveurl` declares `unbound` where no origin is served.
3321
+ ...input.liveView === void 0 ? {} : { liveView: input.liveView },
3322
+ // 390 D-12/D-13: the three quarantine verbs, bound HERE and therefore on BOTH hosts —
3323
+ // the CLI tail and 386's daemon compose the same root, which is the whole point of
3324
+ // 385 D-15 (a second binding site is a second answer to "what is quarantined").
3325
+ //
3326
+ // Present in every local composition for the same reason the knowledge WRITE half is: a
3327
+ // local host owns the committed knowledge root the ledger lives at. It stays an OPTIONAL
3328
+ // dep so a genuinely read-only or remote composition reports all three `unbound` BY NAME
3329
+ // rather than answering an empty list about a ledger it has never been able to open.
3330
+ //
3331
+ // The port takes the root as a FACTORY (the CLI resolves it asynchronously) and the clock
3332
+ // as an argument, so this composition supplies both and the implementation reads neither
3333
+ // of its own accord. Nothing opens here: the first byte is read when a verb is invoked.
3334
+ //
3335
+ // 390-09 / CR-03: the SAME `resolveWriterAuthority` the apply path consumes three bindings
3336
+ // above, and it is what lands each host on the correct lease arm with zero daemon changes.
3337
+ // The daemon supplies it (`cli/src/daemon/host.ts:1839`) because it acquired the lease at
3338
+ // startup and holds it for its lifetime, so the verbs ride that authority instead of asking
3339
+ // for a lease the process cannot be granted twice; the CLI in-process host supplies nothing,
3340
+ // so the verbs acquire and release per call. Conditionally spread like its neighbour rather
3341
+ // than assigned through: an absent authority must be an ABSENT field, not a present
3342
+ // `undefined` one, so the implementation's `?.()` arm distinguishes the two hosts.
3343
+ //
3344
+ // 390-12 / D-01: the identity mint is handed straight through, REQUIRED and unspread. The
3345
+ // verbs key the committed ledger on `filePath` and every reader compares those bytes with
3346
+ // `===`, so a host that supplied nothing would be back to storing whatever a human typed —
3347
+ // the round-2 regression. Unlike the authority above there is no host without a checkout
3348
+ // root, so the absent case is a compile error rather than a second posture.
3349
+ // 394-07 D-12 / WTST-05: the honesty funnel's reads, bound HERE and therefore on BOTH hosts,
3350
+ // which is the whole point of 385 D-15 — a second binding site would be a second answer to
3351
+ // "how many drafts did this session keep", and that number is the one WTST-05 exists to keep
3352
+ // honest.
3353
+ //
3354
+ // UNCONDITIONAL, unlike the loop's two halves above it, and the asymmetry is the point: the
3355
+ // funnel is a READ over records this host owns by owning `agentQaRoot`, so every local
3356
+ // composition can answer it whether or not it can author anything. An empty receipts
3357
+ // directory and an absent one are the same answer, and nothing opens here — the first byte
3358
+ // is read when the verb is invoked.
3359
+ //
3360
+ // THE RECEIPTS PORT IS THE READ HALF ONLY. The evaluator one binding below is handed the
3361
+ // same store's `put`; this bundle names `list` and nothing else, so the projection is
3362
+ // structurally unable to write the records it reports on. Two narrow mirrors over one store
3363
+ // rather than one wide port.
3364
+ //
3365
+ // 2026-10-01 (432-17, D-06): the bundle no longer reads the proposals inbox. 2026-10-02
3366
+ // (quick 261002-ctr): the terminal stage is `writeEligible`, read off the receipts this
3367
+ // bundle lists (see `projectAuthoringFunnel`).
3368
+ //
3369
+ // `outcomes` IS BOUND (394-17, step b): the port's two verbs now LEAD with `product`, so the
3370
+ // binding reads the run-outcome projection on its own primary key instead of guessing a
3371
+ // partition or serving every product's history under one file path.
3372
+ //
3373
+ // ASSEMBLED ABOVE rather than inline, because the gate-2 flip path is handed the SAME
3374
+ // bundle: one composition-level answer to "what does this project know about this suite",
3375
+ // reached by the funnel read op and by the evaluator's consultation alike.
3376
+ authoringFunnel,
3377
+ // 2026-10-01 (432-17, D-05/D-22): the proposals inbox (391 PROP-03) is not bound — it is deleted
3378
+ // with its ops. Old files under `<agentQaRoot>/proposals/` are never opened again.
3379
+ ...input.feedback === void 0 ? {} : { feedback: input.feedback },
3380
+ ...input.feedbackReads === void 0 ? {} : { feedbackReads: input.feedbackReads },
3381
+ quarantine: createQuarantineOperations({
3382
+ resolveKnowledgeRoot: input.knowledgeRoot,
3383
+ now: () => clock.now(),
3384
+ mintIdentityPath: input.mintQuarantineIdentityPath,
3385
+ ...input.resolveWriterAuthority === void 0 ? {} : { resolveWriterAuthority: input.resolveWriterAuthority }
3386
+ }),
3387
+ // 392 D-24: the turn engine assembled above. Bound HERE and therefore on BOTH hosts, so
3388
+ // `agent.ask` is a real capability wherever the composition root runs — and unbound nowhere,
3389
+ // which is what lets 397's parity suite read it off the registry with no chat-specific case.
3390
+ chatTurn: {
3391
+ // The engine also echoes authored clause text on receipts and refusal answers. Mask
3392
+ // the complete outward projection after execution, never the message it dispatches.
3393
+ runTurn: async (turnInput) => projectChatMembers(await chatTurn.runTurn(turnInput), CHAT_TURN_RESULT_RULES, input.mask)
3394
+ },
3395
+ // 393 D-01 / D-15: the keep-gate evaluator's two host halves, bound HERE and therefore on
3396
+ // BOTH hosts — the CLI and 386's daemon compose this same root, which is the whole point of
3397
+ // 385 D-15 (a second binding site would be a second answer to "is this draft worth keeping",
3398
+ // and the two would disagree about a decision that gets written into a durable receipt).
3399
+ //
3400
+ // Conditionally spread on BOTH halves, because a capability that can parse but cannot run is
3401
+ // not a degraded evaluator — it is a host that must say `unbound` by name. `runs.start` is
3402
+ // dispatched through the registry this very call is about to produce, which is the same
3403
+ // deferred reference the chat engine takes and is safe for the same reason: no op can run
3404
+ // before the registry it is registered in has been built.
3405
+ ...input.draftAuthoring === void 0 || input.runExecution === void 0 ? {} : {
3406
+ draftEvaluation: createDraftEvaluation({
3407
+ authoring: input.draftAuthoring,
3408
+ agentQaRoot: input.agentQaRoot,
3409
+ knowledgeRoot: input.knowledgeRoot,
3410
+ authoringFunnel,
3411
+ readRun: (runId) => runsReader.get(runId),
3412
+ startGateRun: async (request) => {
3413
+ if (composedRegistry === void 0) {
3414
+ throw new TypeError("a gate-run started before its registry finished composing");
3415
+ }
3416
+ return await invokeFunction(
3417
+ composedRegistry,
3418
+ RUNS_START_FUNCTION_ID,
3419
+ request,
3420
+ AUTHORING_GATE_RUN_CALLER
3421
+ );
3422
+ }
3423
+ })
3424
+ },
3425
+ // 394 D-01 / WTST-01: the authoring LOOP's two halves, bound HERE and therefore on BOTH
3426
+ // hosts, under the SAME condition the evaluator above is bound under — the loop's stage 4
3427
+ // dispatches `authoring.draft.verify`, so a host that cannot verify a draft cannot author
3428
+ // one either, and a third answer to "can this host author" would be a third thing to keep
3429
+ // in step.
3430
+ //
3431
+ // THE READ HALF IS `getAuthoringBrief`'S FIRST PRODUCTION CALLER. The port has been
3432
+ // implemented and unbound since v51 (`knowledge-local/src/serving/authoring-reader.ts`),
3433
+ // which means every field of its contract is crossing a real seam for the first time here;
3434
+ // a mismatch surfaces at this binding rather than in a surface. The reader is built PER
3435
+ // CALL over a freshly resolved knowledge root, on the standing-approval reader's stated
3436
+ // reasoning — the CLI resolves its root asynchronously and per call, and a root captured
3437
+ // once would serve a brief out of a knowledge tree the invocation is not serving from. No
3438
+ // `product` is bound: the composition does not own one, and the SCOPE arrives on the op's
3439
+ // input, which is the honest half of the pair the reader filters on.
3440
+ //
3441
+ // `suiteTextCorpus` is the SAME host walk gate 3's degraded lane already consumes, reached
3442
+ // here for stage 2's pre-authoring screen. One walk, one notion of what a suite test's text
3443
+ // is; a second enumeration would let the screen and the gate disagree about the same pair.
3444
+ //
3445
+ // THE WRITE HALF bundles the model, the shelf and the verify dispatch, because none of the
3446
+ // three is independently bindable: a host with a model and no shelf cannot author, one with
3447
+ // a shelf and no model has nothing to author with, and one that can do both but cannot
3448
+ // verify would produce a test and have nothing to say about whether it is worth keeping.
3449
+ // The model is a FACTORY, resolved at most once and only when a draft is actually compiled,
3450
+ // the same idiom `openJobs` uses — binding this capability must not construct a client for a
3451
+ // host that never authors.
3452
+ //
3453
+ // WHY THE ROUTER (plan 05) WILL READ `DraftRunPortLike.get`'s `CompletedRunHandoff` AND NOT
3454
+ // `runs.get`'s PROJECTION. D-08 routes on run EVIDENCE — step terminations, planning
3455
+ // failure, cycle flags, the per-step sub-action record — and `runs.get` projects a
3456
+ // `RunDetail` in which none of that survives (`stepCount` is its only step-shaped field).
3457
+ // This is `createDraftEvaluation`'s own posture, stated one layer out: the op is reached for
3458
+ // what an op owns, and the reader is read for evidence no projection carries. A router built
3459
+ // on the projection would have to infer a mechanics failure from a member verdict, and
3460
+ // inferring an app defect out of a bare member verdict is precisely the cardinal error D-08
3461
+ // exists to make unreachable.
3462
+ //
3463
+ // A NOTE FOR WHOEVER EDITS THIS PARAGRAPH. The arch dependency scan reads import specifiers
3464
+ // out of this file's RAW BYTES, comments included, so any prose shaped like an import's
3465
+ // trailing clause — the keyword, then a quoted phrase — is reported as an undeclared
3466
+ // external dependency of this package. Two drafts of this comment tripped it, once on a
3467
+ // sentence about verdicts and once on the sentence explaining the first. Keep quoted phrases
3468
+ // away from that keyword here.
3469
+ ...authoringLoopHost === void 0 ? {} : {
3470
+ authoringRead: {
3471
+ async getAuthoringBrief(intent, scope) {
3472
+ const knowledgeRoot = await input.knowledgeRoot();
3473
+ return createAuthoringReader({ knowledgeRoot }).getAuthoringBrief(intent, scope);
3474
+ },
3475
+ suiteTextCorpus: () => authoringLoopHost.suiteTextCorpus(),
3476
+ // 394-33 · WR-07: the `target` vocabulary stage 3's compile prompt serves. Bound off the
3477
+ // SAME port the brief and the corpus come from, so a host that can run the loop's read
3478
+ // stages can name its own targets by construction — see the port's own doc.
3479
+ configuredTargetNames: () => authoringLoopHost.configuredTargetNames()
3480
+ },
3481
+ // 394-26 · WINDOWS 110: the scope an intent-only call is authored at, bound HERE and so on
3482
+ // BOTH hosts, off the same port the read and compile halves come from. Its own deps key
3483
+ // rather than a third verb on `authoringRead`, because deriving a scope needs the resolved
3484
+ // config and the workspace while the brief needs a knowledge index — a host can have one
3485
+ // without the other, and the op refuses on THIS key by name when a scope-less call arrives
3486
+ // at a composition that never bound it.
3487
+ authoringScope: {
3488
+ deriveDefault: () => authoringLoopHost.deriveDefaultScope()
3489
+ },
3490
+ draftShelf: {
3491
+ model: async () => await input.model(),
3492
+ write: (contents) => authoringLoopHost.stageDraft(contents),
3493
+ // 394-06 D-09/D-20: the ONE RFC-6902 applier, reached at the host because it needs the
3494
+ // yaml CST and neither `agent` nor this package may depend on `core`. Same seam,
3495
+ // same reason and same shape as `stageDraft` right above it.
3496
+ applyPatches: (draft, patchSet) => authoringLoopHost.applyPatchesToDraft(draft, patchSet),
3497
+ // 432 D-06/D-07: the direct writer the loop's final write and the edit op reach. Same seam
3498
+ // and same reason as the two verbs above: the boundary check needs `core`.
3499
+ publish: (request) => authoringLoopHost.publishDraft(request),
3500
+ // 394-06: stage 5's evidence reader and the repair arm's probe runner. The reader is
3501
+ // the same `runsReader` the gate evaluator is bound to — a second reader would be a
3502
+ // second answer to what one run recorded — and the starter dispatches the REGISTERED
3503
+ // `runs.start` on the gate-run caller, so a probe is marker-stamped exactly as a gate
3504
+ // run is and is excluded from outcomes for the same reason.
3505
+ runs: {
3506
+ start: async (request) => {
3507
+ if (composedRegistry === void 0) {
3508
+ throw new TypeError("a repair probe started before its registry finished composing");
3509
+ }
3510
+ return await invokeFunction(
3511
+ composedRegistry,
3512
+ RUNS_START_FUNCTION_ID,
3513
+ request,
3514
+ AUTHORING_GATE_RUN_CALLER
3515
+ );
3516
+ },
3517
+ get: (runId) => runsReader.get(runId)
3518
+ },
3519
+ // 2026-10-01 (432-18/432-19, D-15): no dispatch-scoped grant exists. The authoring loop
3520
+ // runs its dispatch directly, and no gate run it starts consults any approval.
3521
+ verifyDraft: async (verifyInput) => {
3522
+ if (composedRegistry === void 0) {
3523
+ throw new TypeError("a draft was verified before its registry finished composing");
3524
+ }
3525
+ return await invokeFunction(
3526
+ composedRegistry,
3527
+ AUTHORING_DRAFT_VERIFY_FUNCTION_ID,
3528
+ verifyInput,
3529
+ AUTHORING_LOOP_VERIFY_CALLER
3530
+ );
3531
+ }
3532
+ }
3533
+ // 2026-10-01 (432-17, D-06): no authoring proposal writer is bound; an authored test is
3534
+ // written in its own call through the shelf's direct writer.
3535
+ },
3536
+ // 432-16 (D-07): no test-draft or config commit port and no undo source is bound — agent-qa
3537
+ // makes no git commits. The final authored test is written through `draftShelf.publish`.
3538
+ //
3539
+ // 395-01 D-01/D-03/D-17 · CFGM-01/03/04: the config editor's host half, each spread on its
3540
+ // OWN condition rather than as one bundle, so a host missing one reports that one `unbound`.
3541
+ ...input.configFiles === void 0 ? {} : { configFiles: input.configFiles },
3542
+ ...input.configValidate === void 0 ? {} : { configValidate: input.configValidate },
3543
+ ...input.scaffoldContent === void 0 ? {} : { scaffoldContent: input.scaffoldContent },
3544
+ ...input.configWrite === void 0 ? {} : { configWrite: input.configWrite }
3545
+ // 2026-10-14 (441-39, D-15): a config change is not scanned for secrets, so no scan is bound.
3546
+ // 2026-10-01 (432-17, D-05): no config proposal filer is bound; the six config ops write in
3547
+ // the call through `configWrite`.
3548
+ }
3549
+ // 2026-10-15 (441-28, D-15): no audit sink; the guardrail audit went with the guardrails.
3550
+ // 432-13 (D-14/D-15): no standing-approval reader, confirm port or session deny overlay reaches
3551
+ // the registry — `invokeFunction` asks nobody. 432-18 deleted the grant reader with the ledger.
3552
+ // 2026-10-02 (432-39, D-12): the host's execution-context answer is no longer forwarded here.
3553
+ // The registry's option was retired and unread since 432-13 and is deleted; the same answer
3554
+ // still reaches the knowledge review above, which records a direct change under it.
3555
+ });
3556
+ composedRegistry = registry;
3557
+ let opened;
3558
+ let opening;
3559
+ let closing;
3560
+ let lifecycleGeneration = 0;
3561
+ let closed = false;
3562
+ async function openJobs(openGeneration) {
3563
+ const model = await input.model();
3564
+ const knowledgeRoot = await input.knowledgeRoot();
3565
+ const analysisTokenBudget = input.analysisTokenBudget === void 0 ? void 0 : await input.analysisTokenBudget();
3566
+ const proposeGate1MaxPerJob = input.postRun?.proposeGate1MaxPerJob === void 0 ? void 0 : await input.postRun.proposeGate1MaxPerJob();
3567
+ const store = createSqliteJobStore({
3568
+ path: join7(input.agentQaRoot, AGENT_JOB_STORE_DB_FILE),
3569
+ clock,
3570
+ checkpoints: createJobCheckpointStore({ root: input.agentQaRoot })
3571
+ });
3572
+ let jobs;
3573
+ try {
3574
+ const kinds = input.jobKinds ?? createRealJobKindRegistry(
3575
+ postRunCapabilities(input, knowledgeRoot, analysisTokenBudget, store, proposeGate1MaxPerJob)
3576
+ );
3577
+ const idSource = input.idSource ?? DIGEST_JOB_ID_SOURCE;
3578
+ const unclaimableKinds = UNCLAIMABLE_BY_PROSE_POSTURE[input.postRun?.candidateProse ?? "no-masker"];
3579
+ jobs = {
3580
+ queue: createAgentJobQueue({
3581
+ store,
3582
+ registry: kinds,
3583
+ clock,
3584
+ idSource,
3585
+ model,
3586
+ ...unclaimableKinds.length === 0 ? {} : { unclaimableKinds }
3587
+ }),
3588
+ store,
3589
+ kinds,
3590
+ model,
3591
+ idSource,
3592
+ unclaimableKinds
3593
+ };
3594
+ } catch (error) {
3595
+ store.close();
3596
+ throw error;
3597
+ }
3598
+ if (closed || openGeneration !== lifecycleGeneration) {
3599
+ store.close();
3600
+ throw new LocalAgentClosedError(input.agentQaRoot);
3601
+ }
3602
+ opened = jobs;
3603
+ return jobs;
3604
+ }
3605
+ return {
3606
+ registry,
3607
+ chat: {
3608
+ stateFor: (sessionId) => chatScopes.get(sessionId)?.state.read(),
3609
+ async abortActiveTurn() {
3610
+ for (const [sessionId, entry] of liveChatSessions) {
3611
+ const turnId = chatScopes.get(sessionId)?.state.read().activeTurnId;
3612
+ if (turnId === void 0) continue;
3613
+ if (!entry.turnIds.has(turnId)) continue;
3614
+ await entry.handle.append({
3615
+ kind: "aborted",
3616
+ turnId,
3617
+ at: clock.now(),
3618
+ source: "explicit"
3619
+ });
3620
+ await entry.handle.turnBoundary();
3621
+ }
3622
+ }
3623
+ },
3624
+ curation: LOCAL_CURATION_CAPABILITIES,
3625
+ clock,
3626
+ jobs() {
3627
+ if (closed) return Promise.reject(new LocalAgentClosedError(input.agentQaRoot));
3628
+ opening ??= openJobs(lifecycleGeneration);
3629
+ return opening;
3630
+ },
3631
+ close() {
3632
+ if (closing !== void 0) return closing;
3633
+ closed = true;
3634
+ lifecycleGeneration += 1;
3635
+ const pending = opening;
3636
+ const current = opened;
3637
+ opened = void 0;
3638
+ current?.store.close();
3639
+ closing = (async () => {
3640
+ if (pending !== void 0) {
3641
+ try {
3642
+ const late = await pending;
3643
+ if (late !== current) late.store.close();
3644
+ } catch {
3645
+ }
3646
+ }
3647
+ opening = void 0;
3648
+ })();
3649
+ return closing;
3650
+ }
3651
+ };
3652
+ }
3653
+
3654
+ // src/composition/build-ordinals.ts
3655
+ import {
3656
+ BuildOrdinalReservationError,
3657
+ reserveBuildOrdinalForProducts,
3658
+ latestBuildOrdinal
3659
+ } from "@vostride/agent-qa-runner-local";
3660
+ import { resolveBuildTag } from "@vostride/agent-qa-runner-local";
3661
+
3662
+ // src/composition/compose-job-recovery.ts
3663
+ import { join as join8 } from "node:path";
3664
+ import { createAgentJobQueue as createAgentJobQueue2, createRealJobKindRegistry as createRealJobKindRegistry2 } from "@vostride/agent-qa-agent";
3665
+ var JOB_RECOVERY_MAX_RESUMED_JOBS = 64;
3666
+ function composeJobRecovery(input) {
3667
+ const store = createSqliteJobStore({
3668
+ path: join8(input.agentQaRoot, AGENT_JOB_STORE_DB_FILE),
3669
+ clock: input.clock,
3670
+ checkpoints: createJobCheckpointStore({ root: input.agentQaRoot })
3671
+ });
3672
+ const kinds = input.jobKinds ?? createRealJobKindRegistry2();
3673
+ const queue = createAgentJobQueue2({
3674
+ store,
3675
+ registry: kinds,
3676
+ clock: input.clock,
3677
+ idSource: input.idSource,
3678
+ model: input.model
3679
+ });
3680
+ return {
3681
+ store,
3682
+ kinds,
3683
+ queue,
3684
+ recoverAbandonedClaims(fence) {
3685
+ return queue.recoverAbandonedClaims({ fence });
3686
+ },
3687
+ async resumePending(fence, maxJobs = JOB_RECOVERY_MAX_RESUMED_JOBS) {
3688
+ const outcomes = [];
3689
+ for (; ; ) {
3690
+ if (outcomes.length >= maxJobs) {
3691
+ throw new Error(
3692
+ `agent job recovery ran ${maxJobs} jobs without emptying the agent job queue; the bound exists so a chain that keeps enqueuing successors cannot turn recovery into an unbounded drain, and stopping early without saying so would report success over work left behind`
3693
+ );
3694
+ }
3695
+ const outcome = await queue.runNextJob({ fence });
3696
+ if (!outcome.ran) return outcomes;
3697
+ outcomes.push(outcome);
3698
+ }
3699
+ },
3700
+ close() {
3701
+ store.close();
3702
+ }
3703
+ };
3704
+ }
3705
+
3706
+ // src/session/memory-session-log.ts
3707
+ import { repairSession as repairSession2 } from "@vostride/agent-qa-agent";
3708
+ import { CURRENT_CHAT_SESSION_SCHEMA_VERSION as CURRENT_CHAT_SESSION_SCHEMA_VERSION2 } from "@vostride/agent-qa-schema";
3709
+ function createMemorySessionLog(options) {
3710
+ const sessions = /* @__PURE__ */ new Map();
3711
+ const random = options.random ?? Math.random;
3712
+ const liveSessions = /* @__PURE__ */ new Set();
3713
+ function handleFor(id, opened) {
3714
+ let live = true;
3715
+ const requireLive = (verb) => {
3716
+ if (!live) {
3717
+ throw new TypeError(
3718
+ `a chat session ${verb} cannot go through a closed handle for ${id}. Open the session again to write to it.`
3719
+ );
3720
+ }
3721
+ };
3722
+ liveSessions.add(id);
3723
+ return {
3724
+ header: opened.header,
3725
+ events: opened.events,
3726
+ refusedLines: opened.refusedLines,
3727
+ append(event) {
3728
+ requireLive("event");
3729
+ sessions.get(id).events.push(event);
3730
+ return Promise.resolve();
3731
+ },
3732
+ turnBoundary() {
3733
+ requireLive("turn boundary");
3734
+ return Promise.resolve();
3735
+ },
3736
+ close() {
3737
+ if (live) {
3738
+ live = false;
3739
+ liveSessions.delete(id);
3740
+ }
3741
+ return Promise.resolve();
3742
+ }
3743
+ };
3744
+ }
3745
+ return {
3746
+ open(sessionId) {
3747
+ if (sessionId !== void 0) {
3748
+ const existing = sessions.get(sessionId);
3749
+ if (existing === void 0) throw new UnknownChatSessionError(sessionId);
3750
+ if (liveSessions.has(sessionId)) throw new ChatSessionBusyError(sessionId);
3751
+ const repair = repairSession2(
3752
+ existing.events.map((event, index) => ({
3753
+ lineNumber: index + 2,
3754
+ text: JSON.stringify(event)
3755
+ })),
3756
+ { declaredVersion: existing.header.schemaVersion, now: options.now }
3757
+ );
3758
+ existing.events.push(...repair.synthesized);
3759
+ return Promise.resolve(
3760
+ handleFor(sessionId, {
3761
+ header: existing.header,
3762
+ events: [...existing.events],
3763
+ // Always empty here, and that is the honest answer rather than a placeholder: this
3764
+ // twin stores typed records, so no line of it can be damaged in the way a file's can.
3765
+ refusedLines: repair.refusedLines
3766
+ })
3767
+ );
3768
+ }
3769
+ const createdAt = options.now();
3770
+ const id = mintChatSessionId(createdAt, random);
3771
+ const header = {
3772
+ schemaVersion: CURRENT_CHAT_SESSION_SCHEMA_VERSION2,
3773
+ sessionId: id,
3774
+ createdAt,
3775
+ model: options.model(),
3776
+ toolsetHash: options.toolsetHash()
3777
+ };
3778
+ sessions.set(id, { header, events: [] });
3779
+ return Promise.resolve(handleFor(id, { header, events: [], refusedLines: [] }));
3780
+ },
3781
+ snapshot(sessionId) {
3782
+ const found = sessions.get(sessionId);
3783
+ return found === void 0 ? void 0 : { header: found.header, events: [...found.events], refusedLines: [] };
3784
+ }
3785
+ };
3786
+ }
3787
+ export {
3788
+ ALLOW_PRODUCTION_OPT_IN,
3789
+ BRIEF_CEILING,
3790
+ BRIEF_FRACTION,
3791
+ BuildOrdinalReservationError,
3792
+ CAUTION_FRACTION,
3793
+ CHAT_PLANNER_CALLER,
3794
+ CHAT_RESOLVER_CALLER,
3795
+ CHAT_SESSIONS_SUBDIR,
3796
+ ChatSessionBusyError,
3797
+ ChatSessionHeaderError,
3798
+ DIGEST_JOB_ID_SOURCE,
3799
+ DRAIN_BUDGET_MS,
3800
+ EMBEDDER_WORKER_REGISTRATION_FILE,
3801
+ EMBEDDER_WORKER_START_TIMEOUT_MS,
3802
+ EMBEDDER_WORKER_TERM_GRACE_MS,
3803
+ ENTITY_INDEX_SWEEP_INTERVAL_MS,
3804
+ EmbedderWorkerTimeoutError,
3805
+ GLOBAL_RECORD_CAP,
3806
+ InvalidChatSessionIdError,
3807
+ JOB_RECOVERY_MAX_RESUMED_JOBS,
3808
+ LOCAL_CURATION_CAPABILITIES,
3809
+ LOCAL_DAEMON_CAPABILITIES,
3810
+ LOCAL_KNOWLEDGE_PROBES,
3811
+ LocalAgentClosedError,
3812
+ MIN_BRIEF,
3813
+ MIN_CAUTION,
3814
+ MIN_MEMORY_TOOL_CONTEXT_WINDOW,
3815
+ QUARANTINE_CLASS_DISPOSITIONS,
3816
+ QUARANTINE_FLOOR_ELIGIBILITY,
3817
+ READ_BODY_FRACTION,
3818
+ REFUSED_RUN_REQUEST_FIELDS,
3819
+ RunDeniedError,
3820
+ RunIdAlreadyInUseError,
3821
+ RunIdNotMintedError,
3822
+ RunProducedNoEventsError,
3823
+ RunRequestFieldRefusedError,
3824
+ SUITE_LIFECYCLE_LANE,
3825
+ SYSTEM_CLOCK,
3826
+ SuiteBoundaryWhileLiveError,
3827
+ UNCLAIMABLE_BY_PROSE_POSTURE,
3828
+ UnknownChatSessionError,
3829
+ WEAK_MODEL_CAP_SCALE,
3830
+ applyKnowledgeChange,
3831
+ buildMemberRunConfig,
3832
+ buildProductionRefusal,
3833
+ buildRunPorts,
3834
+ buildScreenshotCompressor,
3835
+ buildSuiteMemberExecutor,
3836
+ composeJobRecovery,
3837
+ composeLocalAgent,
3838
+ computeKnowledgeBudgets,
3839
+ computeRunConfigDigests,
3840
+ constructKnowledgeReader,
3841
+ createEvidenceBlobReader,
3842
+ createFrozenKnowledgeReader,
3843
+ createJsonlSessionLog,
3844
+ createKnowledgeReader,
3845
+ createKnowledgeReview,
3846
+ createLocalRunInvocation,
3847
+ createMemorySessionLog,
3848
+ createRunInvocationCore,
3849
+ createRunsReader,
3850
+ createSuiteLifecycle,
3851
+ latestBuildOrdinal,
3852
+ listSessions,
3853
+ lookupEvidenceBlob,
3854
+ mapVerdictToExitCode,
3855
+ memoryToolGate,
3856
+ mintChatSessionId,
3857
+ openRunKnowledgeComposition,
3858
+ readEvidenceBlobBytes,
3859
+ refuseProductionWithoutOptIn,
3860
+ refuseUnavailableRunId,
3861
+ registerAdapterLiveStream,
3862
+ reserveBuildOrdinalForProducts,
3863
+ resolveBuildTag,
3864
+ resolveBuildTagSetting,
3865
+ resolveHostRetrievalRung,
3866
+ resolveRunBuildOrdinal,
3867
+ resolveRunExecutionContext,
3868
+ resolveRunQuarantineSnapshot,
3869
+ runSafetyScope,
3870
+ scaledRecordCap,
3871
+ statEvidenceBlob,
3872
+ sweepEntityIndexProjection,
3873
+ toMemberTest
3874
+ };
3875
+ //# sourceMappingURL=index.js.map