@gate-forge/witness 0.0.0-stage → 0.9.0
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 +202 -0
- package/README.md +114 -2
- package/dist/adapter/contract-suite.d.ts +167 -0
- package/dist/adapter/contract-suite.d.ts.map +1 -0
- package/dist/adapter/contract-suite.js +348 -0
- package/dist/adapter/contract-suite.js.map +1 -0
- package/dist/adapter/contract.d.ts +266 -0
- package/dist/adapter/contract.d.ts.map +1 -0
- package/dist/adapter/contract.js +2 -0
- package/dist/adapter/contract.js.map +1 -0
- package/dist/adapter/index.d.ts +8 -0
- package/dist/adapter/index.d.ts.map +1 -0
- package/dist/adapter/index.js +2 -0
- package/dist/adapter/index.js.map +1 -0
- package/dist/adapter-kit/config.d.ts +163 -0
- package/dist/adapter-kit/config.d.ts.map +1 -0
- package/dist/adapter-kit/config.js +21 -0
- package/dist/adapter-kit/config.js.map +1 -0
- package/dist/adapter-kit/define.d.ts +47 -0
- package/dist/adapter-kit/define.d.ts.map +1 -0
- package/dist/adapter-kit/define.js +336 -0
- package/dist/adapter-kit/define.js.map +1 -0
- package/dist/adapter-kit/index.d.ts +34 -0
- package/dist/adapter-kit/index.d.ts.map +1 -0
- package/dist/adapter-kit/index.js +32 -0
- package/dist/adapter-kit/index.js.map +1 -0
- package/dist/adapter-kit/projection.d.ts +69 -0
- package/dist/adapter-kit/projection.d.ts.map +1 -0
- package/dist/adapter-kit/projection.js +85 -0
- package/dist/adapter-kit/projection.js.map +1 -0
- package/dist/adapter-kit/session.d.ts +105 -0
- package/dist/adapter-kit/session.d.ts.map +1 -0
- package/dist/adapter-kit/session.js +230 -0
- package/dist/adapter-kit/session.js.map +1 -0
- package/dist/client/witness-client.d.ts +193 -0
- package/dist/client/witness-client.d.ts.map +1 -0
- package/dist/client/witness-client.js +321 -0
- package/dist/client/witness-client.js.map +1 -0
- package/dist/constants.d.ts +189 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +197 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/json.d.ts +16 -0
- package/dist/json.d.ts.map +1 -0
- package/dist/json.js +21 -0
- package/dist/json.js.map +1 -0
- package/dist/queue/bullmq.d.ts +28 -0
- package/dist/queue/bullmq.d.ts.map +1 -0
- package/dist/queue/bullmq.js +210 -0
- package/dist/queue/bullmq.js.map +1 -0
- package/dist/queue/observer.d.ts +130 -0
- package/dist/queue/observer.d.ts.map +1 -0
- package/dist/queue/observer.js +179 -0
- package/dist/queue/observer.js.map +1 -0
- package/dist/surface.d.ts +211 -0
- package/dist/surface.d.ts.map +1 -0
- package/dist/surface.js +268 -0
- package/dist/surface.js.map +1 -0
- package/dist/witness/adapter-registry.d.ts +92 -0
- package/dist/witness/adapter-registry.d.ts.map +1 -0
- package/dist/witness/adapter-registry.js +370 -0
- package/dist/witness/adapter-registry.js.map +1 -0
- package/dist/witness/behavior-request.d.ts +80 -0
- package/dist/witness/behavior-request.d.ts.map +1 -0
- package/dist/witness/behavior-request.js +335 -0
- package/dist/witness/behavior-request.js.map +1 -0
- package/dist/witness/behavior.d.ts +52 -0
- package/dist/witness/behavior.d.ts.map +1 -0
- package/dist/witness/behavior.js +118 -0
- package/dist/witness/behavior.js.map +1 -0
- package/dist/witness/bin.d.ts +21 -0
- package/dist/witness/bin.d.ts.map +1 -0
- package/dist/witness/bin.js +254 -0
- package/dist/witness/bin.js.map +1 -0
- package/dist/witness/browser.d.ts +221 -0
- package/dist/witness/browser.d.ts.map +1 -0
- package/dist/witness/browser.js +644 -0
- package/dist/witness/browser.js.map +1 -0
- package/dist/witness/chaos.d.ts +179 -0
- package/dist/witness/chaos.d.ts.map +1 -0
- package/dist/witness/chaos.js +270 -0
- package/dist/witness/chaos.js.map +1 -0
- package/dist/witness/classifications.d.ts +42 -0
- package/dist/witness/classifications.d.ts.map +1 -0
- package/dist/witness/classifications.js +87 -0
- package/dist/witness/classifications.js.map +1 -0
- package/dist/witness/env-attestation.d.ts +98 -0
- package/dist/witness/env-attestation.d.ts.map +1 -0
- package/dist/witness/env-attestation.js +202 -0
- package/dist/witness/env-attestation.js.map +1 -0
- package/dist/witness/fixture-provider.d.ts +89 -0
- package/dist/witness/fixture-provider.d.ts.map +1 -0
- package/dist/witness/fixture-provider.js +116 -0
- package/dist/witness/fixture-provider.js.map +1 -0
- package/dist/witness/loopback-pins.d.ts +79 -0
- package/dist/witness/loopback-pins.d.ts.map +1 -0
- package/dist/witness/loopback-pins.js +244 -0
- package/dist/witness/loopback-pins.js.map +1 -0
- package/dist/witness/run-options.d.ts +47 -0
- package/dist/witness/run-options.d.ts.map +1 -0
- package/dist/witness/run-options.js +179 -0
- package/dist/witness/run-options.js.map +1 -0
- package/dist/witness/server.d.ts +34 -0
- package/dist/witness/server.d.ts.map +1 -0
- package/dist/witness/server.js +4963 -0
- package/dist/witness/server.js.map +1 -0
- package/dist/witness/task.d.ts +77 -0
- package/dist/witness/task.d.ts.map +1 -0
- package/dist/witness/task.js +201 -0
- package/dist/witness/task.js.map +1 -0
- package/dist/witness/twin-shapes.d.ts +51 -0
- package/dist/witness/twin-shapes.d.ts.map +1 -0
- package/dist/witness/twin-shapes.js +127 -0
- package/dist/witness/twin-shapes.js.map +1 -0
- package/dist/witness/types.d.ts +1094 -0
- package/dist/witness/types.d.ts.map +1 -0
- package/dist/witness/types.js +2 -0
- package/dist/witness/types.js.map +1 -0
- package/package.json +98 -4
|
@@ -0,0 +1,1094 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Witness-side data shapes (pin #7 wire + adapter contract, pin #8).
|
|
3
|
+
*/
|
|
4
|
+
import type { Server } from 'node:http';
|
|
5
|
+
import type { EvidenceRecord, TracedSession, TrustTier, TwinShape } from '@gate-forge/core';
|
|
6
|
+
import type { ChaosOptions } from './chaos.js';
|
|
7
|
+
import type { TwinShapePlan } from './twin-shapes.js';
|
|
8
|
+
/**
|
|
9
|
+
* Supervisor-issued session credential (plan Phase 1, work item 2): the
|
|
10
|
+
* trusted supervisor (the Playwright reporter process) opens one session
|
|
11
|
+
* per started test and the worker-side fixture RESOLVES it by the exact
|
|
12
|
+
* (workerIndex, testId) pair. The session token is issued by the witness,
|
|
13
|
+
* never derivable by the suite: submissions without a valid OPEN session
|
|
14
|
+
* are rejected fail-closed, so a suite-supplied testId or annotation
|
|
15
|
+
* alone cannot mint records for an arbitrary session.
|
|
16
|
+
*/
|
|
17
|
+
export interface SessionCredential {
|
|
18
|
+
/** Witness-issued session id (UUID). */
|
|
19
|
+
sessionId: string;
|
|
20
|
+
/** Per-session secret; required on every submission. */
|
|
21
|
+
sessionToken: string;
|
|
22
|
+
/** The supervisor-registered testId (records are forced onto it). */
|
|
23
|
+
testId: string;
|
|
24
|
+
/** The worker the session is bound to. */
|
|
25
|
+
workerIndex: number;
|
|
26
|
+
/**
|
|
27
|
+
* The session's DEDICATED observation-proxy origin (its own loopback
|
|
28
|
+
* port), when the run wires an observation proxy: the worker's browser
|
|
29
|
+
* uses this origin for the whole test, so every request on it —
|
|
30
|
+
* absolute paths included — is attributed to THIS session. Null when
|
|
31
|
+
* no observation proxy is active (nothing to attribute).
|
|
32
|
+
*/
|
|
33
|
+
proxyUrl: string | null;
|
|
34
|
+
/**
|
|
35
|
+
* The supervisor-registered obligation claims for this test (Phase 4
|
|
36
|
+
* claim injection): the merged native-annotation + sidecar-mapping
|
|
37
|
+
* claims the orchestrating CLI resolved from the tracked mappings and
|
|
38
|
+
* the supervisor carried on the session-open path. Declarations only —
|
|
39
|
+
* they route evidence onto obligation identities and never satisfy
|
|
40
|
+
* anything by themselves; every record is still forced onto the
|
|
41
|
+
* supervisor-registered session/test identity and graded from
|
|
42
|
+
* witnessed evidence. Empty/absent for runs without mappings.
|
|
43
|
+
*/
|
|
44
|
+
claims?: string[];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* `POST /records` body (pin #7, Phase 1 extension): a test-side primitive
|
|
48
|
+
* submitting UI-observed evidence. `claimId` is the claimed obligation id —
|
|
49
|
+
* the annotation's `<resourceId>:<contract>` — and `testId` binds the record
|
|
50
|
+
* to the test that produced the claim. Phase 1: `sessionId` +
|
|
51
|
+
* `sessionToken` carry the supervisor-issued session credential; the
|
|
52
|
+
* witness rejects submissions without a valid OPEN session and forces the
|
|
53
|
+
* record's testId onto the session's supervisor-registered value.
|
|
54
|
+
*/
|
|
55
|
+
export interface RecordsRequest {
|
|
56
|
+
claimId: string;
|
|
57
|
+
kind: string;
|
|
58
|
+
payload: unknown;
|
|
59
|
+
testId: string;
|
|
60
|
+
sessionId: string;
|
|
61
|
+
sessionToken: string;
|
|
62
|
+
}
|
|
63
|
+
/** `POST /records` response (pin #7, minimal wire contract). */
|
|
64
|
+
export interface RecordsResponse {
|
|
65
|
+
recordId: string;
|
|
66
|
+
trust: TrustTier;
|
|
67
|
+
runId: string;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* `POST /witness/persistence` body (pin #7): the fixture asks the
|
|
71
|
+
* engine-side witness to run the resource's reviewed adapter (GET-only)
|
|
72
|
+
* for one entity. The issued record carries the ENGINE OBSERVATION
|
|
73
|
+
* (`found`, adapter-normalized `fields`, and a `before` link when a
|
|
74
|
+
* pre-observation was consumed) — expectations NEVER come from the
|
|
75
|
+
* suite (audit round 5).
|
|
76
|
+
*
|
|
77
|
+
* `preObservationId` references a witness-issued pre-observation (`POST
|
|
78
|
+
* /witness/pre-observation`): an id-set snapshot for create
|
|
79
|
+
* postconditions (`before: {entityAbsent}`), or an entity-fields
|
|
80
|
+
* snapshot for update postconditions (`before: {found, fields}`).
|
|
81
|
+
*/
|
|
82
|
+
export interface PersistenceRequest {
|
|
83
|
+
resourceId: string;
|
|
84
|
+
entityId: unknown;
|
|
85
|
+
preObservationId?: string;
|
|
86
|
+
/** Closed UI-action interval whose post-state this persistence read binds to. */
|
|
87
|
+
anchorId?: string;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* `POST /witness/pre-observation` body: an engine-side snapshot taken
|
|
91
|
+
* BEFORE a claimed action. With `entityId` the witness snapshots that
|
|
92
|
+
* entity's observed fields (update postconditions); without it, the
|
|
93
|
+
* resource's observed id set (create postconditions). Phase 1: requires
|
|
94
|
+
* the supervisor-issued session credential.
|
|
95
|
+
*/
|
|
96
|
+
export interface PreObservationRequest {
|
|
97
|
+
resourceId: string;
|
|
98
|
+
testId: string;
|
|
99
|
+
claimId: string;
|
|
100
|
+
entityId?: unknown;
|
|
101
|
+
sessionId: string;
|
|
102
|
+
sessionToken: string;
|
|
103
|
+
}
|
|
104
|
+
/** `POST /witness/pre-observation` response. */
|
|
105
|
+
export interface PreObservationResponse {
|
|
106
|
+
observationId: string;
|
|
107
|
+
observed: number;
|
|
108
|
+
}
|
|
109
|
+
/** `POST /witness/persistence` response (pin #7). */
|
|
110
|
+
export interface PersistenceResponse {
|
|
111
|
+
recordId: string;
|
|
112
|
+
runId: string;
|
|
113
|
+
verdictRelevant: {
|
|
114
|
+
found: boolean;
|
|
115
|
+
fieldsMatch: boolean;
|
|
116
|
+
mismatches?: string[];
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* `POST /runs/server-e2e-declarations` body (SUPERVISOR ONLY): the
|
|
121
|
+
* obligation ids the trusted mapping layer declared kind `server-e2e`.
|
|
122
|
+
* Registration is a PRE-run fact (like the expected set): bound once,
|
|
123
|
+
* identical re-registration idempotent, any change or late registration
|
|
124
|
+
* refused — the witness never stamps `channel: 'server'` records for
|
|
125
|
+
* obligations outside this set, so the suite cannot steer an intent onto
|
|
126
|
+
* a browser-kind obligation and a bearer of an intent can never
|
|
127
|
+
* self-verify.
|
|
128
|
+
*/
|
|
129
|
+
export interface ServerE2eDeclarationsRequest {
|
|
130
|
+
obligations: readonly string[];
|
|
131
|
+
}
|
|
132
|
+
/** `POST /runs/server-e2e-declarations` response. */
|
|
133
|
+
export interface ServerE2eDeclarationsResponse {
|
|
134
|
+
bound: true;
|
|
135
|
+
count: number;
|
|
136
|
+
obligations: string[];
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* `POST /runs/observe-declarations` body (SUPERVISOR ONLY): the
|
|
140
|
+
* obligation ids the trusted mapping layer declared kind `observed-e2e`
|
|
141
|
+
* (Observe channel, Phase 2). Registration is a PRE-run fact like the
|
|
142
|
+
* server-e2e set: bound once, identical re-registration idempotent, any
|
|
143
|
+
* change or late registration refused — the witness never stamps
|
|
144
|
+
* `channel: 'observe'` records for obligations outside this set, so a
|
|
145
|
+
* suite-driven test can never steer observe evidence onto an
|
|
146
|
+
* engine-kind obligation.
|
|
147
|
+
*/
|
|
148
|
+
export interface ObserveDeclarationsRequest {
|
|
149
|
+
obligations: readonly string[];
|
|
150
|
+
}
|
|
151
|
+
/** `POST /runs/observe-declarations` response. */
|
|
152
|
+
export interface ObserveDeclarationsResponse {
|
|
153
|
+
bound: true;
|
|
154
|
+
count: number;
|
|
155
|
+
obligations: string[];
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* `POST /observe/finalize` body (SUPERVISOR ONLY — the drain calls it
|
|
159
|
+
* after a passed test, before sealing the session): resolve one
|
|
160
|
+
* session's observe-eligible claims against the session's own proxied
|
|
161
|
+
* traffic plus independent adapter reads, and stamp witnessed
|
|
162
|
+
* `persistence.observed` records for whatever resolves.
|
|
163
|
+
*
|
|
164
|
+
* The session must still be OPEN (finalize runs before seal; a sealed
|
|
165
|
+
* session is refused) so no record is ever injected after the test
|
|
166
|
+
* ended. Ambiguity, missing traffic, unparsable bodies, and adapter
|
|
167
|
+
* trouble resolve to typed NOTES in the response — never to
|
|
168
|
+
* satisfaction, never to a run failure.
|
|
169
|
+
*/
|
|
170
|
+
export interface ObserveFinalizeRequest {
|
|
171
|
+
sessionId: string;
|
|
172
|
+
}
|
|
173
|
+
/** One obligation the finalize resolved into a witnessed record. */
|
|
174
|
+
export interface ObserveFinalizedObligation {
|
|
175
|
+
/** Obligation id the record was issued under. */
|
|
176
|
+
obligationId: string;
|
|
177
|
+
/** The issued witnessed record id. */
|
|
178
|
+
recordId: string;
|
|
179
|
+
/** The CRUD operation the binding proved. */
|
|
180
|
+
operation: 'create' | 'read' | 'update' | 'delete';
|
|
181
|
+
/** The witness-resolved entity id (scalar or column-keyed object). */
|
|
182
|
+
entityId: unknown;
|
|
183
|
+
}
|
|
184
|
+
/** `POST /observe/finalize` response. */
|
|
185
|
+
export interface ObserveFinalizeResponse {
|
|
186
|
+
finalized: ObserveFinalizedObligation[];
|
|
187
|
+
/** Typed non-satisfaction notes (missing traffic, ambiguity, …). */
|
|
188
|
+
notes: string[];
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* `POST /witness/server-persistence` body (SUPERVISOR ONLY — the drain
|
|
192
|
+
* forwards it with the verifier key; the suite can only write intent
|
|
193
|
+
* spool lines, never call this): one persistence claim intent, already
|
|
194
|
+
* drained from the runner-side intents spool.
|
|
195
|
+
*
|
|
196
|
+
* - create: `pre` (expect-absent) BEFORE the mutation stores a witness
|
|
197
|
+
* pre-observation; `post` (expect-present) probes, consumes it, and
|
|
198
|
+
* stamps `before: {entityAbsent}` into the record — the same shape the
|
|
199
|
+
* browser path grades.
|
|
200
|
+
* - update: `pre` (expect-present) snapshots the entity's fields;
|
|
201
|
+
* `post` consumes it into `before: {found, fields}` (the update delta
|
|
202
|
+
* grades against the classification's updateableFields in the engine).
|
|
203
|
+
* - read: `post` (expect-present) only. delete: `post` (expect-absent
|
|
204
|
+
* for hard; expect-present for archive states) only.
|
|
205
|
+
*
|
|
206
|
+
* `sequence` is strictly increasing per claimId; a replayed or
|
|
207
|
+
* out-of-order line resolves to a typed failure (fail closed), so no
|
|
208
|
+
* bearer of an intent can re-drive a stale observation.
|
|
209
|
+
*/
|
|
210
|
+
export interface ServerPersistenceIntentRequest {
|
|
211
|
+
resourceId: string;
|
|
212
|
+
/** Obligation id `<resourceId>:persistence:<op>` the intent serves. */
|
|
213
|
+
claimId: string;
|
|
214
|
+
operation: 'create' | 'read' | 'update' | 'delete';
|
|
215
|
+
phase: 'pre' | 'post';
|
|
216
|
+
intent: 'expect-present' | 'expect-absent';
|
|
217
|
+
/** The entity key: scalar for single-column PKs, column-keyed object for composite. */
|
|
218
|
+
key: unknown;
|
|
219
|
+
/** Strictly increasing per claimId (replay → typed failure). */
|
|
220
|
+
sequence: number;
|
|
221
|
+
/** The claiming test's id (diagnostic attribution; the claim join key). */
|
|
222
|
+
testId: string;
|
|
223
|
+
}
|
|
224
|
+
/** `POST /witness/server-persistence` response for a `pre` intent. */
|
|
225
|
+
export interface ServerPreObservationResponse {
|
|
226
|
+
resolved: 'pre';
|
|
227
|
+
found: boolean;
|
|
228
|
+
}
|
|
229
|
+
/** `POST /witness/server-persistence` response for a `post` intent. */
|
|
230
|
+
export interface ServerPersistenceResponse {
|
|
231
|
+
recordId: string;
|
|
232
|
+
runId: string;
|
|
233
|
+
trust: TrustTier;
|
|
234
|
+
channel: 'server';
|
|
235
|
+
verdictRelevant: {
|
|
236
|
+
found: boolean;
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
/** `POST /sessions/open` body (Phase 1; Phase 4 adds claim injection). */
|
|
240
|
+
export interface SessionOpenRequest {
|
|
241
|
+
/** The test's runner-assigned id (same id the reporter writes to claims.json). */
|
|
242
|
+
testId: string;
|
|
243
|
+
/** The worker the test runs on (binds worker → session). */
|
|
244
|
+
workerIndex: number;
|
|
245
|
+
/**
|
|
246
|
+
* Supervisor-carried identity of the started test (enforcement-review
|
|
247
|
+
* fix 2a/3): repo-relative file + title path + project, as drained from
|
|
248
|
+
* the runner's lifecycle spool. When an expected set is registered,
|
|
249
|
+
* THIS identity decides membership — a test outside the registered set
|
|
250
|
+
* is refused. Optional only for runs without a registered expected set.
|
|
251
|
+
*/
|
|
252
|
+
file?: string;
|
|
253
|
+
titlePath?: readonly string[];
|
|
254
|
+
project?: string | null;
|
|
255
|
+
/**
|
|
256
|
+
* Phase 4 claim injection: the mapped obligation claims this test must
|
|
257
|
+
* land its evidence on (resolved from the sidecar/native mappings by
|
|
258
|
+
* the orchestrating CLI and carried by the supervisor on the
|
|
259
|
+
* session-open path). Obligation-id-shaped, deduplicated by the
|
|
260
|
+
* witness; empty/absent for annotation-claimed tests, which keep
|
|
261
|
+
* working unchanged. Claims remain DECLARATIONS: they never satisfy
|
|
262
|
+
* anything by themselves.
|
|
263
|
+
*/
|
|
264
|
+
claims?: readonly string[];
|
|
265
|
+
}
|
|
266
|
+
/** `POST /sessions/open` response: the session binding (runId, sessionId, testId, worker). */
|
|
267
|
+
export interface SessionOpenResponse extends SessionCredential {
|
|
268
|
+
/** Witness-monotonic tick at open (the session clock origin). */
|
|
269
|
+
openedTick: number;
|
|
270
|
+
}
|
|
271
|
+
/** `POST /sessions/close` body: the supervisor seals the session with the observed outcome. */
|
|
272
|
+
export interface SessionCloseRequest {
|
|
273
|
+
sessionId: string;
|
|
274
|
+
/** The observed test outcome (e.g. 'passed' | 'failed'); recorded, never graded here. */
|
|
275
|
+
outcome?: string;
|
|
276
|
+
}
|
|
277
|
+
/** `POST /sessions/close` response. */
|
|
278
|
+
export interface SessionCloseResponse {
|
|
279
|
+
sealed: true;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* `POST /sessions/release` body: the supervisor releases a session's
|
|
283
|
+
* WORKER SLOT before the runner's outcome for that test has arrived.
|
|
284
|
+
*
|
|
285
|
+
* The supervisor calls it on the WORKER's own lifecycle end (the end
|
|
286
|
+
* the worker spools as soon as the test itself is finished), so the
|
|
287
|
+
* next test that same worker runs opens its session immediately instead
|
|
288
|
+
* of queueing behind a main-process event that may still be seconds
|
|
289
|
+
* away. Nothing is credited: the session stops accepting submissions,
|
|
290
|
+
* its proxy dies, and only the runner's later `POST /sessions/close`
|
|
291
|
+
* records the outcome — an outcome that never arrives leaves the
|
|
292
|
+
* session without one, which grades not-passed.
|
|
293
|
+
*/
|
|
294
|
+
export interface SessionReleaseRequest {
|
|
295
|
+
sessionId: string;
|
|
296
|
+
}
|
|
297
|
+
/** `POST /sessions/release` response. */
|
|
298
|
+
export interface SessionReleaseResponse {
|
|
299
|
+
released: true;
|
|
300
|
+
}
|
|
301
|
+
/** `POST /sessions/resolve` body: the worker proves WHICH open session it runs under. */
|
|
302
|
+
export interface SessionResolveRequest {
|
|
303
|
+
testId: string;
|
|
304
|
+
workerIndex: number;
|
|
305
|
+
}
|
|
306
|
+
/** `POST /sessions/resolve` response (a resolvable credential, proxy prefix included). */
|
|
307
|
+
export interface SessionResolveResponse extends SessionCredential {
|
|
308
|
+
openedTick: number;
|
|
309
|
+
}
|
|
310
|
+
/** `POST /sessions/intervals/open` body: the fixture marks a UI-action observation interval. */
|
|
311
|
+
export interface IntervalOpenRequest {
|
|
312
|
+
sessionId: string;
|
|
313
|
+
sessionToken: string;
|
|
314
|
+
/** The UI operation the interval covers ('create' | 'read' | 'update' | 'delete'). */
|
|
315
|
+
operation: string;
|
|
316
|
+
}
|
|
317
|
+
/** `POST /sessions/intervals/open` response. */
|
|
318
|
+
export interface IntervalOpenResponse {
|
|
319
|
+
intervalId: string;
|
|
320
|
+
startTick: number;
|
|
321
|
+
}
|
|
322
|
+
/** `POST /sessions/intervals/close` body. */
|
|
323
|
+
export interface IntervalCloseRequest {
|
|
324
|
+
sessionId: string;
|
|
325
|
+
sessionToken: string;
|
|
326
|
+
intervalId: string;
|
|
327
|
+
}
|
|
328
|
+
/** `POST /sessions/intervals/close` response. */
|
|
329
|
+
export interface IntervalCloseResponse {
|
|
330
|
+
startTick: number;
|
|
331
|
+
endTick: number;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* `POST /browser/surface` body (plan Phase 1 item 4): the worker-side
|
|
335
|
+
* fixture registers the consumer-declared surface descriptor for one
|
|
336
|
+
* open session. The descriptor's selectors are locators only — the
|
|
337
|
+
* engine verifies every outcome itself. The driven ORIGIN is never part
|
|
338
|
+
* of this body (a present `appBaseUrl` is rejected): the engine drives
|
|
339
|
+
* exactly the provisioned attested subject from trusted witness
|
|
340
|
+
* configuration. Requires the supervisor-issued session credential on
|
|
341
|
+
* an OPEN session; the record's testId is forced onto the session's
|
|
342
|
+
* value.
|
|
343
|
+
*/
|
|
344
|
+
export interface BrowserSurfaceRequest {
|
|
345
|
+
sessionId: string;
|
|
346
|
+
sessionToken: string;
|
|
347
|
+
testId: string;
|
|
348
|
+
/** The consumer-declared surface descriptor (validated engine-side). */
|
|
349
|
+
surface: unknown;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* `POST /browser/action` body: perform ONE constrained surface
|
|
353
|
+
* operation on the session's engine-owned page and issue
|
|
354
|
+
* engine-observed records for it.
|
|
355
|
+
*/
|
|
356
|
+
export interface BrowserActionRequest {
|
|
357
|
+
sessionId: string;
|
|
358
|
+
sessionToken: string;
|
|
359
|
+
testId: string;
|
|
360
|
+
/** Obligation ids to issue the engine-observed records under. */
|
|
361
|
+
claimIds: string[];
|
|
362
|
+
/** The constrained operation to perform. */
|
|
363
|
+
operation: 'create' | 'read' | 'update' | 'delete';
|
|
364
|
+
/** Entered input (create/update); target entity (read/update/delete). */
|
|
365
|
+
fields?: Record<string, string>;
|
|
366
|
+
entityId?: string;
|
|
367
|
+
}
|
|
368
|
+
/** `POST /browser/action` response: the engine's own observation. */
|
|
369
|
+
export interface BrowserActionResponse {
|
|
370
|
+
/** Entity id OBSERVED from the rendered list (never declared). */
|
|
371
|
+
entityId: string;
|
|
372
|
+
/** The exact input values the engine typed. */
|
|
373
|
+
enteredFields: Record<string, string>;
|
|
374
|
+
/** The rendered fields the engine read back. */
|
|
375
|
+
renderedFields: Record<string, string>;
|
|
376
|
+
/** The app response status the engine captured for the mutation. */
|
|
377
|
+
appStatus: number;
|
|
378
|
+
/** Engine-side pre-observation id (create/update; consumed by persistence verify). */
|
|
379
|
+
preObservationId: string | null;
|
|
380
|
+
/** Closed UI-action interval used to bind the observed action and persistence read. */
|
|
381
|
+
anchorId: string;
|
|
382
|
+
/** Record ids the engine issued for this action (per claim). */
|
|
383
|
+
recordIds: string[];
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* `POST /browser/visible` body: re-read the rendered result for the
|
|
387
|
+
* engine-observed entity on the session's engine page and issue
|
|
388
|
+
* engine-observed visible-result records.
|
|
389
|
+
*/
|
|
390
|
+
export interface BrowserVisibleRequest {
|
|
391
|
+
sessionId: string;
|
|
392
|
+
sessionToken: string;
|
|
393
|
+
testId: string;
|
|
394
|
+
/** Obligation ids to issue the engine-observed records under. */
|
|
395
|
+
claimIds: string[];
|
|
396
|
+
/** The engine-observed entity id (must match the action's). */
|
|
397
|
+
entityId: string;
|
|
398
|
+
/** The original action (decides row vs form readback). */
|
|
399
|
+
operation: 'create' | 'read' | 'update' | 'delete';
|
|
400
|
+
/** Optional for legacy clients; new fixture receipts bind visible reads to one action. */
|
|
401
|
+
anchorId?: string;
|
|
402
|
+
}
|
|
403
|
+
/** `POST /browser/visible` response. */
|
|
404
|
+
export interface BrowserVisibleResponse {
|
|
405
|
+
entityId: string;
|
|
406
|
+
/** The rendered fields the engine read. */
|
|
407
|
+
fields: Record<string, string>;
|
|
408
|
+
/** Record ids the engine issued (per claim). */
|
|
409
|
+
recordIds: string[];
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Witness-held session state (plan Phase 1). Created only by
|
|
413
|
+
* `POST /sessions/open` (the trusted supervisor, verifier-key
|
|
414
|
+
* authenticated — enforcement-review fix 3); sealed by
|
|
415
|
+
* `POST /sessions/close` (same authority). Sealed sessions answer no
|
|
416
|
+
* submission, no interval, and no resolve call.
|
|
417
|
+
*/
|
|
418
|
+
export interface TestSession {
|
|
419
|
+
/** Witness-issued session id (UUID). */
|
|
420
|
+
sessionId: string;
|
|
421
|
+
/** Per-session secret required on every submission. */
|
|
422
|
+
token: string;
|
|
423
|
+
/** The supervisor-registered testId (every record is forced onto it). */
|
|
424
|
+
testId: string;
|
|
425
|
+
/** The worker the session is bound to (one OPEN session per worker). */
|
|
426
|
+
workerIndex: number;
|
|
427
|
+
/**
|
|
428
|
+
* `open` accepts submissions; `sealed` and `outcome-pending` reject
|
|
429
|
+
* everything (fail closed). `outcome-pending` is a RELEASED session:
|
|
430
|
+
* the worker's own lifecycle end unbound its worker slot (so the
|
|
431
|
+
* worker's next test opens its session without waiting for the
|
|
432
|
+
* runner's main process), but the session's OUTCOME is still owed by
|
|
433
|
+
* the runner's reporter and is recorded by the later close.
|
|
434
|
+
*/
|
|
435
|
+
status: 'open' | 'sealed' | 'outcome-pending';
|
|
436
|
+
/** Witness-monotonic tick at open. */
|
|
437
|
+
openedTick: number;
|
|
438
|
+
/** Witness-monotonic tick at seal (null while open). */
|
|
439
|
+
sealedTick: number | null;
|
|
440
|
+
/** The outcome the supervisor observed at close (recorded, never graded). */
|
|
441
|
+
outcome: string | null;
|
|
442
|
+
/**
|
|
443
|
+
* UI-action observation intervals keyed by intervalId. End ticks are
|
|
444
|
+
* null while the interval is open. Exchanges are consumable as this
|
|
445
|
+
* session's evidence only when their tick falls inside one of these
|
|
446
|
+
* intervals — setup traffic outside every interval is never credited.
|
|
447
|
+
*/
|
|
448
|
+
intervals: Map<string, {
|
|
449
|
+
startTick: number;
|
|
450
|
+
endTick: number | null;
|
|
451
|
+
operation: string;
|
|
452
|
+
}>;
|
|
453
|
+
/**
|
|
454
|
+
* The session's dedicated observation-proxy origin when the run wires
|
|
455
|
+
* an observation proxy; null otherwise.
|
|
456
|
+
*/
|
|
457
|
+
proxyUrl: string | null;
|
|
458
|
+
/** The dedicated proxy server (closed when the session seals). */
|
|
459
|
+
proxyServer: Server | null;
|
|
460
|
+
/**
|
|
461
|
+
* Phase 4 claim injection: the mapped obligation claims the supervisor
|
|
462
|
+
* registered at open (sorted, deduplicated, obligation-id-shaped;
|
|
463
|
+
* empty for annotation-only tests). Echoed in the session view so the
|
|
464
|
+
* supervisor writes claims.json from what the witness registered.
|
|
465
|
+
*/
|
|
466
|
+
claims: string[];
|
|
467
|
+
/**
|
|
468
|
+
* The registered expected-set identity this session was minted for
|
|
469
|
+
* (enforcement-review fix 2a/2b); null when the run has no registered
|
|
470
|
+
* expected set. Groups the execution trace by expected test.
|
|
471
|
+
*/
|
|
472
|
+
registered: {
|
|
473
|
+
testId: string | null;
|
|
474
|
+
project: string | null;
|
|
475
|
+
file: string;
|
|
476
|
+
titlePath: string[];
|
|
477
|
+
} | null;
|
|
478
|
+
/**
|
|
479
|
+
* Witness-side activity bound to this session: a monotonically
|
|
480
|
+
* increasing count of everything the witness itself observed under
|
|
481
|
+
* the session — ledger records issued with the session credential,
|
|
482
|
+
* recorded UI-action intervals, consumed engine-observed exchanges,
|
|
483
|
+
* and pre-observations. Diagnostic corroboration only (execution
|
|
484
|
+
* authority is the supervisor-observed trusted lifecycle).
|
|
485
|
+
*/
|
|
486
|
+
activity: number;
|
|
487
|
+
/**
|
|
488
|
+
* Engine-browser surface registration (plan Phase 1 item 4): the
|
|
489
|
+
* validated consumer descriptor the engine drives for this session.
|
|
490
|
+
* The driven ORIGIN is never stored here — it resolves from trusted
|
|
491
|
+
* witness configuration (`targetBaseUrl`) on every call, so a
|
|
492
|
+
* suite-named frontend can never become the engine target.
|
|
493
|
+
*/
|
|
494
|
+
engineSurface: {
|
|
495
|
+
surface: Record<string, unknown>;
|
|
496
|
+
} | null;
|
|
497
|
+
/**
|
|
498
|
+
* Twin path coverage (E64): true when the supervisor marked this
|
|
499
|
+
* session OBSERVATION-ONLY (it is a raw twin). Such a session's
|
|
500
|
+
* requests are recorded as shapes and nothing else: every submission
|
|
501
|
+
* from it is refused, so it can issue no record, no attestation and
|
|
502
|
+
* satisfy nothing. Never true for a session with claims.
|
|
503
|
+
*/
|
|
504
|
+
observationOnly: boolean;
|
|
505
|
+
/**
|
|
506
|
+
* The request shapes this session's proxied traffic exercised, in
|
|
507
|
+
* first-seen order. Computed at record time from the method and
|
|
508
|
+
* target, so no raw URL and no non-allowlisted query value is ever
|
|
509
|
+
* stored. Empty in every run that did not ask for twin shapes.
|
|
510
|
+
*/
|
|
511
|
+
twinShapes: TwinShape[];
|
|
512
|
+
}
|
|
513
|
+
/** One test's recorded twin shapes, as the supervisor read surface returns them. */
|
|
514
|
+
export interface TwinShapeReport {
|
|
515
|
+
/**
|
|
516
|
+
* The registered identity of the test these shapes belong to: the
|
|
517
|
+
* join key both sides speak (project, file, titlePath). Null only when
|
|
518
|
+
* the run registered no expected set, in which case the caller has no
|
|
519
|
+
* honest way to name the test and should say so.
|
|
520
|
+
*/
|
|
521
|
+
identity: {
|
|
522
|
+
file: string;
|
|
523
|
+
titlePath: string[];
|
|
524
|
+
project: string | null;
|
|
525
|
+
} | null;
|
|
526
|
+
/** The runner-assigned test id the session ran under (diagnostic). */
|
|
527
|
+
testId: string;
|
|
528
|
+
/** True when this session was marked observation-only (a raw twin). */
|
|
529
|
+
observationOnly: boolean;
|
|
530
|
+
/** The shapes it exercised, in first-seen order. */
|
|
531
|
+
shapes: readonly TwinShape[];
|
|
532
|
+
}
|
|
533
|
+
/** `GET /runs/twin-shapes` response (SUPERVISOR ONLY). */
|
|
534
|
+
export interface TwinShapesResponse {
|
|
535
|
+
/** True when this run recorded twin shapes at all. */
|
|
536
|
+
enabled: boolean;
|
|
537
|
+
/** Per test id, sorted; empty when the run recorded nothing. */
|
|
538
|
+
twins: readonly TwinShapeReport[];
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* One expected test registered by the supervisor BEFORE the run
|
|
542
|
+
* (enforcement-review fix 2a). Identity is (project, file, titlePath);
|
|
543
|
+
* `testId` is diagnostic (runner-assigned ids are not stable across
|
|
544
|
+
* enumeration and execution).
|
|
545
|
+
*/
|
|
546
|
+
export interface ExpectedTestRegistration {
|
|
547
|
+
/** The runner-assigned test id, when enumeration bound one. */
|
|
548
|
+
testId?: string | null;
|
|
549
|
+
/** Runner project, or null when the runner reports none. */
|
|
550
|
+
project: string | null;
|
|
551
|
+
/** Repo-relative posix file (the identity join key). */
|
|
552
|
+
file: string;
|
|
553
|
+
/** Full title path (the identity join key). */
|
|
554
|
+
titlePath: readonly string[];
|
|
555
|
+
/**
|
|
556
|
+
* Twin path coverage (E64): the supervisor marked this test as the RAW
|
|
557
|
+
* twin of a witnessed one. Absent/false = an ordinary session.
|
|
558
|
+
*
|
|
559
|
+
* The mark travels with the REGISTRATION rather than as a list of
|
|
560
|
+
* runner test ids, because a runner-assigned id is not the id the test
|
|
561
|
+
* runs under: Playwright hashes the test's file path relative to the
|
|
562
|
+
* config it loaded, so an id enumerated from the repository's own
|
|
563
|
+
* config is not the id a supervised run opens the session with. The
|
|
564
|
+
* identity (project, file, titlePath) is what both sides speak.
|
|
565
|
+
*/
|
|
566
|
+
observationOnly?: boolean;
|
|
567
|
+
}
|
|
568
|
+
/** `POST /runs/expected-set` body (SUPERVISOR ONLY). */
|
|
569
|
+
export interface ExpectedSetRequest {
|
|
570
|
+
/** The expected tests, fixed before the run. */
|
|
571
|
+
tests: readonly ExpectedTestRegistration[];
|
|
572
|
+
}
|
|
573
|
+
/** `POST /runs/expected-set` response. */
|
|
574
|
+
export interface ExpectedSetResponse {
|
|
575
|
+
/** The set is bound to this run. */
|
|
576
|
+
bound: true;
|
|
577
|
+
/** Domain-separated digest over the registered set. */
|
|
578
|
+
enumerationDigest: string;
|
|
579
|
+
/** Number of registered expected tests. */
|
|
580
|
+
count: number;
|
|
581
|
+
}
|
|
582
|
+
/** `GET /runs/execution-trace` response (SUPERVISOR ONLY; fix 2b). */
|
|
583
|
+
export interface ExecutionTraceResponse {
|
|
584
|
+
/** The digest of the registered expected set (null when none). */
|
|
585
|
+
enumerationDigest: string | null;
|
|
586
|
+
/** Per expected test, every session the witness recorded. */
|
|
587
|
+
tests: Array<{
|
|
588
|
+
testId: string | null;
|
|
589
|
+
project: string | null;
|
|
590
|
+
file: string;
|
|
591
|
+
titlePath: string[];
|
|
592
|
+
sessions: TracedSession[];
|
|
593
|
+
}>;
|
|
594
|
+
}
|
|
595
|
+
/** Witness runtime configuration (env-derived by the bin, explicit in tests). */
|
|
596
|
+
export interface WitnessOptions {
|
|
597
|
+
/**
|
|
598
|
+
* ADR 0004 D7 (plan §8 / D1): when set, the witness also starts a
|
|
599
|
+
* loopback reverse proxy forwarding to this base URL and records
|
|
600
|
+
* every forwarded request as an engine observation (the runtime HTTP
|
|
601
|
+
* evidence channel). Transport-only: the observation proves the
|
|
602
|
+
* witness observed an HTTP exchange; test attribution is
|
|
603
|
+
* suite-claimed. Must be loopback.
|
|
604
|
+
*/
|
|
605
|
+
proxyTarget?: string;
|
|
606
|
+
/**
|
|
607
|
+
* Timing chaos (E63): the seeded release plan the observation proxy
|
|
608
|
+
* applies to proxied app RESPONSES. Null/absent is the
|
|
609
|
+
* byte-identical no-chaos path; a set plan changes timing only —
|
|
610
|
+
* bytes, status, headers and evidence semantics never move.
|
|
611
|
+
*/
|
|
612
|
+
chaos?: ChaosOptions | null;
|
|
613
|
+
/**
|
|
614
|
+
* Twin path coverage (E64): the observation-only shape recording the
|
|
615
|
+
* proxy keeps for every request, so the CLI can compare a raw test
|
|
616
|
+
* with its witnessed twin by request shape. Null/absent is the
|
|
617
|
+
* byte-identical path: no shape is computed and none is stored. A
|
|
618
|
+
* shape is never evidence — it cannot issue a record, satisfy an
|
|
619
|
+
* obligation, or enter an attestation.
|
|
620
|
+
*/
|
|
621
|
+
twinShapes?: TwinShapePlan | null;
|
|
622
|
+
/**
|
|
623
|
+
* Observation-proxy mount prefix (with `proxyTarget`; e.g. `/api`).
|
|
624
|
+
*
|
|
625
|
+
* WHY this is an explicit deployment-topology declaration (same
|
|
626
|
+
* philosophy as `urlBuilders[].base`): in a real deployment the
|
|
627
|
+
* browser reaches the backend THROUGH the frontend — a dev proxy or
|
|
628
|
+
* edge serves `/api/ops/...` while the backend route is `/ops/...`.
|
|
629
|
+
* Whether such a prefix exists is a property of the deployment the
|
|
630
|
+
* engine cannot derive from source, and guessing wrong would silently
|
|
631
|
+
* mismatch obligation identities. When set, the observation proxy
|
|
632
|
+
* forwards the STRIPPED path to the proxy target AND records the
|
|
633
|
+
* STRIPPED path in observation records, so observations match the
|
|
634
|
+
* backend-derived obligation identities the suite claims
|
|
635
|
+
* (`evidence.http.observe({path: '/ops/x'})`). Requests outside the
|
|
636
|
+
* prefix pass through and are recorded unstripped. Absent/null strips
|
|
637
|
+
* nothing — forwarding and recording stay byte-identical to an
|
|
638
|
+
* unmounted proxy.
|
|
639
|
+
*/
|
|
640
|
+
mountPath?: string | null;
|
|
641
|
+
/** Run manifest identity (pin #4). */
|
|
642
|
+
runId: string;
|
|
643
|
+
/** Per-run token; every call must carry `x-gateforge-run: <token>`. */
|
|
644
|
+
token: string;
|
|
645
|
+
/**
|
|
646
|
+
* Verifier key for the attestation surface (authenticated
|
|
647
|
+
* `POST /run-context` binding, `GET /ledger-attestation`, and the
|
|
648
|
+
* manifest v2 `attestation` envelope): shared by the orchestrator
|
|
649
|
+
* with the witness and the evaluating CLI, NEVER with the tested
|
|
650
|
+
* suite. When absent the witness serves no attestation and its
|
|
651
|
+
* manifest append stays unauthenticated (downstream evaluation fails
|
|
652
|
+
* closed for witnessed records). Environment ONLY (see
|
|
653
|
+
* `witness/bin.ts`) — never argv, stdout, state files, or suite env.
|
|
654
|
+
*/
|
|
655
|
+
verifierKey?: string | null;
|
|
656
|
+
/** Run-state dir; the witness appends its issued recordIds to manifest.json at shutdown. */
|
|
657
|
+
stateDir?: string | null;
|
|
658
|
+
/** Directory of reviewed `.mjs` adapters (default `.gateforge/adapters`). */
|
|
659
|
+
adaptersDir?: string | null;
|
|
660
|
+
/**
|
|
661
|
+
* Operator-issued credential the witness presents on its OWN engine-side
|
|
662
|
+
* adapter reads (GF-10 mediation; dogfood deployment, 2026-09-05/07).
|
|
663
|
+
*
|
|
664
|
+
* WHY: adapters perform GET-only reads of the app's own collection
|
|
665
|
+
* routes to observe persisted state, and those routes authenticate —
|
|
666
|
+
* an unauthenticated loopback read is a 401, so the engine could not
|
|
667
|
+
* observe state at all. The credential is issued by the DEPLOYMENT
|
|
668
|
+
* OPERATOR to the WITNESS ONLY (env `GATEFORGE_ADAPTER_READ_AUTHORIZATION`
|
|
669
|
+
* or `--adapter-read-authorization`), never to the tested suite, and is
|
|
670
|
+
* a read-only service principal of the same trust class as the verifier
|
|
671
|
+
* key. When unset, adapter reads stay unauthenticated (previous
|
|
672
|
+
* behavior) and protected collections simply fail closed with 401/409.
|
|
673
|
+
*/
|
|
674
|
+
adapterReadAuthorization?: string | null;
|
|
675
|
+
/** Classifications document path (YAML) for the primaryKey map + adapter aliases. */
|
|
676
|
+
classificationsPath?: string | null;
|
|
677
|
+
/**
|
|
678
|
+
* Attestation subject (the SUT the UI drives). MUST be loopback
|
|
679
|
+
* (GF-10); the witness probes its env-fingerprint marker at startup
|
|
680
|
+
* when `targetFingerprint` is set (GF-13 minimal v1 attestation).
|
|
681
|
+
*/
|
|
682
|
+
targetBaseUrl?: string | null;
|
|
683
|
+
/** Expected `x-gateforge-env-fingerprint` marker at the attestation subject. */
|
|
684
|
+
targetFingerprint?: string | null;
|
|
685
|
+
/** Default base for adapter reads; a per-adapter `baseUrl` wins. */
|
|
686
|
+
adapterBaseUrl?: string | null;
|
|
687
|
+
/** Per-witness-call timeout (pin #7 default 5s). */
|
|
688
|
+
requestTimeoutMs?: number;
|
|
689
|
+
/**
|
|
690
|
+
* Completion-barrier deadline for async behavior effects (Phase 5):
|
|
691
|
+
* `awaitBarrier` observers get this long to report a real checkpoint.
|
|
692
|
+
* Default 30s. A timeout is failure, never success.
|
|
693
|
+
*/
|
|
694
|
+
barrierTimeoutMs?: number;
|
|
695
|
+
/** Injected clock for `issuedAt` (ISO-8601); default = system now. */
|
|
696
|
+
now?: () => string;
|
|
697
|
+
/** Host to bind; default `127.0.0.1` (loopback). */
|
|
698
|
+
host?: string;
|
|
699
|
+
/**
|
|
700
|
+
* Engine-browser launcher override (programmatic use only — never
|
|
701
|
+
* env-derived): the executable the engine drives stays engine code
|
|
702
|
+
* regardless of which Chromium launches. Tests inject a failing
|
|
703
|
+
* launcher to prove the fail-closed path (E16); production leaves it
|
|
704
|
+
* unset for the pinned Chromium.
|
|
705
|
+
*/
|
|
706
|
+
engineBrowserLauncher?: import('./browser.js').EngineBrowserLauncher;
|
|
707
|
+
/**
|
|
708
|
+
* Trusted fixture/actor provider (plan 2026-09-19 §4.5, Phase 4):
|
|
709
|
+
* injected by the trusted controller harness (engine-owned bundle),
|
|
710
|
+
* never by suite code. Null (default) means strong behavior cases
|
|
711
|
+
* block with a missing-provider cause — suite-supplied fixtures are
|
|
712
|
+
* never a fallback.
|
|
713
|
+
*/
|
|
714
|
+
fixtureProvider?: import('./fixture-provider.js').FixtureProvider | null;
|
|
715
|
+
/**
|
|
716
|
+
* Engine-owned queue channel: the
|
|
717
|
+
* witness's own read of a background queue plus the write side that
|
|
718
|
+
* produces the deliveries it grades. Null (default) means the
|
|
719
|
+
* repository declares no `queueObserver`, so every `engine-task` case
|
|
720
|
+
* blocks with a naming cause — never a suite-supplied substitute.
|
|
721
|
+
*/
|
|
722
|
+
queueChannel?: import('../queue/observer.js').QueueChannel | null;
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* The normalized adapter module contract (pin #8).
|
|
726
|
+
*
|
|
727
|
+
* `probeServer` (optional; the server-witnessed persistence channel) is
|
|
728
|
+
* the adapter's server-side probe: the witness executes it ONLY in the
|
|
729
|
+
* witness process (trusted side) against the app's own database/state —
|
|
730
|
+
* for backend-only tables (a transactional outbox) that can never
|
|
731
|
+
* honestly appear in a UI. Contract:
|
|
732
|
+
*
|
|
733
|
+
* ```js
|
|
734
|
+
* export default {
|
|
735
|
+
* ...,
|
|
736
|
+
* async probeServer(ctx, subject) {
|
|
737
|
+
* const row = await db.query('select * from outbox where id = $1', [subject]);
|
|
738
|
+
* return { found: row !== undefined, fields: row ?? null };
|
|
739
|
+
* },
|
|
740
|
+
* };
|
|
741
|
+
* ```
|
|
742
|
+
*
|
|
743
|
+
* `subject` is the entity key EXACTLY as the persistence intent declared
|
|
744
|
+
* it (scalar for single-column primary keys, column-keyed object for
|
|
745
|
+
* composite keys); the return MUST be
|
|
746
|
+
* `{found: boolean, fields: Record<string, unknown> | null}`. A throw or
|
|
747
|
+
* a malformed shape resolves the intent as a typed
|
|
748
|
+
* SERVER_PROBE_UNAVAILABLE failure — never to satisfaction (fail
|
|
749
|
+
* closed). Adapters without the export simply cannot serve the server
|
|
750
|
+
* channel; the browser path is unaffected.
|
|
751
|
+
*/
|
|
752
|
+
export interface EvidenceAdapter {
|
|
753
|
+
/** GET-only transport. Returns the raw entity body, or null when absent. */
|
|
754
|
+
read: (ctx: AdapterContext, id: unknown) => Promise<unknown> | unknown;
|
|
755
|
+
/**
|
|
756
|
+
* Optional GET-only listing of the resource's entities (raw bodies).
|
|
757
|
+
* Powers engine-side pre-observations for create postconditions; when
|
|
758
|
+
* absent the witness refuses pre-observations for the resource.
|
|
759
|
+
*/
|
|
760
|
+
list?: (ctx: AdapterContext) => Promise<unknown[]> | unknown[];
|
|
761
|
+
/** Enables entity-scoped create absence checks without a collection list. */
|
|
762
|
+
identity?: 'natural-key';
|
|
763
|
+
/** Declares normalized fields the adapter projects for persistence evidence. */
|
|
764
|
+
fields?: readonly string[];
|
|
765
|
+
/**
|
|
766
|
+
* Declares fields the SERVER computes on its own (a derived label, a
|
|
767
|
+
* server-side normalization, a counter). The engine skips the
|
|
768
|
+
* exact-value echo for exactly these keys and REPORTS the skip — a
|
|
769
|
+
* server-changed field is a declared fact about the app, never a
|
|
770
|
+
* silently ignored mismatch. Absent on every hand-written adapter
|
|
771
|
+
* that does not declare one (the previous behavior, unchanged).
|
|
772
|
+
*/
|
|
773
|
+
volatileFields?: readonly string[];
|
|
774
|
+
/** Projects the raw body onto {entityId, fields} — stamped from the RESPONSE. */
|
|
775
|
+
normalize: (body: unknown) => {
|
|
776
|
+
entityId: unknown;
|
|
777
|
+
fields: unknown;
|
|
778
|
+
};
|
|
779
|
+
/** Removal semantics the adapter's resource uses. */
|
|
780
|
+
deletion: 'hard' | 'archive';
|
|
781
|
+
/** Fingerprint the adapter's target environment must present. */
|
|
782
|
+
environmentFingerprint: string;
|
|
783
|
+
/** Optional base override for THIS adapter's reads. */
|
|
784
|
+
baseUrl?: string;
|
|
785
|
+
/**
|
|
786
|
+
* Optional SERVER PROBE, executed witness-side only (see the interface
|
|
787
|
+
* doc): observes the app database directly and reports the entity's
|
|
788
|
+
* presence + observed column state.
|
|
789
|
+
*/
|
|
790
|
+
probeServer?: (ctx: AdapterContext, subject: unknown) => Promise<ServerProbeResult> | ServerProbeResult;
|
|
791
|
+
/**
|
|
792
|
+
* Optional OBSERVE binding (Observe channel, Phase 2): declares which
|
|
793
|
+
* proxied HTTP exchanges count as mutations of this resource when a
|
|
794
|
+
* suite-driven browser test runs. Each declared operation names the
|
|
795
|
+
* method + backend-facing path template; `{id}` marks the single
|
|
796
|
+
* segment carrying the entity id (required on read/update/delete,
|
|
797
|
+
* forbidden on create — a create id comes from the response the
|
|
798
|
+
* witness proxied, verified against the list-diff). Only
|
|
799
|
+
* declared operations are observe-eligible; anything else grades
|
|
800
|
+
* typed-missing. Observe additionally requires `list` (before-
|
|
801
|
+
* snapshots); an adapter without it can serve no observe obligation.
|
|
802
|
+
*/
|
|
803
|
+
observe?: ObserveBinding;
|
|
804
|
+
/**
|
|
805
|
+
* Optional TRUSTED scope observation (plan 2026-09-19 §4.4, Phase 4):
|
|
806
|
+
* observes an independently controlled state source (read-only
|
|
807
|
+
* database credentials, a trusted state sidecar, or equivalent
|
|
808
|
+
* owner-provisioned observer outside candidate code) for one approved
|
|
809
|
+
* scope key within one fixture namespace. Reading only the
|
|
810
|
+
* candidate's own GET handler is insufficient for the protected
|
|
811
|
+
* profile. Adapters lacking this method still serve legacy proofs;
|
|
812
|
+
* they cannot satisfy strong contracts requiring scoped observation.
|
|
813
|
+
*/
|
|
814
|
+
snapshotScope?: (ctx: AdapterContext, input: SnapshotScopeInput) => Promise<ScopeSnapshot> | ScopeSnapshot;
|
|
815
|
+
/**
|
|
816
|
+
* Optional completion-barrier observer (plan 2026-09-19 §4.4): waits
|
|
817
|
+
* for a real completion/queue checkpoint for an engine-issued
|
|
818
|
+
* operation. An observer, not a state maker: it must not sleep-then-
|
|
819
|
+
* true, mutate state, or return true on timeout.
|
|
820
|
+
*/
|
|
821
|
+
awaitBarrier?: (ctx: AdapterContext, input: BarrierInput) => Promise<{
|
|
822
|
+
complete: boolean;
|
|
823
|
+
checkpoint: string;
|
|
824
|
+
}> | {
|
|
825
|
+
complete: boolean;
|
|
826
|
+
checkpoint: string;
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* One observe-eligible mutation shape: the HTTP method (uppercase) and
|
|
831
|
+
* the backend-facing path template. `{id}` matches exactly one non-
|
|
832
|
+
* empty path segment and binds the entity id for read/update/delete.
|
|
833
|
+
* A read may instead declare `collection`, naming its entities in the
|
|
834
|
+
* returned rows instead of in the path.
|
|
835
|
+
*/
|
|
836
|
+
export interface ObserveMutation {
|
|
837
|
+
/** Concrete uppercase HTTP method, e.g. `'POST'`. */
|
|
838
|
+
method: string;
|
|
839
|
+
/** Backend-facing absolute path, e.g. `'/api/v2/accounts/{id}'`. */
|
|
840
|
+
path: string;
|
|
841
|
+
/**
|
|
842
|
+
* Optional COLLECTION read declaration (read + GET only): the
|
|
843
|
+
* entities this operation names come from the returned rows of the
|
|
844
|
+
* response the witness proxied, resolved against the witness's own
|
|
845
|
+
* session-open snapshot. Forbidden on create/update/delete and on
|
|
846
|
+
* any read whose path carries `{id}`; absent, the read keeps binding
|
|
847
|
+
* `{id}` from the path exactly as before.
|
|
848
|
+
*/
|
|
849
|
+
collection?: ObserveCollection;
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* The COLLECTION shape of an observe read: the response the real UI
|
|
853
|
+
* rendered lists its entities, so the entity ids come from the rows
|
|
854
|
+
* themselves rather than from a path template. `rowsKey` names the
|
|
855
|
+
* object property holding the row array; omitting it declares that the
|
|
856
|
+
* response ROOT is the row array. `idKey` names the row property
|
|
857
|
+
* carrying the entity id — the only field the witness reads out of a
|
|
858
|
+
* returned row.
|
|
859
|
+
*
|
|
860
|
+
* Declared, never inferred: an adapter whose read binding carries no
|
|
861
|
+
* `collection` keeps the by-`{id}` behavior unchanged.
|
|
862
|
+
*/
|
|
863
|
+
export interface ObserveCollection {
|
|
864
|
+
/** Property holding the row array; absent = the root is the array. */
|
|
865
|
+
rowsKey?: string;
|
|
866
|
+
/** Row property carrying the entity id. */
|
|
867
|
+
idKey: string;
|
|
868
|
+
}
|
|
869
|
+
/** Per-operation observe bindings for one resource adapter. */
|
|
870
|
+
export interface ObserveBinding {
|
|
871
|
+
create?: ObserveMutation;
|
|
872
|
+
read?: ObserveMutation;
|
|
873
|
+
update?: ObserveMutation;
|
|
874
|
+
delete?: ObserveMutation;
|
|
875
|
+
}
|
|
876
|
+
/** The shape an adapter `probeServer` must return (validated witness-side). */
|
|
877
|
+
export interface ServerProbeResult {
|
|
878
|
+
/** Whether the probed entity exists in the engine-observed state. */
|
|
879
|
+
found: boolean;
|
|
880
|
+
/** The observed column state when found (null when absent). */
|
|
881
|
+
fields: Record<string, unknown> | null;
|
|
882
|
+
}
|
|
883
|
+
/** `POST /runs/behavior-catalog` request (supervisor only, one-time bind). */
|
|
884
|
+
export interface BehaviorCatalogRequest {
|
|
885
|
+
/** Compiled behavior catalog (validated against the core schema). */
|
|
886
|
+
catalog: unknown;
|
|
887
|
+
/**
|
|
888
|
+
* Allowed case/test assignments: testId → required case ids the
|
|
889
|
+
* supervisor permits that test to execute. Every case id must exist
|
|
890
|
+
* in the catalog; every id must belong to an obligation the mapping
|
|
891
|
+
* already claims (checked by the supervisor before sending).
|
|
892
|
+
*/
|
|
893
|
+
assignments: Record<string, string[]>;
|
|
894
|
+
/**
|
|
895
|
+
* Complete route inventory for principal attribution (resourceId,
|
|
896
|
+
* method, canonicalPath per route, sorted). The grader re-resolves
|
|
897
|
+
* every attempt against this exact set — never a claim or payload.
|
|
898
|
+
*/
|
|
899
|
+
routes: unknown;
|
|
900
|
+
/**
|
|
901
|
+
* Authority profile digest the witness seals into every case record
|
|
902
|
+
* (engine bundle binding, provisioned by the trusted controller).
|
|
903
|
+
*/
|
|
904
|
+
authorityProfileDigest: string;
|
|
905
|
+
}
|
|
906
|
+
/** `POST /runs/behavior-catalog` response. */
|
|
907
|
+
export interface BehaviorCatalogResponse {
|
|
908
|
+
bound: true;
|
|
909
|
+
caseCount: number;
|
|
910
|
+
assignmentCount: number;
|
|
911
|
+
routeCount: number;
|
|
912
|
+
}
|
|
913
|
+
/** `POST /behavior/execute` request: exactly these keys, nothing else. */
|
|
914
|
+
export interface BehaviorExecuteRequest {
|
|
915
|
+
sessionId: string;
|
|
916
|
+
sessionToken: string;
|
|
917
|
+
caseId: string;
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* `POST /behavior/execute` response: a REDACTED result reference. Actor
|
|
921
|
+
* credential material, fixture subjects, and expectations never appear
|
|
922
|
+
* here — the worker may name an allowed case but can never read or
|
|
923
|
+
* override actor credentials, expectations, before-state, or origin.
|
|
924
|
+
*/
|
|
925
|
+
export interface BehaviorExecuteResponse {
|
|
926
|
+
caseId: string;
|
|
927
|
+
executionId: string;
|
|
928
|
+
/** Isolated fixture namespace for this execution. */
|
|
929
|
+
namespace: string;
|
|
930
|
+
/** Authoritative before checkpoint (empty until the principal runs). */
|
|
931
|
+
beforeCheckpoint: string | null;
|
|
932
|
+
/** Lifecycle state after this call. */
|
|
933
|
+
state: string;
|
|
934
|
+
}
|
|
935
|
+
/** `POST /behavior/principal` request: drive the principal operation. */
|
|
936
|
+
export interface BehaviorPrincipalRequest {
|
|
937
|
+
sessionId: string;
|
|
938
|
+
sessionToken: string;
|
|
939
|
+
executionId: string;
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* `POST /behavior/principal` response: redacted seal reference. The
|
|
943
|
+
* witness-owned driver executed, the barrier resolved, after-state was
|
|
944
|
+
* observed, and the `behavior.case` record(s) were issued — grading
|
|
945
|
+
* happens core-side, never here.
|
|
946
|
+
*/
|
|
947
|
+
export interface BehaviorPrincipalResponse {
|
|
948
|
+
caseId: string;
|
|
949
|
+
executionId: string;
|
|
950
|
+
recordIds: string[];
|
|
951
|
+
state: string;
|
|
952
|
+
}
|
|
953
|
+
/** Witness-side lifecycle state of one required-case execution. */
|
|
954
|
+
export type CaseExecutionState = 'fixture-prepared' | 'before-snapshot-complete' | 'principal-executing' | 'principal-captured' | 'barrier-reached' | 'after-snapshot-complete' | 'sealed' | 'failed';
|
|
955
|
+
/** One entity inside a trusted scope snapshot. */
|
|
956
|
+
export interface SnapshotEntity {
|
|
957
|
+
/**
|
|
958
|
+
* Entity identity: scalar for single-column keys, complete
|
|
959
|
+
* column-keyed object for composite keys (matching core identity
|
|
960
|
+
* behavior). Unknown identity blocks the case.
|
|
961
|
+
*/
|
|
962
|
+
entityId: unknown;
|
|
963
|
+
/** Projected field map (exactly the declared scope fields). */
|
|
964
|
+
fields: Record<string, unknown>;
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Strict trusted scope snapshot (plan 2026-09-19 §4.4): data, never a
|
|
968
|
+
* verdict. `complete:false`, pagination not exhausted, omitted declared
|
|
969
|
+
* fields, unknown identity, inconsistent checkpoints, or size-limit
|
|
970
|
+
* truncation blocks the case — the harness never silently samples.
|
|
971
|
+
*/
|
|
972
|
+
export interface ScopeSnapshot {
|
|
973
|
+
/** Approved snapshot scope key. */
|
|
974
|
+
scope: string;
|
|
975
|
+
/** Fixture namespace actually observed (must equal the case lease). */
|
|
976
|
+
fixtureNamespace: string;
|
|
977
|
+
/** False when collection is partial for any reason. */
|
|
978
|
+
complete: boolean;
|
|
979
|
+
/** Authoritative checkpoint (before/after comparison + barriers). */
|
|
980
|
+
checkpoint: string;
|
|
981
|
+
/** Entities in canonical order (duplicate identity rejected). */
|
|
982
|
+
entities: SnapshotEntity[];
|
|
983
|
+
/** True when pagination was exhausted (absent = unknown = incomplete). */
|
|
984
|
+
exhausted?: boolean;
|
|
985
|
+
/** Total size when the scope enforces a limit (truncation blocks). */
|
|
986
|
+
totalSize?: number;
|
|
987
|
+
}
|
|
988
|
+
/** Input to an adapter `snapshotScope` observation. */
|
|
989
|
+
export interface SnapshotScopeInput {
|
|
990
|
+
/** Approved snapshot scope key. */
|
|
991
|
+
scope: string;
|
|
992
|
+
/** Fixture namespace to observe (never the whole database). */
|
|
993
|
+
fixtureNamespace: string;
|
|
994
|
+
}
|
|
995
|
+
/** Input to an adapter `awaitBarrier` observation. */
|
|
996
|
+
export interface BarrierInput {
|
|
997
|
+
/** Approved snapshot scope key. */
|
|
998
|
+
scope: string;
|
|
999
|
+
/** Fixture namespace to observe. */
|
|
1000
|
+
fixtureNamespace: string;
|
|
1001
|
+
/** Engine-issued operation id awaiting completion. */
|
|
1002
|
+
operationId: string;
|
|
1003
|
+
/** Hard deadline in milliseconds (timeout is failure, not success). */
|
|
1004
|
+
deadlineMs: number;
|
|
1005
|
+
}
|
|
1006
|
+
/** The transport handed to `read` (GET-only, engine-mediated). */
|
|
1007
|
+
/**
|
|
1008
|
+
* A per-session adapter identity (plan 2026-09-25 Phase 4b item 3b):
|
|
1009
|
+
* the credential one OPEN session registered for ITSELF through
|
|
1010
|
+
* `POST /sessions/identity`, so an adapter read that must happen inside a
|
|
1011
|
+
* tenant the test just created can run as that tenant.
|
|
1012
|
+
*
|
|
1013
|
+
* It changes WHO the engine reads as — nothing else. The engine still
|
|
1014
|
+
* performs every read; a wrong tenant simply makes the row unfound
|
|
1015
|
+
* (the app answers 403/404 and the entity grades absent), so the failure
|
|
1016
|
+
* mode is a closed door, never an open verdict. The values live in
|
|
1017
|
+
* witness memory for the length of the session and are never written to
|
|
1018
|
+
* a record, the run state, a log or a report.
|
|
1019
|
+
*/
|
|
1020
|
+
export interface SessionIdentity {
|
|
1021
|
+
/** The adapter seat this identity serves (must be a declared seat). */
|
|
1022
|
+
readonly seat: string;
|
|
1023
|
+
/**
|
|
1024
|
+
* The seat's credential VALUES, keyed by the same witness environment
|
|
1025
|
+
* variable names the seat declares (`auth.seats.<seat>.credentials`
|
|
1026
|
+
* maps login field to env var). Every variable the seat declares must
|
|
1027
|
+
* be present: a partial identity fails closed instead of silently
|
|
1028
|
+
* completing itself from the process-global seat.
|
|
1029
|
+
*/
|
|
1030
|
+
readonly values: Readonly<Record<string, string>>;
|
|
1031
|
+
}
|
|
1032
|
+
/** `POST /sessions/identity` request (session-authenticated). */
|
|
1033
|
+
export interface SessionIdentityRequest extends SessionIdentity {
|
|
1034
|
+
/** The session the identity belongs to (its own, never another's). */
|
|
1035
|
+
sessionId: string;
|
|
1036
|
+
/** The session's witness-issued secret; authorizes this call alone. */
|
|
1037
|
+
sessionToken: string;
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* `POST /sessions/identity` response. It echoes the SEAT NAME only — the
|
|
1041
|
+
* credential itself never leaves the witness process.
|
|
1042
|
+
*/
|
|
1043
|
+
export interface SessionIdentityResponse {
|
|
1044
|
+
registered: true;
|
|
1045
|
+
seat: string;
|
|
1046
|
+
}
|
|
1047
|
+
/** The transport handed to `read` (GET-only, engine-mediated). */
|
|
1048
|
+
export interface AdapterContext {
|
|
1049
|
+
/**
|
|
1050
|
+
* The supervisor-opened session this read runs under, when the caller
|
|
1051
|
+
* is a test session (absent for engine-driven and probe reads). It is
|
|
1052
|
+
* what a {@link SessionIdentity} is scoped to.
|
|
1053
|
+
*/
|
|
1054
|
+
sessionId?: string;
|
|
1055
|
+
/**
|
|
1056
|
+
* The identity THAT session registered for itself, or null. Present
|
|
1057
|
+
* only while the read belongs to the registering session.
|
|
1058
|
+
*/
|
|
1059
|
+
sessionIdentity?: SessionIdentity | null;
|
|
1060
|
+
/** The resolved read base for this adapter. */
|
|
1061
|
+
baseUrl: string;
|
|
1062
|
+
/** The resource this adapter serves. */
|
|
1063
|
+
resourceId: string;
|
|
1064
|
+
/**
|
|
1065
|
+
* The ONLY outbound primitive available to adapters: a GET returning
|
|
1066
|
+
* a minimal response view (status + JSON/text body + headers).
|
|
1067
|
+
*/
|
|
1068
|
+
get: (path: string) => Promise<{
|
|
1069
|
+
status: number;
|
|
1070
|
+
json(): Promise<unknown>;
|
|
1071
|
+
text(): Promise<string>;
|
|
1072
|
+
headers: Headers;
|
|
1073
|
+
}>;
|
|
1074
|
+
/**
|
|
1075
|
+
* Headers adapters MUST attach to any direct fetch they perform
|
|
1076
|
+
* instead of `get` (dogfood, 2026-09-05): carries the operator-issued
|
|
1077
|
+
* engine read credential (`WitnessOptions.adapterReadAuthorization`)
|
|
1078
|
+
* so engine-side GET-only state observation can authenticate against
|
|
1079
|
+
* the app's own collection routes. Never suite-supplied, never
|
|
1080
|
+
* attached to browser traffic.
|
|
1081
|
+
*/
|
|
1082
|
+
headers?: Record<string, string>;
|
|
1083
|
+
}
|
|
1084
|
+
/** Full witness-issued record (superset of the pinned wire response). */
|
|
1085
|
+
export type IssuedRecord = EvidenceRecord;
|
|
1086
|
+
/** Result of spawning/attaching a witness service. */
|
|
1087
|
+
export interface WitnessHandle {
|
|
1088
|
+
/** Base URL (loopback, OS-assigned port). */
|
|
1089
|
+
url: string;
|
|
1090
|
+
/** Stop the server; appends issued recordIds to the run manifest (pin #4/#7). */
|
|
1091
|
+
proxyUrl: string | null;
|
|
1092
|
+
stop: () => Promise<void>;
|
|
1093
|
+
}
|
|
1094
|
+
//# sourceMappingURL=types.d.ts.map
|