@vostride/agent-qa-agent-local 0.0.0-stage → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2087 @@
1
+ import { EvidenceBlobReaderPort, JobStorePort, ConfigReaderPort, JobModelCapability, StartedRun, ConfigFilesPortLike, ConfigValidatePortLike, ConfigScaffoldContentPortLike, FunctionDeps, CapabilityCatalogPort, LiveViewPort, RunDetailReaderPort, ClockPort, JobIdSource, JobKindRegistry, PostRunJobCapabilities, ReconcileStrategy, ChatLanguageModel, ChatTokenAccounting, FunctionRegistry, ChatStateSnapshot, AgentJobQueue, KnowledgeReaderPort, KnowledgeReviewPort, RunInvocationPort, RunsReaderPort, JobStoreFence, RecoverAbandonedClaimsOutcome, RunNextJobOutcome, PersistencePort } from '@vostride/agent-qa-agent';
2
+ import { TokenAccounting } from '@vostride/agent-qa-model';
3
+ import { ModelCapabilities, TrustPriorTable, ScopeKey, RecordedRetrievalRung, RecordId, ReadTrace, RunEventEnvelope, RunsStartInput, RunSession, RunId, EvidenceBlobLookup, EvidenceBlobPresence, QuarantineAppliedEntry, RunNotification, ExecutionContext, ResolvePromotionPosture, FunctionCaller, MaskFn, FeedbackPort, FeedbackReadsPort, TestEditPatchSet, TestFileWrite, ChatSessionEvent, ArtifactRef, GateReceipt, GateReceiptRecordOutcome, PromotionOutcome, JobKind, SessionLogPort, OpenedChatSession } from '@vostride/agent-qa-schema';
4
+ export { ALLOW_PRODUCTION_OPT_IN, buildProductionRefusal } from '@vostride/agent-qa-schema';
5
+ import { ScreenshotCompressorLogger } from '@vostride/agent-qa-runner-local';
6
+ export { BuildOrdinalReservationError, latestBuildOrdinal, reserveBuildOrdinalForProducts, resolveBuildTag } from '@vostride/agent-qa-runner-local';
7
+ import { KnowledgeHintReader, PlatformAdapter, Planner, Verifier, ActionPlanCache, WorkingMemoryPort, StorageStatePort, ScreenshotCompressor, RecordLedger, ConfigDigests, MemberRunConfig, HookDefinition, SandboxRunnerOptions, SecretStore, SecretRedactorPort, MemberEventSink, EvidenceArtifactStorePort, PlannerConfig, LoopLogger, EventLogPort, HandoffStorePort, RunArtifactStorePort, PostVerdictPort, NonActionToolMap, MemberTestRunContext, TestResult, MemberTestExecutor, MemberTest, RunConfig } from '@vostride/agent-qa-runner';
8
+ export { BaselineProbeResult, ConfigDigests, MemberTestExecutor, PostVerdictOutcome, PostVerdictPort, PostVerdictStepInput, RunConfig, RunMemberDescriptor, mapVerdictToExitCode } from '@vostride/agent-qa-runner';
9
+ export { runSafetyScope } from '@vostride/agent-qa-safety-local';
10
+ import { planMarkEvidence, projectMarkEvidenceOutcome, canaryOutcomeObservationId } from '@vostride/agent-qa-knowledge-local/reader';
11
+ export { QUARANTINE_CLASS_DISPOSITIONS, QUARANTINE_FLOOR_ELIGIBILITY, QuarantineDisposition, QuarantineFloorEligibility } from '@vostride/agent-qa-knowledge-local/reader';
12
+ import { ExplicitReviewRequest, CreateLocalKnowledgeStoreConfig, ExplicitReviewDelegate, ServedRecordFacts, appendMarkEvidence, triageMark, servedIdsOn, procedureReadOn, applyFeedback, openKnowledgeReader, drainQueue, foldRunOutcomes, reconcileRunOutcomesProjection, enqueueCurationCandidateProjection, readCanonicalBundleAnchors, readCanonicalBundleStateDigests, canaryPostureFactsFor, standingReceiptDigestsFor, lowerTrustSharesFor, RUN_OUTCOMES_PROJECTION_VERSION, acquireWriterLease, refreshWriterLeaseAuthority, releaseLease, rebuildIndex, purgeRecord, resolvePurgeAffectedSet, purgeAffectedSetFingerprint, rollbackCampaign, rollbackCurationJob, resolveRollbackAffectedSet, rollbackAffectedSetFingerprint, heartbeatLease, readWriterLease, isLeaseStale, deriveWriterLeaseFreshness, countPendingCurationJobs, countRemainingCurationWork, inspectDrainableCurationWork, listQueuedCandidates, acquireEmbedder, runMaintenanceTail, readEmbedderWorkerRegistration, embedderWorkerProcessIsAlive, embedderWorkerSocketPath, probeEmbedderWorkerSocket, verifyEmbedderWorkerIncarnation, resolveProcessIncarnation, EmbedderWorkerRegistration, removeEmbedderWorkerRegistration, classifyIndexFace, foldRetrievalRung, indexDbPath, resolveEmbedderCapability, EntityIndexSweepReport, ExplicitReviewResult } from '@vostride/agent-qa-knowledge-local';
13
+ export { AcquireEmbedderOutcome, AcquireWriterLeaseOutcome, ApplyFeedbackInput, AuthorOutcome, AuthoringTestRef, CurationCandidateProjection, CurationJobView, CurationProjectionArtifactSource, CuratorAuthor, DRAIN_BUDGET_MS, EMBEDDER_WORKER_REGISTRATION_FILE, EMBEDDER_WORKER_START_TIMEOUT_MS, EMBEDDER_WORKER_TERM_GRACE_MS, ENTITY_INDEX_SWEEP_INTERVAL_MS, EmbedderWorkerTimeoutError, EnqueueCurationCandidateProjectionOutcome, ExplicitReviewDelegate, ExplicitReviewDelegation, ExplicitReviewRequest, ExplicitReviewResult, HeartbeatLeaseOutcome, IndexFace, LeaseOperationOptions, MarkTriage, NeighborRecord, OpenServingKnowledgeReaderConfig, PurgeAffectedSet, PurgeReceipt, ReadEmbedderWorkerRegistrationOutcome, ReadWriterLeaseOutcome, RebuildReport, ReleaseLeaseOutcome, RollbackAffectedSet, RollbackCampaignInput, RollbackCurationJobInput, RollbackReport, ServedRecordFacts, WriterLease, WriterLeaseAuthority, WriterLeaseFreshness } from '@vostride/agent-qa-knowledge-local';
14
+
15
+ /** Provisional share of usable model context reserved for the frozen Channel-1 brief. */
16
+ declare const BRIEF_FRACTION = 0.05;
17
+ /** Provisional share of usable model context reserved for one-shot Channel-3 cautions. */
18
+ declare const CAUTION_FRACTION = 0.01;
19
+ /** Provisional non-zero Channel-1 floor; field evidence may tune it in a later milestone. */
20
+ declare const MIN_BRIEF = 256;
21
+ /** Provisional non-zero Channel-3 floor sized to preserve at least one concise caution line. */
22
+ declare const MIN_CAUTION = 64;
23
+ /** Provisional upper bound for learned Channel-1 brief tokens, independent of model size. */
24
+ declare const BRIEF_CEILING = 2048;
25
+ /** Provisional share of usable context available to cumulative Channel-2 record bodies per turn. */
26
+ declare const READ_BODY_FRACTION = 0.1;
27
+ /** Provisional hard cap on distinct records admitted across all three channels in one turn. */
28
+ declare const GLOBAL_RECORD_CAP = 24;
29
+ /** Provisional multiplier applied to the distinct-record cap for gate-failing weak models. */
30
+ declare const WEAK_MODEL_CAP_SCALE = 0.5;
31
+ /** Provisional minimum context window required before the optional Channel-2 tool surface is useful. */
32
+ declare const MIN_MEMORY_TOOL_CONTEXT_WINDOW = 16384;
33
+ interface KnowledgeBudgets {
34
+ brief: number;
35
+ /** Wrapper-fixed Channel-2 search-result budget; never exposed in the tool schema. */
36
+ searchResults: number;
37
+ caution: number;
38
+ readBody: number;
39
+ recordCap: number;
40
+ }
41
+ interface ComputeKnowledgeBudgetsInput {
42
+ contextWindow: number;
43
+ reservedOutput: number;
44
+ qaRulesTokens: number;
45
+ workingContextTokens: number;
46
+ }
47
+ /**
48
+ * Derive every learned-knowledge budget beside model token accounting. The raw fraction is
49
+ * floored before any floor/ceiling clamp, preserving D-02's exact boundary behavior. The reader
50
+ * and provider-neutral loop receive only the resulting plain numbers.
51
+ */
52
+ declare function computeKnowledgeBudgets(input: ComputeKnowledgeBudgetsInput): KnowledgeBudgets;
53
+ /**
54
+ * Provisional D-09 predicate pinned by Phase 380 research. Channel 2 is absent when the model
55
+ * cannot call tools, is text-only, or has too little context; Channels 1 and 3 remain available.
56
+ */
57
+ declare function memoryToolGate(capabilities: ModelCapabilities): boolean;
58
+ /** Apply D-17's weak-model cap reduction through the exact D-09 gate predicate. */
59
+ declare function scaledRecordCap(capabilities: ModelCapabilities): number;
60
+
61
+ /** Inputs the composition root resolves when opening the run-scoped knowledge reader. */
62
+ interface ConstructKnowledgeReaderInput {
63
+ resolveTrustPriors?: () => TrustPriorTable | undefined;
64
+ knowledgeRoot: string;
65
+ product?: string;
66
+ /** Known containing scopes for a member (the suite scope on prepared-suite runs). */
67
+ parentScopes?: readonly ScopeKey[];
68
+ asOfBuild: number;
69
+ recordCap: number;
70
+ }
71
+ /**
72
+ * Freeze Channel 1 at the composition root. The budget is deliberately absent from the key:
73
+ * budgets are run-fixed, while canonical `[tier,key]` material gives one immutable memo entry per
74
+ * scope. All other reader methods remain live pass-throughs for Channels 2, 3, and canary output.
75
+ *
76
+ * KREV-02 · D-13: the memo holds the WHOLE composite, so a repeat serve returns the trace captured
77
+ * at the moment the brief was actually assembled — not a fresh trace describing the cache hit.
78
+ * That is the deliberate choice and it is what makes the why-verb answer about *then*: a step that
79
+ * replans four times against one frozen brief was served that brief once, and the attribution that
80
+ * explains its lines is the one from that single serve. A "hit"-flavoured trace would describe the
81
+ * memo rather than the retrieval, and there would be no honest rank or score to put in it. The
82
+ * value type is written out rather than inferred so this stays a decision instead of a side effect
83
+ * of `ReturnType`; `knowledge-budgets.test.ts` pins that a repeat carries the original trace.
84
+ */
85
+ declare function createFrozenKnowledgeReader(reader: KnowledgeHintReader): KnowledgeHintReader;
86
+ /**
87
+ * Construct the run-scoped reader through the physically read-only package subpath, then freeze
88
+ * its Channel-1 brief for the lifetime of the run.
89
+ */
90
+ declare function constructKnowledgeReader(input: ConstructKnowledgeReaderInput): KnowledgeHintReader;
91
+
92
+ /** Mirror of schema's ExecutionContext union (safety-local's detectExecutionContext returns it). */
93
+ type RunExecutionContext = 'local-interactive' | 'local-headless' | 'ci' | 'cloud';
94
+ /**
95
+ * The provider-substrate model handle, named through the runner's own config rather than through
96
+ * the substrate package. It is the identical type — the runner types both model slots with it —
97
+ * and taking it this way keeps a provider-substrate edge off a package whose declared external
98
+ * dependency set is empty and whose whole point is that it installs nothing (JOBQ-02).
99
+ */
100
+ type RunLanguageModel = MemberRunConfig['plannerModel'];
101
+ type KnowledgeAccounting = Pick<TokenAccounting, 'estimate' | 'getCeilings'>;
102
+ /**
103
+ * 407 D-13/D-17 — the shape of THE retrieval-rung resolver, declared structurally here and
104
+ * INJECTED rather than imported.
105
+ *
106
+ * knowledge-local's main entry has exactly ONE importer in this workspace by ratified rule
107
+ * (385 D-14/SC5, enforced at file granularity by `findKnowledgeWriterSingletonOffenders`), and
108
+ * that importer is `compose-local-agent.ts` — the local composition root. `resolveRetrievalRung`
109
+ * lives on the main entry deliberately (407-03: it opens an index probe and may load a model, so
110
+ * it is not `./reader` material), so naming it from this file would have made a second importer
111
+ * and quietly retired the invariant. The seam keeps the singleton intact while leaving exactly
112
+ * one implementation in the tree: `compose-local-agent.ts` re-exports the real resolver as
113
+ * `resolveHostRetrievalRung`, and both CLI doors pass THAT.
114
+ *
115
+ * It is REQUIRED, not optional-with-a-default. A default would be a second place the production
116
+ * resolver could be chosen from, and an omission at a new door would show up as a run with no
117
+ * rung — a banner that silently says nothing — rather than as a compile error.
118
+ */
119
+ type RetrievalRungResolver = (options: {
120
+ knowledgeRoot: string;
121
+ agentQaRoot?: string;
122
+ }) => Promise<RecordedRetrievalRung>;
123
+ interface RunKnowledgeComposition {
124
+ reader: KnowledgeHintReader;
125
+ budgets: KnowledgeBudgets;
126
+ /**
127
+ * 407 D-13 — the HOST's retrieval ladder at THIS run's serve time, started here and awaited
128
+ * exactly once by the first step's evidence recorder. It is a promise rather than a value
129
+ * because the resolver's embedder probe can cost a model load, and overlapping that with run
130
+ * start keeps it off the critical path (T-407-16). Never re-probed per step: a run's rung is a
131
+ * fact about when it was served, not a live indicator of the machine.
132
+ */
133
+ retrievalRung: Promise<RecordedRetrievalRung>;
134
+ recordLedger: RecordLedger;
135
+ nonActionTools?: NonActionToolMap;
136
+ beginMemoryTurn?: (scope?: ScopeKey, exposedIds?: readonly RecordId[]) => void;
137
+ drainMemoryReadTraces?: () => ReadTrace[];
138
+ }
139
+ interface OpenRunKnowledgeCompositionInput {
140
+ resolveTrustPriors?: ConstructKnowledgeReaderInput['resolveTrustPriors'];
141
+ knowledgeRoot: string;
142
+ product?: string;
143
+ scope: ScopeKey;
144
+ /** Known containing scopes for a member (the suite scope on prepared-suite runs). */
145
+ parentScopes?: readonly ScopeKey[];
146
+ asOfBuild: number;
147
+ capabilities: ModelCapabilities;
148
+ accounting: KnowledgeAccounting;
149
+ qaRulesText: string;
150
+ workingContextText: string;
151
+ /**
152
+ * 407 D-13 — the hosted runtime root (`<configDir>/.agent-qa`) the embedder probe discovers its
153
+ * worker under. Absent falls back to the acquirer's own rule, so a caller that does not know the
154
+ * root still gets an honest answer rather than an error.
155
+ */
156
+ agentQaRoot?: string;
157
+ /**
158
+ * 407 D-17 — the injected rung resolver. Production callers pass `resolveHostRetrievalRung`
159
+ * (the composition root's binding of THE one knowledge-local resolver); tests pass a fake and
160
+ * never touch the host. See {@link RetrievalRungResolver} for why it is injected and required.
161
+ */
162
+ resolveRetrievalRung: RetrievalRungResolver;
163
+ }
164
+ /**
165
+ * Open and bind the complete serving stack once per run/planner scope. The composition root can
166
+ * estimate only prompt material it owns, so `qaRulesText` is the run-start capability/rules appendix
167
+ * and `workingContextText` is the resolved suite/test context plus steps. Both ride the same model
168
+ * accounting estimator as planning calls; static runner rules remain covered by the reserved
169
+ * output and conservative fractional allocation rather than a second character heuristic.
170
+ */
171
+ declare function openRunKnowledgeComposition(input: OpenRunKnowledgeCompositionInput): RunKnowledgeComposition;
172
+ /** One member's file inputs to the digest mint. `suiteFileContent` is absent on the single-test path. */
173
+ interface RunDigestMemberInput {
174
+ testFileContent: string;
175
+ suiteFileContent?: string;
176
+ }
177
+ /**
178
+ * The per-member cache pair AND the run-level handoff pair, both produced by the SINGLE call
179
+ * below (D-20). The cache-key threading consumes `perMember[i]`; the handoff header carries
180
+ * `runLevel`. Because both draw from one mint, run identity and handoff identity cannot fork.
181
+ */
182
+ interface RunConfigDigests {
183
+ runLevel: ConfigDigests;
184
+ perMember: ConfigDigests[];
185
+ }
186
+ /**
187
+ * Mint the run's config digests ONCE (D-20; RESEARCH A1 pinning).
188
+ *
189
+ * - Per-member cache pair:
190
+ * suiteConfigDigest = sha256hex16(configContent + '\n' + (suiteFileContent ?? ''))
191
+ * testConfigDigest = sha256hex16(testFileContent)
192
+ * - Run-level handoff pair:
193
+ * suiteConfigDigest = sha256hex16(ordered DISTINCT per-member suite digests joined by '\n')
194
+ * testConfigDigest = sha256hex16(ordered per-member test digests joined by '\n')
195
+ * A single-member run collapses the run-level pair to the member's values.
196
+ *
197
+ * Both consumers (per-member cache, run-level handoff) draw from this one call — identities
198
+ * cannot fork. This is the ONLY digest computation in the composition layer.
199
+ */
200
+ declare function computeRunConfigDigests(input: {
201
+ configContent: string;
202
+ members: readonly RunDigestMemberInput[];
203
+ }): RunConfigDigests;
204
+ /**
205
+ * Resolve the run's execution context via safety-local's canonical `detectExecutionContext`
206
+ * (RESEARCH Q1.4 recommendation (a) — already tested; an explicit override wins). Carried into
207
+ * `RunConfig.executionContext` and the handoff header.
208
+ */
209
+ declare function resolveRunExecutionContext(override?: RunExecutionContext): RunExecutionContext;
210
+ /** The durable run ports the shell wires behind `RunConfig.ports` (event log + handoff + artifact). */
211
+ interface RunPersistencePorts {
212
+ eventLog: EventLogPort;
213
+ handoffStore: HandoffStorePort;
214
+ runArtifactStore: RunArtifactStorePort;
215
+ /**
216
+ * CASS-02 run-scoped decision-screenshot store (Plan 13). Constructed once here from the SAME
217
+ * persistence root + runId as the other adapters; the shell hands this exact instance to every
218
+ * member so Plan 12's live-decision producer persists into one durable, run-addressable namespace.
219
+ */
220
+ evidenceArtifacts: EvidenceArtifactStorePort;
221
+ /**
222
+ * 403-13 (WR-06, LIVE-03): where a live-frame VIEWER's fault is reported. Declared here as well
223
+ * as on the runner's `RunPorts`, because `assembleCliRun` spreads THIS interface into
224
+ * `RunConfig.ports` — a member declared on only one side is invisible in the type across that
225
+ * hop. Non-durable and off the bus by construction; the run's transcript never records a viewer.
226
+ */
227
+ onSubscriberFault?: (code: string, message: string) => void;
228
+ /**
229
+ * 404-01 (D-05/D-10): the host-composed post-verdict step the shell awaits between `verdict` and
230
+ * `runFinished`. Declared here for the SAME reason `onSubscriberFault` is — `assembleCliRun`
231
+ * spreads THIS interface into `RunConfig.ports`, so a member declared on only one side of that
232
+ * hop is invisible in the type and would silently never reach `startRun`.
233
+ *
234
+ * It is NOT built by `buildRunPorts`: this package's runner seam names no queue and no writer
235
+ * lease, and inventing a second spelling of either here is what D-05 exists to prevent. The core
236
+ * threads whatever the HOST handed it (`RunInvocationCoreOptions.postVerdict`) and nothing else.
237
+ */
238
+ postVerdict?: PostVerdictPort;
239
+ }
240
+ /**
241
+ * Construct the runner-local persistence ports rooted at the run's `.agent-qa` runtime dir (the
242
+ * FsActionCache rooting idiom). `FsEventLog`/`FsHandoffStore`/`FsRunArtifactStore`/
243
+ * `FsEvidenceArtifactStore` all write under the SAME `<configDir>/.agent-qa/runs/<slug(runId)>/…`
244
+ * family (D-08 — one self-contained run dir).
245
+ */
246
+ declare function buildRunPorts(input: {
247
+ configDir: string;
248
+ runId: string;
249
+ }): RunPersistencePorts;
250
+ /**
251
+ * Construct the D-08/Q10 screenshot compressor at the composition root (sharp's host-adapter home).
252
+ * The runner-local `SharpScreenshotCompressor` is injected into every member's runner loop via
253
+ * `RunSuiteConfig.compressor` / `RunTestConfig.compressor` — core no longer constructs one.
254
+ */
255
+ declare function buildScreenshotCompressor(logger?: ScreenshotCompressorLogger): ScreenshotCompressor;
256
+ /** Resolve D-16 build-tag precedence without reading ambient process state. */
257
+ declare function resolveBuildTagSetting(input: {
258
+ flag?: string;
259
+ env?: string;
260
+ use?: {
261
+ buildTag?: string;
262
+ };
263
+ }): string | undefined;
264
+ /**
265
+ * Advance the per-product buildOrdinal counter under the same `.agent-qa` root (D-18), then
266
+ * record D-14/D-16's first-seen tag mapping beside it when supplied. Both files are lock-free,
267
+ * best-effort runtime truth; the resolved ordinal rides `RunConfig.buildOrdinal` into the handoff.
268
+ */
269
+ declare function resolveRunBuildOrdinal(input: {
270
+ configDir: string;
271
+ product: string;
272
+ buildTag?: string;
273
+ }): Promise<number>;
274
+ /**
275
+ * The ONE exclusive lane every suite member declares (402-02, D-09). The runner's bounded member
276
+ * pool never runs two members of the same lane concurrently, and the suite path needs that for two
277
+ * independent reasons, either of which alone would be sufficient:
278
+ *
279
+ * 1. Every member of a suite drives the ONE `PlatformAdapter` its suite set up — one browser, one
280
+ * page. Two members on it at once would interleave navigations and clicks on a shared surface.
281
+ * D-09 requires each concurrent member its own browser/CDP handle; the suite path does not
282
+ * supply that today, so it does not get concurrency today.
283
+ * 2. `createSuiteLifecycle` admits ONE ACTIVE SUITE at a time. Two suites live at once is a
284
+ * lifecycle violation, not a scheduling preference.
285
+ *
286
+ * A single lane string (rather than one lane per suite) is what encodes BOTH: it serializes within
287
+ * a suite and across suites. When a producer starts handing each member its own session AND the
288
+ * lifecycle admits concurrent suites, this becomes a per-suite key — or disappears — and the shell
289
+ * needs no argument re-litigated for that to be safe.
290
+ *
291
+ * STATUS 2026-08-24 (402-02): reason (2) is CLOSED — `createSuiteLifecycle` below is now a
292
+ * once-latch with member reference counting, so two members of one suite merge into one setup and
293
+ * a boundary crossed while members are live is a named error rather than a mid-flight teardown.
294
+ * Reason (1) is OPEN and is what still holds the lane: `suiteConfig.adapter` is per-SUITE, so
295
+ * within-suite concurrency would still put two tests on one browser page. Giving each suite member
296
+ * its own session is a change to what a suite MEANS (suite setup navigates once and members
297
+ * inherit that page state, per-test hooks run against it, auth state is captured from it) and is
298
+ * not this plan's to make. Do not drop this lane until that is decided.
299
+ */
300
+ declare const SUITE_LIFECYCLE_LANE = "suite-lifecycle";
301
+ /** The setup/teardown pair a suite runs when it first becomes active / when it is torn down. */
302
+ interface SuiteLifecycleHooks {
303
+ /** Suite-level setup: setup hooks + adapter.setup + suite-URL navigation. */
304
+ setup(): Promise<void>;
305
+ /** Suite-level teardown: teardown hooks + auth-state capture (when all passed) + adapter.cleanup. */
306
+ teardown(): Promise<void>;
307
+ }
308
+ /**
309
+ * One member's hold on the active suite (402-02). Held from the moment `ensureReady` admits the
310
+ * member until the member settles; the suite cannot be torn down while any hold is outstanding.
311
+ */
312
+ interface SuiteMemberLease {
313
+ /** Drop this member's hold. Idempotent — a double release never double-decrements. */
314
+ release(): void;
315
+ }
316
+ /**
317
+ * Thrown when `ensureReady` is asked for a suite while a DIFFERENT suite still has live members
318
+ * (402-02). This is the one-active-suite invariant made loud. The alternative — tearing the live
319
+ * suite down to make room — closes the browser out from under a test that is still running in it,
320
+ * and does so silently. The shell's exclusive lane (`SUITE_LIFECYCLE_LANE`) means no shipped
321
+ * composition can reach this, so it reads as an assertion rather than a runtime condition; that is
322
+ * exactly the point of naming it instead of assuming it.
323
+ */
324
+ declare class SuiteBoundaryWhileLiveError extends Error {
325
+ readonly activeSuiteKey: string;
326
+ readonly requestedSuiteKey: string;
327
+ readonly liveMembers: number;
328
+ constructor(activeSuiteKey: string, requestedSuiteKey: string, liveMembers: number);
329
+ }
330
+ interface SuiteLifecycle {
331
+ /**
332
+ * Admit a member to `suiteKey` and resolve once the suite is ready. The FIRST member to arrive
333
+ * starts the suite's setup; every other member of that suite awaits the SAME setup promise
334
+ * rather than starting a second one. Crossing a suite boundary tears down the previous suite —
335
+ * but only once its last member has released; a boundary crossed while members are live is a
336
+ * {@link SuiteBoundaryWhileLiveError}, never a silent teardown.
337
+ *
338
+ * The returned {@link SuiteMemberLease} MUST be released when the member settles (pass or fail);
339
+ * `buildSuiteMemberExecutor` does it in a `finally`.
340
+ */
341
+ ensureReady(suiteKey: string, hooks: SuiteLifecycleHooks): Promise<SuiteMemberLease>;
342
+ /** Tear down the last active suite (if any). Called once at run end after `session.done`. */
343
+ finalize(): Promise<void>;
344
+ }
345
+ /**
346
+ * A lazy, CONCURRENCY-SAFE suite-lifecycle manager (the Q1.2 "pre/post-run composition" rows,
347
+ * redesigned for the 402-02 member pool). A wholly SKIPPED suite never sets up — the shell only
348
+ * invokes the executor (hence `ensureReady`) for non-skipped members, so a suite whose members are
349
+ * all fail-fast/cancel skips is never touched.
350
+ *
351
+ * WHAT CHANGED AND WHY. The pre-402 version read the active key, awaited `hooks.setup()`, and THEN
352
+ * assigned the key — three steps with awaits between them. Under a `for` loop that is fine because
353
+ * only one member is ever inside it. Under a pool, two members of one suite both observe "no active
354
+ * suite" and both run setup: two browsers for one suite, and the second teardown handle silently
355
+ * overwriting the first, so the first suite's browser is never closed. The fix is not a lock; it is
356
+ * to make the state transition SYNCHRONOUS and let the promise be the shared thing:
357
+ *
358
+ * - the once-latch: `activeKey`, `activeTeardown` and `activeSetup` are all assigned in the same
359
+ * synchronous region, BEFORE any await, so there is no window in which a second member can
360
+ * observe the pre-setup state. Every later member of the suite awaits `activeSetup` itself.
361
+ * - member reference counting: the count is incremented before awaiting setup, so a concurrent
362
+ * boundary crossing sees the hold. Teardown fires at a BOUNDARY (or at `finalize`) and only
363
+ * when the count is zero — never merely because it hit zero, which would tear the suite down
364
+ * and set it back up between every pair of members.
365
+ * - a failed setup poisons the suite: the rejected latch is kept and re-thrown to every later
366
+ * member, so setup is attempted exactly once and no member half-starts against a suite that
367
+ * never came up. Such a suite is never torn down — nothing came up to take down.
368
+ */
369
+ declare function createSuiteLifecycle(): SuiteLifecycle;
370
+ /**
371
+ * One prepared suite member: its suite key + lifecycle hooks (setup runs on the first member of
372
+ * that suite) and a `run(ctx)` that builds the member's bound `SuiteMemberInput` around core's
373
+ * per-test machinery and returns `executeSuiteMember(...)` with the shell-provided ctx seams
374
+ * (abortSignal, events, per-member configDigests) threaded in.
375
+ */
376
+ interface SuiteMemberBinding {
377
+ suiteKey: string;
378
+ hooks: SuiteLifecycleHooks;
379
+ /**
380
+ * 403 D-05: the suite's ONE adapter, read only for its live-stream declaration. Present so the
381
+ * executor can register the capability AFTER `ensureReady` has resolved the suite's `setup()`
382
+ * (which is where `adapter.setup()` runs, so `this.page` exists) and BEFORE the first step.
383
+ */
384
+ adapter?: PlatformAdapter;
385
+ run(ctx: MemberTestRunContext): Promise<TestResult>;
386
+ }
387
+ /**
388
+ * Declare (or withdraw) a member's live stream on the shell-provided context (403 D-05), by the one
389
+ * selection rule `liveStreamFor` (428 D-11/D-12). A continuous stream is registered with the
390
+ * labelled 1s snapshot producer over `screenshot()` as its fallback, which the hub starts when the
391
+ * stream fails to start. An adapter with no continuous stream but a `screenshot()` registers the
392
+ * snapshot producer itself; an adapter whose own stream is already a snapshot registers that alone;
393
+ * an adapter with neither registers `undefined` and its handle reads `{ kind: 'none' }`.
394
+ * The `ctx.registerLiveStream?.()` call is itself optional so a composition that predates the port
395
+ * is a no-op rather than a crash.
396
+ */
397
+ declare function registerAdapterLiveStream(ctx: MemberTestRunContext, adapter: PlatformAdapter | undefined): void;
398
+ /**
399
+ * Build the injected `MemberTestExecutor` (D-01). Per member the closure looks up the prepared
400
+ * binding by the shell-derived `testIndex` (array position — authoritative), calls
401
+ * `lifecycle.ensureReady(...)` to lazily set up / cross-boundary tear down the member's suite,
402
+ * then runs the member behind `executeSuiteMember`. Wrapping ONE audited function instead of
403
+ * replicating the loop body.
404
+ */
405
+ declare function buildSuiteMemberExecutor(input: {
406
+ lifecycle: SuiteLifecycle;
407
+ bindings: readonly SuiteMemberBinding[];
408
+ }): MemberTestExecutor;
409
+ /** The runner-owned `MemberTest` shape derived from a resolved (already suite-patched) TestDefinition.
410
+ * `filePath` is the member's own file. A suite-patched test carries no `url` (the suite owns nav), so
411
+ * `runMemberTest` skips the per-test navigate for suite members. */
412
+ declare function toMemberTest(test: any, filePath: string): MemberTest;
413
+ /** Everything both executor closures resolve at the composition root and hand to
414
+ * {@link buildMemberRunConfig}. The adapter-backed injected handlers are constructed here from
415
+ * `adapter` + `secretStore` so the two sites never drift (Pitfall 8). */
416
+ interface BuildMemberRunConfigInput {
417
+ adapter: PlatformAdapter;
418
+ planner: Planner;
419
+ verifier: Verifier;
420
+ cache?: ActionPlanCache;
421
+ workingMemory?: WorkingMemoryPort;
422
+ storageState?: StorageStatePort;
423
+ compressor?: ScreenshotCompressor;
424
+ knowledgeReader?: KnowledgeHintReader;
425
+ /** 407 D-13 — the composed rung, carried through untouched to the loop's evidence recorder. */
426
+ knowledgeRetrievalRung?: Promise<RecordedRetrievalRung>;
427
+ knowledgeBudgets?: KnowledgeBudgets;
428
+ knowledgeAsOfBuild?: number;
429
+ recordLedger?: RecordLedger;
430
+ beginMemoryTurn?: (scope?: ScopeKey, exposedIds?: readonly RecordId[]) => void;
431
+ drainMemoryReadTraces?: () => ReadTrace[];
432
+ configDigests?: ConfigDigests;
433
+ plannerModel: RunLanguageModel;
434
+ verifierModel: RunLanguageModel;
435
+ stepModelScope?: MemberRunConfig['stepModelScope'];
436
+ envFileVars?: Record<string, string>;
437
+ inlineVars?: Record<string, string>;
438
+ suiteVars?: Record<string, string>;
439
+ cliVars?: Record<string, string>;
440
+ hookSetupVars?: Record<string, string>;
441
+ inlineHookDefs?: Map<string, HookDefinition>;
442
+ sandboxOptions?: SandboxRunnerOptions;
443
+ secretStore?: SecretStore;
444
+ secretRedactor?: SecretRedactorPort;
445
+ sink: MemberEventSink;
446
+ testIndex: number;
447
+ abortSignal: AbortSignal;
448
+ /** CASS-02 run-scoped evidence store the shell threads onto the member ctx (Plan 13). */
449
+ evidenceArtifacts?: EvidenceArtifactStorePort;
450
+ captureScreenshots?: boolean;
451
+ accessibility?: MemberRunConfig['accessibility'];
452
+ timeouts?: MemberRunConfig['timeouts'];
453
+ plannerConfig?: PlannerConfig;
454
+ logger?: LoopLogger;
455
+ logCapture?: {
456
+ console?: boolean;
457
+ network?: boolean;
458
+ };
459
+ vision?: {
460
+ screenshotSize?: number;
461
+ effectiveResolution?: number;
462
+ };
463
+ visionGate?: {
464
+ attachImage: boolean;
465
+ captureMode: 'full' | 'textOnly';
466
+ promptNote: string;
467
+ };
468
+ contextWindow?: number;
469
+ suiteContext?: string;
470
+ /** The member's own file, for the core-parity fileUpload path resolver. */
471
+ filePath: string;
472
+ }
473
+ /**
474
+ * Assemble the {@link MemberRunConfig} `runMemberTest` slots behind, wiring the adapter-backed
475
+ * injected handlers (runJS/appium/a11y/failure-shot), the store-neutral secret resolver, and the
476
+ * fileUpload path resolver — reproducing core's `runTest` per-test behavior on runner soil (D-05).
477
+ */
478
+ declare function buildMemberRunConfig(input: BuildMemberRunConfigInput): MemberRunConfig;
479
+
480
+ /** A bus subscriber a host attaches: an envelope `handler` plus an optional post-run `flush`. */
481
+ interface RunSubscriberHandle {
482
+ handler: (envelope: RunEventEnvelope) => void;
483
+ flush?: () => Promise<void>;
484
+ }
485
+ /** The minimal RunSession surface a subscriber attachment needs (subscribe replay-then-live). */
486
+ interface AttachableRunSession {
487
+ subscribe(fromSeq: number | undefined, handler: (envelope: RunEventEnvelope) => void): () => void;
488
+ }
489
+
490
+ /**
491
+ * A target's declared environment, as the config carries it (`TargetSchema.environment`).
492
+ *
493
+ * `undefined` is a first-class member and means UNDECLARED — not "unknown, assume harmless".
494
+ *
495
+ * `production` (432-03, D-18) outranks every other class in a fold. A production run is refused
496
+ * before any effect unless the config opts in (`refuseProductionWithoutOptIn`); one that is opted
497
+ * in starts like any other run (432-13).
498
+ */
499
+ type RunTargetClass = 'local' | 'shared' | 'production' | undefined;
500
+
501
+ /**
502
+ * The request fields this seam refuses, and the reason is not the one the CLI host had.
503
+ *
504
+ * `createCliRunExecution` refused these because the `run` COMMAND had no argv to express them
505
+ * (`local-agent-host.ts`). This core can express both — and honouring either would be the defect
506
+ * RESEARCH Pitfall 7 names:
507
+ *
508
+ * `executionContext` — the context a run records comes from the composition that owns the door
509
+ * (D-07) and from nowhere else. The FIELD survives on the contract as the label a run RECORDS,
510
+ * and it is refused here until some door has a use for recording a label that differs from the
511
+ * context it was started in.
512
+ *
513
+ * `scope` — resolves from the config's own target/product resolution. Accepting an override
514
+ * would let a caller address one product's run ledger while running another's.
515
+ *
516
+ * Read OFF the request rather than enumerated positively, so a field added to `RunsStartInput`
517
+ * with no plan for carrying it shows up here as a refusal instead of as a silent drop.
518
+ */
519
+ declare const REFUSED_RUN_REQUEST_FIELDS: readonly ["scope", "executionContext"];
520
+ /**
521
+ * The request named a field this seam refuses, so the request is refused rather than reshaped.
522
+ *
523
+ * The successor to `UnsupportedRunRequestError` (cli `local-agent-host.ts`), which dies with the
524
+ * spawn port in 401-05. Same class of refusal, different rationale: that one could not express the
525
+ * fields, this one declines to.
526
+ */
527
+ declare class RunRequestFieldRefusedError extends Error {
528
+ readonly refusedFields: readonly string[];
529
+ constructor(refusedFields: readonly string[]);
530
+ }
531
+ /**
532
+ * The request named a run id this system would not have minted (2026-08-23, 401-20 · CR-04).
533
+ *
534
+ * A DECISION, NOT A FAULT, and it joins `RunRequestFieldRefusedError` under the same rule: the
535
+ * caller asked for something this seam declines, and says so by name before anything happens.
536
+ *
537
+ * WHY IT IS ASKED HERE AND NOT IN THE SCHEMA. `packages/schema` owns PATH-SAFETY on the id — the
538
+ * id becomes a directory name and that is judgeable with no dependencies — but it may not name
539
+ * `@vostride/agent-qa-ids` and must not re-spell the canonical id shape. So "is this an id this
540
+ * system would have minted" is asked here, through a predicate supplied beside `mintRunId` and for
541
+ * `mintRunId`'s stated reason: a locally invented answer would be a second spelling of a run's
542
+ * identity. Measured pre-fix: `runId: 'not-a-minted-id'` was acknowledged, and `runId: ''` ran a
543
+ * whole run into a shared `runs/default/` directory before the op reported `RunNotRecordedError`.
544
+ */
545
+ declare class RunIdNotMintedError extends Error {
546
+ readonly runId: string;
547
+ constructor(runId: string);
548
+ }
549
+ /**
550
+ * The request named a run id that already belongs to a run (2026-08-23, 401-20 · CR-04).
551
+ *
552
+ * WHAT A COLLISION COSTS, measured rather than reasoned about. Pre-fix, a second `runs.start`
553
+ * naming a completed run's id APPENDED seven more envelopes to that run's `events.jsonl`
554
+ * (2111 → 4221 bytes), OVERWROTE its `handoff.json`, and added a second row to its
555
+ * `execution_audit.jsonl` — and then acknowledged the caller with the victim's own id. The
556
+ * acknowledgment law makes that worse rather than better: `runs.start` reads every ack field back
557
+ * off the run's durable record specifically so a host cannot fabricate one, and a colliding id
558
+ * satisfies that read with a PRIOR run's record. So the refusal has to land before the record is
559
+ * touched at all — which is why it sits in the identity step, above assembly.
560
+ *
561
+ * THE TEST IS THE RUN'S OWN DIRECTORY, derived through `runDirFor` — the same composition the
562
+ * run's ports use — because every artifact a collision could tamper with lives inside it.
563
+ * Asking after `handoff.json` alone would let a run that crashed before writing one be silently
564
+ * appended to.
565
+ */
566
+ declare class RunIdAlreadyInUseError extends Error {
567
+ readonly runId: string;
568
+ constructor(runId: string);
569
+ }
570
+ /**
571
+ * This run was refused before it started.
572
+ *
573
+ * Since 432-13 the one producer is the production refusal (`refuseProductionWithoutOptIn`): its
574
+ * reason is the plain sentence naming the target and the config line. 2026-10-01 (432-19, D-15):
575
+ * it carries no remedy list, because the remedy vocabulary left with the execution gate.
576
+ * `neutralReason` is the same sentence; the dashboard and run-service doors read that member to
577
+ * tell a run denial from any other rejection, and a door renders it and never composes its own.
578
+ */
579
+ declare class RunDeniedError extends Error {
580
+ readonly runId: string;
581
+ readonly reason: string;
582
+ readonly neutralReason: string;
583
+ constructor(runId: string, reason: string);
584
+ }
585
+ /**
586
+ * What one resolution of this request's targets answers (401-14).
587
+ *
588
+ * THE CLASS AND THE NAMES COME BACK TOGETHER because they are two renderings of ONE pass over the
589
+ * run's members: `targetClass` is the most-restrictive fold over them, `targetNames` the declared
590
+ * names of the members it folded over. A door that answered them from two passes could refuse on
591
+ * one reading of the config while the run it describes came from another.
592
+ */
593
+ interface RunTargetDeclaration {
594
+ /** The DECLARED class of the run's members — a config read, never a probe (D-06). */
595
+ readonly targetClass: RunTargetClass;
596
+ /**
597
+ * The distinct declared target names, in first-appearance member order.
598
+ *
599
+ * DISPLAY-ONLY (T-401-G3b). Members that declare no target contribute nothing, which is how an
600
+ * all-undeclared run arrives here as an empty list rather than as a list of invented names.
601
+ */
602
+ readonly targetNames: readonly string[];
603
+ /**
604
+ * The members' targets DECLARED `environment: production`, in first-appearance member order
605
+ * (432-03, D-18). Repeats are allowed; the refusal names each target once.
606
+ *
607
+ * REQUIRED, so a door that forgets it does not compile. It comes from the SAME resolution as
608
+ * `targetClass` and `targetNames`, which is what keeps a mixed run from hiding a production member
609
+ * behind its siblings.
610
+ */
611
+ readonly productionTargetNames: readonly string[];
612
+ /**
613
+ * The config's top-level `allowProduction`, read from the same config the targets resolved
614
+ * against. Only the literal `true` opts in; absence and `false` do not. No request field and no
615
+ * CLI flag reaches this.
616
+ */
617
+ readonly allowProduction?: boolean;
618
+ }
619
+ /**
620
+ * Refuse a run that drives a production target the config has not opted into (432-03, D-18).
621
+ *
622
+ * A production CLASS with no names still refuses, so a producer that folded the class but lost the
623
+ * names fails closed. The refusal is a plain `RunDeniedError` carrying the sentence from
624
+ * `buildProductionRefusal`.
625
+ *
626
+ * EXPORTED because a producer that must acquire a device or an adapter before the core runs (the
627
+ * suite half) calls it first, so production is refused before that acquisition too.
628
+ */
629
+ declare function refuseProductionWithoutOptIn(runId: string, declaration: Pick<RunTargetDeclaration, 'productionTargetNames' | 'allowProduction'> & {
630
+ readonly targetClass?: RunTargetClass;
631
+ }): void;
632
+ /**
633
+ * The identity refusals, asked against the run's own directory (401-20, CR-04).
634
+ *
635
+ * EXPORTED for the same producers `refuseProductionWithoutOptIn` is: a door that refuses production
636
+ * ahead of the core asks these first, so a duplicate or unminted id keeps its own refusal on every
637
+ * path.
638
+ */
639
+ declare function refuseUnavailableRunId(input: {
640
+ readonly configDir: string;
641
+ readonly runId: string;
642
+ readonly isMintedRunId: (value: string) => boolean;
643
+ }): void;
644
+ /** What the assembler is handed: everything the core resolved, and nothing it did not. */
645
+ interface RunAssemblyContext {
646
+ /** The run's identity, already resolved. The core starts the run under THIS id and no other. */
647
+ readonly runId: string;
648
+ /** The caller's request, refused fields already rejected. */
649
+ readonly request: RunsStartInput;
650
+ /** The context the door carried — the same value, so a run cannot record a different one. */
651
+ readonly executionContext: RunExecutionContext;
652
+ /** The durable ports, rooted at `<configDir>/.agent-qa` (Pitfall 5 — the evidence check's root). */
653
+ readonly ports: RunPersistencePorts;
654
+ /** `dirname(agentQaRoot)` — the project directory the config sits in. */
655
+ readonly configDir: string;
656
+ /** The config file this run resolves against (request override honoured). */
657
+ readonly configPath: string;
658
+ /** The declared class the door resolved. Handed over so an assembler need not re-resolve it. */
659
+ readonly targetClass: RunTargetClass;
660
+ }
661
+ /**
662
+ * Build the `RunConfig` the runner shell starts.
663
+ *
664
+ * A PORT rather than shared code, on `RunExecutionPort`'s own argument: members, executor,
665
+ * reporters and masking are decisions a host answers differently. What crosses into the core is
666
+ * the type; what stays with the door is the assembly. The core supplies the identity fields
667
+ * (`runId` and the markers) over whatever the assembler returns, so a door cannot start a run
668
+ * under an id the caller was not acknowledged with.
669
+ *
670
+ * EVERYTHING WITH A SIDE EFFECT BELONGS HERE, not in the door's pre-flight: the baseline probe
671
+ * (network), the build-ordinal advance (a durable write), the quarantine fold. This function is
672
+ * called below every refusal, so a refused run costs the target nothing.
673
+ */
674
+ type RunAssembler = (context: RunAssemblyContext) => Promise<RunConfig> | RunConfig;
675
+ interface RunInvocationCoreOptions {
676
+ /** The config file runs resolve against unless the request names another. */
677
+ readonly configPath: string;
678
+ /** The project's runtime root, i.e. `<configDir>/.agent-qa`. */
679
+ readonly agentQaRoot: string;
680
+ /**
681
+ * The context this door's runs are started in and record (D-07).
682
+ *
683
+ * CALLER-RESOLVED, NEVER SNIFFED. The CLI resolves its TTY posture, the daemon carries
684
+ * `local-headless`, CI passes its own. A core that detected this itself would be answering a
685
+ * question about the machine when the question is about the door.
686
+ */
687
+ readonly executionContext: RunExecutionContext;
688
+ /**
689
+ * Resolve what this request's targets DECLARE (D-06; names added 401-14).
690
+ *
691
+ * INJECTED BECAUSE THE CONFIG LIVES BEHIND THE DOOR, not because any of it is negotiable. Target
692
+ * resolution needs the CLI's `resolveTarget`/`AgentQaConfig` vocabulary, which this package may
693
+ * not name. `undefined` is undeclared, which is not production.
694
+ *
695
+ * ONE RESOLUTION, ALL FIELDS, AND THAT IS THE POINT OF THE SHAPE. The class, the names, the
696
+ * production members and the opt-in come back from one pass, so the production refusal is asked
697
+ * about the members the run will actually drive.
698
+ *
699
+ * Must not touch the target, and must not probe. A resolver that read a URL would put a network
700
+ * call before the production refusal, which is the one ordering this file exists to hold.
701
+ */
702
+ readonly resolveTargetDeclaration: (request: RunsStartInput) => RunTargetDeclaration | Promise<RunTargetDeclaration>;
703
+ /** Build the `RunConfig`. Called ONLY once every refusal has passed. */
704
+ readonly assembleRun: RunAssembler;
705
+ /**
706
+ * Mint a run id when the request carries none.
707
+ *
708
+ * REQUIRED rather than defaulted: the id generator lives in `@vostride/agent-qa-ids` (via core),
709
+ * which this package does not depend on, and a locally invented id shape would be a second
710
+ * spelling of a run's identity. Every shipped door already holds `generateRunId`.
711
+ */
712
+ readonly mintRunId: () => string;
713
+ /**
714
+ * Is this a run id this system would have minted? (2026-08-23, 401-20 · CR-04)
715
+ *
716
+ * REQUIRED, WITH NO DEFAULT AND NO FALLBACK, for exactly `mintRunId`'s reason and one more. The
717
+ * canonical predicate lives in `@vostride/agent-qa-ids`, which this package does not depend on,
718
+ * and a locally invented shape check would be a second spelling of a run's identity — the same
719
+ * argument that made minting an option. The extra reason is that an OPTIONAL predicate with a
720
+ * permissive fallback is this defect wearing a type: every composition that forgot it would be
721
+ * back to accepting any string, and the build would say nothing. A composition that omits this
722
+ * does not compile.
723
+ *
724
+ * It answers about the SHAPE only. Whether the id is free is this file's own question, asked
725
+ * against the run's own directory.
726
+ */
727
+ readonly isMintedRunId: (value: string) => boolean;
728
+ /**
729
+ * Notified with the started session, synchronously, before the core awaits it (D-09's bridge
730
+ * hook). Subscribers attach here; the bus replays from seq 1, so nothing is missed.
731
+ */
732
+ readonly onSession?: (session: RunSession) => void;
733
+ /**
734
+ * Build this run's post-verdict step (404 D-05/D-10) — the durable `analyze-run` enqueue plus
735
+ * ONE writer-lease consult, awaited by the shell between `verdict` and `runFinished`, whose
736
+ * answer becomes `runFinished.disposition`.
737
+ *
738
+ * HOST-COMPOSED, CORE-THREADED, and both halves are load-bearing. It is host-composed because
739
+ * the step speaks queue and writer-lease vocabulary this package's runner seam may not name —
740
+ * the same argument that made `mintRunId` an option rather than a local. It is threaded HERE
741
+ * rather than left to each door because the enqueue is precisely what the `runs.start` door and
742
+ * the daemon door forgot: a door that supplies no factory gets a run whose wire honestly reads
743
+ * `detached`, and a door that supplies one cannot then forget to wire it in.
744
+ *
745
+ * Called ONCE per run, after every refusal has passed and the persistence ports exist, so a
746
+ * refused run never composes one. Absent ⇒ no `postVerdict` member reaches `RunConfig.ports` at all
747
+ * (conditional spread, never an `undefined` field), and the shell reports `detached` with no
748
+ * warning — which is the truth about a host from which nothing will be drained.
749
+ */
750
+ readonly postVerdict?: (input: {
751
+ readonly runId: string;
752
+ readonly configDir: string;
753
+ }) => PostVerdictPort;
754
+ /** The door's first third-party code, awaited once the request is allowed and before any port. */
755
+ readonly afterRefusal?: () => Promise<void>;
756
+ }
757
+ /**
758
+ * The one in-process run execution core.
759
+ *
760
+ * Returns a `RunExecutionPort` (the shape `createLocalRunInvocation` binds `runs.start` over), so
761
+ * the op's evidence check — read the run's own event log back, refuse an empty one — applies to
762
+ * this seam unchanged.
763
+ *
764
+ * NO DEFENSIVE CATCH ANYWHERE (D-03). A run that crashes hard takes its host with it, on purpose:
765
+ * crash-through surfaces bugs instead of absorbing them, and the runner's budget/abort machinery
766
+ * is the orderly containment. The only `throw`s this file introduces are the two refusals above,
767
+ * which are decisions rather than faults.
768
+ */
769
+ declare function createRunInvocationCore(options: RunInvocationCoreOptions): {
770
+ execute(request: RunsStartInput): Promise<{
771
+ runId: RunId;
772
+ }>;
773
+ };
774
+
775
+ interface CreateEvidenceBlobReaderInput {
776
+ /** The runtime root, i.e. `<configDir>/.agent-qa` — the parent of `runs/`. */
777
+ readonly agentQaRoot: string;
778
+ }
779
+ declare function lookupEvidenceBlob(input: CreateEvidenceBlobReaderInput, runId: string, sha: string): EvidenceBlobLookup;
780
+ /**
781
+ * THE SAME RULE, MINUS THE BYTES (Phase 406, D-17) — presence for a whole run's artifact refs.
782
+ *
783
+ * WHY NOT `lookupEvidenceBlob(...).status`: a run page annotates every ref it is about to render,
784
+ * and the read arm loads a screenshot into memory to reach the status it then discards. This
785
+ * shaping walks the identical three-way split — malformed sha, unknown run, then the store's own
786
+ * answer — and asks the store to STAT instead of read. The two arms above `runDirectoryExists`
787
+ * are shared code, not a copy, so `found` here and `found` there are the same claim.
788
+ */
789
+ declare function statEvidenceBlob(input: CreateEvidenceBlobReaderInput, runId: string, sha: string): EvidenceBlobPresence;
790
+ /**
791
+ * The op's PORT over {@link lookupEvidenceBlob}.
792
+ *
793
+ * BOTH MEMBERS ARE BOUND HERE (406, D-17): this host holds run directories, so it can honestly
794
+ * answer the optional presence member, and an op reading it gets `present` for every ref rather
795
+ * than the omitted key a host without local storage would leave.
796
+ *
797
+ * ASYNC EVEN THOUGH THE STORE IS NOT, because the port is the shape a host that does not hold its
798
+ * blobs on a local disk would also have to satisfy — and a synchronous contract is one such a host
799
+ * could never implement without lying about it. The local answer is simply already resolved.
800
+ */
801
+ declare function createEvidenceBlobReader(input: CreateEvidenceBlobReaderInput): EvidenceBlobReaderPort;
802
+ /**
803
+ * The analysis job's shaping: the bytes, or nothing, with the two absences collapsed.
804
+ *
805
+ * THE COLLAPSE IS THIS CALLER'S, not the rule's. An evidence-diet pass has the same work to do
806
+ * whether a run is unknown or its blob was already collected — there is no screenshot either way —
807
+ * so it takes the narrower answer rather than making the shared rule narrower for everyone.
808
+ */
809
+ declare function readEvidenceBlobBytes(input: CreateEvidenceBlobReaderInput, runId: string, sha: string): Uint8Array | undefined;
810
+
811
+ interface RunQuarantineSnapshotInput {
812
+ /** The committed knowledge root, already resolved (`resolveKnowledgeRoot`). */
813
+ knowledgeRoot: string;
814
+ /** The run's RAW product / scope key; slugged here to match the projection partition law. */
815
+ product: string;
816
+ /** Run-start instant as an ISO-8601 string — the ONE clock read, made by the caller. */
817
+ now: string;
818
+ warn?: (message: string) => void;
819
+ }
820
+ /**
821
+ * Resolve the entries this run will apply: active (`'quarantined'`) and belonging to this run's
822
+ * product. Expired entries are deliberately EXCLUDED — a lapsed entry's test fails builds again
823
+ * (D-11) — and released entries are gone.
824
+ *
825
+ * Measured consequence worth knowing at THIS seam, because this is where the exclusion is decided:
826
+ * for a test still crossing the threshold that is exactly ONE red build. The snapshot below is
827
+ * taken at run start (entry lapsed ⇒ not applied ⇒ red), and that same run's post-run fold
828
+ * re-enters it with a fresh full-lifetime deadline before the next run takes its own snapshot. No
829
+ * human is scheduled into that loop at any point, and nothing bounds the repetition
830
+ * (measured 2026-08-06).
831
+ */
832
+ declare function resolveRunQuarantineSnapshot(input: RunQuarantineSnapshotInput): readonly QuarantineAppliedEntry[];
833
+
834
+ /**
835
+ * Hand every entity id an output surfaced to the addressing ledger's sighting seam.
836
+ *
837
+ * Fire-and-forget by contract: it resolves when the sighting is recorded, and a rejection is
838
+ * not the read's problem. An address is a permanent fact about an entity and a listing is a
839
+ * question about the store; failing the question because the bookkeeping failed would let the
840
+ * ledger take down the answer it exists to annotate.
841
+ */
842
+ type SightMemoryEntities = (bundleIds: readonly string[]) => Promise<void>;
843
+
844
+ interface LogicalDumpTable {
845
+ readonly table: string;
846
+ readonly orderBy: readonly string[];
847
+ readonly rows: readonly Record<string, unknown>[];
848
+ }
849
+ interface LogicalDumpCheckpoint {
850
+ /** Relative to the checkpoint root, so two runs under different temp roots compare. */
851
+ readonly path: string;
852
+ readonly digest: string;
853
+ }
854
+ interface LogicalDump {
855
+ readonly schemaVersion: number;
856
+ readonly tables: readonly LogicalDumpTable[];
857
+ readonly checkpoints: readonly LogicalDumpCheckpoint[];
858
+ }
859
+
860
+ /** One chain-terminal notification row, with its payload guarded on the way out (388 D-09). */
861
+ interface JobStoreNotification {
862
+ readonly runId: string;
863
+ readonly payload: RunNotification;
864
+ readonly createdAt: string;
865
+ }
866
+ interface SqliteJobStore extends JobStorePort {
867
+ close(): void;
868
+ /**
869
+ * The chain-terminal notifications this database holds, ordered by run.
870
+ *
871
+ * On the implementation rather than the neutral port, beside `logicalDump`, and for the
872
+ * same reason: the port is the ENGINE's vocabulary and the engine never reads a
873
+ * notification — it writes one, once, as part of ending a job. Every reader of these rows
874
+ * is a surface, and 388 D-14 routes surfaces through the function layer, not through here.
875
+ *
876
+ * The payload is parsed through the schema guard rather than handed back as text: the
877
+ * column is bytes another build could have written, and a row that lost a stage cannot be
878
+ * allowed to travel as a `RunNotification`.
879
+ */
880
+ listNotifications(): Promise<readonly JobStoreNotification[]>;
881
+ /**
882
+ * The D-03 comparison basis, in full: every table this database holds plus a digest per
883
+ * checkpoint file.
884
+ *
885
+ * Kept beside `dumpState()` rather than replacing it, because the two answer different
886
+ * questions and collapsing them would weaken both. `dumpState()` is the typed, engine-facing
887
+ * projection of the job rows and it is part of the neutral port. This one is the storage
888
+ * implementation's own full-state instrument: it knows about the checkpoint directory and
889
+ * about tables the port has no vocabulary for, and it is the thing a crash comparison holds
890
+ * up against another run.
891
+ */
892
+ logicalDump(): Promise<LogicalDump>;
893
+ /** The same dump as one comparable string. Byte equality here is state equality. */
894
+ dumpLogicalState(): Promise<string>;
895
+ }
896
+
897
+ /**
898
+ * 407 D-13/D-17 — THE retrieval-rung resolver, bound at the one edge that may name the writer
899
+ * entry (385 D-14/SC5) and handed to `openRunKnowledgeComposition` by every run door.
900
+ *
901
+ * The binding exists so there is exactly ONE resolver in the tree with exactly ONE importer of
902
+ * the package that owns it. `run-composition.ts` declares the seam's shape structurally and takes
903
+ * the function as a required input rather than importing it — naming knowledge-local's main entry
904
+ * from a second file would retire the singleton the arch gate enforces, and the fix would look
905
+ * like relaxing the gate.
906
+ *
907
+ * Typed as {@link RetrievalRungResolver} rather than by its own signature, so the pass-through
908
+ * that reaches the CLI is the narrow two-root contract the doors actually use — the embedder
909
+ * `probes` seam and the preferred profile stay knowledge-local's business.
910
+ */
911
+ declare const resolveHostRetrievalRung: RetrievalRungResolver;
912
+ /**
913
+ * 407 D-18/D-20 — the READ-ONLY knowledge probes a diagnostic grades a host with.
914
+ *
915
+ * `doctor` needs the two axes SEPARATELY, not just the folded word: its `Knowledge store` row is
916
+ * about the index face, its `Embedder` row is about the capability outcome, and only its third row
917
+ * is the fold. {@link resolveHostRetrievalRung} above hands back the fold alone, so a surface that
918
+ * had only that would have to re-derive the other two — which is a second fold in everything but
919
+ * name, and exactly what D-17's one-resolver rule exists to prevent.
920
+ *
921
+ * Published as its own frozen table rather than folded into {@link LOCAL_DAEMON_CAPABILITIES}
922
+ * because every member here is a READ. Nothing in it acquires a lease, spawns a worker, downloads
923
+ * an artifact or writes to the knowledge root, and a caller that holds this object cannot mutate
924
+ * the thing it is reporting on. That is the whole mitigation for T-407-19: the boundary is the
925
+ * shape of the capability, not a promise in a doc comment at the call site.
926
+ *
927
+ * `foldRetrievalRung` rides along for the same reason the resolver does — one fold table in one
928
+ * package, reached through the one edge that may name the writer entry (385 D-14/SC5).
929
+ */
930
+ interface LocalKnowledgeProbeCapabilities {
931
+ readonly classifyIndexFace: typeof classifyIndexFace;
932
+ readonly foldRetrievalRung: typeof foldRetrievalRung;
933
+ readonly indexDbPath: typeof indexDbPath;
934
+ readonly readEmbedderWorkerRegistration: typeof readEmbedderWorkerRegistration;
935
+ readonly readWriterLease: typeof readWriterLease;
936
+ readonly resolveEmbedderCapability: typeof resolveEmbedderCapability;
937
+ }
938
+ declare const LOCAL_KNOWLEDGE_PROBES: LocalKnowledgeProbeCapabilities;
939
+
940
+ /**
941
+ * The standing daemon's writer-authority substrate, published through the same one composition
942
+ * edge as every other local writer capability. Surface packages may import agent-local, but the
943
+ * architecture firewall deliberately forbids them from naming knowledge-local or safety
944
+ * directly; keeping this frozen table here preserves that boundary without duplicating lease
945
+ * behavior in the CLI host.
946
+ */
947
+ interface LocalDaemonCapabilities {
948
+ readonly acquireWriterLease: typeof acquireWriterLease;
949
+ readonly heartbeatLease: typeof heartbeatLease;
950
+ readonly releaseLease: typeof releaseLease;
951
+ readonly readWriterLease: typeof readWriterLease;
952
+ readonly isLeaseStale: typeof isLeaseStale;
953
+ /**
954
+ * CR-01/CR-08: the one three-valued freshness verdict — fresh, reclaimable, or unknown.
955
+ *
956
+ * Any surface that classifies a holder as recoverable must read this and not the lease file
957
+ * alone, and must name what it does with `unknown` rather than inheriting a boolean it never
958
+ * chose. An authority state that cannot be read is not evidence that nobody holds it.
959
+ */
960
+ readonly deriveWriterLeaseFreshness: typeof deriveWriterLeaseFreshness;
961
+ readonly countPendingCurationJobs: typeof countPendingCurationJobs;
962
+ readonly countRemainingCurationWork: typeof countRemainingCurationWork;
963
+ readonly inspectDrainableCurationWork: typeof inspectDrainableCurationWork;
964
+ readonly listQueuedCandidates: typeof listQueuedCandidates;
965
+ readonly acquireEmbedder: typeof acquireEmbedder;
966
+ readonly drainQueue: typeof drainQueue;
967
+ readonly runMaintenanceTail: typeof runMaintenanceTail;
968
+ readonly resolveEmbedderWorkerEntryPath: () => string;
969
+ readonly readEmbedderWorkerRegistration: typeof readEmbedderWorkerRegistration;
970
+ readonly embedderWorkerProcessIsAlive: typeof embedderWorkerProcessIsAlive;
971
+ readonly embedderWorkerSocketPath: typeof embedderWorkerSocketPath;
972
+ readonly probeEmbedderWorkerSocket: typeof probeEmbedderWorkerSocket;
973
+ /** CR-05: no surface may signal a registered worker pid without this verdict returning 'match'. */
974
+ readonly verifyEmbedderWorkerIncarnation: typeof verifyEmbedderWorkerIncarnation;
975
+ readonly resolveProcessIncarnation: typeof resolveProcessIncarnation;
976
+ readonly removeEmbedderWorkerRegistration: (agentQaRoot: string, expected: EmbedderWorkerRegistration) => ReturnType<typeof removeEmbedderWorkerRegistration>;
977
+ readonly resetEmbedderCapability: () => void;
978
+ readonly pinLocalHeadlessExecutionContext: () => void;
979
+ }
980
+ declare const LOCAL_DAEMON_CAPABILITIES: LocalDaemonCapabilities;
981
+ /**
982
+ * Index maintenance and the campaign/job rollback verbs, plus the lease every one of them is only
983
+ * legal to hold under. Grouped rather than flattened because they share that precondition: a host
984
+ * reaching for `purgeRecord` without a lease is the mistake the grouping makes visible.
985
+ */
986
+ interface LocalKnowledgeMaintenanceCapabilities {
987
+ readonly acquireWriterLease: typeof acquireWriterLease;
988
+ readonly refreshWriterLeaseAuthority: typeof refreshWriterLeaseAuthority;
989
+ readonly releaseLease: typeof releaseLease;
990
+ readonly rebuildIndex: typeof rebuildIndex;
991
+ readonly purgeRecord: typeof purgeRecord;
992
+ readonly resolvePurgeAffectedSet: typeof resolvePurgeAffectedSet;
993
+ readonly purgeAffectedSetFingerprint: typeof purgeAffectedSetFingerprint;
994
+ readonly rollbackCampaign: typeof rollbackCampaign;
995
+ readonly rollbackCurationJob: typeof rollbackCurationJob;
996
+ readonly resolveRollbackAffectedSet: typeof resolveRollbackAffectedSet;
997
+ readonly rollbackAffectedSetFingerprint: typeof rollbackAffectedSetFingerprint;
998
+ }
999
+ interface ReceiptWriterInput extends Pick<CreateLocalKnowledgeStoreConfig, 'knowledgeRoot' | 'executionContext' | 'resolvePromotionPosture' | 'resolveTrustPriors' | 'sourceBuildOrdinal' | 'sourceRunId'> {
1000
+ /** The caller already holds this authority; these capabilities never acquire a lease. */
1001
+ lease: {
1002
+ leaseId: string;
1003
+ epoch: number;
1004
+ };
1005
+ }
1006
+ interface RecordGateReceiptInput extends ReceiptWriterInput {
1007
+ receipt: GateReceipt;
1008
+ }
1009
+ interface PromoteWithReceiptsInput extends ReceiptWriterInput {
1010
+ candidateId: RecordId;
1011
+ receipts?: readonly GateReceipt[];
1012
+ }
1013
+ declare function recordGateReceipt(input: RecordGateReceiptInput): Promise<GateReceiptRecordOutcome>;
1014
+ declare function promoteWithReceipts(input: PromoteWithReceiptsInput): Promise<PromotionOutcome>;
1015
+ /** Internal facts only: public showRecord deliberately omits bundle construction fields. */
1016
+ declare function readFeedbackRecord(input: {
1017
+ knowledgeRoot: string;
1018
+ recordId: string;
1019
+ evidenceRef?: ArtifactRef;
1020
+ }): ServedRecordFacts | undefined;
1021
+ interface LocalCurationCapabilities {
1022
+ readonly readFeedbackRecord: typeof readFeedbackRecord;
1023
+ readonly appendMarkEvidence: typeof appendMarkEvidence;
1024
+ /**
1025
+ * 2026-09-15, 423 D-16/D-17: the lease-free planning half appendMarkEvidence executes, so a
1026
+ * consequence preview names every confirm/overturn effect without the writer. Reads only.
1027
+ */
1028
+ readonly planMarkEvidence: typeof planMarkEvidence;
1029
+ readonly projectMarkEvidenceOutcome: typeof projectMarkEvidenceOutcome;
1030
+ readonly triageMark: typeof triageMark;
1031
+ readonly servedIdsOn: typeof servedIdsOn;
1032
+ readonly procedureReadOn: typeof procedureReadOn;
1033
+ readonly applyFeedback: typeof applyFeedback;
1034
+ readonly recordGateReceipt: typeof recordGateReceipt;
1035
+ readonly promoteWithReceipts: typeof promoteWithReceipts;
1036
+ /** Serving-side reader the tail folds against; read-only despite arriving on the writer edge. */
1037
+ readonly openKnowledgeReader: typeof openKnowledgeReader;
1038
+ readonly drainQueue: typeof drainQueue;
1039
+ readonly foldRunOutcomes: typeof foldRunOutcomes;
1040
+ /**
1041
+ * THE mechanism a version bump rides (393 CR-02). The per-run receipt below carries the
1042
+ * version, but invalidating receipts only re-runs folds that answer 'already-applied' on the
1043
+ * durable marker — so the refold a bump promises happens through THIS capability, as a rebuild
1044
+ * of the derived family, and nowhere else.
1045
+ *
1046
+ * It is invoked at exactly two sites, one per supported host (393 round 6, CR-02-daemon): once
1047
+ * per CLI curation-tail sweep, and once per daemon lease acquisition. Both, because the CLI
1048
+ * tail is unreachable whenever a standing daemon takes the hand-off — a promise true of one
1049
+ * host is, on the other host, a projection that never converges.
1050
+ */
1051
+ readonly reconcileRunOutcomesProjection: typeof reconcileRunOutcomesProjection;
1052
+ readonly enqueueCurationCandidateProjection: typeof enqueueCurationCandidateProjection;
1053
+ readonly readCanonicalBundleAnchors: typeof readCanonicalBundleAnchors;
1054
+ readonly readCanonicalBundleStateDigests: typeof readCanonicalBundleStateDigests;
1055
+ readonly readCanaryPostureFacts: typeof canaryPostureFactsFor;
1056
+ readonly readStandingGateReceipts: typeof standingReceiptDigestsFor;
1057
+ readonly readLowerTrustShares: typeof lowerTrustSharesFor;
1058
+ /** 418 D-07: reuse the writer's observation identity through the existing CLI bridge. */
1059
+ readonly canaryOutcomeObservationId: typeof canaryOutcomeObservationId;
1060
+ /**
1061
+ * Component of the tail's receipt: a bump refolds runs already receipted at the old version.
1062
+ * The receipt is the per-run half of that promise; `reconcileRunOutcomesProjection` above is
1063
+ * the half that actually re-derives the rows.
1064
+ */
1065
+ readonly runOutcomesProjectionVersion: typeof RUN_OUTCOMES_PROJECTION_VERSION;
1066
+ readonly maintenance: LocalKnowledgeMaintenanceCapabilities;
1067
+ }
1068
+ declare const LOCAL_CURATION_CAPABILITIES: LocalCurationCapabilities;
1069
+ /**
1070
+ * Enumerate `<agentQaRoot>/runs/<slug>/handoff.json`. A project with no runs directory is an
1071
+ * ordinary empty result, not an error; an individual unreadable or version-rejected document is
1072
+ * skipped so one bad run never hides every other run.
1073
+ */
1074
+ declare function createRunsReader(agentQaRoot: string): RunsReaderPort;
1075
+ /**
1076
+ * The knowledge READ port, served through the PHYSICALLY read-only `/reader` package entry.
1077
+ *
1078
+ * The five read verbs live in `knowledge-review-reader.ts`, which is registered in the arch
1079
+ * reader carve-out's pinned file list and therefore CANNOT name the writer-gated entry. This
1080
+ * file can (it also binds the curator), so keeping the verbs here would have put them in the
1081
+ * one place in the workspace where a write import is legal — separation by discipline instead
1082
+ * of by rule. What stays here is the half that genuinely belongs to a composition root: the
1083
+ * D-01 sighting site, which mints into the durable addressing ledger and so must live where
1084
+ * the writer entry may be named.
1085
+ *
1086
+ * The knowledge ROOT arrives as a host-supplied resolver rather than being derived here: deciding
1087
+ * which configuration file is in force, layering an environment over it and choosing whether to
1088
+ * load and execute auth plugins are three decisions a daemon makes differently from a terminal,
1089
+ * and by the plan-08 dissection rule a thing a headless host would substitute is a port.
1090
+ */
1091
+ declare function createKnowledgeReader(resolveKnowledgeRoot: () => Promise<string> | string, sightMemoryEntities?: SightMemoryEntities, resolvePromotionPosture?: () => Promise<ResolvePromotionPosture | undefined>, resolveTrustPriors?: CreateLocalKnowledgeStoreConfig['resolveTrustPriors']): KnowledgeReaderPort;
1092
+ interface CreateKnowledgeReviewInput {
1093
+ readonly resolveTrustPriors?: CreateLocalKnowledgeStoreConfig['resolveTrustPriors'];
1094
+ readonly resolvePromotionPosture?: () => Promise<ResolvePromotionPosture | undefined>;
1095
+ readonly agentQaRoot: string;
1096
+ readonly resolveKnowledgeRoot: () => Promise<string> | string;
1097
+ /**
1098
+ * The writer lease this host ALREADY holds, when it is the holder (D-18).
1099
+ *
1100
+ * The daemon's half of the routing rule. A holder cannot acquire its own lease a second time, so
1101
+ * it hands the authority it owns to the change; supplying this is mutually exclusive with
1102
+ * supplying a delegate, and a host that supplies both is a host claiming to be two places.
1103
+ */
1104
+ readonly resolveWriterAuthority?: () => {
1105
+ readonly leaseId: string;
1106
+ readonly epoch: number;
1107
+ } | undefined;
1108
+ /**
1109
+ * 432-12 (D-05): how a direct change reaches a live writer-lease holder. The holder receives the
1110
+ * validated request and the change's stable identity, bounded; the lease is never taken. Absent,
1111
+ * a held lease refuses the change by name. Exclusive with {@link resolveWriterAuthority}: a holder
1112
+ * never delegates. The daemon supplies none, which is why its own changes cannot recurse.
1113
+ */
1114
+ readonly knowledgeChangeDelegate?: ExplicitReviewDelegate;
1115
+ /**
1116
+ * 432-12: the context a direct change is recorded under. Absent means the process resolver, the
1117
+ * default every registry in this composition already reads. The context is recorded as-is; an
1118
+ * explicit change applies in every context (D-04/D-08).
1119
+ */
1120
+ readonly resolveExecutionContext?: () => ExecutionContext;
1121
+ }
1122
+ /**
1123
+ * Bind the mutating review verbs over the review modules and the Curator's one commit path.
1124
+ *
1125
+ * A SEPARATE factory from `createKnowledgeReader`, mirroring the port split it implements: this
1126
+ * is the object whose presence makes accept/reject/forget report `bound`, and a host that wants
1127
+ * only the read verbs simply does not build one. The `MEM-` short-ID leg resolves through the
1128
+ * addressing ledger, opened per call and closed again on exactly the reasoning the entity
1129
+ * directory uses — a durable file the composition root owns, never a handle a review module holds.
1130
+ */
1131
+ declare function createKnowledgeReview(input: CreateKnowledgeReviewInput): KnowledgeReviewPort;
1132
+ interface ApplyKnowledgeChangeInput {
1133
+ readonly agentQaRoot: string;
1134
+ readonly knowledgeRoot: string;
1135
+ readonly request: ExplicitReviewRequest;
1136
+ /** The caller's stable identity for this change: a retry with the same identity lands nothing twice. */
1137
+ readonly operationId: string;
1138
+ /** The op making the change, stamped on its Gate-3 receipts. */
1139
+ readonly opId: string;
1140
+ /** The caller's actual context, recorded as-is. An explicit change applies in every context. */
1141
+ readonly executionContext: ExecutionContext;
1142
+ readonly resolveTrustPriors?: CreateLocalKnowledgeStoreConfig['resolveTrustPriors'];
1143
+ readonly resolvePromotionPosture?: () => Promise<ResolvePromotionPosture | undefined>;
1144
+ /**
1145
+ * How this host reaches a live writer-lease holder. The holder receives exactly the validated
1146
+ * request and identity, bounded; the lease is never taken. Absent, a held lease refuses by name.
1147
+ */
1148
+ readonly delegate?: ExplicitReviewDelegate;
1149
+ /** The lease this host already holds, when it is the holder. Exclusive with {@link delegate}. */
1150
+ readonly writerAuthority?: {
1151
+ readonly leaseId: string;
1152
+ readonly epoch: number;
1153
+ };
1154
+ }
1155
+ /**
1156
+ * Apply one explicit knowledge change through the direct writer (432-06), with this host's short-id
1157
+ * resolver. Nothing is filed: the change is committed, or it is refused by type.
1158
+ */
1159
+ declare function applyKnowledgeChange(input: ApplyKnowledgeChangeInput): Promise<ExplicitReviewResult>;
1160
+ interface CreateEntityDirectoryInput {
1161
+ readonly agentQaRoot: string;
1162
+ readonly resolveKnowledgeRoot: () => Promise<string> | string;
1163
+ }
1164
+ interface SweepEntityIndexInput extends CreateEntityDirectoryInput {
1165
+ /**
1166
+ * Test seam ONLY. Production omission runs the real acquisition ladder, which is what makes
1167
+ * the dense rung appear on hosts that have a model and stay absent on hosts that do not.
1168
+ */
1169
+ readonly acquireEmbedderCapability?: typeof acquireEmbedder;
1170
+ }
1171
+ interface SweepEntityIndexOutcome extends EntityIndexSweepReport {
1172
+ /** How many renderings gained a vector this sweep. Zero on a floor host, by design. */
1173
+ readonly embedded: number;
1174
+ }
1175
+ /**
1176
+ * The daemon's maintenance-cadence sweep over the D-06 projection.
1177
+ *
1178
+ * Three properties, and each is a deliberate refusal rather than an omission:
1179
+ *
1180
+ * - it takes NO writer lease. Everything it writes lives in the entity index's own file, and
1181
+ * a projection that needed the lease could not be maintained by a host that does not hold
1182
+ * one — which is every one-shot CLI process.
1183
+ * - it never writes the addressing ledger. It READS the ledger to know what exists (D-02: the
1184
+ * ledger is the durable truth, not a projection) and writes only the rendering of it.
1185
+ * - it embeds only what a host can already embed. `acquireEmbedder` with no download is the
1186
+ * same ladder the retrieval legs ride: ready means the dense rung joins the resolver on this
1187
+ * host, and anything else leaves the projection lexical-only rather than blocking the sweep.
1188
+ */
1189
+ declare function sweepEntityIndexProjection(input: SweepEntityIndexInput): Promise<SweepEntityIndexOutcome>;
1190
+ /**
1191
+ * The host's run-execution capability: run this request to completion, and say which run it was.
1192
+ *
1193
+ * A PORT rather than shared code, by the plan-08 dissection rule. Would a headless drainer
1194
+ * execute a suite the way a terminal host does? It would substitute — the process model
1195
+ * (in-process, a child, a worker pool), the signal handling, the reporters and the exit
1196
+ * discipline are all decisions the host owns and answers differently. What crosses into the
1197
+ * composition is the type; what stays with the host is the orchestration.
1198
+ *
1199
+ * The verb resolves the IDENTITY of the run and nothing else. Anything richer would be the
1200
+ * host telling the composition what happened, and the whole point of the binding below is
1201
+ * that the composition does not take the host's word for it.
1202
+ */
1203
+ interface RunExecutionPort {
1204
+ /**
1205
+ * 2026-10-01 (432-18, D-15): the optional `gate` argument (401-08, D-08) carried the standing
1206
+ * approvals in force — the durable ledger's answer unioned with any dispatch-scoped grant — to a
1207
+ * run gate behind the port. The run gate went in 432-13 and the ledger and the dispatch overlay
1208
+ * go in 432-18, so a host is handed the request and nothing else.
1209
+ */
1210
+ execute(request: RunsStartInput): Promise<StartedRun>;
1211
+ }
1212
+ /**
1213
+ * The run this host reported did not leave a durable event log behind.
1214
+ *
1215
+ * The composition's evidence check, and the reason `runs.start` cannot be satisfied by a host
1216
+ * that returns a plausible run id without running anything. The v49 bus writes `runStarted`
1217
+ * before anything else can happen, so a run that began at all has a non-empty log; an empty
1218
+ * one means no run took place under that id. Read through {@link buildRunPorts} — the same
1219
+ * `FsEventLog` family the runner appends with — so what is inspected is the run's own record
1220
+ * rather than a path this file re-derives.
1221
+ */
1222
+ declare class RunProducedNoEventsError extends Error {
1223
+ readonly runId: string;
1224
+ constructor(runId: string);
1225
+ }
1226
+ /**
1227
+ * Bind `runs.start` over a host's run execution.
1228
+ *
1229
+ * The composition contributes the half a host must not be trusted with: after the host says a
1230
+ * run happened, the run's own durable event log is read back and an empty one is refused. The
1231
+ * op then builds its acknowledgment from the completed-run record through the SAME reader
1232
+ * `runs.get` answers from, so "the run is real" and "the run is addressable" are one fact.
1233
+ */
1234
+ declare function createLocalRunInvocation(input: {
1235
+ readonly agentQaRoot: string;
1236
+ readonly execution: RunExecutionPort;
1237
+ }): RunInvocationPort;
1238
+ /**
1239
+ * A parsed draft, as the HOST hands it over.
1240
+ *
1241
+ * Structurally identical to `packages/agent`'s `ParsedDraftLike`, and mirrored here rather than
1242
+ * imported for the reason every mirror in this graph exists: that type lives inside `agent`'s
1243
+ * `authoring/` and is not on its barrel, and publishing it to give a composition a name would put
1244
+ * a gate-orchestration type on the surface that publishes exactly one dispatch.
1245
+ *
1246
+ * `filePath` MUST be the workspace-relative identity the runner records on
1247
+ * `PerTestOutcome.filePath` — `workspaceRelativeIdentityPath`, the one spelling of that recipe.
1248
+ * Gates 1-2 find the draft's own member inside each run document by comparing against those bytes,
1249
+ * so a path spelled any other way makes every gate-run report `absent` for its own draft and gate
1250
+ * 1 fail for a join reason wearing a draft reason's clothes.
1251
+ */
1252
+ interface ParsedDraftDescriptor {
1253
+ readonly testId: string;
1254
+ readonly filePath: string;
1255
+ readonly testName: string;
1256
+ /**
1257
+ * The parsed test's `target`. REQUIRED, exactly as `ParsedDraftLike.target` and
1258
+ * `TestDefinitionSchema` require it (394-37, WR-04).
1259
+ *
1260
+ * A mirror that dropped it would hand `packages/agent` a draft whose target the compile stage
1261
+ * cannot check against the vocabulary it just served — and the cost of not checking is paid as
1262
+ * real child gate runs on a name `resolveTarget` throws on, against a key no repair patch may
1263
+ * edit.
1264
+ */
1265
+ readonly target: string;
1266
+ readonly steps: readonly string[];
1267
+ /**
1268
+ * The parsed test's `context` block — an EDITABLE root, and therefore one the credential gate
1269
+ * scans. Structurally identical to `ParsedDraftLike.context`; a mirror that dropped it would
1270
+ * hand `packages/agent` a draft whose context can no longer be screened.
1271
+ */
1272
+ readonly context?: string;
1273
+ readonly contentDigest: string;
1274
+ }
1275
+ /**
1276
+ * What a host can say about a caller-supplied draft path.
1277
+ *
1278
+ * Four refusal arms and one success, because `packages/agent` is `storageNeutral` and genuinely
1279
+ * cannot answer any of them: it can neither stat a path nor know where the workspace ends. The op
1280
+ * turns each arm into its own NAMED error, so the refusal a caller catches is declared beside the
1281
+ * capability while the syscall stays with the host that owns a filesystem (T-393-01).
1282
+ */
1283
+ type DraftParseOutcome = {
1284
+ readonly kind: 'parsed';
1285
+ readonly draft: ParsedDraftDescriptor;
1286
+ } | {
1287
+ readonly kind: 'not-found';
1288
+ } | {
1289
+ readonly kind: 'not-a-file';
1290
+ } | {
1291
+ readonly kind: 'outside-workspace';
1292
+ } | {
1293
+ readonly kind: 'unparsable';
1294
+ readonly reason: string;
1295
+ };
1296
+ /** One authored suite test as TEXT, for gate 3's degraded lane (D-16). */
1297
+ interface SuiteTextEntry {
1298
+ readonly filePath: string;
1299
+ readonly testName: string;
1300
+ readonly stepsText: readonly string[];
1301
+ }
1302
+ /**
1303
+ * The host's authoring capability: parse ONE draft, and enumerate the authored-test universe.
1304
+ *
1305
+ * BOTH VERBS OR NEITHER, and the bundling is a report rather than a convenience. A host that
1306
+ * supplied only the parse would bind a capability whose gate-3 text lane is permanently empty —
1307
+ * evidence-less suite tests would silently stop joining the comparison, and a draft would look
1308
+ * novel purely because whatever it duplicates has never been run. That is D-16's unsafe direction
1309
+ * arrived at by omission, and the only way to make it un-omittable is to make the two arrive
1310
+ * together.
1311
+ *
1312
+ * Both verbs live at the HOST because both need `parseTestFile` from `@vostride/agent-qa-core`,
1313
+ * which neither this package nor `agent` may import (the FUNC-02 firewall) — the parse-at-host
1314
+ * precedent `cli/src/curation/authoring-test-discovery.ts` established.
1315
+ */
1316
+ interface DraftAuthoringPort {
1317
+ parseDraft(filePath: string): Promise<DraftParseOutcome>;
1318
+ suiteTextCorpus(): Promise<readonly SuiteTextEntry[]>;
1319
+ /**
1320
+ * 394-33 · WR-07: the names a `target` may legally take in this project.
1321
+ *
1322
+ * ON THIS PORT for the reason `deriveDefaultScope` is: both hosts build their authoring
1323
+ * capability from ONE factory, so a verb that lives here is reached by the CLI `fn` surface and
1324
+ * the chat/daemon host without a second binding either could forget. A vocabulary served by only
1325
+ * one of the two hosts would make "does stage 3 guess at `target`" depend on which surface a
1326
+ * person happened to author from.
1327
+ *
1328
+ * IT ENUMERATES AND NEVER REFUSES — the opposite posture to `deriveDefaultScope` right below it,
1329
+ * which throws when the answer is not unique. There is no such thing as an ambiguous list, and a
1330
+ * project declaring no targets has an honest empty answer rather than a failure.
1331
+ */
1332
+ configuredTargetNames(): Promise<readonly string[]>;
1333
+ /**
1334
+ * 394-26 · WINDOWS 110: which scope this project authors for, when the caller named none.
1335
+ *
1336
+ * ON THIS PORT rather than beside it, and that placement is the guarantee. Both hosts build
1337
+ * their authoring capability from one factory, so a verb that lives here is reached by the CLI
1338
+ * `fn` surface and the chat/daemon host without a second binding either could forget — which is
1339
+ * what makes "there is ONE derivation" a fact about the wiring rather than a convention.
1340
+ *
1341
+ * IT THROWS RATHER THAN ANSWERING `undefined` when the project has no unique product: the host
1342
+ * is the only side that knows which candidates it saw, and a refusal that cannot name them
1343
+ * leaves an author with no way to write the `scope` that would have worked.
1344
+ */
1345
+ deriveDefaultScope(): Promise<ScopeKey>;
1346
+ /**
1347
+ * 394 D-01 · WTST-01: PARSE the compiled draft, then WRITE it to the shelf — in that order, as
1348
+ * one verb, joining for exactly the reason the two above bundle.
1349
+ *
1350
+ * ONE VERB AND NOT TWO, because the order is the mitigation (T-394-06). Model-authored YAML is
1351
+ * the one input on the authoring path that no schema at the op boundary can vet, and separate
1352
+ * verbs would let a caller write first and discover the parse failure afterwards — with bytes
1353
+ * already on this workspace's disk. Here an unparsable draft answers `unparsable` having
1354
+ * created nothing at all.
1355
+ *
1356
+ * THE DIGEST IS THE HOST'S, over the bytes it is writing, and it is the caller's only
1357
+ * addressing: the shelf path is minted from it, so nothing the model emitted reaches the
1358
+ * filesystem as a path component. `DraftRef.contentDigest`'s whole promise — a later edit
1359
+ * cannot inherit an earlier receipt — is only true while the digest is over what was stored.
1360
+ */
1361
+ stageDraft(contents: string): Promise<DraftStageOutcome>;
1362
+ /**
1363
+ * 394-06 D-09/D-20: apply an RFC-6902 patch set to a staged draft and stage the result.
1364
+ *
1365
+ * AT THE HOST for the same reason its two neighbours are: `applyPatchSetToTestDoc` needs the
1366
+ * yaml CST from `@vostride/agent-qa-core`, which neither this package nor `agent` may import.
1367
+ * It answers the same `DraftStageOutcome`, so a patch that renders an unrunnable document is
1368
+ * refused with the shelf untouched rather than written and discovered later.
1369
+ */
1370
+ applyPatchesToDraft(draft: ParsedDraftDescriptor, patchSet: TestEditPatchSet): Promise<DraftStageOutcome>;
1371
+ /**
1372
+ * 432 D-06/D-07: write a staged draft's bytes to its test path — the direct final-test writer.
1373
+ *
1374
+ * AT THE HOST for its neighbours' reason: the workspace identity mint lives in
1375
+ * `@vostride/agent-qa-core`. The host checks the boundary and that the target still holds the
1376
+ * bytes the caller read (absent for a new test), writes through a sibling temp and a rename, runs
1377
+ * no git, and answers `not-written` with its reason whenever the bytes did not land.
1378
+ */
1379
+ publishDraft(request: DraftPublishRequest): Promise<TestFileWrite>;
1380
+ }
1381
+ /** One draft to write: its shelf path, its target, and what the target held when it was read. */
1382
+ interface DraftPublishRequest {
1383
+ readonly draftPath: string;
1384
+ readonly targetPath: string;
1385
+ readonly sourceDigest?: string;
1386
+ }
1387
+ /**
1388
+ * What staging produced: a parsed draft that IS on the shelf, or a refusal that wrote nothing.
1389
+ *
1390
+ * Structurally the mirror of `agent`'s `DraftStageOutcome`, on this file's own mirror discipline
1391
+ * — that type lives inside `agent`'s `authoring/` and the value crossing this seam is assignable
1392
+ * to it unchanged.
1393
+ */
1394
+ type DraftStageOutcome = {
1395
+ readonly kind: 'staged';
1396
+ readonly draft: ParsedDraftDescriptor;
1397
+ } | {
1398
+ readonly kind: 'unparsable';
1399
+ readonly reason: string;
1400
+ };
1401
+ /** Wall-clock in the one format every store column and dump row is written in. */
1402
+ declare const SYSTEM_CLOCK: ClockPort;
1403
+ declare const DIGEST_JOB_ID_SOURCE: JobIdSource;
1404
+ /**
1405
+ * The durable half of the host, opened on demand.
1406
+ *
1407
+ * `store` is the concrete sqlite store rather than the neutral port: the host that opened the
1408
+ * database is the one entitled to close it and to take the D-03 logical dump, and both verbs live
1409
+ * on the implementation. Nothing else in the package publishes it.
1410
+ */
1411
+ interface LocalAgentJobs {
1412
+ readonly queue: AgentJobQueue;
1413
+ readonly store: SqliteJobStore;
1414
+ readonly kinds: JobKindRegistry;
1415
+ /** The capability the queue was constructed with, exposed so a host can assert what it bound. */
1416
+ readonly model: JobModelCapability;
1417
+ /**
1418
+ * The id source the queue was constructed with.
1419
+ *
1420
+ * Exposed because a caller that watches this queue's outcomes must be able to derive the same
1421
+ * ids the enqueue addressed — the push half of a run's chip trail and the pull half of its
1422
+ * status have to agree about which rows belong to which run. Publishing the SOURCE makes that
1423
+ * agreement structural; re-deriving a default at the call site would make it a convention, and
1424
+ * a convention is exactly what breaks the first time a host is composed with a different one.
1425
+ */
1426
+ readonly idSource: JobIdSource;
1427
+ /**
1428
+ * The kind exclusion the queue was constructed with, resolved from
1429
+ * `UNCLAIMABLE_BY_PROSE_POSTURE` and published for the same stated reason `idSource` is:
1430
+ * a caller that must assert what its queue was constructed with should read the value
1431
+ * rather than re-derive a default. Empty when the host declared `'masker-active'` or was
1432
+ * composed with no `postRun` at all.
1433
+ */
1434
+ readonly unclaimableKinds: readonly JobKind[];
1435
+ }
1436
+ /** Refusal returned when durable capabilities are requested after their composition was closed. */
1437
+ declare class LocalAgentClosedError extends Error {
1438
+ readonly agentQaRoot: string;
1439
+ readonly name = "LocalAgentClosedError";
1440
+ constructor(agentQaRoot: string);
1441
+ }
1442
+ interface LocalAgentHost {
1443
+ /** The 384 function registry with every seed capability bound over local storage. */
1444
+ readonly registry: FunctionRegistry;
1445
+ /**
1446
+ * The live chat session composed beside the registry (392 plan 08).
1447
+ *
1448
+ * Present on EVERY host, because the turn engine is (D-24) — a host whose chat session were
1449
+ * optional would make `agent.ask` bound and its state absent, which is two answers to whether
1450
+ * this host can hold a conversation.
1451
+ */
1452
+ readonly chat: LocalAgentChatSession;
1453
+ /** The knowledge-writer powers this host grants — the same frozen object for every host. */
1454
+ readonly curation: LocalCurationCapabilities;
1455
+ /** The clock every store column, dump row and audit stamp in this host is written against. */
1456
+ readonly clock: ClockPort;
1457
+ /**
1458
+ * Open (once) the durable job queue and its sqlite store over this host's runtime root.
1459
+ *
1460
+ * A method rather than an eager member, and the reason is a behaviour rather than a taste:
1461
+ * constructing the store CREATES `<agentQaRoot>/agent-jobs.db` and the checkpoint directory. A
1462
+ * host that only lists the registry — `agent-qa fn --list` is exactly that — would otherwise
1463
+ * bring a queue database into existence in a project that has never enqueued anything. Repeated
1464
+ * calls return the same instance, so "opened once" is a property of the handle and not of the
1465
+ * caller's discipline.
1466
+ */
1467
+ jobs(): Promise<LocalAgentJobs>;
1468
+ /** Close whatever was opened and join an open already in flight. Idempotent. */
1469
+ close(): Promise<void>;
1470
+ }
1471
+ /**
1472
+ * What a host's curation binding does with candidate prose (388-10, UF-388-01 / D-11).
1473
+ *
1474
+ * A closed union, REQUIRED wherever `postRun` is supplied: a host that cannot say what its
1475
+ * fold does with candidate prose has not been reviewed for UF-388-01, and the typechecker is
1476
+ * what makes that review mandatory rather than customary.
1477
+ *
1478
+ * - `'masker-active'` — the fold has the declared-secret mask bound (441-01 decision 10), or the
1479
+ * binding never reaches candidate emission at all.
1480
+ * - `'no-masker'` — the binding folds with NO declared mask. A statement about DISCLOSURE, not
1481
+ * about capability: the fold would run, but its prose masking would degrade to a pass-through,
1482
+ * so a host declaring this may not claim the kind that runs it.
1483
+ */
1484
+ type CandidateProsePosture = 'masker-active' | 'no-masker';
1485
+ /**
1486
+ * The chain kinds a host of each posture MAY NOT CLAIM — one table, no second spelling.
1487
+ *
1488
+ * A `Record` over the union rather than a condition, deliberately: this phase's named defect
1489
+ * class is a guard whose input domain is written in prose while the code ranges over a
1490
+ * subset, and a total `Record` stops compiling the day a third posture is added. The queue's
1491
+ * `unclaimableKinds` is derived from this table and from nothing else.
1492
+ */
1493
+ declare const UNCLAIMABLE_BY_PROSE_POSTURE: Record<CandidateProsePosture, readonly JobKind[]>;
1494
+ interface ComposeLocalAgentInput {
1495
+ readonly mask: MaskFn;
1496
+ /** Immutable neutral data resolved by the host, reused until recomposition. */
1497
+ readonly resolveTrustPriors?: CreateLocalKnowledgeStoreConfig['resolveTrustPriors'];
1498
+ readonly feedback?: FeedbackPort;
1499
+ /**
1500
+ * 423 D-19/D-16: read-only mark history and consequence preview; absent leaves runs.marks.history
1501
+ * and runs.mark.preview unbound by name, and a port without `preview` leaves only the preview unbound.
1502
+ */
1503
+ readonly feedbackReads?: FeedbackReadsPort;
1504
+ /** DATA across the wall: host preparation may await; candidate evaluation is synchronous. */
1505
+ readonly resolvePromotionPosture?: () => Promise<ResolvePromotionPosture | undefined>;
1506
+ /** The project's runtime root, i.e. `<configDir>/.agent-qa`. Absolute. */
1507
+ readonly agentQaRoot: string;
1508
+ /**
1509
+ * Masked configuration reads. Injected because flattening and masking a resolved configuration
1510
+ * needs the host's own loader — its file layering, its environment, its plugin policy — and the
1511
+ * masking decision is a disclosure boundary the host owns (T-384-07 / T-384-17).
1512
+ */
1513
+ readonly config: ConfigReaderPort;
1514
+ /** Resolves the knowledge root this invocation serves from. See `createKnowledgeReader`. */
1515
+ readonly knowledgeRoot: () => Promise<string> | string;
1516
+ /**
1517
+ * The model a job may author with (D-10, plan 05).
1518
+ *
1519
+ * A required factory. REQUIRED because the queue's own `model` option is required and not
1520
+ * defaultable — an optional capability means a queue can be built with none, and the only place
1521
+ * to discover that is inside an author callback far from the composition that forgot it. A
1522
+ * FACTORY because `jobs()` is deferred, and resolving a provider handle costs a configuration
1523
+ * read and an auth resolution that a host listing the registry must not pay.
1524
+ *
1525
+ * It is called at most once, when the queue is opened. `agent-local` never constructs a model:
1526
+ * its ownership contract forbids provider invocation, so the handle arrives already built and no
1527
+ * provider name is named here.
1528
+ */
1529
+ readonly model: () => JobModelCapability | Promise<JobModelCapability>;
1530
+ /**
1531
+ * How this host executes a run (D-16). OPTIONAL, and the optionality is the reporting
1532
+ * mechanism rather than a convenience: a host that supplies it lists `runs.start` as
1533
+ * `bound` and can run it; a host that does not lists the same capability as `unbound` and
1534
+ * refuses it by name. Not every composition can start a run — a listing, a read-only
1535
+ * surface, 386's daemon before it wires an executor — and D-09 requires that to be a
1536
+ * visible state rather than a call that fails somewhere far from the composition.
1537
+ */
1538
+ readonly runExecution?: RunExecutionPort;
1539
+ /**
1540
+ * How this host parses a draft and enumerates its authored suite (393 D-01), on the exact
1541
+ * optional-operation idiom `runExecution` above uses.
1542
+ *
1543
+ * OPTIONAL, and — like its neighbour — the optionality is the reporting mechanism rather than a
1544
+ * convenience. `authoring.draft.verify` is bound only where this AND `runExecution` are both
1545
+ * present, because a host that cannot parse a test file and a host that cannot start a run are
1546
+ * both hosts that cannot verify a draft. Either one missing lists the capability `unbound` BY
1547
+ * NAME, which is the honest answer; the alternative is a receipt whose four booleans were
1548
+ * computed from no runs at all, and a refusal wearing a verdict's clothes is the one answer that
1549
+ * could get a draft kept on the strength of nothing.
1550
+ *
1551
+ * Host-supplied for the reason `mintQuarantineIdentityPath` is: it needs `parseTestFile` and the
1552
+ * CHECKOUT ROOT, and neither this package nor `agent` may name `@vostride/agent-qa-core`.
1553
+ */
1554
+ readonly draftAuthoring?: DraftAuthoringPort;
1555
+ /**
1556
+ * 395-01 D-03/D-17 · CFGM-01/04: the committed config file SET as RAW BYTES, plus the
1557
+ * comment-preserving field edit over them.
1558
+ *
1559
+ * Host-supplied because the edit is `yaml`'s Document API, and neither this package nor `agent`
1560
+ * may name that dependency under their ownership contracts. The CLI host already depends on it and already owns the file format.
1561
+ */
1562
+ readonly configFiles?: ConfigFilesPortLike;
1563
+ /**
1564
+ * 395-01 CFGM-01: whole-config validation, run BEFORE a proposal surfaces.
1565
+ *
1566
+ * Host-supplied because `AgentQaConfigSchema` lives in `@vostride/agent-qa-core`, which neither
1567
+ * this package nor `agent` may name — the `mintQuarantineIdentityPath` arrangement exactly.
1568
+ */
1569
+ readonly configValidate?: ConfigValidatePortLike;
1570
+ /**
1571
+ * 395-08 D-04 · CFGM-05: the shared scaffold CONTENT, rendered by the host that owns it.
1572
+ *
1573
+ * A SEPARATE OPTIONAL BINDING. The substance lives beside
1574
+ * `init`'s own write path in `packages/cli`, which neither this package nor `packages/agent` may
1575
+ * import; its absence leaves `config.project.scaffold` — and only that op — unbound, which is the
1576
+ * honest catalogue answer for a host that can edit a configuration but cannot create one.
1577
+ */
1578
+ readonly scaffoldContent?: ConfigScaffoldContentPortLike;
1579
+ /**
1580
+ * 2026-09-30, 432-07 D-05/D-07: the direct config writer. Host-supplied because its transaction
1581
+ * is the dashboard's config file transaction, shared per path with the dashboard's own writes.
1582
+ */
1583
+ readonly configWrite?: FunctionDeps['configWrite'];
1584
+ /**
1585
+ * 2026-08-17 (398 D-09 / D-11 · XPLN-02): the four host halves `agent.explain` needs — the
1586
+ * committed capability catalog as data, the model's citation over it, and the two coverage hooks.
1587
+ *
1588
+ * Host-supplied for the reason `scaffoldContent` is, and the boundary is harder here rather than
1589
+ * softer: the artifact is read by `loadCapabilityCatalog` in `packages/cli`, which walks the
1590
+ * package root and touches `fs`, and this package is host-neutral. The citation call needs the
1591
+ * model the chat host already composed through `createModel`, which is a resolution neither this
1592
+ * package nor `packages/agent` may perform. The coverage append is a file write.
1593
+ *
1594
+ * OPTIONAL, and the absence is the report. A composition that omits it lists `agent.explain`
1595
+ * `unbound` BY NAME rather than degrading to a model-only answer — which is the one behaviour
1596
+ * XPLN-02 exists to forbid, and the one that would be hardest to notice, because an explain
1597
+ * answering from model memory still sounds exactly like an explain.
1598
+ */
1599
+ readonly capabilityCatalog?: CapabilityCatalogPort;
1600
+ /**
1601
+ * 403 D-03: the live-view mint seam `runs.liveurl` binds through, handed straight through from
1602
+ * the host — this composition decides nothing about it.
1603
+ *
1604
+ * OPTIONAL because the absence is a genuine host fact and not a degradation: a host serving no
1605
+ * HTTP origin has no URL it could honestly mint, so the op lists `unbound` BY NAME rather than
1606
+ * returning a link that resolves to nothing.
1607
+ */
1608
+ readonly liveView?: LiveViewPort;
1609
+ /**
1610
+ * 405 D-03: the durable run-detail projection read `runs.detail` binds through, handed straight
1611
+ * through from the host — this composition decides nothing about it and constructs no database.
1612
+ *
1613
+ * OPTIONAL because a host may have no projection to read, and then `runs.detail` lists `unbound`
1614
+ * BY NAME. The CLI host binds a reader over the configured or default runs database path whenever
1615
+ * its config parses (a read before that file exists answers not recorded in the runs database);
1616
+ * the dashboard passes its own handle.
1617
+ */
1618
+ readonly runDetail?: RunDetailReaderPort;
1619
+ /**
1620
+ * 432-12 (D-05): how a direct knowledge change reaches the writer-lease holder when one is alive,
1621
+ * as an OPTIONAL operation on the input bag rather than a new production seam (the 386-41 D-2
1622
+ * idiom): `agent-local` cannot name a socket and does not. The CLI host injects a closure
1623
+ * over `invokeViaDaemon` that invokes the change op there with the same identity; the daemon's
1624
+ * own composition injects nothing, because it holds the lease.
1625
+ */
1626
+ readonly knowledgeChangeDelegate?: ExplicitReviewDelegate;
1627
+ /** The writer lease a leaseholding host already owns (387-08 D-18). See `createKnowledgeReview`. */
1628
+ readonly resolveWriterAuthority?: () => {
1629
+ readonly leaseId: string;
1630
+ readonly epoch: number;
1631
+ } | undefined;
1632
+ /**
1633
+ * Mints the durable workspace-relative identity the quarantine ledger keys `filePath` on
1634
+ * (390-12 / D-01). Core's `workspaceRelativeIdentityPath`, pre-bound by the host to its own
1635
+ * config directory.
1636
+ *
1637
+ * Host-supplied for the same reason `resolveWriterAuthority` one field up is: the CHECKOUT ROOT is
1638
+ * a fact only a host knows. Neither this composition nor the verbs beneath it can discover it, and
1639
+ * a layer that guessed would mint identities against the wrong root.
1640
+ *
1641
+ * REQUIRED, and — unlike its neighbour — not conditionally spread. The optionality of the writer
1642
+ * authority is a real report about a real difference between hosts: one holds the lease, one does
1643
+ * not. There is no host that can legitimately lack a checkout root, so an absent mint is not a
1644
+ * posture, it is exactly the unvalidated pass-through that let the write verbs author permanently
1645
+ * unreadable lines into a committed artifact. It belongs at compile time, at the composition that
1646
+ * forgot it.
1647
+ */
1648
+ readonly mintQuarantineIdentityPath: (filePath: string) => string;
1649
+ readonly clock?: ClockPort;
1650
+ readonly idSource?: JobIdSource;
1651
+ /** The kind registry the queue dispatches through. Defaults to the six real declarations. */
1652
+ readonly jobKinds?: JobKindRegistry;
1653
+ /**
1654
+ * What the post-run chain's real handlers may do (388 D-01), on the optional-operation
1655
+ * idiom `runExecution` and `applyDelegate` already use.
1656
+ *
1657
+ * OPTIONAL, and the optionality is the report rather than a convenience: a host that
1658
+ * supplies it dispatches the REAL analyze-run and curate-memory bodies, and a host that
1659
+ * does not keeps the scripted stand-ins. Only `curateRun` is asked for — the handoff read
1660
+ * is built here, from `agentQaRoot`, because it is a path derivation this package already
1661
+ * owns and a caller-supplied one would be a second layout to keep in step with the writer's.
1662
+ *
1663
+ * `curateRun` is the caller's because it is not a local concern at all: it needs the writer
1664
+ * lease, a knowledge root and the v51 drain, and the CLI's run tail is where all three
1665
+ * already meet (plan 02 binds it).
1666
+ */
1667
+ readonly postRun?: {
1668
+ readonly curateRun: PostRunJobCapabilities['curateRun'];
1669
+ /**
1670
+ * REQUIRED beside the binding it describes: a `postRun` that omits it must stop
1671
+ * typechecking, because the posture is the UF-388-01 review of this binding and an
1672
+ * unreviewed binding is exactly what let the daemon fold unmasked. See
1673
+ * `CandidateProsePosture` for what each member does and does not certify.
1674
+ */
1675
+ readonly candidateProse: CandidateProsePosture;
1676
+ /**
1677
+ * The 422 D-13 proposal stage's staging port, passed straight through.
1678
+ *
1679
+ * OPTIONAL beside a REQUIRED `curateRun`, and the asymmetry is the contract: without
1680
+ * `curateRun` there is no real fold and therefore no real chain, whereas a host that binds
1681
+ * no staging port still runs the third stage — it stages nothing, records no counts, and
1682
+ * the terminal says exactly that. Passed through rather than built here for the same reason
1683
+ * `curateRun` is: the adversarial staging is not a local concern, and this package may not
1684
+ * name the seam it lives behind.
1685
+ */
1686
+ readonly stageAdversarialProposal?: PostRunJobCapabilities['stageAdversarialProposal'];
1687
+ /**
1688
+ * The 422 D-14 Gate-1 leg's four ports, passed straight through (422-09).
1689
+ *
1690
+ * NEUTRAL FORWARDING, and the neutrality is the contract rather than a style note: every one
1691
+ * of these needs a committed corpus, a screening run and a writer this package may not name,
1692
+ * and a version of this file that could construct them could not be this file. What crosses
1693
+ * here is four closures and a number; what stays on the other side is every fact about where
1694
+ * they read from.
1695
+ *
1696
+ * Each is OPTIONAL and each is spread on presence, because a host that binds none is the
1697
+ * shipped posture — the stage runs, screens nothing, and says so by omitting the member.
1698
+ */
1699
+ readonly runReplayGate?: PostRunJobCapabilities['runReplayGate'];
1700
+ readonly listBundles?: PostRunJobCapabilities['listBundles'];
1701
+ readonly gateReceiptsFor?: PostRunJobCapabilities['gateReceiptsFor'];
1702
+ readonly readProposeCorpusPosture?: PostRunJobCapabilities['readProposeCorpusPosture'];
1703
+ /**
1704
+ * 2026-09-13, 422 D-19, gap plan 422-27: the host's durable per-run evaluation-root
1705
+ * posture, forwarded as a neutral closure. Absence leaves the handler's fail-closed
1706
+ * default in force; this composition neither resolves nor interprets the posture.
1707
+ */
1708
+ readonly readMisdirectionRootPosture?: PostRunJobCapabilities['readMisdirectionRootPosture'];
1709
+ /**
1710
+ * The operator's per-job Gate-1 bound, as a FACTORY resolved once at queue open.
1711
+ *
1712
+ * A factory on the exact `model` / `analysisTokenBudget` idiom, for the same reason those are:
1713
+ * reading it needs a configuration read, and paying for one at composition would charge every
1714
+ * host that only lists the registry. The capability itself is plain data — a number the
1715
+ * handler reads and never a reader it calls.
1716
+ */
1717
+ readonly proposeGate1MaxPerJob?: () => Promise<number | undefined> | number | undefined;
1718
+ };
1719
+ /**
1720
+ * D-01's TokenAccounting substrate for the analyze-run author (389 plan 06). OPTIONAL
1721
+ * and host-resolved: both production hosts derive it from the SAME model-profile
1722
+ * resolution as the job model itself (one source of truth, no second config surface —
1723
+ * 386 D-15), and `composeLocalAgent` only passes the resolved value through. A FACTORY
1724
+ * on the exact `model` idiom — resolution may need a configuration read, and it is paid
1725
+ * once, at queue open, never by a host that only lists the registry. A factory that
1726
+ * answers `undefined` composes the plan-04 no-ladder posture: packs travel whole.
1727
+ */
1728
+ readonly analysisTokenBudget?: () => Promise<PostRunJobCapabilities['analysisTokenBudget']> | PostRunJobCapabilities['analysisTokenBudget'];
1729
+ /**
1730
+ * The host's execution context for this composition (400-01, D-05).
1731
+ *
1732
+ * 2026-10-02 (432-39, D-12): it reaches the knowledge review, which records a direct change under
1733
+ * it, and nothing else here; the registry option it was also forwarded to is deleted, because no
1734
+ * op decides in a context. The history below explains why it is injected per composition.
1735
+ *
1736
+ * INJECTED RATHER THAN PINNED ON THE PROCESS, and that is the whole reason this field exists.
1737
+ * The registry accepted a `resolveExecutionContext` from 384 until 432-39; what it did NOT have
1738
+ * was a way for a host to supply one, so the only lever a host had was
1739
+ * `setActiveExecutionContextResolver` — a PROCESS GLOBAL. Reaching for it changes the answer
1740
+ * for everything composed in that process, including compositions the host does not own, and a
1741
+ * host that forgot to restore it leaks its posture into every later caller. The dashboard door
1742
+ * is the first host that has to state a posture per composition rather than per process
1743
+ * (D-01/D-05: the browser confirm channel is what makes the context interactive, and it is a
1744
+ * property of a server, not of the machine), so the injected form is what it binds.
1745
+ *
1746
+ * ABSENT MEANS THE SHIPPED AMBIENT RESOLVER, unchanged: the knowledge review's own default reads
1747
+ * safety's process resolver, so every existing host composes exactly as it did before. This is
1748
+ * a passthrough and nothing here decides what a context MEANS. (432-19: `requiresFailClosed`, the
1749
+ * gate's reading of a context, is deleted; the four-member vocabulary stays.)
1750
+ */
1751
+ readonly resolveExecutionContext?: () => ExecutionContext;
1752
+ /**
1753
+ * Notified AFTER one session event has been appended to its log (400-01, D-03).
1754
+ *
1755
+ * AN OBSERVER, AND EVERY WORD OF THAT IS THE CONTRACT. It is called with an event that is
1756
+ * ALREADY ON THE FILE — after `append` resolves, never before and never instead — so a host
1757
+ * pushing these somewhere cannot show a person an event the durable log does not carry. It
1758
+ * cannot append, cannot rewrite and cannot refuse: the return value is discarded and a throw is
1759
+ * swallowed at the notify site, because a surface's push channel failing must not fail the turn
1760
+ * that produced the event.
1761
+ *
1762
+ * WHY THIS AND NOT A HOST-SUPPLIED `SessionLogPort`. The obvious alternative — let a host wrap
1763
+ * or replace the log — is exactly what D-04 forbids: sessions belong to the agent, not to a
1764
+ * surface, and a replaceable log is a second store one conversation at a time. This seam widens
1765
+ * nothing. The log stays the single producer and the single writer; what a host gets is a
1766
+ * carbon copy of what was written.
1767
+ *
1768
+ * The SESSION ID travels with the event because the event itself carries only a `turnId` — the
1769
+ * grammar's exact-key guard means it never will carry more (Pitfall 4), so the fact a subscriber
1770
+ * needs is supplied beside the record rather than smuggled into it.
1771
+ */
1772
+ readonly observeSessionEvent?: (sessionId: string, event: ChatSessionEvent) => void;
1773
+ /**
1774
+ * The turn engine's reconciliation strategy (392 D-16).
1775
+ *
1776
+ * AN INTERNAL COMPOSITION OPTION, and every word of that is load-bearing. It is here so the
1777
+ * mutation proof can assemble the REAL host twice — override on, override off — and read the
1778
+ * difference off a named compound scenario, which is the only differential that measures what
1779
+ * production actually wires (Pitfall 3, the produced-not-literal law).
1780
+ *
1781
+ * IT IS NOT, AND MUST NEVER BECOME, CONFIG-REACHABLE. No field of `AgentQaConfig` names it and
1782
+ * none may: the deterministic override is the mechanism that stops a model's plan from beating
1783
+ * a proven mapping, so a production knob that switched it off would be a supported way to
1784
+ * disable a safety property. Absent, the shipped strategy is used — which is what makes the
1785
+ * absence the safe default rather than an unconfigured state.
1786
+ */
1787
+ readonly chatReconcile?: ReconcileStrategy;
1788
+ /**
1789
+ * The model stage 2 plans with, ALREADY BUILT by the host through `createModel` (CHAT-06).
1790
+ *
1791
+ * A model OBJECT and never a model id: the SDK's own type also admits an id it resolves by
1792
+ * constructing a client, which would put a provider client behind chat without the masking
1793
+ * middleware and the profile resolution `createModel` owns. The type closes that door here.
1794
+ *
1795
+ * ABSENT IS A REAL POSTURE, NOT AN UNCONFIGURED ONE. A host with no model bound is HEADLESS: any
1796
+ * clause the deterministic table cannot resolve fails the turn closed by name (D-08), which is
1797
+ * the correct behaviour rather than a degraded one. That is also why no planner is stubbed in —
1798
+ * a stub answering "I could not plan that" would say the same words about an entirely different
1799
+ * fact, that a model was asked and declined.
1800
+ */
1801
+ readonly chatModel?: ChatLanguageModel;
1802
+ /**
1803
+ * The accounting instance chat's degrade pass measures against (CHAT-04, plan 06).
1804
+ *
1805
+ * HOST-RESOLVED and passed through untouched, on `analysisTokenBudget`'s precedent and for its
1806
+ * reason: the ceilings must come from the SAME capability resolution as the chat model itself,
1807
+ * or a session would compact against a window the model does not have. This composition adds no
1808
+ * knob of its own and cannot construct one — `agent-local` reaches no provider.
1809
+ *
1810
+ * Absent ⇒ no degrade pass runs and the `compaction` seat stays empty, which is the posture
1811
+ * every host had before a model was bound.
1812
+ */
1813
+ readonly chatAccounting?: ChatTokenAccounting;
1814
+ /**
1815
+ * How this host resolves a model NAMED by `/model <name>` (D-04, plan 08).
1816
+ *
1817
+ * Supplied by the host for the same reason `chatModel` is, and enforced by the same rule: the
1818
+ * chat subtree may not construct a model, so a session-scoped switch has to arrive as a
1819
+ * capability the composition was handed. Absent ⇒ `/model` still records the session's declared
1820
+ * model and emits its `model_change` (the header, the prompt and the drift check all follow it),
1821
+ * and no new client is resolved — the honest behaviour for a host that composed one model.
1822
+ */
1823
+ readonly chatModelFor?: (name: string) => ChatLanguageModel | Promise<ChatLanguageModel>;
1824
+ /**
1825
+ * Where a chat session PRINTS a mid-turn artifact a person reads (395-13, D-14).
1826
+ *
1827
+ * A PLAIN FUNCTION: printing needs nothing from this composition. The surface is handed text and
1828
+ * writes it. (432-16 deleted the `chatConfirm` factory that used to sit beside it.)
1829
+ *
1830
+ * PASSED THROUGH UNTOUCHED to the turn engine's `present`. Absent ⇒ the engine renders nothing
1831
+ * mid-turn, which is every host that is not a REPL — and no gate, receipt or session row moves.
1832
+ */
1833
+ readonly chatPresent?: (text: string) => void | Promise<void>;
1834
+ }
1835
+ /**
1836
+ * The live chat session a host holds beside the registry (plan 08, RESEARCH Open Q2).
1837
+ *
1838
+ * Exposed because two of its facts have no other route to a surface: the state a REPL renders
1839
+ * from, and the ability to close an interrupted turn honestly. Everything else a conversation does
1840
+ * goes through `agent.ask` like every other capability (D-24).
1841
+ */
1842
+ interface LocalAgentChatSession {
1843
+ /**
1844
+ * The live state of ONE conversation, or `undefined` for a session this host has not opened
1845
+ * (392 plan 13, CR-02).
1846
+ *
1847
+ * A LOOKUP RATHER THAN A CONTAINER, because the facts inside it — the turn in flight, the
1848
+ * conversation, the model in force, what `/pin` denies, whether `/quit` was typed — are facts
1849
+ * about a session and never about a host. A composition serving two conversations that handed
1850
+ * out one container was answering both of them with whichever turn wrote last, which is what
1851
+ * let a planner read the wrong history (CR-02) and a pin outlive its session (W-03).
1852
+ *
1853
+ * `undefined` is the honest answer for an id nobody here is holding, and reading NEVER creates
1854
+ * a scope: a surface asking about a session cannot bring one into being by asking.
1855
+ */
1856
+ stateFor(sessionId: string): ChatStateSnapshot | undefined;
1857
+ /**
1858
+ * Write the explicit `aborted` event for the turn in flight, and fsync (D-19's graceful arm).
1859
+ *
1860
+ * A no-op between turns, deliberately: an `aborted` row for a turn that already carries its
1861
+ * `turn_complete` would make the repair on the next open trim a turn that finished.
1862
+ */
1863
+ abortActiveTurn(): Promise<void>;
1864
+ }
1865
+ /** Compose the local agent host. */
1866
+ /**
1867
+ * The two callers chat mints, one per stage (392, RESEARCH Pitfall 1 / 391 D-06).
1868
+ *
1869
+ * IDENTICAL BUT FOR `origin`. `FunctionCaller.origin` answers "did a person decide this", and inside
1870
+ * ONE turn both answers occur: a person typing a canonical alias decided that call, and a model
1871
+ * planning one did not. The receipts a turn logs record that origin per clause.
1872
+ *
1873
+ * `surface` is `'chat'` on both and is ATTRIBUTION ONLY. Nothing in the function layer may branch
1874
+ * on it (`invoke.ts:28-29`).
1875
+ *
1876
+ * 2026-10-01 (432-20/432-21, D-12): no scope set and no safe-mode flag. `CHAT_STAGE_SCOPES`, the
1877
+ * whole closed scope union both stages held, is deleted: every caller can call every operation, and
1878
+ * the human-origin restriction the origin split once protected is deleted with `requiresHumanOrigin`.
1879
+ */
1880
+ declare const CHAT_RESOLVER_CALLER: FunctionCaller;
1881
+ declare const CHAT_PLANNER_CALLER: FunctionCaller;
1882
+ declare function composeLocalAgent(input: ComposeLocalAgentInput): LocalAgentHost;
1883
+
1884
+ /**
1885
+ * How many jobs one `resumePending` call will run before it refuses to continue.
1886
+ *
1887
+ * A bound rather than a loop that trusts the queue to empty: recovery runs against a store any
1888
+ * process may have written, and a chain that keeps enqueuing successors would otherwise turn a
1889
+ * recovery call into an unbounded drain. Exceeding it raises rather than truncating — a resume that
1890
+ * quietly stopped early would report success over work it left behind.
1891
+ */
1892
+ declare const JOB_RECOVERY_MAX_RESUMED_JOBS = 64;
1893
+ interface ComposeJobRecoveryInput {
1894
+ /** The project's runtime root, i.e. `<configDir>/.agent-qa`. Absolute. */
1895
+ readonly agentQaRoot: string;
1896
+ /** Every column and dump row this host writes is stamped from here (D-03). */
1897
+ readonly clock: ClockPort;
1898
+ /** Ids derived from `(kind, idempotencyKey)`, so a resumed chain addresses the same rows. */
1899
+ readonly idSource: JobIdSource;
1900
+ /**
1901
+ * The capability a resumed job may author with, supplied by the host.
1902
+ *
1903
+ * Required and not defaultable, on the same reasoning as the queue's own option: an optional
1904
+ * capability means a host can compose recovery with none, and the only place to discover that is
1905
+ * inside an author callback far from the composition that forgot it.
1906
+ */
1907
+ readonly model: JobModelCapability;
1908
+ /** Defaults to the six real declarations. Injected so a harness can drive its own handlers. */
1909
+ readonly jobKinds?: JobKindRegistry;
1910
+ }
1911
+ interface JobRecoveryHost {
1912
+ readonly store: SqliteJobStore;
1913
+ readonly kinds: JobKindRegistry;
1914
+ readonly queue: AgentJobQueue;
1915
+ /**
1916
+ * Return every outstanding claim to the queue, keeping the attempt each one spent.
1917
+ *
1918
+ * Call this FIRST. Only one process drains under a given fence, so at the moment authority is
1919
+ * adopted every outstanding claim belongs to a host that is gone; calling it beside a live pump
1920
+ * would reclaim a job from the handler still running it.
1921
+ */
1922
+ recoverAbandonedClaims(fence: JobStoreFence): Promise<RecoverAbandonedClaimsOutcome>;
1923
+ /** Pump until no row is claimable. No timers, no sleeping — not a dispatcher. */
1924
+ resumePending(fence: JobStoreFence, maxJobs?: number): Promise<readonly RunNextJobOutcome[]>;
1925
+ close(): void;
1926
+ }
1927
+ declare function composeJobRecovery(input: ComposeJobRecoveryInput): JobRecoveryHost;
1928
+
1929
+ /** Directory segment under the agent-qa runtime root. Gitignored, per D-17. */
1930
+ declare const CHAT_SESSIONS_SUBDIR = "sessions";
1931
+ /** A caller-supplied session id that could not be a path segment (T-392-01). */
1932
+ declare class InvalidChatSessionIdError extends Error {
1933
+ readonly sessionId: string;
1934
+ readonly name = "InvalidChatSessionIdError";
1935
+ constructor(sessionId: string);
1936
+ }
1937
+ /**
1938
+ * A session id that names no session.
1939
+ *
1940
+ * A REFUSAL and never a create (D-17, RESEARCH Open Q4). Creating a fresh session for a mistyped
1941
+ * `--session` would tell a person their history was lost when in fact it was never looked at, and
1942
+ * would do so in the one direction no later read can undo.
1943
+ */
1944
+ declare class UnknownChatSessionError extends Error {
1945
+ readonly sessionId: string;
1946
+ readonly name = "UnknownChatSessionError";
1947
+ constructor(sessionId: string);
1948
+ }
1949
+ /**
1950
+ * A session somebody is already holding open (Phase 392 plan 12, T-392-BUSY).
1951
+ *
1952
+ * A REFUSAL BY NAME rather than a second descriptor. Two handles on one file would interleave two
1953
+ * turns inside a single conversation — the same damage the cross-session aliasing did, one level
1954
+ * down — and it is exactly what a double-submit of one `sessionId` over the daemon socket asks for.
1955
+ * The remedy is to wait for the holder, which is why the id is on the error rather than only in
1956
+ * the message.
1957
+ */
1958
+ declare class ChatSessionBusyError extends Error {
1959
+ readonly sessionId: string;
1960
+ readonly name = "ChatSessionBusyError";
1961
+ constructor(sessionId: string);
1962
+ }
1963
+ /** Bytes that do not describe a session this build can append to. */
1964
+ declare class ChatSessionHeaderError extends Error {
1965
+ readonly sessionId: string;
1966
+ readonly detail: string;
1967
+ readonly name = "ChatSessionHeaderError";
1968
+ constructor(sessionId: string, detail: string);
1969
+ }
1970
+ /**
1971
+ * What a production caller supplies: a root and the session's opening context.
1972
+ *
1973
+ * The absence of an fs member is the guarantee, exactly as it is on `CreateJobCheckpointStoreOptions`.
1974
+ */
1975
+ interface CreateJsonlSessionLogOptions {
1976
+ /** Absolute path to the agent-qa runtime root, typically `<project>/.agent-qa`. */
1977
+ readonly root: string;
1978
+ /** ISO instant source — the composition's clock, never `Date.now()` reached for here. */
1979
+ readonly now: () => string;
1980
+ /**
1981
+ * The model in force when the session opens. Drift is `model_change` events, never a rewrite.
1982
+ *
1983
+ * A THUNK rather than a string, because a composition root resolves its model lazily — a host
1984
+ * that only lists the registry must not pay a provider resolution — and a header built from a
1985
+ * value captured at construction would name whatever was true before that resolution ran.
1986
+ */
1987
+ readonly model: () => string;
1988
+ /** The toolset fingerprint at open, on the same thunk reasoning as `model` above. */
1989
+ readonly toolsetHash: () => string;
1990
+ /**
1991
+ * WHICH DOOR THIS LOG BELONGS TO (Phase 400 plan 03, D-04). Absent ⇒ no tag is written.
1992
+ *
1993
+ * A VALUE AND NOT A THUNK, which is the opposite of `model` and `toolsetHash` above and for the
1994
+ * mirror of their reason. Those are resolved lazily because a composition's model can arrive
1995
+ * after the log does and can drift afterwards; a surface cannot. Which door composed this host is
1996
+ * decided at the composition root and is a constant for the life of the process, so a thunk would
1997
+ * be a seam for a value to change through — and a header is written once and never rewritten,
1998
+ * so a changing surface has nowhere to go.
1999
+ *
2000
+ * ABSENT IS THE PRE-400 CLI-CHAT POSTURE, stated in `ChatSessionHeader.surface` and preserved
2001
+ * here: a host that names no surface writes a header byte-identical to the ones every build
2002
+ * before this one wrote.
2003
+ */
2004
+ readonly surface?: string;
2005
+ /** Random source for the id suffix, injectable so a test can pin an id. */
2006
+ readonly random?: () => number;
2007
+ }
2008
+ /** One session, as a listing knows it: everything the header line says, plus how long the log is. */
2009
+ interface ChatSessionListEntry {
2010
+ readonly sessionId: string;
2011
+ readonly createdAt: string;
2012
+ /** The model in force when the session OPENED. Drift after that is `model_change` events. */
2013
+ readonly model: string;
2014
+ /**
2015
+ * Event lines on the file, counted without parsing one.
2016
+ *
2017
+ * An EVENT count and not a turn count, and the difference is not a shortcut. A turn count is not
2018
+ * a header fact and cannot become one — the header is written once and never rewritten, which is
2019
+ * the property CHAT-05 asks for by name — so the only way to produce one is to parse every line
2020
+ * of every log, which is the work a listing exists to avoid. The line count answers "is there
2021
+ * anything in here" for a fraction of the cost, and `open()` answers everything else.
2022
+ */
2023
+ readonly eventCount: number;
2024
+ }
2025
+ /** One session file the listing could not read, surfaced as data rather than thrown over. */
2026
+ interface RefusedChatSessionFile {
2027
+ /** The file's own name, which is what a person has to go and look at. */
2028
+ readonly file: string;
2029
+ readonly reason: string;
2030
+ }
2031
+ interface ChatSessionListing {
2032
+ readonly sessions: readonly ChatSessionListEntry[];
2033
+ readonly refused: readonly RefusedChatSessionFile[];
2034
+ }
2035
+ /**
2036
+ * `chat-<UTCyyyymmdd-HHMMSS>-<4 base36>`.
2037
+ *
2038
+ * Sorted-by-name equals sorted-by-time, which is what makes a plain directory listing the session
2039
+ * list; the random tail is what keeps two sessions opened in the same second distinct. Every
2040
+ * character is in `SAFE_PATH_SEGMENT` by construction, so a minted id can never be refused by the
2041
+ * guard that protects a supplied one.
2042
+ */
2043
+ declare function mintChatSessionId(nowIso: string, random: () => number): string;
2044
+ /**
2045
+ * Every session under one runtime root, read from headers alone (D-17).
2046
+ *
2047
+ * A DAMAGED FILE IS DATA, NOT AN EXCEPTION — the posture the grant ledger had (391; deleted in 432-18).
2048
+ * A corrupt neighbour costs the caller that one entry and nothing else; throwing would let a single
2049
+ * unreadable file hide every healthy session from the person trying to find their conversation.
2050
+ *
2051
+ * It reads through the real filesystem with no injection seam, and that absence is on purpose: the
2052
+ * seam beside `createJsonlSessionLogOn` exists because syscall ORDER is a durability property no
2053
+ * crash test can observe. A listing has no such property. What it returns is its whole contract,
2054
+ * and a temp directory proves that directly.
2055
+ */
2056
+ declare function listSessions(root: string): ChatSessionListing;
2057
+ declare function createJsonlSessionLog(options: CreateJsonlSessionLogOptions): SessionLogPort;
2058
+
2059
+ interface CreateMemorySessionLogOptions {
2060
+ readonly now: () => string;
2061
+ readonly model: () => string;
2062
+ readonly toolsetHash: () => string;
2063
+ readonly random?: () => number;
2064
+ }
2065
+ interface MemorySessionLog extends SessionLogPort {
2066
+ /** Everything written, for assertions. A copy, so a caller cannot edit the log through it. */
2067
+ snapshot(sessionId: string): OpenedChatSession | undefined;
2068
+ }
2069
+ declare function createMemorySessionLog(options: CreateMemorySessionLogOptions): MemorySessionLog;
2070
+
2071
+ /**
2072
+ * The local implementation of the neutral persistence port.
2073
+ *
2074
+ * 2026-07-29 (Phase 385 plan 02, D-06): this alias now covers approvals and patches ONLY.
2075
+ * Job rows and checkpoints left the port for the agent job queue's own store, whose verbs are
2076
+ * compound and transactional rather than generic CRUD, and the conversation surface left it
2077
+ * entirely. The alias is deliberately narrower than the package's name suggests, and saying
2078
+ * so here is cheaper than letting a later reader infer that anything job-shaped belongs
2079
+ * behind it.
2080
+ *
2081
+ * The job store itself is not on this barrel. It is reachable by relative path within the
2082
+ * package and is published, if at all, by the composition root — a store that any importer
2083
+ * can construct is a second way to open the database beside the one the host owns.
2084
+ */
2085
+ type LocalPersistenceStore = PersistencePort;
2086
+
2087
+ export { type ApplyKnowledgeChangeInput, type AttachableRunSession, BRIEF_CEILING, BRIEF_FRACTION, type BuildMemberRunConfigInput, CAUTION_FRACTION, CHAT_PLANNER_CALLER, CHAT_RESOLVER_CALLER, CHAT_SESSIONS_SUBDIR, type CandidateProsePosture, ChatSessionBusyError, ChatSessionHeaderError, type ChatSessionListEntry, type ChatSessionListing, type ComposeJobRecoveryInput, type ComposeLocalAgentInput, type ComputeKnowledgeBudgetsInput, type ConstructKnowledgeReaderInput, type CreateEvidenceBlobReaderInput, type CreateJsonlSessionLogOptions, type CreateKnowledgeReviewInput, type CreateMemorySessionLogOptions, DIGEST_JOB_ID_SOURCE, type DraftAuthoringPort, type DraftParseOutcome, type DraftStageOutcome, GLOBAL_RECORD_CAP, InvalidChatSessionIdError, JOB_RECOVERY_MAX_RESUMED_JOBS, type JobRecoveryHost, type KnowledgeBudgets, LOCAL_CURATION_CAPABILITIES, LOCAL_DAEMON_CAPABILITIES, LOCAL_KNOWLEDGE_PROBES, LocalAgentClosedError, type LocalAgentHost, type LocalAgentJobs, type LocalCurationCapabilities, type LocalDaemonCapabilities, type LocalKnowledgeMaintenanceCapabilities, type LocalKnowledgeProbeCapabilities, type LocalPersistenceStore, MIN_BRIEF, MIN_CAUTION, MIN_MEMORY_TOOL_CONTEXT_WINDOW, type MemorySessionLog, type OpenRunKnowledgeCompositionInput, type ParsedDraftDescriptor, type PromoteWithReceiptsInput, READ_BODY_FRACTION, REFUSED_RUN_REQUEST_FIELDS, type RecordGateReceiptInput, type RefusedChatSessionFile, type RetrievalRungResolver, type RunAssembler, type RunAssemblyContext, type RunConfigDigests, RunDeniedError, type RunDigestMemberInput, type RunExecutionContext, type RunExecutionPort, RunIdAlreadyInUseError, RunIdNotMintedError, type RunInvocationCoreOptions, type RunKnowledgeComposition, type RunPersistencePorts, RunProducedNoEventsError, type RunQuarantineSnapshotInput, RunRequestFieldRefusedError, type RunSubscriberHandle, type RunTargetClass, type RunTargetDeclaration, SUITE_LIFECYCLE_LANE, SYSTEM_CLOCK, SuiteBoundaryWhileLiveError, type SuiteLifecycle, type SuiteLifecycleHooks, type SuiteMemberBinding, type SuiteMemberLease, type SuiteTextEntry, type SweepEntityIndexInput, type SweepEntityIndexOutcome, UNCLAIMABLE_BY_PROSE_POSTURE, UnknownChatSessionError, WEAK_MODEL_CAP_SCALE, applyKnowledgeChange, buildMemberRunConfig, buildRunPorts, buildScreenshotCompressor, buildSuiteMemberExecutor, composeJobRecovery, composeLocalAgent, computeKnowledgeBudgets, computeRunConfigDigests, constructKnowledgeReader, createEvidenceBlobReader, createFrozenKnowledgeReader, createJsonlSessionLog, createKnowledgeReader, createKnowledgeReview, createLocalRunInvocation, createMemorySessionLog, createRunInvocationCore, createRunsReader, createSuiteLifecycle, listSessions, lookupEvidenceBlob, memoryToolGate, mintChatSessionId, openRunKnowledgeComposition, readEvidenceBlobBytes, refuseProductionWithoutOptIn, refuseUnavailableRunId, registerAdapterLiveStream, resolveBuildTagSetting, resolveHostRetrievalRung, resolveRunBuildOrdinal, resolveRunExecutionContext, resolveRunQuarantineSnapshot, scaledRecordCap, statEvidenceBlob, sweepEntityIndexProjection, toMemberTest };