@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.cjs +452 -600
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +299 -1197
- package/dist/index.d.ts +299 -1197
- package/dist/index.js +401 -561
- package/dist/index.js.map +1 -1
- package/package.json +12 -11
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
|
-
//
|
|
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
|
-
//
|
|
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()
|
|
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
|
-
//
|
|
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
|
-
--
|
|
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
|
-
--
|
|
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
|
-
--
|
|
1426
|
+
-- A retrying row is not claimable before this instant.
|
|
1515
1427
|
not_before TEXT
|
|
1516
1428
|
)`,
|
|
1517
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
2264
|
-
if (!
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
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,
|