@vostride/agent-qa-agent-local 0.0.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +105 -0
- package/NOTICE.md +11 -0
- package/dist/index.cjs +3801 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2087 -0
- package/dist/index.d.ts +2087 -0
- package/dist/index.js +3875 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -3
package/dist/index.d.cts
ADDED
|
@@ -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 };
|