@glubean/scanner 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/contract-extraction.d.ts +201 -4
- package/dist/contract-extraction.d.ts.map +1 -1
- package/dist/contract-extraction.js +266 -22
- package/dist/contract-extraction.js.map +1 -1
- package/dist/extractor-static.d.ts +12 -0
- package/dist/extractor-static.d.ts.map +1 -1
- package/dist/extractor-static.js +14 -1
- package/dist/extractor-static.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.js +10 -9
- package/dist/scanner.js.map +1 -1
- package/dist/template-sentinel.d.ts +93 -0
- package/dist/template-sentinel.d.ts.map +1 -0
- package/dist/template-sentinel.js +149 -0
- package/dist/template-sentinel.js.map +1 -0
- package/package.json +1 -1
|
@@ -23,6 +23,18 @@ export type CaseSeverity = "critical" | "warning" | "info";
|
|
|
23
23
|
export type CaseRequires = "headless" | "browser" | "out-of-band";
|
|
24
24
|
/** Case default run policy. */
|
|
25
25
|
export type CaseDefaultRun = "always" | "opt-in";
|
|
26
|
+
/** Projectable description of opaque verify() semantics. */
|
|
27
|
+
export type VerifyRule = string | {
|
|
28
|
+
id?: string;
|
|
29
|
+
description: string;
|
|
30
|
+
severity?: CaseSeverity;
|
|
31
|
+
extensions?: Record<string, unknown>;
|
|
32
|
+
};
|
|
33
|
+
/** Runnability gates mirrored from the SDK's CaseRunnability. */
|
|
34
|
+
export interface NormalizedRunnability {
|
|
35
|
+
requireAttachment?: boolean;
|
|
36
|
+
requireSession?: boolean;
|
|
37
|
+
}
|
|
26
38
|
/** A named example entry for OpenAPI docs. */
|
|
27
39
|
export interface NormalizedExample {
|
|
28
40
|
value: unknown;
|
|
@@ -56,6 +68,35 @@ export interface NormalizedCaseMeta {
|
|
|
56
68
|
schemas?: unknown;
|
|
57
69
|
/** Adapter-defined free-form meta (opaque). */
|
|
58
70
|
meta?: unknown;
|
|
71
|
+
/**
|
|
72
|
+
* World-state precondition — attachment-model §0.9. Adapter threads
|
|
73
|
+
* through from `BaseCaseSpec.given`. Not a contract semantic input,
|
|
74
|
+
* but projected because it changes what `expect` means.
|
|
75
|
+
*/
|
|
76
|
+
given?: string;
|
|
77
|
+
/** True when executable semantics also live in an adapter verify() callback. */
|
|
78
|
+
hasVerify?: boolean;
|
|
79
|
+
/** Projectable companion rules for opaque verify() callbacks. */
|
|
80
|
+
verifyRules?: VerifyRule[];
|
|
81
|
+
/**
|
|
82
|
+
* Runnability metadata — attachment-model §7.2. `requireAttachment`
|
|
83
|
+
* blocks raw execution (case MUST run via a bootstrap overlay). Lives
|
|
84
|
+
* as a first-class field, not in `extensions`, per proposal.
|
|
85
|
+
*/
|
|
86
|
+
runnability?: NormalizedRunnability;
|
|
87
|
+
/**
|
|
88
|
+
* True iff the case declared `needs`. Authoritative trigger for
|
|
89
|
+
* `rawBypass` in the attachment inventory. Decoupled from `needsSchema`:
|
|
90
|
+
* a case can have needs whose schema isn't projectable.
|
|
91
|
+
*/
|
|
92
|
+
hasNeeds?: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* v10 attachment-model — JSON-safe `needs` schema. May be undefined
|
|
95
|
+
* even when `hasNeeds` is true (e.g. opaque custom validator). Use
|
|
96
|
+
* `hasNeeds` for inventory/rawBypass decisions; this field is a
|
|
97
|
+
* decoration for consumers that want the schema shape when available.
|
|
98
|
+
*/
|
|
99
|
+
needsSchema?: unknown;
|
|
59
100
|
}
|
|
60
101
|
/**
|
|
61
102
|
* Protocol-agnostic contract metadata. Mirrors `ExtractedContractProjection`.
|
|
@@ -123,15 +164,98 @@ export interface NormalizedFlowMeta {
|
|
|
123
164
|
setupDynamic?: true;
|
|
124
165
|
steps: NormalizedFlowStep[];
|
|
125
166
|
}
|
|
126
|
-
/**
|
|
167
|
+
/**
|
|
168
|
+
* Default execution mode for a contract case — runs the case directly with
|
|
169
|
+
* an explicit input (or void when no needs declared). Lives in
|
|
170
|
+
* `attachments[]` until a bootstrap overlay replaces it.
|
|
171
|
+
*/
|
|
172
|
+
export interface NormalizedRawAttachment {
|
|
173
|
+
kind: "raw";
|
|
174
|
+
/** Contract case test id (`${contractId}.${caseKey}`). */
|
|
175
|
+
testId: string;
|
|
176
|
+
contractId: string;
|
|
177
|
+
caseKey: string;
|
|
178
|
+
/** Export name of the contract this case belongs to. */
|
|
179
|
+
exportName: string;
|
|
180
|
+
/** Runnability gates declared by the case. */
|
|
181
|
+
runnability?: NormalizedRunnability;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Bootstrap overlay attachment — `contract.bootstrap(ref, spec)` registers
|
|
185
|
+
* a setup-and-cleanup that wraps the case. Replaces the raw entry for the
|
|
186
|
+
* target testId. Body of `run`/`spec` is opaque per attachment-model §4.2.
|
|
187
|
+
*
|
|
188
|
+
* `paramsSchema` is undefined in v0 — structured-form param schema
|
|
189
|
+
* extraction needs the bootstrap registry, deferred to Spike 3.
|
|
190
|
+
*
|
|
191
|
+
* `rawBypass` exposes the explicit-input execution path (§5.1) so the
|
|
192
|
+
* inventory is the single authoritative source for both modes per testId.
|
|
193
|
+
*/
|
|
194
|
+
export interface NormalizedBootstrapOverlayAttachment {
|
|
195
|
+
kind: "bootstrap-overlay";
|
|
196
|
+
testId: string;
|
|
197
|
+
/** The overlay's own export name (NOT the contract's). */
|
|
198
|
+
exportName: string;
|
|
199
|
+
targetRef: {
|
|
200
|
+
contractId: string;
|
|
201
|
+
caseKey: string;
|
|
202
|
+
};
|
|
203
|
+
bootstrap: {
|
|
204
|
+
/** v0: undefined; populated when structured-form `params` schema lands. */
|
|
205
|
+
paramsSchema?: unknown;
|
|
206
|
+
};
|
|
207
|
+
/** Present iff the target case has `needs` (explicit-input bypass available). */
|
|
208
|
+
rawBypass?: {
|
|
209
|
+
available: true;
|
|
210
|
+
/** JSON-safe form of the case's `needs` schema (may be undefined when SDK can't convert). */
|
|
211
|
+
needsSchema: unknown;
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Flow attachment — orchestrates a sequence of contract calls under a
|
|
216
|
+
* single test id. Distinct from raw / overlay (no underlying case).
|
|
217
|
+
*/
|
|
218
|
+
export interface NormalizedFlowAttachment {
|
|
219
|
+
kind: "flow";
|
|
220
|
+
/** Flow test id (`flowId`). */
|
|
221
|
+
testId: string;
|
|
222
|
+
exportName: string;
|
|
223
|
+
flow: NormalizedFlowMeta;
|
|
224
|
+
}
|
|
225
|
+
/** Discriminated attachment entry. One per testId in the inventory. */
|
|
226
|
+
export type NormalizedAttachmentMeta = NormalizedRawAttachment | NormalizedBootstrapOverlayAttachment | NormalizedFlowAttachment;
|
|
227
|
+
/** Result of extracting contracts/attachments from one or more files. */
|
|
127
228
|
export interface ExtractionResult {
|
|
128
229
|
contracts: NormalizedContractMeta[];
|
|
129
|
-
|
|
230
|
+
/**
|
|
231
|
+
* Attachment inventory per attachment-model §7.3. Always present
|
|
232
|
+
* (may be empty). Replaces the previous `flows?: NormalizedFlowMeta[]`
|
|
233
|
+
* top-level field — flows now appear here as `kind: "flow"` entries.
|
|
234
|
+
*/
|
|
235
|
+
attachments: NormalizedAttachmentMeta[];
|
|
130
236
|
errors: Array<{
|
|
131
237
|
file: string;
|
|
132
238
|
error: string;
|
|
133
239
|
}>;
|
|
134
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* Check if a value looks like a BootstrapAttachment (v10 attachment model).
|
|
243
|
+
*
|
|
244
|
+
* BootstrapAttachment is the runtime marker returned by `contract.bootstrap()`.
|
|
245
|
+
* Carries `__glubean_type: "bootstrap-attachment"` and `testId`
|
|
246
|
+
* (= `${contractId}.${caseKey}`). Scanner identifies attachments via this
|
|
247
|
+
* marker without importing @glubean/sdk types.
|
|
248
|
+
*
|
|
249
|
+
* v0 attachment projection only surfaces metadata (kind, testId,
|
|
250
|
+
* contractId, caseKey). Bootstrap params schema (structured-form `params`)
|
|
251
|
+
* is not yet exposed on the export object — extracting it requires reading
|
|
252
|
+
* the bootstrap registry, deferred to Spike 3 when the runner input
|
|
253
|
+
* channel ships.
|
|
254
|
+
*/
|
|
255
|
+
export declare function isBootstrapAttachment(val: unknown): val is {
|
|
256
|
+
__glubean_type: "bootstrap-attachment";
|
|
257
|
+
testId: string;
|
|
258
|
+
};
|
|
135
259
|
/**
|
|
136
260
|
* Check if a value looks like a ProtocolContract.
|
|
137
261
|
* ProtocolContract extends Array<Test> and has `_projection` with protocol + target.
|
|
@@ -204,14 +328,87 @@ export declare function protocolContractToNormalized(value: {
|
|
|
204
328
|
_extracted: any;
|
|
205
329
|
}, exportName: string): NormalizedContractMeta;
|
|
206
330
|
/**
|
|
207
|
-
*
|
|
331
|
+
* Bootstrap overlay marker collected during file walk — pre-synthesis raw
|
|
332
|
+
* material. Not yet a final attachment entry; `synthesizeAttachments`
|
|
333
|
+
* decides whether to keep it (replaces a raw entry) or report duplicate.
|
|
334
|
+
*/
|
|
335
|
+
export interface BootstrapOverlayMarker {
|
|
336
|
+
exportName: string;
|
|
337
|
+
testId: string;
|
|
338
|
+
contractId: string;
|
|
339
|
+
caseKey: string;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Convert a BootstrapAttachment runtime export to a marker. Splits
|
|
343
|
+
* `testId` into contractId + caseKey at the LAST dot (contractId can have
|
|
344
|
+
* dots, e.g. `v2.orders.create.success`). Returns null for malformed shapes
|
|
345
|
+
* (no dot, leading dot, trailing dot) — caller skips them.
|
|
346
|
+
*/
|
|
347
|
+
export declare function bootstrapAttachmentToNormalized(attachment: {
|
|
348
|
+
testId: string;
|
|
349
|
+
}, exportName: string): BootstrapOverlayMarker | null;
|
|
350
|
+
/**
|
|
351
|
+
* Synthesize the §7.3 attachment inventory from raw materials.
|
|
352
|
+
*
|
|
353
|
+
* Algorithm (per attachment-model §7.3):
|
|
354
|
+
* 1. Seed: each contract case → `kind: "raw"` entry (testId = `${id}.${key}`).
|
|
355
|
+
* 2. Apply overlays: each marker → REPLACE the raw entry with the same
|
|
356
|
+
* testId by a `kind: "bootstrap-overlay"` entry. Carry `rawBypass`
|
|
357
|
+
* when the target case has `needsSchema` (explicit-input bypass
|
|
358
|
+
* available per §5.1).
|
|
359
|
+
* 3. Duplicate detection: two markers with the same testId → push a
|
|
360
|
+
* load-time error. Keep the first; subsequent overlays ignored.
|
|
361
|
+
* 4. Append flows as `kind: "flow"` entries.
|
|
362
|
+
*
|
|
363
|
+
* Overlays whose testId doesn't match any contract case stand alone —
|
|
364
|
+
* we still emit them as `bootstrap-overlay` (no `rawBypass`, no targetRef
|
|
365
|
+
* verification). This keeps cross-file overlay registration discoverable
|
|
366
|
+
* even when the contract module hasn't been scanned yet.
|
|
367
|
+
*/
|
|
368
|
+
export declare function synthesizeAttachments(contracts: NormalizedContractMeta[], flows: NormalizedFlowMeta[], markers: BootstrapOverlayMarker[]): {
|
|
369
|
+
attachments: NormalizedAttachmentMeta[];
|
|
370
|
+
errors: ExtractionResult["errors"];
|
|
371
|
+
};
|
|
372
|
+
/**
|
|
373
|
+
* Extract contracts + attachments from a single file by dynamic import.
|
|
208
374
|
* One file's import failure does not block others.
|
|
375
|
+
*
|
|
376
|
+
* Per-file synthesis: only this file's contracts/flows/markers are seen.
|
|
377
|
+
* Cross-file overlay replacement (overlay in fileA targets case in fileB)
|
|
378
|
+
* only resolves at project level via `extractContractsFromProject`.
|
|
209
379
|
*/
|
|
210
380
|
export declare function extractContractFromFile(filePath: string): Promise<ExtractionResult>;
|
|
211
381
|
/**
|
|
212
|
-
* Extract contracts +
|
|
382
|
+
* Extract contracts + attachments from all recognized files in a project.
|
|
383
|
+
*
|
|
384
|
+
* Project-level synthesis: collects raw materials (contracts, flows,
|
|
385
|
+
* overlay markers) across all `.contract.` / `.flow.` / `.bootstrap.`
|
|
386
|
+
* files, then runs `synthesizeAttachments` once. This is what enables
|
|
387
|
+
* cross-file overlay replacement (overlay in `signup.bootstrap.ts`
|
|
388
|
+
* replacing the raw entry for a case in `signup.contract.ts`) and
|
|
389
|
+
* project-wide duplicate-overlay detection per §7.3.
|
|
213
390
|
*/
|
|
214
391
|
export declare function extractContractsFromProject(dir: string): Promise<ExtractionResult>;
|
|
392
|
+
/**
|
|
393
|
+
* Eagerly import every `*.bootstrap.{ts,js,mjs}` file under `dir` so
|
|
394
|
+
* `contract.bootstrap()` calls execute during module evaluation and
|
|
395
|
+
* register their overlays in the SDK's bootstrap registry.
|
|
396
|
+
*
|
|
397
|
+
* Idempotent: subsequent calls re-import the same module URLs; Node's
|
|
398
|
+
* ESM module cache short-circuits unless the file's mtime changed (the
|
|
399
|
+
* same cache-busting strategy `collectRawMaterials` uses).
|
|
400
|
+
*
|
|
401
|
+
* Errors: per-file failures are returned; the caller decides whether
|
|
402
|
+
* to abort or continue. We return errors instead of throwing because a
|
|
403
|
+
* single broken bootstrap file shouldn't kill an otherwise-working run.
|
|
404
|
+
*/
|
|
405
|
+
export declare function loadProjectOverlays(dir: string): Promise<{
|
|
406
|
+
loaded: string[];
|
|
407
|
+
errors: Array<{
|
|
408
|
+
file: string;
|
|
409
|
+
error: string;
|
|
410
|
+
}>;
|
|
411
|
+
}>;
|
|
215
412
|
/** @deprecated Removed in v0.2 — HTTP now goes through isProtocolContract. */
|
|
216
413
|
export declare function isHttpContract(_val: unknown): never;
|
|
217
414
|
//# sourceMappingURL=contract-extraction.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract-extraction.d.ts","sourceRoot":"","sources":["../src/contract-extraction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAUH,sBAAsB;AACtB,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,UAAU,GAAG,YAAY,CAAC;AAEjE,qBAAqB;AACrB,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,CAAC;AAE3D,kCAAkC;AAClC,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,SAAS,GAAG,aAAa,CAAC;AAElE,+BAA+B;AAC/B,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEjD,8CAA8C;AAC9C,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,iDAAiD;AACjD,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,aAAa,CAAC;IACzB,QAAQ,EAAE,YAAY,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,yDAAyD;IACzD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,+CAA+C;IAC/C,IAAI,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"contract-extraction.d.ts","sourceRoot":"","sources":["../src/contract-extraction.ts"],"names":[],"mappings":"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"}
|
|
@@ -21,6 +21,26 @@ import { readdirSync, statSync } from "node:fs";
|
|
|
21
21
|
// =============================================================================
|
|
22
22
|
// Duck typing
|
|
23
23
|
// =============================================================================
|
|
24
|
+
/**
|
|
25
|
+
* Check if a value looks like a BootstrapAttachment (v10 attachment model).
|
|
26
|
+
*
|
|
27
|
+
* BootstrapAttachment is the runtime marker returned by `contract.bootstrap()`.
|
|
28
|
+
* Carries `__glubean_type: "bootstrap-attachment"` and `testId`
|
|
29
|
+
* (= `${contractId}.${caseKey}`). Scanner identifies attachments via this
|
|
30
|
+
* marker without importing @glubean/sdk types.
|
|
31
|
+
*
|
|
32
|
+
* v0 attachment projection only surfaces metadata (kind, testId,
|
|
33
|
+
* contractId, caseKey). Bootstrap params schema (structured-form `params`)
|
|
34
|
+
* is not yet exposed on the export object — extracting it requires reading
|
|
35
|
+
* the bootstrap registry, deferred to Spike 3 when the runner input
|
|
36
|
+
* channel ships.
|
|
37
|
+
*/
|
|
38
|
+
export function isBootstrapAttachment(val) {
|
|
39
|
+
return (typeof val === "object" &&
|
|
40
|
+
val !== null &&
|
|
41
|
+
val.__glubean_type === "bootstrap-attachment" &&
|
|
42
|
+
typeof val.testId === "string");
|
|
43
|
+
}
|
|
24
44
|
/**
|
|
25
45
|
* Check if a value looks like a ProtocolContract.
|
|
26
46
|
* ProtocolContract extends Array<Test> and has `_projection` with protocol + target.
|
|
@@ -90,6 +110,12 @@ export function protocolContractToNormalized(value, exportName) {
|
|
|
90
110
|
extensions: c.extensions,
|
|
91
111
|
schemas: c.schemas,
|
|
92
112
|
meta: c.meta,
|
|
113
|
+
given: c.given,
|
|
114
|
+
hasVerify: c.hasVerify,
|
|
115
|
+
verifyRules: c.verifyRules,
|
|
116
|
+
runnability: c.runnability,
|
|
117
|
+
hasNeeds: c.hasNeeds,
|
|
118
|
+
needsSchema: c.needsSchema,
|
|
93
119
|
})),
|
|
94
120
|
};
|
|
95
121
|
}
|
|
@@ -180,20 +206,142 @@ function autoBuildFlowBuilder(val) {
|
|
|
180
206
|
}
|
|
181
207
|
return val;
|
|
182
208
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
209
|
+
/**
|
|
210
|
+
* Convert a BootstrapAttachment runtime export to a marker. Splits
|
|
211
|
+
* `testId` into contractId + caseKey at the LAST dot (contractId can have
|
|
212
|
+
* dots, e.g. `v2.orders.create.success`). Returns null for malformed shapes
|
|
213
|
+
* (no dot, leading dot, trailing dot) — caller skips them.
|
|
214
|
+
*/
|
|
215
|
+
export function bootstrapAttachmentToNormalized(attachment, exportName) {
|
|
216
|
+
const dotIndex = attachment.testId.lastIndexOf(".");
|
|
217
|
+
if (dotIndex <= 0 || dotIndex === attachment.testId.length - 1) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
const contractId = attachment.testId.slice(0, dotIndex);
|
|
221
|
+
const caseKey = attachment.testId.slice(dotIndex + 1);
|
|
222
|
+
return {
|
|
223
|
+
exportName,
|
|
224
|
+
testId: attachment.testId,
|
|
225
|
+
contractId,
|
|
226
|
+
caseKey,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function normalizeRunnability(runnability) {
|
|
230
|
+
if (!runnability)
|
|
231
|
+
return undefined;
|
|
232
|
+
const normalized = {};
|
|
233
|
+
if (runnability.requireAttachment !== undefined) {
|
|
234
|
+
normalized.requireAttachment = runnability.requireAttachment;
|
|
235
|
+
}
|
|
236
|
+
if (runnability.requireSession !== undefined) {
|
|
237
|
+
normalized.requireSession = runnability.requireSession;
|
|
238
|
+
}
|
|
239
|
+
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Synthesize the §7.3 attachment inventory from raw materials.
|
|
243
|
+
*
|
|
244
|
+
* Algorithm (per attachment-model §7.3):
|
|
245
|
+
* 1. Seed: each contract case → `kind: "raw"` entry (testId = `${id}.${key}`).
|
|
246
|
+
* 2. Apply overlays: each marker → REPLACE the raw entry with the same
|
|
247
|
+
* testId by a `kind: "bootstrap-overlay"` entry. Carry `rawBypass`
|
|
248
|
+
* when the target case has `needsSchema` (explicit-input bypass
|
|
249
|
+
* available per §5.1).
|
|
250
|
+
* 3. Duplicate detection: two markers with the same testId → push a
|
|
251
|
+
* load-time error. Keep the first; subsequent overlays ignored.
|
|
252
|
+
* 4. Append flows as `kind: "flow"` entries.
|
|
253
|
+
*
|
|
254
|
+
* Overlays whose testId doesn't match any contract case stand alone —
|
|
255
|
+
* we still emit them as `bootstrap-overlay` (no `rawBypass`, no targetRef
|
|
256
|
+
* verification). This keeps cross-file overlay registration discoverable
|
|
257
|
+
* even when the contract module hasn't been scanned yet.
|
|
258
|
+
*/
|
|
259
|
+
export function synthesizeAttachments(contracts, flows, markers) {
|
|
260
|
+
const errors = [];
|
|
261
|
+
// Index cases by testId for O(1) lookup during overlay application.
|
|
262
|
+
const caseByTestId = new Map();
|
|
263
|
+
for (const contract of contracts) {
|
|
264
|
+
for (const c of contract.cases) {
|
|
265
|
+
caseByTestId.set(`${contract.id}.${c.key}`, { contract, case: c });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
// Step 1: seed raw entries. Reads `runnability` directly from the case
|
|
269
|
+
// projection (post-Phase-2f-fix: the adapter now threads it as a
|
|
270
|
+
// first-class field, no longer hidden under `extensions`).
|
|
271
|
+
const byTestId = new Map();
|
|
272
|
+
for (const [testId, { contract, case: c }] of caseByTestId) {
|
|
273
|
+
const runnability = normalizeRunnability(c.runnability);
|
|
274
|
+
byTestId.set(testId, {
|
|
275
|
+
kind: "raw",
|
|
276
|
+
testId,
|
|
277
|
+
contractId: contract.id,
|
|
278
|
+
caseKey: c.key,
|
|
279
|
+
exportName: contract.exportName,
|
|
280
|
+
...(runnability ? { runnability } : {}),
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
// Step 2 + 3: apply overlays, detect duplicates.
|
|
284
|
+
const seenOverlayIds = new Set();
|
|
285
|
+
for (const marker of markers) {
|
|
286
|
+
if (seenOverlayIds.has(marker.testId)) {
|
|
287
|
+
errors.push({
|
|
288
|
+
file: marker.exportName,
|
|
289
|
+
error: `Duplicate bootstrap overlay for testId "${marker.testId}" (export "${marker.exportName}"). Per attachment-model §7.3 testId uniqueness is enforced.`,
|
|
290
|
+
});
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
seenOverlayIds.add(marker.testId);
|
|
294
|
+
// rawBypass is available iff the target case declared `needs`
|
|
295
|
+
// (hasNeeds === true). This is decoupled from needsSchema projection:
|
|
296
|
+
// a case with a custom safeParse-only validator still satisfies
|
|
297
|
+
// "explicit input can run the raw case" even when the JSON Schema
|
|
298
|
+
// projection is null. If the schema projected, we decorate the
|
|
299
|
+
// bypass slot with it; otherwise bypass is still advertised.
|
|
300
|
+
const target = caseByTestId.get(marker.testId);
|
|
301
|
+
const overlay = {
|
|
302
|
+
kind: "bootstrap-overlay",
|
|
303
|
+
testId: marker.testId,
|
|
304
|
+
exportName: marker.exportName,
|
|
305
|
+
targetRef: { contractId: marker.contractId, caseKey: marker.caseKey },
|
|
306
|
+
bootstrap: {},
|
|
307
|
+
...(target?.case.hasNeeds
|
|
308
|
+
? {
|
|
309
|
+
rawBypass: {
|
|
310
|
+
available: true,
|
|
311
|
+
needsSchema: target.case.needsSchema,
|
|
312
|
+
},
|
|
313
|
+
}
|
|
314
|
+
: {}),
|
|
315
|
+
};
|
|
316
|
+
byTestId.set(marker.testId, overlay);
|
|
317
|
+
}
|
|
318
|
+
// Step 4: append flows.
|
|
319
|
+
const attachments = Array.from(byTestId.values());
|
|
320
|
+
for (const flow of flows) {
|
|
321
|
+
attachments.push({
|
|
322
|
+
kind: "flow",
|
|
323
|
+
testId: flow.id,
|
|
324
|
+
exportName: flow.exportName,
|
|
325
|
+
flow,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
return { attachments, errors };
|
|
329
|
+
}
|
|
330
|
+
// Per-path mtime cache used by collectRawMaterials() to decide when to
|
|
331
|
+
// bust Node's ESM module cache on re-import. See the comment inside that
|
|
332
|
+
// function for the full reasoning.
|
|
189
333
|
const _importMtimeCache = new Map();
|
|
190
334
|
/**
|
|
191
|
-
*
|
|
192
|
-
*
|
|
335
|
+
* Internal: dynamically import a file and collect raw materials
|
|
336
|
+
* (contracts / flows / overlay markers / errors) without synthesis.
|
|
337
|
+
* Both `extractContractFromFile` and `extractContractsFromProject` use
|
|
338
|
+
* this — the former synthesizes per-file, the latter synthesizes
|
|
339
|
+
* project-wide so cross-file overlay replacement and dedup work.
|
|
193
340
|
*/
|
|
194
|
-
|
|
341
|
+
async function collectRawMaterials(filePath) {
|
|
195
342
|
const contracts = [];
|
|
196
343
|
const flows = [];
|
|
344
|
+
const markers = [];
|
|
197
345
|
const errors = [];
|
|
198
346
|
const absolutePath = resolve(filePath);
|
|
199
347
|
try {
|
|
@@ -242,19 +390,47 @@ export async function extractContractFromFile(filePath) {
|
|
|
242
390
|
else if (isProtocolContract(value)) {
|
|
243
391
|
contracts.push(protocolContractToNormalized(value, exportName));
|
|
244
392
|
}
|
|
393
|
+
else if (isBootstrapAttachment(value)) {
|
|
394
|
+
const marker = bootstrapAttachmentToNormalized(value, exportName);
|
|
395
|
+
if (marker)
|
|
396
|
+
markers.push(marker);
|
|
397
|
+
}
|
|
245
398
|
}
|
|
246
399
|
}
|
|
247
400
|
catch (err) {
|
|
248
401
|
const message = err instanceof Error ? err.message : String(err);
|
|
249
402
|
errors.push({ file: absolutePath, error: message });
|
|
250
403
|
}
|
|
251
|
-
return { contracts, flows
|
|
404
|
+
return { contracts, flows, markers, errors };
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Extract contracts + attachments from a single file by dynamic import.
|
|
408
|
+
* One file's import failure does not block others.
|
|
409
|
+
*
|
|
410
|
+
* Per-file synthesis: only this file's contracts/flows/markers are seen.
|
|
411
|
+
* Cross-file overlay replacement (overlay in fileA targets case in fileB)
|
|
412
|
+
* only resolves at project level via `extractContractsFromProject`.
|
|
413
|
+
*/
|
|
414
|
+
export async function extractContractFromFile(filePath) {
|
|
415
|
+
const raw = await collectRawMaterials(filePath);
|
|
416
|
+
const synth = synthesizeAttachments(raw.contracts, raw.flows, raw.markers);
|
|
417
|
+
return {
|
|
418
|
+
contracts: raw.contracts,
|
|
419
|
+
attachments: synth.attachments,
|
|
420
|
+
errors: [...raw.errors, ...synth.errors],
|
|
421
|
+
};
|
|
252
422
|
}
|
|
253
423
|
// =============================================================================
|
|
254
424
|
// Project-level extraction
|
|
255
425
|
// =============================================================================
|
|
256
426
|
/**
|
|
257
|
-
* Find all .contract.{ts,js,mjs}
|
|
427
|
+
* Find all .contract.{ts,js,mjs}, .flow.{ts,js,mjs}, and
|
|
428
|
+
* .bootstrap.{ts,js,mjs} files in a directory tree.
|
|
429
|
+
*
|
|
430
|
+
* v10 attachment model §7.4 mandates eager loading of `.bootstrap.` files
|
|
431
|
+
* so overlay registrations fire during module evaluation. Without this,
|
|
432
|
+
* filtered runs (CLI / MCP) could miss overlay registrations and silently
|
|
433
|
+
* fall through to the no-overlay path.
|
|
258
434
|
*/
|
|
259
435
|
function findContractAndFlowFiles(dir) {
|
|
260
436
|
const files = [];
|
|
@@ -267,7 +443,9 @@ function findContractAndFlowFiles(dir) {
|
|
|
267
443
|
walk(full);
|
|
268
444
|
else {
|
|
269
445
|
const base = basename(entry);
|
|
270
|
-
if (base.includes(".contract.") ||
|
|
446
|
+
if (base.includes(".contract.") ||
|
|
447
|
+
base.includes(".flow.") ||
|
|
448
|
+
base.includes(".bootstrap.")) {
|
|
271
449
|
files.push(full);
|
|
272
450
|
}
|
|
273
451
|
}
|
|
@@ -277,29 +455,95 @@ function findContractAndFlowFiles(dir) {
|
|
|
277
455
|
return files;
|
|
278
456
|
}
|
|
279
457
|
/**
|
|
280
|
-
* Extract contracts +
|
|
458
|
+
* Extract contracts + attachments from all recognized files in a project.
|
|
459
|
+
*
|
|
460
|
+
* Project-level synthesis: collects raw materials (contracts, flows,
|
|
461
|
+
* overlay markers) across all `.contract.` / `.flow.` / `.bootstrap.`
|
|
462
|
+
* files, then runs `synthesizeAttachments` once. This is what enables
|
|
463
|
+
* cross-file overlay replacement (overlay in `signup.bootstrap.ts`
|
|
464
|
+
* replacing the raw entry for a case in `signup.contract.ts`) and
|
|
465
|
+
* project-wide duplicate-overlay detection per §7.3.
|
|
281
466
|
*/
|
|
282
467
|
export async function extractContractsFromProject(dir) {
|
|
283
468
|
const files = findContractAndFlowFiles(dir);
|
|
284
|
-
if (files.length === 0)
|
|
285
|
-
return { contracts: [], errors: [] };
|
|
469
|
+
if (files.length === 0) {
|
|
470
|
+
return { contracts: [], attachments: [], errors: [] };
|
|
471
|
+
}
|
|
286
472
|
const allContracts = [];
|
|
287
473
|
const allFlows = [];
|
|
474
|
+
const allMarkers = [];
|
|
288
475
|
const allErrors = [];
|
|
289
476
|
for (const filePath of files) {
|
|
290
|
-
const
|
|
291
|
-
allContracts.push(...contracts);
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
allErrors.push(...errors);
|
|
477
|
+
const raw = await collectRawMaterials(filePath);
|
|
478
|
+
allContracts.push(...raw.contracts);
|
|
479
|
+
allFlows.push(...raw.flows);
|
|
480
|
+
allMarkers.push(...raw.markers);
|
|
481
|
+
allErrors.push(...raw.errors);
|
|
295
482
|
}
|
|
483
|
+
const synth = synthesizeAttachments(allContracts, allFlows, allMarkers);
|
|
296
484
|
return {
|
|
297
485
|
contracts: allContracts,
|
|
298
|
-
|
|
299
|
-
errors: allErrors,
|
|
486
|
+
attachments: synth.attachments,
|
|
487
|
+
errors: [...allErrors, ...synth.errors],
|
|
300
488
|
};
|
|
301
489
|
}
|
|
302
490
|
// =============================================================================
|
|
491
|
+
// Eager overlay loading — attachment-model §7.4
|
|
492
|
+
//
|
|
493
|
+
// "Runner MUST eagerly load all *.contract.ts and *.bootstrap.ts files in
|
|
494
|
+
// the contracts root before resolving attachments. Overlay registration is
|
|
495
|
+
// a semantic input to runnable inventory; making it depend on import path
|
|
496
|
+
// leads to nondeterministic runs."
|
|
497
|
+
//
|
|
498
|
+
// CLI/MCP/ProjectRunner call this before any test runs so a filtered run
|
|
499
|
+
// (e.g. `glubean run path/to/single.contract.ts`) still picks up sibling
|
|
500
|
+
// `*.bootstrap.ts` overlay registrations.
|
|
501
|
+
// =============================================================================
|
|
502
|
+
/**
|
|
503
|
+
* Eagerly import every `*.bootstrap.{ts,js,mjs}` file under `dir` so
|
|
504
|
+
* `contract.bootstrap()` calls execute during module evaluation and
|
|
505
|
+
* register their overlays in the SDK's bootstrap registry.
|
|
506
|
+
*
|
|
507
|
+
* Idempotent: subsequent calls re-import the same module URLs; Node's
|
|
508
|
+
* ESM module cache short-circuits unless the file's mtime changed (the
|
|
509
|
+
* same cache-busting strategy `collectRawMaterials` uses).
|
|
510
|
+
*
|
|
511
|
+
* Errors: per-file failures are returned; the caller decides whether
|
|
512
|
+
* to abort or continue. We return errors instead of throwing because a
|
|
513
|
+
* single broken bootstrap file shouldn't kill an otherwise-working run.
|
|
514
|
+
*/
|
|
515
|
+
export async function loadProjectOverlays(dir) {
|
|
516
|
+
const allFiles = findContractAndFlowFiles(dir);
|
|
517
|
+
const bootstrapFiles = allFiles.filter((f) => basename(f).includes(".bootstrap."));
|
|
518
|
+
const loaded = [];
|
|
519
|
+
const errors = [];
|
|
520
|
+
for (const filePath of bootstrapFiles) {
|
|
521
|
+
const absolutePath = resolve(filePath);
|
|
522
|
+
try {
|
|
523
|
+
let mtimeKey = 0;
|
|
524
|
+
try {
|
|
525
|
+
mtimeKey = statSync(absolutePath).mtimeMs;
|
|
526
|
+
}
|
|
527
|
+
catch {
|
|
528
|
+
// fall through with mtimeKey=0
|
|
529
|
+
}
|
|
530
|
+
const baseUrl = pathToFileURL(absolutePath).href;
|
|
531
|
+
const lastSeen = _importMtimeCache.get(absolutePath);
|
|
532
|
+
const importUrl = lastSeen === undefined || lastSeen === mtimeKey
|
|
533
|
+
? baseUrl
|
|
534
|
+
: `${baseUrl}?t=${mtimeKey}`;
|
|
535
|
+
_importMtimeCache.set(absolutePath, mtimeKey);
|
|
536
|
+
await import(importUrl);
|
|
537
|
+
loaded.push(absolutePath);
|
|
538
|
+
}
|
|
539
|
+
catch (err) {
|
|
540
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
541
|
+
errors.push({ file: absolutePath, error: message });
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return { loaded, errors };
|
|
545
|
+
}
|
|
546
|
+
// =============================================================================
|
|
303
547
|
// Backward-compat: isHttpContract — removed permanently in v0.2.
|
|
304
548
|
// =============================================================================
|
|
305
549
|
/** @deprecated Removed in v0.2 — HTTP now goes through isProtocolContract. */
|