@vostride/agent-qa-agent-local 0.2.3 → 0.2.4

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 CHANGED
@@ -85,7 +85,6 @@ function createFrozenKnowledgeReader(reader) {
85
85
  }
86
86
  function constructKnowledgeReader(input) {
87
87
  return createFrozenKnowledgeReader(openKnowledgeReader({
88
- ...input.resolveTrustPriors === void 0 ? {} : { priors: input.resolveTrustPriors() },
89
88
  knowledgeRoot: input.knowledgeRoot,
90
89
  ...input.product === void 0 ? {} : { product: input.product },
91
90
  scopeDefaults: {
@@ -116,7 +115,6 @@ function openRunKnowledgeComposition(input) {
116
115
  recordCap: scaledRecordCap(input.capabilities)
117
116
  };
118
117
  const reader = constructKnowledgeReader({
119
- ...input.resolveTrustPriors === void 0 ? {} : { resolveTrustPriors: input.resolveTrustPriors },
120
118
  knowledgeRoot: input.knowledgeRoot,
121
119
  ...input.product === void 0 ? {} : { product: input.product },
122
120
  ...input.parentScopes === void 0 ? {} : { parentScopes: input.parentScopes },
@@ -367,8 +365,7 @@ function buildMemberRunConfig(input) {
367
365
  cache: input.cache,
368
366
  workingMemory: input.workingMemory,
369
367
  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.
368
+ // The model's canonical compaction applies to every tool-output value.
372
369
  guardBlob: guardToolOutput,
373
370
  compressor: input.compressor,
374
371
  knowledgeReader: input.knowledgeReader,
@@ -423,28 +420,6 @@ function buildMemberRunConfig(input) {
423
420
  import { existsSync } from "node:fs";
424
421
  import { dirname as dirname2 } from "node:path";
425
422
  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
423
  var RunIdNotMintedError = class extends Error {
449
424
  constructor(runId) {
450
425
  super(
@@ -463,22 +438,6 @@ var RunIdAlreadyInUseError = class extends Error {
463
438
  this.name = "RunIdAlreadyInUseError";
464
439
  }
465
440
  };
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
441
  function refuseUnavailableRunId(input) {
483
442
  if (!input.isMintedRunId(input.runId)) throw new RunIdNotMintedError(input.runId);
484
443
  if (existsSync(runDirFor({ configDir: input.configDir, runId: input.runId }))) {
@@ -489,8 +448,6 @@ function createRunInvocationCore(options) {
489
448
  const configDir = dirname2(options.agentQaRoot);
490
449
  return {
491
450
  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
451
  if (request.runId !== void 0) {
495
452
  refuseUnavailableRunId({
496
453
  configDir,
@@ -499,9 +456,6 @@ function createRunInvocationCore(options) {
499
456
  });
500
457
  }
501
458
  const runId = request.runId ?? options.mintRunId();
502
- const declaration = await options.resolveTargetDeclaration(request);
503
- const targetClass = declaration.targetClass;
504
- refuseProductionWithoutOptIn(runId, declaration);
505
459
  await options.afterRefusal?.();
506
460
  const ports = buildRunPorts({ configDir, runId });
507
461
  const postVerdict = options.postVerdict?.({ runId, configDir });
@@ -514,8 +468,7 @@ function createRunInvocationCore(options) {
514
468
  ...postVerdict === void 0 ? {} : { postVerdict }
515
469
  },
516
470
  configDir,
517
- configPath: request.configPath ?? options.configPath,
518
- targetClass
471
+ configPath: request.configPath ?? options.configPath
519
472
  });
520
473
  const session = startRun({
521
474
  ...assembled,
@@ -638,9 +591,7 @@ import {
638
591
  deterministicOverrideStrategy,
639
592
  foldFunctionResult,
640
593
  invokeFunction,
641
- parseProposeGate1Allowance,
642
594
  projectPlannerTools,
643
- PROPOSE_GATE1_ALLOWANCE_CHECKPOINT,
644
595
  RUNS_START_FUNCTION_ID,
645
596
  splitClauses
646
597
  } from "@vostride/agent-qa-agent";
@@ -690,10 +641,6 @@ import {
690
641
  probeEmbedderWorkerSocket,
691
642
  purgeRecord,
692
643
  readCanonicalBundleAnchors,
693
- readCanonicalBundleStateDigests,
694
- canaryPostureFactsFor,
695
- standingReceiptDigestsFor,
696
- lowerTrustSharesFor,
697
644
  readEmbedderWorkerRegistration,
698
645
  readWriterLease,
699
646
  rebuildIndex,
@@ -756,7 +703,6 @@ import {
756
703
  // src/composition/knowledge-review-reader.ts
757
704
  import {
758
705
  canaryAttributionsFor,
759
- candidateInjectionFor,
760
706
  gateReceiptsFor,
761
707
  diffSince,
762
708
  inspectRecords,
@@ -773,31 +719,20 @@ function createKnowledgeReaderPort(input) {
773
719
  }
774
720
  }
775
721
  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.
722
+ // The caller supplied the identity; forward the reader's answer verbatim.
786
723
  async gateReceipts({ candidateId }) {
787
724
  return gateReceiptsFor({
788
725
  knowledgeRoot: await input.resolveKnowledgeRoot(),
789
- candidateId,
790
- resolvePromotionPosture: await input.resolvePromotionPosture?.()
726
+ candidateId
791
727
  });
792
728
  },
793
729
  async listBundles() {
794
- const rows = listBundles(await input.resolveKnowledgeRoot(), input.resolveTrustPriors?.());
730
+ const rows = listBundles(await input.resolveKnowledgeRoot());
795
731
  await sight(rows.map((row) => row.bundleId));
796
732
  return rows;
797
733
  },
798
734
  async inspectRecords({ filter }) {
799
735
  const records = inspectRecords({
800
- ...input.resolveTrustPriors === void 0 ? {} : { priors: input.resolveTrustPriors() },
801
736
  knowledgeRoot: await input.resolveKnowledgeRoot(),
802
737
  filter
803
738
  });
@@ -814,7 +749,6 @@ function createKnowledgeReaderPort(input) {
814
749
  },
815
750
  async showRecord({ recordId }) {
816
751
  const result = showRecord({
817
- ...input.resolveTrustPriors === void 0 ? {} : { priors: input.resolveTrustPriors() },
818
752
  knowledgeRoot: await input.resolveKnowledgeRoot(),
819
753
  recordId
820
754
  });
@@ -826,19 +760,7 @@ function createKnowledgeReaderPort(input) {
826
760
  async showRecords({ recordIds }) {
827
761
  return showRecords({ knowledgeRoot: await input.resolveKnowledgeRoot(), recordIds });
828
762
  },
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
- */
763
+ // Not sighted; `skippedBundles` is forwarded verbatim so a half-read store never looks clean.
842
764
  async canaryAttributions({ runId, tuples }) {
843
765
  return canaryAttributionsFor({
844
766
  knowledgeRoot: await input.resolveKnowledgeRoot(),
@@ -1356,11 +1278,7 @@ function createJsonlSessionLogOn(fs, options) {
1356
1278
  createdAt,
1357
1279
  model: options.model(),
1358
1280
  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.
1281
+ // Spread conditionally, so absent means the option is not given.
1364
1282
  ...options.surface === void 0 ? {} : { surface: options.surface }
1365
1283
  };
1366
1284
  const created = fs.openSync(pathFor(id), "wx");
@@ -1501,22 +1419,14 @@ var SCHEMA_STATEMENTS = [
1501
1419
  -- Nullable because most rows never fail; written only by recordFailure.
1502
1420
  last_failure_kind TEXT,
1503
1421
  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.
1422
+ -- A stop request, not a lifecycle stage. New columns go last, or positional reads would move.
1509
1423
  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.
1424
+ -- When the row was first claimed; survives lease release, cleared only by operator requeue.
1513
1425
  started_at TEXT,
1514
- -- 444.1 D-23: a retrying row is not claimable before this instant. Appended last, as above.
1426
+ -- A retrying row is not claimable before this instant.
1515
1427
  not_before TEXT
1516
1428
  )`,
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.
1429
+ // The dedupe is this unique index, so no caller in any process can enqueue the same work twice.
1520
1430
  `CREATE UNIQUE INDEX IF NOT EXISTS jobs_kind_idempotency_key
1521
1431
  ON jobs (kind, idempotency_key)`,
1522
1432
  `CREATE TABLE IF NOT EXISTS job_store_fence (
@@ -1536,18 +1446,13 @@ var SCHEMA_STATEMENTS = [
1536
1446
  epoch INTEGER NOT NULL,
1537
1447
  PRIMARY KEY (job_id, attempt, checkpoint_id)
1538
1448
  )`,
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.
1449
+ // `created_at` comes from the injected clock; a SQL default would leak host time into the dump.
1543
1450
  `CREATE TABLE IF NOT EXISTS notifications (
1544
1451
  run_id TEXT NOT NULL,
1545
1452
  payload TEXT NOT NULL,
1546
1453
  created_at TEXT NOT NULL
1547
1454
  )`,
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.
1455
+ // One notification per run, ever: a crash-replayed terminal hits this unique index.
1551
1456
  `CREATE UNIQUE INDEX IF NOT EXISTS notifications_run_id
1552
1457
  ON notifications (run_id)`
1553
1458
  ];
@@ -1796,8 +1701,7 @@ function createSqliteJobStore(options) {
1796
1701
  attempts: toNumber(row.attempts),
1797
1702
  claimed: row.claimed_at !== null,
1798
1703
  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.
1704
+ // Omitted rather than `undefined`, so a serialized row publishes no empty conclusion key.
1801
1705
  ...conclusionKind === void 0 ? {} : { conclusionKind },
1802
1706
  createdAt: String(row.created_at),
1803
1707
  updatedAt: String(row.updated_at)
@@ -1919,15 +1823,7 @@ function createSqliteJobStore(options) {
1919
1823
  markCancelHonoured.run(now, String(jobId));
1920
1824
  });
1921
1825
  },
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.
1826
+ // Checkpoint rows go with the status, or the real handler resumes on the stand-in's value.
1931
1827
  async requeueTerminal(jobId, fence) {
1932
1828
  const now = clock.now();
1933
1829
  const decided = inTransaction(() => {
@@ -2116,15 +2012,11 @@ function createPostProcessingReader(options) {
2116
2012
  const curateMemory = toRow(
2117
2013
  selectJob.get(String(ids.curateMemory))
2118
2014
  );
2119
- const proposeImprovements = toRow(
2120
- selectJob.get(String(ids.proposeImprovements))
2121
- );
2122
2015
  const notification = notificationRow === void 0 ? void 0 : guardRunNotification2(JSON.parse(String(notificationRow.payload)));
2123
2016
  const curationSnapshot = readCurationSnapshot(db, options.agentQaRoot, ids, runId);
2124
2017
  return {
2125
2018
  ...analyzeRun === void 0 ? {} : { analyzeRun },
2126
2019
  ...curateMemory === void 0 ? {} : { curateMemory },
2127
- ...proposeImprovements === void 0 ? {} : { proposeImprovements },
2128
2020
  ...notification === void 0 ? {} : { notification },
2129
2021
  ...curationSnapshot === void 0 ? {} : { curationSnapshot }
2130
2022
  };
@@ -2183,8 +2075,6 @@ function receiptWriter(input) {
2183
2075
  executionContext: input.executionContext,
2184
2076
  writerLeaseId: input.lease.leaseId,
2185
2077
  epoch: input.lease.epoch,
2186
- ...input.resolveTrustPriors === void 0 ? {} : { resolveTrustPriors: input.resolveTrustPriors },
2187
- ...input.resolvePromotionPosture === void 0 ? {} : { resolvePromotionPosture: input.resolvePromotionPosture },
2188
2078
  ...input.sourceBuildOrdinal === void 0 ? {} : { sourceBuildOrdinal: input.sourceBuildOrdinal },
2189
2079
  ...input.sourceRunId === void 0 ? {} : { sourceRunId: input.sourceRunId }
2190
2080
  });
@@ -2233,10 +2123,6 @@ var LOCAL_CURATION_CAPABILITIES = Object.freeze({
2233
2123
  reconcileRunOutcomesProjection,
2234
2124
  enqueueCurationCandidateProjection,
2235
2125
  readCanonicalBundleAnchors,
2236
- readCanonicalBundleStateDigests,
2237
- readCanaryPostureFacts: canaryPostureFactsFor,
2238
- readStandingGateReceipts: standingReceiptDigestsFor,
2239
- readLowerTrustShares: lowerTrustSharesFor,
2240
2126
  runOutcomesProjectionVersion: RUN_OUTCOMES_PROJECTION_VERSION,
2241
2127
  canaryOutcomeObservationId,
2242
2128
  maintenance: Object.freeze({
@@ -2260,21 +2146,11 @@ function isTargetName(value) {
2260
2146
  return typeof value === "string" && value.trim().length > 0;
2261
2147
  }
2262
2148
  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() };
2149
+ const targets = isRecord(artifact) ? artifact.targets : void 0;
2150
+ if (!Array.isArray(targets) || targets.length === 0 || !targets.every(isTargetName)) {
2151
+ return { status: "unknown" };
2152
+ }
2153
+ return { status: "recorded", names: [...new Set(targets)].sort() };
2278
2154
  }
2279
2155
  function createRunsReader(agentQaRoot) {
2280
2156
  function enumerate() {
@@ -2297,25 +2173,11 @@ function createRunsReader(agentQaRoot) {
2297
2173
  list() {
2298
2174
  return Promise.resolve(enumerate());
2299
2175
  },
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
- */
2176
+ // Matches the recorded `runId` over enumerated documents, so `../../etc` addresses nothing.
2306
2177
  get(runId) {
2307
2178
  return Promise.resolve(enumerate().find((document) => document.runId === runId));
2308
2179
  },
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
- */
2180
+ // A resume point below the earliest surviving seq throws `ReplayIncompleteError`.
2319
2181
  readEvents(runId, fromSeq) {
2320
2182
  const recorded = [...new FsEventLog2({ root: agentQaRoot, runId }).read(0)].sort((left, right) => left.seq - right.seq);
2321
2183
  if (recorded.length === 0) return Promise.resolve([]);
@@ -2325,22 +2187,7 @@ function createRunsReader(agentQaRoot) {
2325
2187
  }
2326
2188
  return Promise.resolve(recorded.filter((event) => event.seq >= fromSeq));
2327
2189
  },
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
- */
2190
+ // Read through the version guard; an ambiguous step reports its candidates instead of guessing.
2344
2191
  readStepEvidence(runId, address) {
2345
2192
  const document = enumerate().find((candidate) => candidate.runId === runId);
2346
2193
  if (document === void 0) return Promise.resolve({ kind: "run-not-recorded" });
@@ -2358,12 +2205,7 @@ function createRunsReader(agentQaRoot) {
2358
2205
  evidence === void 0 ? { kind: "step-not-found" } : { kind: "found", evidence }
2359
2206
  );
2360
2207
  },
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
- */
2208
+ // The caller's id is compared, never joined onto a path; an absent snapshot is `unknown`.
2367
2209
  getRecordedTargets(runId) {
2368
2210
  const runsDir = join7(agentQaRoot, RUNS_SUBDIR3);
2369
2211
  if (!existsSync4(runsDir)) return Promise.resolve({ status: "unknown" });
@@ -2383,11 +2225,9 @@ function createRunsReader(agentQaRoot) {
2383
2225
  }
2384
2226
  };
2385
2227
  }
2386
- function createKnowledgeReader(resolveKnowledgeRoot, sightMemoryEntities, resolvePromotionPosture, resolveTrustPriors) {
2228
+ function createKnowledgeReader(resolveKnowledgeRoot, sightMemoryEntities) {
2387
2229
  return createKnowledgeReaderPort({
2388
2230
  resolveKnowledgeRoot,
2389
- ...resolveTrustPriors === void 0 ? {} : { resolveTrustPriors },
2390
- ...resolvePromotionPosture === void 0 ? {} : { resolvePromotionPosture },
2391
2231
  ...sightMemoryEntities === void 0 ? {} : { sightMemoryEntities }
2392
2232
  });
2393
2233
  }
@@ -2421,10 +2261,7 @@ function createKnowledgeReview(input) {
2421
2261
  return next;
2422
2262
  }
2423
2263
  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.
2264
+ // A missing identity is minted once per call and returned, so a retry is the same change.
2428
2265
  change: (request) => serialized(async () => {
2429
2266
  const { knowledgeRoot } = await roots();
2430
2267
  const operationId = request.operationId ?? randomUUID3();
@@ -2438,18 +2275,14 @@ function createKnowledgeReview(input) {
2438
2275
  // `knowledge.reject` or `knowledge.forget`, which is the verb's own op id.
2439
2276
  opId: `knowledge.${request.verb}`,
2440
2277
  executionContext: (input.resolveExecutionContext ?? getActiveExecutionContext)(),
2441
- ...input.resolveTrustPriors === void 0 ? {} : { resolveTrustPriors: input.resolveTrustPriors },
2442
- ...input.resolvePromotionPosture === void 0 ? {} : { resolvePromotionPosture: input.resolvePromotionPosture },
2443
2278
  ...input.knowledgeChangeDelegate === void 0 ? {} : { delegate: input.knowledgeChangeDelegate },
2444
2279
  ...authority === void 0 ? {} : { writerAuthority: authority }
2445
2280
  });
2446
2281
  return knowledgeChangeOutput(request.verb, operationId, result);
2447
2282
  })
2448
- // 2026-10-01 (432-17, D-05/D-22): `propose` and `apply` are deleted with the proposal inbox.
2449
2283
  };
2450
2284
  }
2451
2285
  async function applyKnowledgeChange(input) {
2452
- const posture = await input.resolvePromotionPosture?.();
2453
2286
  const ledger = openAddressingLedgerForReview({
2454
2287
  path: join7(input.agentQaRoot, ADDRESSING_LEDGER_DB_FILE)
2455
2288
  });
@@ -2462,8 +2295,6 @@ async function applyKnowledgeChange(input) {
2462
2295
  opId: input.opId,
2463
2296
  executionContext: input.executionContext,
2464
2297
  resolveBundleShortId: (shortId) => ledger.lookupByShortId(shortId)?.machineId,
2465
- ...input.resolveTrustPriors === void 0 ? {} : { resolveTrustPriors: input.resolveTrustPriors },
2466
- ...posture === void 0 ? {} : { resolvePromotionPosture: posture },
2467
2298
  ...input.delegate === void 0 ? {} : { delegate: input.delegate },
2468
2299
  ...input.writerAuthority === void 0 ? {} : { writerAuthority: input.writerAuthority }
2469
2300
  });
@@ -2496,9 +2327,6 @@ async function withEntityResolutionDeps(input, body) {
2496
2327
  return await body({
2497
2328
  ledger,
2498
2329
  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
2330
  sources: [createMemorySightingSource(() => knowledgeRoot)]
2503
2331
  });
2504
2332
  } finally {
@@ -2631,16 +2459,9 @@ function createDraftEvaluation(input) {
2631
2459
  elementsForTest: (filePath, testName) => fingerprints().elements.get(draftIdentityKey({ filePath, testName })) ?? []
2632
2460
  },
2633
2461
  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.
2462
+ // Receipts are machine-local working state under the runtime root, not committed artifacts.
2637
2463
  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.
2464
+ // `product` is gate 3's observed product: stability and the index ask about one app.
2644
2465
  suiteStability: {
2645
2466
  port: input.authoringFunnel,
2646
2467
  product: () => observedProduct
@@ -2674,10 +2495,7 @@ var LocalAgentClosedError = class extends Error {
2674
2495
  name = "LocalAgentClosedError";
2675
2496
  };
2676
2497
  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.
2498
+ // Frozen deep, because the inner arrays are what the queue claims against.
2681
2499
  "masker-active": Object.freeze([]),
2682
2500
  "no-masker": Object.freeze(["curate-memory"])
2683
2501
  });
@@ -2803,63 +2621,24 @@ function createAnalysisArtifactReader(agentQaRoot) {
2803
2621
  }
2804
2622
  };
2805
2623
  }
2806
- function postRunCapabilities(input, knowledgeRoot, analysisTokenBudget, jobStore, proposeGate1MaxPerJob) {
2624
+ function postRunCapabilities(input, knowledgeRoot, analysisTokenBudget) {
2807
2625
  const postRun = input.postRun;
2808
2626
  if (postRun === void 0) return void 0;
2809
2627
  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
2628
  loadCompletedRunHandoff: (runId) => loadCompletedRunHandoff(
2820
2629
  join7(input.agentQaRoot, RUNS_SUBDIR3, slugBranch4(runId), HANDOFF_FILE)
2821
2630
  ),
2822
2631
  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.
2632
+ // Derived from the artifact's own `runId` through `slugBranch`; a payload carries no path.
2842
2633
  persistAnalysis: (artifact) => writeAnalysisArtifact(
2843
2634
  join7(input.agentQaRoot, RUNS_SUBDIR3, slugBranch4(artifact.runId), ANALYSIS_FILE),
2844
2635
  artifact
2845
2636
  ),
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.
2637
+ // Keyed by the stable identity (product, filePath, testName), never a per-run id; read-only.
2849
2638
  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.
2639
+ // Through the shared `lookupEvidenceBlob`, so `runs.artifact` and this job agree on blobs.
2860
2640
  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.
2641
+ // Host-resolved from the job model's profile; this composition adds no knob.
2863
2642
  ...analysisTokenBudget === void 0 ? {} : { analysisTokenBudget }
2864
2643
  };
2865
2644
  }
@@ -2973,7 +2752,6 @@ function composeLocalAgent(input) {
2973
2752
  // which is also what its header will say. A container seeded from another session's override
2974
2753
  // would make one conversation's verb the next conversation's opening posture.
2975
2754
  state: createChatState({ activeModel: composedChatModelName })
2976
- // 432-13 (D-14): no pin holder. `/pin` left chat resolution with the op gate it tightened.
2977
2755
  };
2978
2756
  chatScopes.set(sessionId, scope);
2979
2757
  return scope;
@@ -2984,11 +2762,7 @@ function composeLocalAgent(input) {
2984
2762
  const chatSessionLog = createJsonlSessionLog({
2985
2763
  root: input.agentQaRoot,
2986
2764
  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.
2765
+ // The composed model, masked: a live override would stamp one session's switch on later ones.
2992
2766
  model: () => input.mask(composedChatModelName),
2993
2767
  toolsetHash: () => chatToolsetHash(composedRegistry)
2994
2768
  });
@@ -3039,14 +2813,7 @@ function composeLocalAgent(input) {
3039
2813
  stage = createPlannerStage({
3040
2814
  model: chatModelsByName.get(activeName) ?? input.chatModel,
3041
2815
  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.
2816
+ // Read per request: a stage is shared by every session planning under one model.
3050
2817
  context: (contextRequest) => {
3051
2818
  const scope = chatScopeFor(contextRequest.sessionId).state.read();
3052
2819
  return { activeModel: scope.activeModel, conversation: scope.conversation };
@@ -3060,41 +2827,22 @@ function composeLocalAgent(input) {
3060
2827
  const chatCompaction = input.chatAccounting === void 0 ? void 0 : createChatCompactionHook({
3061
2828
  accounting: input.chatAccounting,
3062
2829
  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.
2830
+ // What compaction leaves is the conversation the prompt renders, kept per session.
3069
2831
  onConversation: (messages, sessionId) => {
3070
2832
  chatScopeFor(sessionId).state.setConversation(messages);
3071
2833
  }
3072
2834
  });
3073
2835
  const chatTurn = createTurnEngine({
3074
2836
  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.
2837
+ // The real conjunction grammar, quote-aware.
3077
2838
  split: splitClauses,
3078
2839
  reconcile: input.chatReconcile ?? deterministicOverrideStrategy,
3079
2840
  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.
2841
+ // Minted per stage: a table hit is a person typing (`human`); a planned call is `agent`.
3087
2842
  mint: {
3088
2843
  forStage: (stage) => stage === "resolver" ? CHAT_RESOLVER_CALLER : CHAT_PLANNER_CALLER
3089
2844
  },
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.
2845
+ // Classify the original bytes before projecting: `invokeFunction` does not mask.
3098
2846
  invoke: async (functionId, invokeInput, caller) => {
3099
2847
  if (composedRegistry === void 0) {
3100
2848
  throw new TypeError("a chat turn ran before its registry finished composing");
@@ -3105,24 +2853,9 @@ function composeLocalAgent(input) {
3105
2853
  },
3106
2854
  ...chatPlanner === void 0 ? {} : { planner: chatPlanner },
3107
2855
  ...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.
2856
+ // Resolved per session: `/model` and `/quit` act on the conversation running the turn.
3116
2857
  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.
2858
+ // Applies nothing: `/model` resolves and caches by name, so the apply cannot throw midway.
3126
2859
  validate: async (directive, argument) => {
3127
2860
  if (directive === "model" && argument !== void 0 && input.chatModelFor !== void 0) {
3128
2861
  chatModelsByName.set(argument, await input.chatModelFor(argument));
@@ -3144,17 +2877,7 @@ function composeLocalAgent(input) {
3144
2877
  }
3145
2878
  }),
3146
2879
  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.
2880
+ // Re-read per turn and per session, and masked like the log it is compared with.
3158
2881
  config: (sessionId) => {
3159
2882
  const ids = chatToolsetIds(composedRegistry);
3160
2883
  return {
@@ -3162,20 +2885,9 @@ function composeLocalAgent(input) {
3162
2885
  toolset: { hash: chatToolsetHash(composedRegistry), ids }
3163
2886
  };
3164
2887
  },
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.
2888
+ // Bound only where the loop is; elsewhere an empty vocabulary refuses with the wrong cause.
3175
2889
  ...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.
2890
+ // Spread conditionally, so a host binding no surface leaves `present` absent.
3179
2891
  ...input.chatPresent === void 0 ? {} : { present: input.chatPresent },
3180
2892
  now: () => clock.now(),
3181
2893
  mintTurnId: () => `turn-${randomUUID3()}`
@@ -3191,19 +2903,7 @@ function composeLocalAgent(input) {
3191
2903
  ...input.resolveWriterAuthority === void 0 ? {} : { resolveWriterAuthority: input.resolveWriterAuthority }
3192
2904
  }).list()
3193
2905
  },
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.
2906
+ // One read-only connection per verb; absent data answers empty, other failures propagate.
3207
2907
  outcomes: {
3208
2908
  testsIn: async (product, filePath) => {
3209
2909
  const knowledgeRoot = await input.knowledgeRoot();
@@ -3237,145 +2937,47 @@ function composeLocalAgent(input) {
3237
2937
  return runsReader.get(runId);
3238
2938
  }
3239
2939
  },
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.
2940
+ // List verbs are live sighting sites: an entity gets its address when first surfaced.
3244
2941
  knowledge: createKnowledgeReader(
3245
2942
  input.knowledgeRoot,
3246
- createMemorySightingHook(entityResolutionInput),
3247
- input.resolvePromotionPosture,
3248
- input.resolveTrustPriors
2943
+ createMemorySightingHook(entityResolutionInput)
3249
2944
  ),
3250
2945
  config: input.config,
3251
2946
  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.
2947
+ // Optional, so a read-only host reports the mutating verbs as `unbound` by name.
3256
2948
  knowledgeReview: createKnowledgeReview({
3257
- ...input.resolveTrustPriors === void 0 ? {} : { resolveTrustPriors: input.resolveTrustPriors },
3258
- ...input.resolvePromotionPosture === void 0 ? {} : { resolvePromotionPosture: input.resolvePromotionPosture },
3259
2949
  agentQaRoot: input.agentQaRoot,
3260
2950
  resolveKnowledgeRoot: input.knowledgeRoot,
3261
2951
  ...input.knowledgeChangeDelegate === void 0 ? {} : { knowledgeChangeDelegate: input.knowledgeChangeDelegate },
3262
2952
  ...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.)
2953
+ // The host's own context, so a direct change records the context it is made in.
3265
2954
  ...input.resolveExecutionContext === void 0 ? {} : { resolveExecutionContext: input.resolveExecutionContext }
3266
2955
  }),
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).
2956
+ // On every host, unlike `postRun`: a host that never drains still reads what the chain did.
3276
2957
  postProcessing: createPostProcessingReader({
3277
2958
  agentQaRoot: input.agentQaRoot,
3278
2959
  idSource: input.idSource ?? DIGEST_JOB_ID_SOURCE
3279
2960
  }),
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.
2961
+ // An absent port must be an absent field: the registry lists capabilities off this record.
3288
2962
  ...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.
2963
+ // On every host, and the same reader the analysis job uses, so both agree where a blob is.
3295
2964
  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).
2965
+ // Present only if this host can run; the registry lists `runs.start` from the same record.
3299
2966
  ...input.runExecution === void 0 ? {} : {
3300
2967
  runInvocation: createLocalRunInvocation({
3301
2968
  agentQaRoot: input.agentQaRoot,
3302
2969
  execution: input.runExecution
3303
2970
  })
3304
2971
  },
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).
2972
+ // Bindable from the roots alone, so every host serves the floor or the no-history clause.
3310
2973
  analysis: createAnalysisArtifactReader(input.agentQaRoot),
3311
2974
  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).
2975
+ // Spread conditionally so `agent.explain` lists `unbound` where no catalog is handed in.
3318
2976
  ...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.
2977
+ // Spread conditionally, so `runs.liveurl` declares `unbound` where no origin is served.
3321
2978
  ...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.
2979
+ // The funnel reads records this host owns, so it is unconditional; its receipts port lists.
3376
2980
  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
2981
  ...input.feedback === void 0 ? {} : { feedback: input.feedback },
3380
2982
  ...input.feedbackReads === void 0 ? {} : { feedbackReads: input.feedbackReads },
3381
2983
  quarantine: createQuarantineOperations({
@@ -3384,24 +2986,13 @@ function composeLocalAgent(input) {
3384
2986
  mintIdentityPath: input.mintQuarantineIdentityPath,
3385
2987
  ...input.resolveWriterAuthority === void 0 ? {} : { resolveWriterAuthority: input.resolveWriterAuthority }
3386
2988
  }),
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.
2989
+ // Bound here, so `agent.ask` is a real capability on every host.
3390
2990
  chatTurn: {
3391
2991
  // The engine also echoes authored clause text on receipts and refusal answers. Mask
3392
2992
  // the complete outward projection after execution, never the message it dispatches.
3393
2993
  runTurn: async (turnInput) => projectChatMembers(await chatTurn.runTurn(turnInput), CHAT_TURN_RESULT_RULES, input.mask)
3394
2994
  },
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.
2995
+ // Both halves spread conditionally: a host that can parse but not run says `unbound`.
3405
2996
  ...input.draftAuthoring === void 0 || input.runExecution === void 0 ? {} : {
3406
2997
  draftEvaluation: createDraftEvaluation({
3407
2998
  authoring: input.draftAuthoring,
@@ -3422,50 +3013,7 @@ function composeLocalAgent(input) {
3422
3013
  }
3423
3014
  })
3424
3015
  },
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.
3016
+ // The arch import scan reads comments too: never put a quote after the word `from`.
3469
3017
  ...authoringLoopHost === void 0 ? {} : {
3470
3018
  authoringRead: {
3471
3019
  async getAuthoringBrief(intent, scope) {
@@ -3473,35 +3021,21 @@ function composeLocalAgent(input) {
3473
3021
  return createAuthoringReader({ knowledgeRoot }).getAuthoringBrief(intent, scope);
3474
3022
  },
3475
3023
  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.
3024
+ // Off the same port as the brief and corpus, so a loop host names its own targets.
3479
3025
  configuredTargetNames: () => authoringLoopHost.configuredTargetNames()
3480
3026
  },
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.
3027
+ // Its own key: a scope needs config and workspace, the brief needs an index.
3487
3028
  authoringScope: {
3488
3029
  deriveDefault: () => authoringLoopHost.deriveDefaultScope()
3489
3030
  },
3490
3031
  draftShelf: {
3491
3032
  model: async () => await input.model(),
3492
3033
  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.
3034
+ // The one RFC 6902 applier, at the host because it needs `core`'s yaml CST.
3496
3035
  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`.
3036
+ // Same seam as the verbs above: the boundary check needs `core`.
3499
3037
  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.
3038
+ // The evaluator's reader; probes use the gate-run caller, so outcomes exclude them.
3505
3039
  runs: {
3506
3040
  start: async (request) => {
3507
3041
  if (composedRegistry === void 0) {
@@ -3516,8 +3050,6 @@ function composeLocalAgent(input) {
3516
3050
  },
3517
3051
  get: (runId) => runsReader.get(runId)
3518
3052
  },
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
3053
  verifyDraft: async (verifyInput) => {
3522
3054
  if (composedRegistry === void 0) {
3523
3055
  throw new TypeError("a draft was verified before its registry finished composing");
@@ -3530,28 +3062,13 @@ function composeLocalAgent(input) {
3530
3062
  );
3531
3063
  }
3532
3064
  }
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
3065
  },
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`.
3066
+ // Each config-editor half spreads on its own condition, so a missing one reads `unbound`.
3541
3067
  ...input.configFiles === void 0 ? {} : { configFiles: input.configFiles },
3542
3068
  ...input.configValidate === void 0 ? {} : { configValidate: input.configValidate },
3543
3069
  ...input.scaffoldContent === void 0 ? {} : { scaffoldContent: input.scaffoldContent },
3544
3070
  ...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
3071
  }
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
3072
  });
3556
3073
  composedRegistry = registry;
3557
3074
  let opened;
@@ -3563,7 +3080,6 @@ function composeLocalAgent(input) {
3563
3080
  const model = await input.model();
3564
3081
  const knowledgeRoot = await input.knowledgeRoot();
3565
3082
  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
3083
  const store = createSqliteJobStore({
3568
3084
  path: join7(input.agentQaRoot, AGENT_JOB_STORE_DB_FILE),
3569
3085
  clock,
@@ -3572,7 +3088,7 @@ function composeLocalAgent(input) {
3572
3088
  let jobs;
3573
3089
  try {
3574
3090
  const kinds = input.jobKinds ?? createRealJobKindRegistry(
3575
- postRunCapabilities(input, knowledgeRoot, analysisTokenBudget, store, proposeGate1MaxPerJob)
3091
+ postRunCapabilities(input, knowledgeRoot, analysisTokenBudget)
3576
3092
  );
3577
3093
  const idSource = input.idSource ?? DIGEST_JOB_ID_SOURCE;
3578
3094
  const unclaimableKinds = UNCLAIMABLE_BY_PROSE_POSTURE[input.postRun?.candidateProse ?? "no-masker"];
@@ -3659,13 +3175,330 @@ import {
3659
3175
  } from "@vostride/agent-qa-runner-local";
3660
3176
  import { resolveBuildTag } from "@vostride/agent-qa-runner-local";
3661
3177
 
3178
+ // src/store/mark-ledger.ts
3179
+ import { closeSync as closeSync5, existsSync as existsSync5, fsyncSync as fsyncSync5, openSync as openSync5, readFileSync as readFileSync6, renameSync as renameSync4, unlinkSync, writeFileSync as writeFileSync5 } from "node:fs";
3180
+ import { join as join9 } from "node:path";
3181
+ import {
3182
+ canonicalJson as canonicalJson6,
3183
+ ImprovementPlanSchema,
3184
+ MarkReceiptSchema,
3185
+ markAddressKey,
3186
+ slugBranch as slugBranch5
3187
+ } from "@vostride/agent-qa-schema";
3188
+ import { z as z2 } from "zod";
3189
+
3190
+ // src/composition/run-layout.ts
3191
+ import { HANDOFF_FILE as HANDOFF_FILE2, RUNS_SUBDIR as RUNS_SUBDIR4, loadCompletedRunHandoff as loadCompletedRunHandoff2 } from "@vostride/agent-qa-runner-local";
3192
+
3193
+ // src/store/mark-lock.ts
3194
+ import { randomUUID as randomUUID4 } from "node:crypto";
3195
+ import { closeSync as closeSync4, fsyncSync as fsyncSync4, lstatSync, mkdirSync as mkdirSync4, openSync as openSync4, readFileSync as readFileSync5, renameSync as renameSync3, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "node:fs";
3196
+ import { dirname as dirname7, join as join8 } from "node:path";
3197
+ import { z } from "zod";
3198
+ var ownerSchema = z.object({
3199
+ schemaVersion: z.literal(2),
3200
+ kind: z.literal("mark-ledger-lock"),
3201
+ pid: z.number().int().positive(),
3202
+ token: z.uuid(),
3203
+ runId: z.string().min(1)
3204
+ }).strict();
3205
+ function syncDirectory(path) {
3206
+ const fd = openSync4(path, "r");
3207
+ try {
3208
+ fsyncSync4(fd);
3209
+ } finally {
3210
+ closeSync4(fd);
3211
+ }
3212
+ }
3213
+ function readOwner(path, runId) {
3214
+ try {
3215
+ const directory = lstatSync(path);
3216
+ if (!directory.isDirectory() || directory.isSymbolicLink()) return void 0;
3217
+ const ownerFile = join8(path, "owner.json");
3218
+ const file = lstatSync(ownerFile);
3219
+ if (!file.isFile() || file.isSymbolicLink()) return void 0;
3220
+ const parsed = ownerSchema.safeParse(JSON.parse(readFileSync5(ownerFile, "utf8")));
3221
+ return parsed.success && parsed.data.runId === runId ? parsed.data : void 0;
3222
+ } catch {
3223
+ return void 0;
3224
+ }
3225
+ }
3226
+ function positivelyDead(pid) {
3227
+ try {
3228
+ process.kill(pid, 0);
3229
+ return false;
3230
+ } catch (error) {
3231
+ return error.code === "ESRCH";
3232
+ }
3233
+ }
3234
+ async function acquireMarkLock(lock, runId, busy) {
3235
+ const owner = { schemaVersion: 2, kind: "mark-ledger-lock", pid: process.pid, token: randomUUID4(), runId };
3236
+ const candidate = `${lock}.candidate-${owner.token}`;
3237
+ mkdirSync4(candidate);
3238
+ const fd = openSync4(join8(candidate, "owner.json"), "wx", 384);
3239
+ try {
3240
+ writeFileSync4(fd, JSON.stringify(owner));
3241
+ fsyncSync4(fd);
3242
+ } finally {
3243
+ closeSync4(fd);
3244
+ }
3245
+ syncDirectory(candidate);
3246
+ const started = Date.now();
3247
+ let acquired = false;
3248
+ try {
3249
+ while (!acquired) {
3250
+ try {
3251
+ mkdirSync4(lock);
3252
+ renameSync3(join8(candidate, "owner.json"), join8(lock, "owner.json"));
3253
+ syncDirectory(lock);
3254
+ syncDirectory(dirname7(lock));
3255
+ rmSync3(candidate, { recursive: true });
3256
+ acquired = true;
3257
+ } catch (error) {
3258
+ if (!["EEXIST", "ENOTEMPTY", "ENOTDIR", "EISDIR", "EPERM", "EACCES"].includes(error.code ?? "")) throw error;
3259
+ const current = readOwner(lock, runId);
3260
+ if (current !== void 0 && positivelyDead(current.pid)) {
3261
+ try {
3262
+ renameSync3(lock, `${lock}.dead-${current.token}`);
3263
+ syncDirectory(dirname7(lock));
3264
+ continue;
3265
+ } catch (recoveryError) {
3266
+ if (recoveryError.code === "ENOENT") continue;
3267
+ if (!["EEXIST", "ENOTEMPTY", "ENOTDIR", "EISDIR", "EPERM", "EACCES"].includes(recoveryError.code ?? "")) throw recoveryError;
3268
+ }
3269
+ }
3270
+ const remaining = 1e4 - (Date.now() - started);
3271
+ if (remaining <= 0) throw busy();
3272
+ await new Promise((resolve2) => setTimeout(resolve2, Math.min(50, remaining)));
3273
+ }
3274
+ }
3275
+ } finally {
3276
+ if (!acquired) rmSync3(candidate, { recursive: true, force: true });
3277
+ }
3278
+ return () => {
3279
+ if (readOwner(lock, runId)?.token !== owner.token) return;
3280
+ const released = `${lock}.released-${owner.token}`;
3281
+ renameSync3(lock, released);
3282
+ syncDirectory(dirname7(lock));
3283
+ rmSync3(released, { recursive: true });
3284
+ syncDirectory(dirname7(lock));
3285
+ };
3286
+ }
3287
+
3288
+ // src/store/mark-ledger.ts
3289
+ var MARKS_LEDGER_FILE = "marks.jsonl";
3290
+ var MARKS_LEDGER_LOCK_FILE = "marks.jsonl.lock";
3291
+ var MARK_INTENT_FILE = "mark-intent.json";
3292
+ var MARK_INTENT_STAGES = ["prepared", "evidence", "applied"];
3293
+ var intentSchema = z2.object({
3294
+ schemaVersion: z2.literal(2),
3295
+ mark: MarkReceiptSchema,
3296
+ stage: z2.enum(MARK_INTENT_STAGES),
3297
+ operationId: z2.uuid(),
3298
+ plan: ImprovementPlanSchema.optional()
3299
+ }).strict().superRefine((intent, ctx) => {
3300
+ if (intent.stage === "applied" && intent.mark.disposition.kind === "drafted") {
3301
+ ctx.addIssue({ code: "custom", path: ["mark", "disposition"], message: "a mark applies its fix; it never files a draft" });
3302
+ }
3303
+ });
3304
+ function atomicPublish(path, text) {
3305
+ const staged = `${path}.pending`;
3306
+ const fd = openSync5(staged, "w", 384);
3307
+ try {
3308
+ writeFileSync5(fd, text);
3309
+ fsyncSync5(fd);
3310
+ } finally {
3311
+ closeSync5(fd);
3312
+ }
3313
+ renameSync4(staged, path);
3314
+ }
3315
+ var MarkLedgerBusyError = class extends Error {
3316
+ constructor() {
3317
+ super("Mark ledger is busy; retry the mark after the current transaction finishes");
3318
+ this.name = "MarkLedgerBusyError";
3319
+ }
3320
+ };
3321
+ var MarkLedgerCorruptError = class extends Error {
3322
+ constructor(line) {
3323
+ super(`Mark ledger is corrupt at line ${line}`);
3324
+ this.line = line;
3325
+ this.name = "MarkLedgerCorruptError";
3326
+ }
3327
+ };
3328
+ var MarkIntentUnusableError = class extends Error {
3329
+ constructor(intentPath, reason) {
3330
+ const what = reason === "earlier-version" ? "was written by an earlier agent-qa version, which this version does not read" : reason === "unreadable" ? "is unreadable" : "does not match this run's mark ledger";
3331
+ super(`The unfinished-mark record ${intentPath} ${what}; nothing was marked. Remove that file, then mark again.`);
3332
+ this.intentPath = intentPath;
3333
+ this.reason = reason;
3334
+ this.name = "MarkIntentUnusableError";
3335
+ }
3336
+ };
3337
+ function isEarlierVersionIntent(value) {
3338
+ if (value === null || typeof value !== "object") return false;
3339
+ const intent = value;
3340
+ return "proposalUuid" in intent || "testDraft" in intent || intent["stage"] === "drafted";
3341
+ }
3342
+ function latestMarkReceiptsPerAddress(receipts) {
3343
+ const latest = /* @__PURE__ */ new Map();
3344
+ for (const receipt of receipts) {
3345
+ const key = markAddressKey(receipt);
3346
+ if ((latest.get(key)?.ordinal ?? 0) < receipt.ordinal) latest.set(key, receipt);
3347
+ }
3348
+ return [...latest.values()].sort(
3349
+ (a, b) => a.testIndex - b.testIndex || (a.stepIndex ?? -1) - (b.stepIndex ?? -1)
3350
+ );
3351
+ }
3352
+ function latestMarkReceiptFor(receipts, runId, address) {
3353
+ if (address.runId !== runId) return void 0;
3354
+ return latestMarkReceiptsPerAddress(receipts).find((receipt) => markAddressKey(receipt) === markAddressKey(address) && (address.testId === void 0 || receipt.testId === address.testId));
3355
+ }
3356
+ var MarkLedgerNoRunError = class extends Error {
3357
+ constructor() {
3358
+ super("run-not-found: mark ledger requires a completed run handoff");
3359
+ this.name = "MarkLedgerNoRunError";
3360
+ }
3361
+ };
3362
+ var FsMarkLedger = class {
3363
+ dir;
3364
+ file;
3365
+ lock;
3366
+ runId;
3367
+ runSlug;
3368
+ inTransaction = false;
3369
+ intentValue;
3370
+ constructor(config) {
3371
+ this.runId = config.runId;
3372
+ this.runSlug = slugBranch5(config.runId);
3373
+ this.dir = join9(config.root, RUNS_SUBDIR4, this.runSlug);
3374
+ this.file = join9(this.dir, MARKS_LEDGER_FILE);
3375
+ this.lock = join9(this.dir, MARKS_LEDGER_LOCK_FILE);
3376
+ }
3377
+ read() {
3378
+ let text;
3379
+ try {
3380
+ text = readFileSync6(this.file, "utf8");
3381
+ } catch (error) {
3382
+ if (error.code === "ENOENT") return [];
3383
+ throw error;
3384
+ }
3385
+ if (text === "") return [];
3386
+ const lines = text.split("\n");
3387
+ if (lines.pop() !== "") throw new MarkLedgerCorruptError(lines.length + 1);
3388
+ return lines.map((line, index) => {
3389
+ try {
3390
+ const receipt = MarkReceiptSchema.parse(JSON.parse(line));
3391
+ if (receipt.ordinal !== index + 1 || receipt.runId !== this.runId || receipt.runSlug !== this.runSlug) {
3392
+ throw new Error("ledger identity or ordinal mismatch");
3393
+ }
3394
+ return receipt;
3395
+ } catch {
3396
+ throw new MarkLedgerCorruptError(index + 1);
3397
+ }
3398
+ });
3399
+ }
3400
+ latestPerAddress() {
3401
+ return latestMarkReceiptsPerAddress(this.read());
3402
+ }
3403
+ latestFor(address) {
3404
+ if (address.runId !== this.runId) return void 0;
3405
+ return latestMarkReceiptFor(this.read(), this.runId, address);
3406
+ }
3407
+ /** Private resumable state is admitted only while this run's existing lock is held. */
3408
+ prepareIntent(mark, operationId) {
3409
+ if (!this.inTransaction) throw new Error("mark intent requires the run transaction");
3410
+ const prior = this.intentValue;
3411
+ if (prior !== void 0) {
3412
+ const request = (value) => ({
3413
+ runId: value.runId,
3414
+ testIndex: value.testIndex,
3415
+ testId: value.testId,
3416
+ stepIndex: value.stepIndex,
3417
+ kind: value.kind,
3418
+ note: value.note
3419
+ });
3420
+ if (canonicalJson6(request(prior.mark)) !== canonicalJson6(request(mark))) {
3421
+ throw new Error("An unfinished mark must be retried with its original address, kind and note");
3422
+ }
3423
+ return prior;
3424
+ }
3425
+ return this.checkpointIntent({ schemaVersion: 2, mark, operationId, stage: "prepared" });
3426
+ }
3427
+ checkpointIntent(value) {
3428
+ if (!this.inTransaction) throw new Error("mark intent requires the run transaction");
3429
+ const intent = intentSchema.parse(value);
3430
+ if (intent.mark.runId !== this.runId || intent.mark.runSlug !== this.runSlug || intent.mark.ordinal !== this.read().length + 1) throw new MarkLedgerCorruptError(intent.mark.ordinal);
3431
+ atomicPublish(join9(this.dir, MARK_INTENT_FILE), `${canonicalJson6(intent)}
3432
+ `);
3433
+ this.intentValue = intent;
3434
+ return intent;
3435
+ }
3436
+ async transact(fn) {
3437
+ if (!existsSync5(join9(this.dir, HANDOFF_FILE2))) throw new MarkLedgerNoRunError();
3438
+ const release = await acquireMarkLock(this.lock, this.runId, () => new MarkLedgerBusyError());
3439
+ try {
3440
+ const receipts = this.read();
3441
+ const nextOrdinal = receipts.length + 1;
3442
+ const intentPath = join9(this.dir, MARK_INTENT_FILE);
3443
+ this.intentValue = void 0;
3444
+ if (existsSync5(intentPath)) {
3445
+ let raw;
3446
+ try {
3447
+ raw = JSON.parse(readFileSync6(intentPath, "utf8"));
3448
+ } catch {
3449
+ throw new MarkIntentUnusableError(intentPath, "unreadable");
3450
+ }
3451
+ const parsed = intentSchema.safeParse(raw);
3452
+ if (!parsed.success) {
3453
+ throw new MarkIntentUnusableError(
3454
+ intentPath,
3455
+ isEarlierVersionIntent(raw) ? "earlier-version" : "unreadable"
3456
+ );
3457
+ }
3458
+ const intent = parsed.data;
3459
+ if (intent.mark.runId !== this.runId || intent.mark.runSlug !== this.runSlug) {
3460
+ throw new MarkIntentUnusableError(intentPath, "mismatched");
3461
+ }
3462
+ if (intent.mark.ordinal === nextOrdinal) this.intentValue = intent;
3463
+ else if (canonicalJson6(receipts[intent.mark.ordinal - 1]) === canonicalJson6(intent.mark)) unlinkSync(intentPath);
3464
+ else throw new MarkIntentUnusableError(intentPath, "mismatched");
3465
+ }
3466
+ this.inTransaction = true;
3467
+ const record = await fn(nextOrdinal);
3468
+ if (record === void 0) return void 0;
3469
+ const receipt = MarkReceiptSchema.parse({ ...record, ordinal: nextOrdinal });
3470
+ if (receipt.runId !== this.runId || receipt.runSlug !== this.runSlug) {
3471
+ throw new Error("mark receipt does not belong to this run ledger");
3472
+ }
3473
+ if (this.intentValue !== void 0 && (this.intentValue.stage !== "applied" || canonicalJson6(this.intentValue.mark) !== canonicalJson6(receipt))) {
3474
+ throw new MarkLedgerCorruptError(nextOrdinal);
3475
+ }
3476
+ const before = existsSync5(this.file) ? readFileSync6(this.file, "utf8") : "";
3477
+ atomicPublish(this.file, `${before}${canonicalJson6(receipt)}
3478
+ `);
3479
+ if (this.intentValue !== void 0) unlinkSync(intentPath);
3480
+ return receipt;
3481
+ } finally {
3482
+ this.inTransaction = false;
3483
+ this.intentValue = void 0;
3484
+ release();
3485
+ }
3486
+ }
3487
+ append(record) {
3488
+ return this.transact(async () => record);
3489
+ }
3490
+ };
3491
+
3492
+ // src/composition/action-cache.ts
3493
+ import { purgeActionCache } from "@vostride/agent-qa-runner-local";
3494
+
3662
3495
  // src/composition/compose-job-recovery.ts
3663
- import { join as join8 } from "node:path";
3496
+ import { join as join10 } from "node:path";
3664
3497
  import { createAgentJobQueue as createAgentJobQueue2, createRealJobKindRegistry as createRealJobKindRegistry2 } from "@vostride/agent-qa-agent";
3665
3498
  var JOB_RECOVERY_MAX_RESUMED_JOBS = 64;
3666
3499
  function composeJobRecovery(input) {
3667
3500
  const store = createSqliteJobStore({
3668
- path: join8(input.agentQaRoot, AGENT_JOB_STORE_DB_FILE),
3501
+ path: join10(input.agentQaRoot, AGENT_JOB_STORE_DB_FILE),
3669
3502
  clock: input.clock,
3670
3503
  checkpoints: createJobCheckpointStore({ root: input.agentQaRoot })
3671
3504
  });
@@ -3785,7 +3618,6 @@ function createMemorySessionLog(options) {
3785
3618
  };
3786
3619
  }
3787
3620
  export {
3788
- ALLOW_PRODUCTION_OPT_IN,
3789
3621
  BRIEF_CEILING,
3790
3622
  BRIEF_FRACTION,
3791
3623
  BuildOrdinalReservationError,
@@ -3802,25 +3634,32 @@ export {
3802
3634
  EMBEDDER_WORKER_TERM_GRACE_MS,
3803
3635
  ENTITY_INDEX_SWEEP_INTERVAL_MS,
3804
3636
  EmbedderWorkerTimeoutError,
3637
+ FsMarkLedger,
3805
3638
  GLOBAL_RECORD_CAP,
3639
+ HANDOFF_FILE2 as HANDOFF_FILE,
3806
3640
  InvalidChatSessionIdError,
3807
3641
  JOB_RECOVERY_MAX_RESUMED_JOBS,
3808
3642
  LOCAL_CURATION_CAPABILITIES,
3809
3643
  LOCAL_DAEMON_CAPABILITIES,
3810
3644
  LOCAL_KNOWLEDGE_PROBES,
3811
3645
  LocalAgentClosedError,
3646
+ MARKS_LEDGER_FILE,
3647
+ MARKS_LEDGER_LOCK_FILE,
3648
+ MARK_INTENT_FILE,
3812
3649
  MIN_BRIEF,
3813
3650
  MIN_CAUTION,
3814
3651
  MIN_MEMORY_TOOL_CONTEXT_WINDOW,
3652
+ MarkIntentUnusableError,
3653
+ MarkLedgerBusyError,
3654
+ MarkLedgerCorruptError,
3655
+ MarkLedgerNoRunError,
3815
3656
  QUARANTINE_CLASS_DISPOSITIONS,
3816
3657
  QUARANTINE_FLOOR_ELIGIBILITY,
3817
3658
  READ_BODY_FRACTION,
3818
- REFUSED_RUN_REQUEST_FIELDS,
3819
- RunDeniedError,
3659
+ RUNS_SUBDIR4 as RUNS_SUBDIR,
3820
3660
  RunIdAlreadyInUseError,
3821
3661
  RunIdNotMintedError,
3822
3662
  RunProducedNoEventsError,
3823
- RunRequestFieldRefusedError,
3824
3663
  SUITE_LIFECYCLE_LANE,
3825
3664
  SYSTEM_CLOCK,
3826
3665
  SuiteBoundaryWhileLiveError,
@@ -3829,7 +3668,6 @@ export {
3829
3668
  WEAK_MODEL_CAP_SCALE,
3830
3669
  applyKnowledgeChange,
3831
3670
  buildMemberRunConfig,
3832
- buildProductionRefusal,
3833
3671
  buildRunPorts,
3834
3672
  buildScreenshotCompressor,
3835
3673
  buildSuiteMemberExecutor,
@@ -3849,15 +3687,17 @@ export {
3849
3687
  createRunsReader,
3850
3688
  createSuiteLifecycle,
3851
3689
  latestBuildOrdinal,
3690
+ latestMarkReceiptFor,
3691
+ latestMarkReceiptsPerAddress,
3852
3692
  listSessions,
3693
+ loadCompletedRunHandoff2 as loadCompletedRunHandoff,
3853
3694
  lookupEvidenceBlob,
3854
3695
  mapVerdictToExitCode,
3855
3696
  memoryToolGate,
3856
3697
  mintChatSessionId,
3857
3698
  openRunKnowledgeComposition,
3699
+ purgeActionCache,
3858
3700
  readEvidenceBlobBytes,
3859
- refuseProductionWithoutOptIn,
3860
- refuseUnavailableRunId,
3861
3701
  registerAdapterLiveStream,
3862
3702
  reserveBuildOrdinalForProducts,
3863
3703
  resolveBuildTag,