@gate-forge/witness 0.0.0-stage → 0.8.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 +4899 -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,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter loading + registry (pin #8, plan §5.4).
|
|
3
|
+
*
|
|
4
|
+
* Reviewed evidence adapters load from `.gateforge/adapters/<name>.mjs`
|
|
5
|
+
* at WITNESS START (engine-side — the test process never imports them).
|
|
6
|
+
* Default export contract:
|
|
7
|
+
*
|
|
8
|
+
* ```js
|
|
9
|
+
* export default {
|
|
10
|
+
* read: async (ctx, id) => body | null, // GET-only; ctx.get(path) is the transport
|
|
11
|
+
* normalize: (body) => ({ entityId, fields }), // stamped from the RESPONSE, never caller args
|
|
12
|
+
* deletion: 'hard' | 'archive',
|
|
13
|
+
* fields: ['id', 'name'], // optional normalized field projection
|
|
14
|
+
* baseUrl: 'http://…', // optional override of the witness's adapter base
|
|
15
|
+
* identity: 'natural-key', // optional entity-scoped create absence proof
|
|
16
|
+
* list: async (ctx) => bodies, // optional: powers create pre-observations + Observe snapshots
|
|
17
|
+
* observe: { // optional: Observe-channel mutation bindings (Phase 2)
|
|
18
|
+
* create: { method: 'POST', path: '/api/v2/accounts' },
|
|
19
|
+
* update: { method: 'PATCH', path: '/api/v2/accounts/{id}' },
|
|
20
|
+
* },
|
|
21
|
+
* probeServer: async (ctx, subject) => ({ found, fields }), // optional: the
|
|
22
|
+
* // SERVER-WITNESSED persistence channel probe — executed
|
|
23
|
+
* // ONLY in this trusted witness process
|
|
24
|
+
* };
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* The registry loads fail-closed: a missing/invalid module, a missing
|
|
28
|
+
* `read`/`normalize`/`deletion`/`environmentFingerprint`, an unknown
|
|
29
|
+
* deletion value, or a duplicate name aborts witness startup with an
|
|
30
|
+
* actionable diagnostic naming the file. The registry is frozen after
|
|
31
|
+
* load — there is no registration path a test could reach (invariant 6,
|
|
32
|
+
* GF-11 "registration path does not exist").
|
|
33
|
+
*/
|
|
34
|
+
import { readdirSync } from 'node:fs';
|
|
35
|
+
import { join } from 'node:path';
|
|
36
|
+
import { pathToFileURL } from 'node:url';
|
|
37
|
+
/**
|
|
38
|
+
* Loads every `.mjs` adapter in `dir` into a frozen registry.
|
|
39
|
+
*
|
|
40
|
+
* Args:
|
|
41
|
+
* dir: the configured adapters directory (absolute).
|
|
42
|
+
*
|
|
43
|
+
* Returns:
|
|
44
|
+
* Map<string, EvidenceAdapter>: keyed by the file basename sans `.mjs`.
|
|
45
|
+
*
|
|
46
|
+
* Throws:
|
|
47
|
+
* AdapterRegistryError: fail-closed load problems — unreadable dir,
|
|
48
|
+
* unimportable module, or a module missing any contract member.
|
|
49
|
+
*/
|
|
50
|
+
export async function loadAdapters(dir) {
|
|
51
|
+
const registry = new Map();
|
|
52
|
+
let entries;
|
|
53
|
+
try {
|
|
54
|
+
entries = readdirSync(dir);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (error.code === 'ENOENT') {
|
|
58
|
+
// No adapters directory at all: the registry stays empty; the
|
|
59
|
+
// witness then answers 400 for every persistence request (a run
|
|
60
|
+
// with persistence obligations and no adapters is misconfigured).
|
|
61
|
+
return registry;
|
|
62
|
+
}
|
|
63
|
+
throw new AdapterRegistryError(`cannot read adapters directory '${dir}': ${error.message}`);
|
|
64
|
+
}
|
|
65
|
+
for (const entry of entries.sort()) {
|
|
66
|
+
if (!entry.endsWith('.mjs'))
|
|
67
|
+
continue;
|
|
68
|
+
const name = entry.slice(0, -'.mjs'.length);
|
|
69
|
+
if (registry.has(name)) {
|
|
70
|
+
throw new AdapterRegistryError(`duplicate adapter '${name}' (multiple '${entry}' files is a config error)`);
|
|
71
|
+
}
|
|
72
|
+
const module = await importAdapter(dir, entry, name);
|
|
73
|
+
const adapter = validateAdapter(module, name);
|
|
74
|
+
registry.set(name, adapter);
|
|
75
|
+
}
|
|
76
|
+
return Object.freeze(registry);
|
|
77
|
+
}
|
|
78
|
+
/** Imports one adapter module; import errors carry an actionable message. */
|
|
79
|
+
async function importAdapter(dir, entry, name) {
|
|
80
|
+
try {
|
|
81
|
+
const loaded = (await import(pathToFileURL(join(dir, entry)).href));
|
|
82
|
+
return loaded.default;
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
throw new AdapterRegistryError(`adapter '${name}' (${join(dir, entry)}) could not be imported: ` +
|
|
86
|
+
`${error instanceof Error ? error.message : String(error)}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Validates one adapter module against the pin-#8 contract.
|
|
91
|
+
*
|
|
92
|
+
* Args:
|
|
93
|
+
* module: the default export of the adapter file.
|
|
94
|
+
* name: adapter name, used in diagnostics.
|
|
95
|
+
*
|
|
96
|
+
* Returns:
|
|
97
|
+
* EvidenceAdapter: the validated adapter.
|
|
98
|
+
*
|
|
99
|
+
* Throws:
|
|
100
|
+
* AdapterRegistryError: when any required member is missing or shaped
|
|
101
|
+
* wrong. Optional `baseUrl`, `identity`, and `fields` refine read and
|
|
102
|
+
* create-proof capabilities without changing adapters that omit them.
|
|
103
|
+
*/
|
|
104
|
+
export function validateAdapter(module, name) {
|
|
105
|
+
if (typeof module !== 'object' || module === null) {
|
|
106
|
+
throw new AdapterRegistryError(`adapter '${name}' must default-export an object {read, normalize, deletion, environmentFingerprint}`);
|
|
107
|
+
}
|
|
108
|
+
const adapter = module;
|
|
109
|
+
const problems = [];
|
|
110
|
+
if (typeof adapter['read'] !== 'function')
|
|
111
|
+
problems.push('read must be an async function (ctx, id)');
|
|
112
|
+
if (typeof adapter['normalize'] !== 'function')
|
|
113
|
+
problems.push('normalize must be a function (body) => {entityId, fields}');
|
|
114
|
+
if (adapter['deletion'] !== 'hard' && adapter['deletion'] !== 'archive') {
|
|
115
|
+
problems.push("deletion must be 'hard' or 'archive'");
|
|
116
|
+
}
|
|
117
|
+
if (typeof adapter['environmentFingerprint'] !== 'string') {
|
|
118
|
+
problems.push('environmentFingerprint must be a string');
|
|
119
|
+
}
|
|
120
|
+
if (adapter['baseUrl'] !== undefined &&
|
|
121
|
+
(typeof adapter['baseUrl'] !== 'string' || adapter['baseUrl'].length === 0)) {
|
|
122
|
+
problems.push('baseUrl must be a non-empty string when present');
|
|
123
|
+
}
|
|
124
|
+
if (adapter['identity'] !== undefined && adapter['identity'] !== 'natural-key') {
|
|
125
|
+
problems.push("identity must be 'natural-key' when present");
|
|
126
|
+
}
|
|
127
|
+
if (adapter['list'] !== undefined && typeof adapter['list'] !== 'function') {
|
|
128
|
+
problems.push('list must be a function (ctx) => entity[] when present');
|
|
129
|
+
}
|
|
130
|
+
if (adapter['fields'] !== undefined &&
|
|
131
|
+
(!Array.isArray(adapter['fields']) ||
|
|
132
|
+
adapter['fields'].some((field) => typeof field !== 'string' || field.length === 0) ||
|
|
133
|
+
new Set(adapter['fields']).size !== adapter['fields'].length)) {
|
|
134
|
+
problems.push('fields must be an array of unique non-empty field names when present');
|
|
135
|
+
}
|
|
136
|
+
if (adapter['volatileFields'] !== undefined &&
|
|
137
|
+
(!Array.isArray(adapter['volatileFields']) ||
|
|
138
|
+
adapter['volatileFields'].some((field) => typeof field !== 'string' || field.length === 0) ||
|
|
139
|
+
new Set(adapter['volatileFields']).size !== adapter['volatileFields'].length)) {
|
|
140
|
+
problems.push('volatileFields must be an array of unique non-empty field names when present');
|
|
141
|
+
}
|
|
142
|
+
// Server probe (server-witnessed persistence channel): OPTIONAL — an
|
|
143
|
+
// adapter without it simply cannot serve the channel and every server
|
|
144
|
+
// intent for the resource resolves to a typed
|
|
145
|
+
// SERVER_PROBE_UNAVAILABLE failure. A PRESENT but malformed export is
|
|
146
|
+
// a contract violation caught here at load, fail-closed, not at
|
|
147
|
+
// intent time.
|
|
148
|
+
if (adapter['probeServer'] !== undefined && typeof adapter['probeServer'] !== 'function') {
|
|
149
|
+
problems.push('probeServer must be an async function (ctx, subject) => {found, fields} when present');
|
|
150
|
+
}
|
|
151
|
+
// Observe binding (Observe channel, Phase 2): OPTIONAL — an adapter
|
|
152
|
+
// without it serves no observe obligation. Present-but-malformed is a
|
|
153
|
+
// load-time contract violation (fail closed), validated by
|
|
154
|
+
// validateObserveBinding below.
|
|
155
|
+
if (adapter['observe'] !== undefined) {
|
|
156
|
+
const observeProblem = validateObserveBinding(adapter['observe']);
|
|
157
|
+
if (observeProblem !== null)
|
|
158
|
+
problems.push(observeProblem);
|
|
159
|
+
}
|
|
160
|
+
// Trusted scope/barrier observation (plan 2026-09-19 §4.4, Phase 4):
|
|
161
|
+
// OPTIONAL — adapters lacking these methods still serve legacy proofs
|
|
162
|
+
// but cannot satisfy strong contracts requiring scoped observation.
|
|
163
|
+
// Present-but-not-a-function is a load-time violation (fail closed).
|
|
164
|
+
if (adapter['snapshotScope'] !== undefined && typeof adapter['snapshotScope'] !== 'function') {
|
|
165
|
+
problems.push('snapshotScope must be an async function (ctx, {scope, fixtureNamespace}) => ScopeSnapshot when present');
|
|
166
|
+
}
|
|
167
|
+
if (adapter['awaitBarrier'] !== undefined && typeof adapter['awaitBarrier'] !== 'function') {
|
|
168
|
+
problems.push('awaitBarrier must be an async function (ctx, {scope, fixtureNamespace, operationId, deadlineMs}) when present');
|
|
169
|
+
}
|
|
170
|
+
if (problems.length > 0) {
|
|
171
|
+
throw new AdapterRegistryError(`adapter '${name}' violates the adapter contract: ${problems.join('; ')}`);
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
read: adapter['read'],
|
|
175
|
+
normalize: adapter['normalize'],
|
|
176
|
+
deletion: adapter['deletion'],
|
|
177
|
+
environmentFingerprint: adapter['environmentFingerprint'],
|
|
178
|
+
...(adapter['identity'] !== undefined ? { identity: 'natural-key' } : {}),
|
|
179
|
+
baseUrl: adapter['baseUrl'],
|
|
180
|
+
...(adapter['fields'] !== undefined
|
|
181
|
+
? { fields: [...adapter['fields']] }
|
|
182
|
+
: {}),
|
|
183
|
+
...(adapter['volatileFields'] !== undefined
|
|
184
|
+
? { volatileFields: [...adapter['volatileFields']] }
|
|
185
|
+
: {}),
|
|
186
|
+
...(adapter['list'] !== undefined
|
|
187
|
+
? { list: adapter['list'] }
|
|
188
|
+
: {}),
|
|
189
|
+
...(adapter['probeServer'] !== undefined
|
|
190
|
+
? { probeServer: adapter['probeServer'] }
|
|
191
|
+
: {}),
|
|
192
|
+
...(adapter['observe'] !== undefined
|
|
193
|
+
? { observe: adapter['observe'] }
|
|
194
|
+
: {}),
|
|
195
|
+
...(adapter['snapshotScope'] !== undefined
|
|
196
|
+
? { snapshotScope: adapter['snapshotScope'] }
|
|
197
|
+
: {}),
|
|
198
|
+
...(adapter['awaitBarrier'] !== undefined
|
|
199
|
+
? { awaitBarrier: adapter['awaitBarrier'] }
|
|
200
|
+
: {}),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/** Fail-closed adapter-loading error (witness startup aborts). */
|
|
204
|
+
export class AdapterRegistryError extends Error {
|
|
205
|
+
constructor(message) {
|
|
206
|
+
super(message);
|
|
207
|
+
this.name = 'AdapterRegistryError';
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/** Concrete HTTP methods an observe binding may name (uppercase). */
|
|
211
|
+
const OBSERVE_METHODS = new Set([
|
|
212
|
+
'GET',
|
|
213
|
+
'HEAD',
|
|
214
|
+
'POST',
|
|
215
|
+
'PUT',
|
|
216
|
+
'PATCH',
|
|
217
|
+
'DELETE',
|
|
218
|
+
'OPTIONS',
|
|
219
|
+
]);
|
|
220
|
+
/** Observe-eligible operations (the CRUD verbs the grader knows). */
|
|
221
|
+
const OBSERVE_OPERATIONS = ['create', 'read', 'update', 'delete'];
|
|
222
|
+
/**
|
|
223
|
+
* Validates one adapter's optional `observe` binding (Observe channel,
|
|
224
|
+
* Phase 2): a plain object with per-operation `{method, path}`
|
|
225
|
+
* entries. Method must be a concrete uppercase verb; path must be an
|
|
226
|
+
* absolute backend-facing path whose only template segment is `{id}` —
|
|
227
|
+
* required on read/update/delete (the entity-id carrier), forbidden on
|
|
228
|
+
* create (create ids come from the adapter list-diff, never from a
|
|
229
|
+
* path guess).
|
|
230
|
+
*
|
|
231
|
+
* Args:
|
|
232
|
+
* value: the adapter's `observe` export.
|
|
233
|
+
*
|
|
234
|
+
* Returns:
|
|
235
|
+
* string | null: the contract-violation description, or null when valid.
|
|
236
|
+
*/
|
|
237
|
+
export function validateObserveBinding(value) {
|
|
238
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
239
|
+
return 'observe must be an object {create?/read?/update?/delete?: {method, path}} when present';
|
|
240
|
+
}
|
|
241
|
+
const binding = value;
|
|
242
|
+
for (const key of Object.keys(binding)) {
|
|
243
|
+
if (!OBSERVE_OPERATIONS.includes(key)) {
|
|
244
|
+
return `observe carries unknown operation '${key}' (allowed: ${OBSERVE_OPERATIONS.join(', ')})`;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
for (const operation of OBSERVE_OPERATIONS) {
|
|
248
|
+
const entry = binding[operation];
|
|
249
|
+
if (entry === undefined)
|
|
250
|
+
continue;
|
|
251
|
+
if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {
|
|
252
|
+
return `observe.${operation} must be {method, path}`;
|
|
253
|
+
}
|
|
254
|
+
const { method, path } = entry;
|
|
255
|
+
if (typeof method !== 'string' || !OBSERVE_METHODS.has(method)) {
|
|
256
|
+
return (`observe.${operation}.method must be a concrete uppercase HTTP verb ` +
|
|
257
|
+
`(${[...OBSERVE_METHODS].sort().join(', ')})`);
|
|
258
|
+
}
|
|
259
|
+
if (typeof path !== 'string' || !path.startsWith('/')) {
|
|
260
|
+
return `observe.${operation}.path must be an absolute backend-facing path starting with '/'`;
|
|
261
|
+
}
|
|
262
|
+
if (path.includes('//') || /[\s?#]/.test(path)) {
|
|
263
|
+
return `observe.${operation}.path must be a plain path template (no duplicate slashes, query, fragment, or whitespace)`;
|
|
264
|
+
}
|
|
265
|
+
const segments = path.split('/').filter((segment) => segment.length > 0);
|
|
266
|
+
const idSegments = segments.filter((segment) => segment === '{id}').length;
|
|
267
|
+
const braced = segments.filter((segment) => segment.startsWith('{') || segment.endsWith('}'));
|
|
268
|
+
if (braced.length !== idSegments) {
|
|
269
|
+
return `observe.${operation}.path may template only the '{id}' segment (got '${path}')`;
|
|
270
|
+
}
|
|
271
|
+
if (operation === 'create' && idSegments > 0) {
|
|
272
|
+
return `observe.create.path must not template '{id}' (create ids come from the adapter list-diff, got '${path}')`;
|
|
273
|
+
}
|
|
274
|
+
// Optional COLLECTION read shape (declared, never inferred): a
|
|
275
|
+
// real UI renders a LIST, so a read may name its entities in the
|
|
276
|
+
// returned rows instead of in the path. Only a read may declare
|
|
277
|
+
// it, only over GET, and never alongside the `{id}` template the
|
|
278
|
+
// by-id reader binds — every shape violation fails here, at load.
|
|
279
|
+
const collection = entry['collection'];
|
|
280
|
+
const declaresCollection = collection !== undefined;
|
|
281
|
+
if (declaresCollection) {
|
|
282
|
+
const problem = validateObserveCollection(collection, operation, method, path);
|
|
283
|
+
if (problem !== null)
|
|
284
|
+
return problem;
|
|
285
|
+
}
|
|
286
|
+
if (operation !== 'create' && idSegments !== 1 && !declaresCollection) {
|
|
287
|
+
return `observe.${operation}.path must carry exactly one '{id}' segment binding the entity id (got '${path}')`;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Validates one operation's optional `collection` declaration: the
|
|
294
|
+
* response shape whose ROWS name the entities an observe read credits.
|
|
295
|
+
* A collection read must be a `read` over `GET` on a path that carries
|
|
296
|
+
* no `{id}` template (the by-id reader owns that), and must declare the
|
|
297
|
+
* row `idKey`; `rowsKey` is optional and its absence declares that the
|
|
298
|
+
* response root is the row array. Every failure is a load-time
|
|
299
|
+
* contract violation — an adapter can never quietly fall back to
|
|
300
|
+
* inferring a collection from an arbitrary response body.
|
|
301
|
+
*
|
|
302
|
+
* Args:
|
|
303
|
+
* value: the operation entry's `collection` export.
|
|
304
|
+
* operation: the observe operation the declaration sits on.
|
|
305
|
+
* method: the operation's already-validated HTTP method.
|
|
306
|
+
* path: the operation's already-validated path template.
|
|
307
|
+
*
|
|
308
|
+
* Returns:
|
|
309
|
+
* string | null: the contract-violation description, or null when valid.
|
|
310
|
+
*/
|
|
311
|
+
export function validateObserveCollection(value, operation, method, path) {
|
|
312
|
+
if (operation !== 'read') {
|
|
313
|
+
return (`observe.${operation}.collection is not supported — only a read renders a collection ` +
|
|
314
|
+
`(${operation} ids come from the list-diff or the '{id}' path segment)`);
|
|
315
|
+
}
|
|
316
|
+
if (method !== 'GET') {
|
|
317
|
+
return `observe.read.collection requires method 'GET' (got '${method}') — a collection is read, never written`;
|
|
318
|
+
}
|
|
319
|
+
if (path.split('/').includes('{id}')) {
|
|
320
|
+
return (`observe.read.collection must not be declared on a path carrying '{id}' (got '${path}') — a ` +
|
|
321
|
+
'by-id read and a collection read are two shapes, never one');
|
|
322
|
+
}
|
|
323
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
324
|
+
return `observe.read.collection must be an object {idKey, rowsKey?}`;
|
|
325
|
+
}
|
|
326
|
+
const declaration = value;
|
|
327
|
+
for (const key of Object.keys(declaration)) {
|
|
328
|
+
if (key !== 'idKey' && key !== 'rowsKey') {
|
|
329
|
+
return `observe.read.collection carries unknown key '${key}' (allowed: idKey, rowsKey)`;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const idKey = declaration['idKey'];
|
|
333
|
+
if (typeof idKey !== 'string' || idKey.length === 0 || /[\s]/.test(idKey)) {
|
|
334
|
+
return "observe.read.collection.idKey must be a non-empty property name (the row field carrying the entity id)";
|
|
335
|
+
}
|
|
336
|
+
const rowsKey = declaration['rowsKey'];
|
|
337
|
+
if (rowsKey !== undefined && (typeof rowsKey !== 'string' || rowsKey.length === 0 || /[\s]/.test(rowsKey))) {
|
|
338
|
+
return ("observe.read.collection.rowsKey must be a non-empty property name when present (omit it when the " +
|
|
339
|
+
'response root is the row array)');
|
|
340
|
+
}
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* The transport the witness hands to adapter `read` calls (GET-only).
|
|
345
|
+
*
|
|
346
|
+
* Args:
|
|
347
|
+
* baseUrl: the resolved read base.
|
|
348
|
+
* resourceId: the resource this adapter serves.
|
|
349
|
+
* get: the mediated GET primitive.
|
|
350
|
+
* headers: engine-side read headers, when the run issues one.
|
|
351
|
+
* session: the test session the read belongs to and the identity THAT
|
|
352
|
+
* session registered (plan Phase 4b item 3b). Omitted for
|
|
353
|
+
* engine-driven, probe and supervisor reads — those are not a test
|
|
354
|
+
* session's read and must never inherit one.
|
|
355
|
+
*
|
|
356
|
+
* Returns:
|
|
357
|
+
* AdapterContext: the frozen read context.
|
|
358
|
+
*/
|
|
359
|
+
export function makeAdapterContext(baseUrl, resourceId, get, headers, session) {
|
|
360
|
+
return Object.freeze({
|
|
361
|
+
baseUrl,
|
|
362
|
+
resourceId,
|
|
363
|
+
get,
|
|
364
|
+
...(session !== undefined
|
|
365
|
+
? { sessionId: session.sessionId, sessionIdentity: session.sessionIdentity }
|
|
366
|
+
: {}),
|
|
367
|
+
...(headers ? { headers } : {}),
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
//# sourceMappingURL=adapter-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter-registry.js","sourceRoot":"","sources":["../../src/witness/adapter-registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { CredentialMaterial, FixtureLease } from './fixture-provider.js';
|
|
2
|
+
/** Proof-input bound (request and response bodies alike). */
|
|
3
|
+
export declare const BEHAVIOR_BODY_LIMIT_BYTES: number;
|
|
4
|
+
/** Typed driver failure (always a blocking diagnostic, never proof). */
|
|
5
|
+
export declare class BehaviorDriverError extends Error {
|
|
6
|
+
constructor(message: string);
|
|
7
|
+
}
|
|
8
|
+
/** Approved request action shape (subset used by the Phase 5 driver). */
|
|
9
|
+
export interface BehaviorRequestAction {
|
|
10
|
+
kind: 'request';
|
|
11
|
+
method: string;
|
|
12
|
+
pathTemplate: string;
|
|
13
|
+
path: Record<string, {
|
|
14
|
+
from: string;
|
|
15
|
+
value?: unknown;
|
|
16
|
+
key?: string;
|
|
17
|
+
}>;
|
|
18
|
+
query: Record<string, {
|
|
19
|
+
from: string;
|
|
20
|
+
value?: unknown;
|
|
21
|
+
key?: string;
|
|
22
|
+
}>;
|
|
23
|
+
body: {
|
|
24
|
+
encoding: string;
|
|
25
|
+
fields?: Record<string, {
|
|
26
|
+
from: string;
|
|
27
|
+
value?: unknown;
|
|
28
|
+
key?: string;
|
|
29
|
+
}>;
|
|
30
|
+
fixture?: string;
|
|
31
|
+
};
|
|
32
|
+
credentialVariant: 'valid' | 'missing' | 'corrupted';
|
|
33
|
+
signatureProfile?: string;
|
|
34
|
+
}
|
|
35
|
+
/** What the driver observed (headers excluded — they carry secrets). */
|
|
36
|
+
export interface DrivenRequest {
|
|
37
|
+
engineRequestId: string;
|
|
38
|
+
method: string;
|
|
39
|
+
path: string;
|
|
40
|
+
query: Record<string, unknown>;
|
|
41
|
+
/** Submitted body as observed (parsed JSON or form map). */
|
|
42
|
+
body: unknown;
|
|
43
|
+
status: number | null;
|
|
44
|
+
/** Response body as observed (parsed JSON or text). */
|
|
45
|
+
responseBody: unknown;
|
|
46
|
+
requestDigest: string;
|
|
47
|
+
responseDigest: string | null;
|
|
48
|
+
}
|
|
49
|
+
export interface DriveBehaviorRequestInput {
|
|
50
|
+
/** Approved request action from the bound catalog. */
|
|
51
|
+
action: BehaviorRequestAction;
|
|
52
|
+
/** Trusted lease (subjects + actors resolve here). */
|
|
53
|
+
lease: FixtureLease;
|
|
54
|
+
/** Actor profile key from the case definition. */
|
|
55
|
+
actorProfile: string;
|
|
56
|
+
/** Approved subject origin (the app under test; loopback only). */
|
|
57
|
+
origin: string;
|
|
58
|
+
/** Resolve engine-side credential material (never suite-visible). */
|
|
59
|
+
resolveCredential: (credentialRef: string) => Promise<CredentialMaterial | null> | CredentialMaterial | null;
|
|
60
|
+
/** Fetch timeout in milliseconds. */
|
|
61
|
+
timeoutMs: number;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Drives one approved request and captures the bounded observation.
|
|
65
|
+
*
|
|
66
|
+
* Args:
|
|
67
|
+
* input: action, lease, actor profile, origin, credential resolver,
|
|
68
|
+
* and timeout.
|
|
69
|
+
*
|
|
70
|
+
* Returns:
|
|
71
|
+
* DrivenRequest: method/path/query/body/status/response plus
|
|
72
|
+
* identity digests (headers never leave the driver).
|
|
73
|
+
*
|
|
74
|
+
* Throws:
|
|
75
|
+
* BehaviorDriverError: unsupported encoding, unresolvable value,
|
|
76
|
+
* non-loopback origin, timeout, over-limit body, or transport
|
|
77
|
+
* failure — always a blocking diagnostic, never proof.
|
|
78
|
+
*/
|
|
79
|
+
export declare function driveBehaviorRequest(input: DriveBehaviorRequestInput): Promise<DrivenRequest>;
|
|
80
|
+
//# sourceMappingURL=behavior-request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"behavior-request.d.ts","sourceRoot":"","sources":["../../src/witness/behavior-request.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE9E,6DAA6D;AAC7D,eAAO,MAAM,yBAAyB,QAAa,CAAC;AAEpD,wEAAwE;AACxE,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED,yEAAyE;AACzE,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvE,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,CAAC,EAAE,OAAO,CAAC;YAAC,GAAG,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvH,iBAAiB,EAAE,OAAO,GAAG,SAAS,GAAG,WAAW,CAAC;IACrD,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,wEAAwE;AACxE,MAAM,WAAW,aAAa;IAC5B,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,4DAA4D;IAC5D,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,uDAAuD;IACvD,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAiID,MAAM,WAAW,yBAAyB;IACxC,sDAAsD;IACtD,MAAM,EAAE,qBAAqB,CAAC;IAC9B,sDAAsD;IACtD,KAAK,EAAE,YAAY,CAAC;IACpB,kDAAkD;IAClD,YAAY,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,iBAAiB,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,GAAG,kBAAkB,GAAG,IAAI,CAAC;IAC7G,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,yBAAyB,GAAG,OAAO,CAAC,aAAa,CAAC,CAkLnG"}
|