@gate-forge/pack-playwright 0.1.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.
Files changed (140) hide show
  1. package/LICENSE +202 -0
  2. package/bin/gateforge-witness.js +40 -0
  3. package/dist/attestation/proxy.d.ts +40 -0
  4. package/dist/attestation/proxy.d.ts.map +1 -0
  5. package/dist/attestation/proxy.js +78 -0
  6. package/dist/attestation/proxy.js.map +1 -0
  7. package/dist/constants.d.ts +130 -0
  8. package/dist/constants.d.ts.map +1 -0
  9. package/dist/constants.js +138 -0
  10. package/dist/constants.js.map +1 -0
  11. package/dist/discovery/adapters.d.ts +167 -0
  12. package/dist/discovery/adapters.d.ts.map +1 -0
  13. package/dist/discovery/adapters.js +276 -0
  14. package/dist/discovery/adapters.js.map +1 -0
  15. package/dist/discovery/discover.d.ts +57 -0
  16. package/dist/discovery/discover.d.ts.map +1 -0
  17. package/dist/discovery/discover.js +544 -0
  18. package/dist/discovery/discover.js.map +1 -0
  19. package/dist/discovery/index.d.ts +15 -0
  20. package/dist/discovery/index.d.ts.map +1 -0
  21. package/dist/discovery/index.js +15 -0
  22. package/dist/discovery/index.js.map +1 -0
  23. package/dist/discovery/inference.d.ts +63 -0
  24. package/dist/discovery/inference.d.ts.map +1 -0
  25. package/dist/discovery/inference.js +173 -0
  26. package/dist/discovery/inference.js.map +1 -0
  27. package/dist/discovery/pytest-adapter.d.ts +205 -0
  28. package/dist/discovery/pytest-adapter.d.ts.map +1 -0
  29. package/dist/discovery/pytest-adapter.js +548 -0
  30. package/dist/discovery/pytest-adapter.js.map +1 -0
  31. package/dist/discovery/reconcile.d.ts +106 -0
  32. package/dist/discovery/reconcile.d.ts.map +1 -0
  33. package/dist/discovery/reconcile.js +268 -0
  34. package/dist/discovery/reconcile.js.map +1 -0
  35. package/dist/discovery/runner-env.d.ts +80 -0
  36. package/dist/discovery/runner-env.d.ts.map +1 -0
  37. package/dist/discovery/runner-env.js +139 -0
  38. package/dist/discovery/runner-env.js.map +1 -0
  39. package/dist/discovery/static-discovery.d.ts +121 -0
  40. package/dist/discovery/static-discovery.d.ts.map +1 -0
  41. package/dist/discovery/static-discovery.js +1028 -0
  42. package/dist/discovery/static-discovery.js.map +1 -0
  43. package/dist/discovery/supervised-run.d.ts +110 -0
  44. package/dist/discovery/supervised-run.d.ts.map +1 -0
  45. package/dist/discovery/supervised-run.js +328 -0
  46. package/dist/discovery/supervised-run.js.map +1 -0
  47. package/dist/discovery/trusted-config.d.ts +103 -0
  48. package/dist/discovery/trusted-config.d.ts.map +1 -0
  49. package/dist/discovery/trusted-config.js +117 -0
  50. package/dist/discovery/trusted-config.js.map +1 -0
  51. package/dist/fixture/evidence.d.ts +191 -0
  52. package/dist/fixture/evidence.d.ts.map +1 -0
  53. package/dist/fixture/evidence.js +366 -0
  54. package/dist/fixture/evidence.js.map +1 -0
  55. package/dist/fixture/fixture.d.ts +37 -0
  56. package/dist/fixture/fixture.d.ts.map +1 -0
  57. package/dist/fixture/fixture.js +38 -0
  58. package/dist/fixture/fixture.js.map +1 -0
  59. package/dist/fixture/witness-client.d.ts +164 -0
  60. package/dist/fixture/witness-client.d.ts.map +1 -0
  61. package/dist/fixture/witness-client.js +253 -0
  62. package/dist/fixture/witness-client.js.map +1 -0
  63. package/dist/index.d.ts +64 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +55 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/json.d.ts +16 -0
  68. package/dist/json.d.ts.map +1 -0
  69. package/dist/json.js +21 -0
  70. package/dist/json.js.map +1 -0
  71. package/dist/reporter/ledger.d.ts +105 -0
  72. package/dist/reporter/ledger.d.ts.map +1 -0
  73. package/dist/reporter/ledger.js +197 -0
  74. package/dist/reporter/ledger.js.map +1 -0
  75. package/dist/reporter/reporter.cjs +105 -0
  76. package/dist/reporter/reporter.cjs.map +1 -0
  77. package/dist/reporter/reporter.d.cts +35 -0
  78. package/dist/reporter/reporter.d.cts.map +1 -0
  79. package/dist/reporter/reporter.d.ts +167 -0
  80. package/dist/reporter/reporter.d.ts.map +1 -0
  81. package/dist/reporter/reporter.js +673 -0
  82. package/dist/reporter/reporter.js.map +1 -0
  83. package/dist/setup.d.ts +49 -0
  84. package/dist/setup.d.ts.map +1 -0
  85. package/dist/setup.js +167 -0
  86. package/dist/setup.js.map +1 -0
  87. package/dist/supervisor/client.d.ts +73 -0
  88. package/dist/supervisor/client.d.ts.map +1 -0
  89. package/dist/supervisor/client.js +164 -0
  90. package/dist/supervisor/client.js.map +1 -0
  91. package/dist/supervisor/drain.d.ts +45 -0
  92. package/dist/supervisor/drain.d.ts.map +1 -0
  93. package/dist/supervisor/drain.js +235 -0
  94. package/dist/supervisor/drain.js.map +1 -0
  95. package/dist/supervisor/index.d.ts +16 -0
  96. package/dist/supervisor/index.d.ts.map +1 -0
  97. package/dist/supervisor/index.js +13 -0
  98. package/dist/supervisor/index.js.map +1 -0
  99. package/dist/supervisor/spool.d.ts +137 -0
  100. package/dist/supervisor/spool.d.ts.map +1 -0
  101. package/dist/supervisor/spool.js +263 -0
  102. package/dist/supervisor/spool.js.map +1 -0
  103. package/dist/surface.d.ts +134 -0
  104. package/dist/surface.d.ts.map +1 -0
  105. package/dist/surface.js +139 -0
  106. package/dist/surface.js.map +1 -0
  107. package/dist/witness/adapter-registry.d.ts +38 -0
  108. package/dist/witness/adapter-registry.d.ts.map +1 -0
  109. package/dist/witness/adapter-registry.js +158 -0
  110. package/dist/witness/adapter-registry.js.map +1 -0
  111. package/dist/witness/bin.d.ts +21 -0
  112. package/dist/witness/bin.d.ts.map +1 -0
  113. package/dist/witness/bin.js +172 -0
  114. package/dist/witness/bin.js.map +1 -0
  115. package/dist/witness/browser.d.ts +173 -0
  116. package/dist/witness/browser.d.ts.map +1 -0
  117. package/dist/witness/browser.js +580 -0
  118. package/dist/witness/browser.js.map +1 -0
  119. package/dist/witness/classifications.d.ts +42 -0
  120. package/dist/witness/classifications.d.ts.map +1 -0
  121. package/dist/witness/classifications.js +87 -0
  122. package/dist/witness/classifications.js.map +1 -0
  123. package/dist/witness/env-attestation.d.ts +98 -0
  124. package/dist/witness/env-attestation.d.ts.map +1 -0
  125. package/dist/witness/env-attestation.js +202 -0
  126. package/dist/witness/env-attestation.js.map +1 -0
  127. package/dist/witness/loopback-pins.d.ts +79 -0
  128. package/dist/witness/loopback-pins.d.ts.map +1 -0
  129. package/dist/witness/loopback-pins.js +244 -0
  130. package/dist/witness/loopback-pins.js.map +1 -0
  131. package/dist/witness/server.d.ts +34 -0
  132. package/dist/witness/server.d.ts.map +1 -0
  133. package/dist/witness/server.js +2539 -0
  134. package/dist/witness/server.js.map +1 -0
  135. package/dist/witness/types.d.ts +643 -0
  136. package/dist/witness/types.d.ts.map +1 -0
  137. package/dist/witness/types.js +2 -0
  138. package/dist/witness/types.js.map +1 -0
  139. package/package.json +64 -0
  140. package/python/gateforge_persistence_intents.py +100 -0
@@ -0,0 +1,263 @@
1
+ /**
2
+ * The runner-side lifecycle SPOOL (enforcement-review fix 3): the
3
+ * untrusted runner child cannot call the witness's supervisor surface,
4
+ * so the gateforge reporter instead writes test lifecycle events
5
+ * (testBegin / testEnd with the observed outcome) as NUL-safe JSON lines
6
+ * into the run-state spool. The TRUSTED CLI drains this spool while the
7
+ * suite runs and performs the witness's supervisor calls (session
8
+ * open/close) with credentials (run token + verifier key) that exist
9
+ * ONLY in the CLI process. The spool file itself carries no secrets —
10
+ * test identities and outcomes only.
11
+ *
12
+ * Protocol: one JSON object per line, canonical JSON (control characters
13
+ * — NUL included — are escaped, so the format is NUL-safe), terminated
14
+ * by '\n'. Consumers only treat a line as complete once its '\n' has
15
+ * been written, which makes partial writes impossible to misparse.
16
+ */
17
+ import { mkdirSync, openSync, closeSync, writeSync, readFileSync, statSync } from 'node:fs';
18
+ import { join } from 'node:path';
19
+ import { canonicalOf } from '../json.js';
20
+ import { SPOOL_DIR_NAME, SPOOL_EVENTS_FILE, SPOOL_INTENTS_FILE } from '../constants.js';
21
+ /**
22
+ * Resolves the spool events file for one run.
23
+ *
24
+ * Args:
25
+ * stateDir: absolute run-state directory.
26
+ * runId: the run identity both sides agree on (env-carried).
27
+ *
28
+ * Returns:
29
+ * string: absolute `<stateDir>/spool/<runId>/events.jsonl` path.
30
+ */
31
+ export function spoolPathFor(stateDir, runId) {
32
+ return join(stateDir, SPOOL_DIR_NAME, runId, SPOOL_EVENTS_FILE);
33
+ }
34
+ /**
35
+ * Appends one lifecycle event to the run's spool (runner-side; the
36
+ * reporter calls this). The event is sanitized: string fields are
37
+ * coerced/validated, unknown statuses ride as strings, and canonical
38
+ * JSON escapes every control character (NUL included) — the line is
39
+ * NUL-safe by construction. A failed append never crashes the run: the
40
+ * CLI drain then simply never opens that session and the witness
41
+ * rejects the suite's submissions fail-closed.
42
+ *
43
+ * Args:
44
+ * spoolFile: absolute spool events file (see {@link spoolPathFor}).
45
+ * event: the lifecycle event to append.
46
+ */
47
+ export function appendSpoolEvent(spoolFile, event) {
48
+ try {
49
+ const clean = {
50
+ kind: event.kind,
51
+ testId: String(event.testId),
52
+ workerIndex: Number.isInteger(event.workerIndex) && event.workerIndex >= 0 ? event.workerIndex : 0,
53
+ file: typeof event.file === 'string' ? event.file : null,
54
+ titlePath: Array.isArray(event.titlePath) ? event.titlePath.map((part) => String(part)) : [],
55
+ project: typeof event.project === 'string' ? event.project : null,
56
+ ...(event.kind === 'testEnd'
57
+ ? {
58
+ // Omitted (never null) when the runner reported no string
59
+ // outcome: `undefined` reads as "outcome unknown" end to end,
60
+ // and the drain seals the session WITHOUT an outcome — which
61
+ // the trace grades not-passed.
62
+ ...(typeof event.outcome === 'string' ? { outcome: event.outcome } : {}),
63
+ attempt: Number.isInteger(event.attempt) && event.attempt >= 1 ? event.attempt : 1,
64
+ }
65
+ : {}),
66
+ ...(Array.isArray(event.claims) && event.claims.length > 0
67
+ ? { claims: event.claims.map((claim) => String(claim)) }
68
+ : {}),
69
+ };
70
+ mkdirSync(join(spoolFile, '..'), { recursive: true });
71
+ const line = `${canonicalOf(clean)}\n`;
72
+ const fd = openSync(spoolFile, 'a');
73
+ try {
74
+ writeSync(fd, line, undefined, 'utf8');
75
+ }
76
+ finally {
77
+ closeSync(fd);
78
+ }
79
+ }
80
+ catch (error) {
81
+ console.warn(`[gateforge] cannot append to the lifecycle spool: ${error.message}`);
82
+ }
83
+ }
84
+ /**
85
+ * Reads every COMPLETE spool line past the given byte offset (CLI-side
86
+ * drain). A trailing fragment without its terminating newline is left
87
+ * for the next poll — partial writes are never parsed.
88
+ *
89
+ * Args:
90
+ * spoolFile: absolute spool events file.
91
+ * offset: byte offset to read from (0 = from the start).
92
+ *
93
+ * Returns:
94
+ * {events, nextOffset}: parsed events in file order and the offset to
95
+ * resume from (equal to `offset` when nothing complete was added).
96
+ */
97
+ export function readSpoolEvents(spoolFile, offset) {
98
+ let size;
99
+ try {
100
+ size = statSync(spoolFile).size;
101
+ }
102
+ catch {
103
+ return { events: [], nextOffset: offset };
104
+ }
105
+ if (size <= offset)
106
+ return { events: [], nextOffset: offset };
107
+ let raw;
108
+ try {
109
+ raw = readFileSync(spoolFile, 'utf8');
110
+ }
111
+ catch {
112
+ return { events: [], nextOffset: offset };
113
+ }
114
+ // Byte offsets are only tracked for complete lines; converting the
115
+ // whole file each poll is bounded by the spool's lifetime size.
116
+ const events = [];
117
+ let consumed = 0;
118
+ const lines = raw.split('\n');
119
+ for (const line of lines) {
120
+ const lineBytes = Buffer.byteLength(line, 'utf8') + 1; // + '\n'
121
+ const start = consumed;
122
+ consumed += lineBytes;
123
+ if (start < offset)
124
+ continue;
125
+ if (line.trim().length === 0)
126
+ continue;
127
+ let parsed;
128
+ try {
129
+ parsed = JSON.parse(line);
130
+ }
131
+ catch {
132
+ continue; // never crash the drain on a corrupt line
133
+ }
134
+ if (typeof parsed !== 'object' || parsed === null)
135
+ continue;
136
+ const row = parsed;
137
+ if (typeof row['kind'] !== 'string' || typeof row['testId'] !== 'string')
138
+ continue;
139
+ events.push(parsed);
140
+ }
141
+ const trailing = lines[lines.length - 1] ?? '';
142
+ const completeBytes = raw.endsWith('\n') ? size : size - Buffer.byteLength(trailing, 'utf8');
143
+ return { events, nextOffset: Math.max(offset, completeBytes) };
144
+ }
145
+ /** Resolves the intents spool file for one run (`<stateDir>/spool/<runId>/persistence-intents.jsonl`). */
146
+ export function persistenceIntentsPathFor(stateDir, runId) {
147
+ return join(stateDir, SPOOL_DIR_NAME, runId, SPOOL_INTENTS_FILE);
148
+ }
149
+ /**
150
+ * Appends one persistence intent to the run's intents spool (test-side
151
+ * helper; the CONTRACT is the JSONL line, this writer only keeps it
152
+ * canonical and NUL-safe). A failed append never crashes the test: the
153
+ * drain then simply never forwards the intent and the claim grades
154
+ * fail-closed without its witnessed record.
155
+ *
156
+ * Args:
157
+ * intentsFile: absolute intents spool file (see {@link persistenceIntentsPathFor}).
158
+ * intent: the claim intent to append.
159
+ */
160
+ export function appendPersistenceIntent(intentsFile, intent) {
161
+ try {
162
+ const clean = {
163
+ entity: String(intent.entity),
164
+ operation: intent.operation,
165
+ phase: intent.phase,
166
+ intent: intent.intent,
167
+ key: intent.key,
168
+ claimId: String(intent.claimId),
169
+ testId: String(intent.testId),
170
+ sequence: Number.isInteger(intent.sequence) && intent.sequence >= 1 ? intent.sequence : 1,
171
+ };
172
+ mkdirSync(join(intentsFile, '..'), { recursive: true });
173
+ const line = `${canonicalOf(clean)}\n`;
174
+ const fd = openSync(intentsFile, 'a');
175
+ try {
176
+ writeSync(fd, line, undefined, 'utf8');
177
+ }
178
+ finally {
179
+ closeSync(fd);
180
+ }
181
+ }
182
+ catch (error) {
183
+ console.warn(`[gateforge] cannot append to the persistence-intents spool: ${error.message}`);
184
+ }
185
+ }
186
+ /** Minimal structural validation for one drained intent line. */
187
+ function isPersistenceIntent(value) {
188
+ if (typeof value !== 'object' || value === null)
189
+ return false;
190
+ const row = value;
191
+ return (typeof row['entity'] === 'string' &&
192
+ (row['operation'] === 'create' ||
193
+ row['operation'] === 'read' ||
194
+ row['operation'] === 'update' ||
195
+ row['operation'] === 'delete') &&
196
+ (row['phase'] === 'pre' || row['phase'] === 'post') &&
197
+ (row['intent'] === 'expect-present' || row['intent'] === 'expect-absent') &&
198
+ (typeof row['key'] === 'string' ||
199
+ typeof row['key'] === 'number' ||
200
+ typeof row['key'] === 'boolean' ||
201
+ (typeof row['key'] === 'object' && row['key'] !== null && !Array.isArray(row['key']))) &&
202
+ typeof row['claimId'] === 'string' &&
203
+ typeof row['testId'] === 'string' &&
204
+ typeof row['sequence'] === 'number' &&
205
+ Number.isInteger(row['sequence']));
206
+ }
207
+ /**
208
+ * Reads every COMPLETE intent line past the given byte offset (CLI-side
209
+ * drain). Trailing fragments without their newline are left for the next
210
+ * poll; structurally invalid lines are skipped (the claim simply never
211
+ * resolves — fail closed, never crash the drain).
212
+ *
213
+ * Args:
214
+ * intentsFile: absolute intents spool file.
215
+ * offset: byte offset to read from (0 = from the start).
216
+ *
217
+ * Returns:
218
+ * {intents, nextOffset}: parsed intents in file order and the offset to
219
+ * resume from.
220
+ */
221
+ export function readPersistenceIntents(intentsFile, offset) {
222
+ let size;
223
+ try {
224
+ size = statSync(intentsFile).size;
225
+ }
226
+ catch {
227
+ return { intents: [], nextOffset: offset };
228
+ }
229
+ if (size <= offset)
230
+ return { intents: [], nextOffset: offset };
231
+ let raw;
232
+ try {
233
+ raw = readFileSync(intentsFile, 'utf8');
234
+ }
235
+ catch {
236
+ return { intents: [], nextOffset: offset };
237
+ }
238
+ const intents = [];
239
+ let consumed = 0;
240
+ const lines = raw.split('\n');
241
+ for (const line of lines) {
242
+ const lineBytes = Buffer.byteLength(line, 'utf8') + 1; // + '\n'
243
+ const start = consumed;
244
+ consumed += lineBytes;
245
+ if (start < offset)
246
+ continue;
247
+ if (line.trim().length === 0)
248
+ continue;
249
+ let parsed;
250
+ try {
251
+ parsed = JSON.parse(line);
252
+ }
253
+ catch {
254
+ continue; // never crash the drain on a corrupt line
255
+ }
256
+ if (isPersistenceIntent(parsed))
257
+ intents.push(parsed);
258
+ }
259
+ const trailing = lines[lines.length - 1] ?? '';
260
+ const completeBytes = raw.endsWith('\n') ? size : size - Buffer.byteLength(trailing, 'utf8');
261
+ return { intents, nextOffset: Math.max(offset, completeBytes) };
262
+ }
263
+ //# sourceMappingURL=spool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spool.js","sourceRoot":"","sources":["../../src/supervisor/spool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * The consumer-declared surface descriptor (plan Phase 1 item 7): the
3
+ * APPLICATION-SPECIFIC half of evidence — how the rendered UI is
4
+ * observed and driven (list page, rows and field cells, form templates,
5
+ * archive control, status values). The pack ships selectors for NO
6
+ * application; the consumer declares its own.
7
+ *
8
+ * OWNERSHIP: the descriptor's selectors are LOCATORS, never proof. The
9
+ * engine-owned browser (witness `browser.ts`) drives the rendered app
10
+ * with them and verifies navigation, the application request, the
11
+ * visible outcome, and persistence itself — a lying descriptor fails
12
+ * closed (elements never match) instead of manufacturing proof.
13
+ */
14
+ /**
15
+ * The surface-descriptor contract version this pack speaks. Consumers
16
+ * must declare `schemaVersion: SURFACE_DESCRIPTOR_VERSION` in their
17
+ * descriptor; a different version fails closed with a precise error
18
+ * instead of silently misreading selectors.
19
+ */
20
+ export declare const SURFACE_DESCRIPTOR_VERSION = 1;
21
+ /** How the rendered LIST page is observed (rows and their cells). */
22
+ export interface SurfaceList {
23
+ /** List page path relative to the app base (e.g. `/`). */
24
+ path: string;
25
+ /** Selector proving the list page rendered (e.g. the page heading). */
26
+ readySelector: string;
27
+ /** Selector matching one entity row inside the list. */
28
+ rowSelector: string;
29
+ /** Index of the row cell carrying the entity id (0-based). */
30
+ idCellIndex: number;
31
+ /** Field name → 0-based row-cell index, for reading rendered fields. */
32
+ fieldCellIndexes: Record<string, number>;
33
+ }
34
+ /** How the CREATE form is driven. */
35
+ export interface SurfaceCreate {
36
+ /** Create-form page path relative to the app base. */
37
+ formPath: string;
38
+ /** Selector proving the create form rendered. */
39
+ formReadySelector: string;
40
+ /** Field name → input selector on the create form. */
41
+ fields: Record<string, string>;
42
+ /** Submit control selector on the create form. */
43
+ submitSelector: string;
44
+ }
45
+ /**
46
+ * How the EDIT form is reached and driven. `{id}` templates are
47
+ * substituted with the entity id; the edit link is located INSIDE the
48
+ * entity's row.
49
+ */
50
+ export interface SurfaceEdit {
51
+ /** Row-scoped selector of the control navigating to the edit form. */
52
+ linkSelector: string;
53
+ /** `{id}`-templated selector proving the edit form rendered. */
54
+ formReadySelectorTemplate: string;
55
+ /** Field name → input selector on the edit form. */
56
+ fields: Record<string, string>;
57
+ /** `{id}`-templated page-scoped selector of the edit SAVE control. */
58
+ saveSelectorTemplate: string;
59
+ }
60
+ /** How the archive/delete control is located (row-scoped, `{id}`-templated). */
61
+ export interface SurfaceArchive {
62
+ /** `{id}`-templated row-scoped selector of the archive/delete control. */
63
+ controlSelectorTemplate: string;
64
+ }
65
+ /**
66
+ * Rendered-status observations the primitives assert after create and
67
+ * archive (fail-closed visible postconditions).
68
+ */
69
+ export interface SurfaceStatus {
70
+ /** Field name the status renders under (must be a row field). */
71
+ field: string;
72
+ /** Expected rendered value right after a create (e.g. `'active'`). */
73
+ createdValue?: string;
74
+ /** Expected rendered value after archive (e.g. `'archived'`). */
75
+ archivedValue?: string;
76
+ }
77
+ /**
78
+ * The declarative, consumer-owned description of the application
79
+ * surface the evidence primitives observe (plan Phase 1 item 7): the
80
+ * pack ships selectors for NO application — the consumer declares its
81
+ * own. Selector/path strings containing `{id}` are substituted with the
82
+ * entity id before use.
83
+ */
84
+ export interface SurfaceDescriptor {
85
+ /** Must equal {@link SURFACE_DESCRIPTOR_VERSION} (fail-closed otherwise). */
86
+ schemaVersion: number;
87
+ list: SurfaceList;
88
+ create: SurfaceCreate;
89
+ edit: SurfaceEdit;
90
+ archive: SurfaceArchive;
91
+ status: SurfaceStatus;
92
+ /**
93
+ * Where the browser lands after a successful mutation (the rendered
94
+ * result page); `path` is the expected `location.pathname`.
95
+ */
96
+ afterAction: {
97
+ path: string;
98
+ };
99
+ /**
100
+ * Fields recorded on a delete/archive `ui.action` record (the
101
+ * owner-declared archived state the journey drove, e.g.
102
+ * `{status: 'archived'}`).
103
+ */
104
+ deleteFields: Record<string, string>;
105
+ }
106
+ /**
107
+ * Substitutes `{id}` in a consumer-declared selector/path template with
108
+ * the entity id. Fail closed on ids that could break selector context
109
+ * (quotes/control characters) — a malformed id must error here, not
110
+ * silently match a different element.
111
+ */
112
+ export declare function renderSurfaceTemplate(template: string, entityId: string): string;
113
+ /**
114
+ * Structural validation of the consumer-declared surface descriptor
115
+ * (fail closed with the EXACT missing piece — a misdeclared surface must
116
+ * never surface later as a confusing selector timeout).
117
+ *
118
+ * Args:
119
+ * surface: the candidate descriptor.
120
+ *
121
+ * Returns:
122
+ * SurfaceDescriptor: the same descriptor, validated.
123
+ *
124
+ * Throws:
125
+ * Error: naming the first structural problem found.
126
+ */
127
+ export declare function validateSurface(surface: SurfaceDescriptor): SurfaceDescriptor;
128
+ /**
129
+ * Fills descriptor input fields exactly: every declared key must be a
130
+ * selector-known field with a string value (fail closed — the engine
131
+ * never invents form fields the consumer did not declare).
132
+ */
133
+ export declare function declaredSurfaceFields(where: string, input: Record<string, string>, known: Record<string, string>): Record<string, string>;
134
+ //# sourceMappingURL=surface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"surface.d.ts","sourceRoot":"","sources":["../src/surface.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C,qEAAqE;AACrE,MAAM,WAAW,WAAW;IAC1B,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,aAAa,EAAE,MAAM,CAAC;IACtB,wDAAwD;IACxD,WAAW,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,WAAW,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C;AAED,qCAAqC;AACrC,MAAM,WAAW,aAAa;IAC5B,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,kDAAkD;IAClD,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,sEAAsE;IACtE,YAAY,EAAE,MAAM,CAAC;IACrB,gEAAgE;IAChE,yBAAyB,EAAE,MAAM,CAAC;IAClC,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,sEAAsE;IACtE,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED,gFAAgF;AAChF,MAAM,WAAW,cAAc;IAC7B,0EAA0E;IAC1E,uBAAuB,EAAE,MAAM,CAAC;CACjC;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iEAAiE;IACjE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,6EAA6E;IAC7E,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,EAAE,aAAa,CAAC;IACtB,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,aAAa,CAAC;IACtB;;;OAGG;IACH,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9B;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQhF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,iBAAiB,GAAG,iBAAiB,CAuE7E;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC7B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAmBxB"}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * The consumer-declared surface descriptor (plan Phase 1 item 7): the
3
+ * APPLICATION-SPECIFIC half of evidence — how the rendered UI is
4
+ * observed and driven (list page, rows and field cells, form templates,
5
+ * archive control, status values). The pack ships selectors for NO
6
+ * application; the consumer declares its own.
7
+ *
8
+ * OWNERSHIP: the descriptor's selectors are LOCATORS, never proof. The
9
+ * engine-owned browser (witness `browser.ts`) drives the rendered app
10
+ * with them and verifies navigation, the application request, the
11
+ * visible outcome, and persistence itself — a lying descriptor fails
12
+ * closed (elements never match) instead of manufacturing proof.
13
+ */
14
+ /**
15
+ * The surface-descriptor contract version this pack speaks. Consumers
16
+ * must declare `schemaVersion: SURFACE_DESCRIPTOR_VERSION` in their
17
+ * descriptor; a different version fails closed with a precise error
18
+ * instead of silently misreading selectors.
19
+ */
20
+ export const SURFACE_DESCRIPTOR_VERSION = 1;
21
+ /**
22
+ * Substitutes `{id}` in a consumer-declared selector/path template with
23
+ * the entity id. Fail closed on ids that could break selector context
24
+ * (quotes/control characters) — a malformed id must error here, not
25
+ * silently match a different element.
26
+ */
27
+ export function renderSurfaceTemplate(template, entityId) {
28
+ if (/['"\r\n\\]/.test(entityId)) {
29
+ throw new Error(`surface template '${template}' cannot be rendered for entity id ${JSON.stringify(entityId)}: ` +
30
+ 'ids carrying quotes or control characters are rejected fail-closed');
31
+ }
32
+ return template.replaceAll('{id}', entityId);
33
+ }
34
+ /**
35
+ * Structural validation of the consumer-declared surface descriptor
36
+ * (fail closed with the EXACT missing piece — a misdeclared surface must
37
+ * never surface later as a confusing selector timeout).
38
+ *
39
+ * Args:
40
+ * surface: the candidate descriptor.
41
+ *
42
+ * Returns:
43
+ * SurfaceDescriptor: the same descriptor, validated.
44
+ *
45
+ * Throws:
46
+ * Error: naming the first structural problem found.
47
+ */
48
+ export function validateSurface(surface) {
49
+ const requireSelectorMap = (where, map) => {
50
+ if (typeof map !== 'object' || map === null || Array.isArray(map)) {
51
+ throw new Error(`surface.${where} must be a record of field name → selector`);
52
+ }
53
+ for (const [field, selector] of Object.entries(map)) {
54
+ if (typeof selector !== 'string' || selector.length === 0) {
55
+ throw new Error(`surface.${where}.${field} must be a non-empty selector string`);
56
+ }
57
+ }
58
+ };
59
+ const requireIndexMap = (where, map) => {
60
+ if (typeof map !== 'object' || map === null || Array.isArray(map)) {
61
+ throw new Error(`surface.${where} must be a record of field name → cell index`);
62
+ }
63
+ for (const [field, index] of Object.entries(map)) {
64
+ if (!Number.isInteger(index) || index < 0) {
65
+ throw new Error(`surface.${where}.${field} must be a non-negative integer cell index`);
66
+ }
67
+ }
68
+ };
69
+ if (typeof surface !== 'object' || surface === null || Array.isArray(surface)) {
70
+ throw new Error('surface must be a SurfaceDescriptor object declared by the consumer');
71
+ }
72
+ if (surface.schemaVersion !== SURFACE_DESCRIPTOR_VERSION) {
73
+ throw new Error(`surface.schemaVersion ${String(surface.schemaVersion)} is not supported: this pack speaks ` +
74
+ `surface-descriptor version ${String(SURFACE_DESCRIPTOR_VERSION)} — redeclare the surface ` +
75
+ 'against the current version');
76
+ }
77
+ for (const section of ['list', 'create', 'edit', 'archive', 'status', 'afterAction']) {
78
+ if (typeof surface[section] !== 'object' || surface[section] === null) {
79
+ throw new Error(`surface.${section} is required by the surface descriptor`);
80
+ }
81
+ }
82
+ for (const key of ['path', 'readySelector', 'rowSelector']) {
83
+ if (typeof surface.list[key] !== 'string' || surface.list[key].length === 0) {
84
+ throw new Error(`surface.list.${key} must be a non-empty string`);
85
+ }
86
+ }
87
+ if (!Number.isInteger(surface.list.idCellIndex) || surface.list.idCellIndex < 0) {
88
+ throw new Error('surface.list.idCellIndex must be a non-negative integer');
89
+ }
90
+ requireIndexMap('list.fieldCellIndexes', surface.list.fieldCellIndexes);
91
+ for (const key of ['formPath', 'formReadySelector', 'submitSelector']) {
92
+ if (typeof surface.create[key] !== 'string' || surface.create[key].length === 0) {
93
+ throw new Error(`surface.create.${key} must be a non-empty string`);
94
+ }
95
+ }
96
+ requireSelectorMap('create.fields', surface.create.fields);
97
+ for (const key of ['linkSelector', 'formReadySelectorTemplate', 'saveSelectorTemplate']) {
98
+ if (typeof surface.edit[key] !== 'string' || surface.edit[key].length === 0) {
99
+ throw new Error(`surface.edit.${key} must be a non-empty string`);
100
+ }
101
+ }
102
+ requireSelectorMap('edit.fields', surface.edit.fields);
103
+ if (typeof surface.archive.controlSelectorTemplate !== 'string' ||
104
+ surface.archive.controlSelectorTemplate.length === 0) {
105
+ throw new Error('surface.archive.controlSelectorTemplate must be a non-empty string');
106
+ }
107
+ if (typeof surface.status.field !== 'string' || surface.status.field.length === 0) {
108
+ throw new Error('surface.status.field must be a non-empty field name');
109
+ }
110
+ if (typeof surface.afterAction.path !== 'string' || surface.afterAction.path.length === 0) {
111
+ throw new Error('surface.afterAction.path must be a non-empty path string');
112
+ }
113
+ requireSelectorMap('deleteFields', surface.deleteFields);
114
+ return surface;
115
+ }
116
+ /**
117
+ * Fills descriptor input fields exactly: every declared key must be a
118
+ * selector-known field with a string value (fail closed — the engine
119
+ * never invents form fields the consumer did not declare).
120
+ */
121
+ export function declaredSurfaceFields(where, input, known) {
122
+ const entries = Object.entries(input ?? {});
123
+ if (entries.length === 0) {
124
+ throw new Error(`${where} requires at least one input field the journey enters in the UI`);
125
+ }
126
+ const declared = {};
127
+ for (const [field, value] of entries) {
128
+ if (!(field in known)) {
129
+ throw new Error(`${where} declares field '${field}' which the consumer's surface descriptor does not ` +
130
+ `declare a selector for (declared fields: ${Object.keys(known).sort().join(', ')})`);
131
+ }
132
+ if (typeof value !== 'string') {
133
+ throw new Error(`${where}.${field} must be a string (form inputs are text)`);
134
+ }
135
+ declared[field] = value;
136
+ }
137
+ return declared;
138
+ }
139
+ //# sourceMappingURL=surface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"surface.js","sourceRoot":"","sources":["../src/surface.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import type { AdapterContext, EvidenceAdapter } from './types.js';
2
+ /**
3
+ * Loads every `.mjs` adapter in `dir` into a frozen registry.
4
+ *
5
+ * Args:
6
+ * dir: the configured adapters directory (absolute).
7
+ *
8
+ * Returns:
9
+ * Map<string, EvidenceAdapter>: keyed by the file basename sans `.mjs`.
10
+ *
11
+ * Throws:
12
+ * AdapterRegistryError: fail-closed load problems — unreadable dir,
13
+ * unimportable module, or a module missing any contract member.
14
+ */
15
+ export declare function loadAdapters(dir: string): Promise<Map<string, EvidenceAdapter>>;
16
+ /**
17
+ * Validates one adapter module against the pin-#8 contract.
18
+ *
19
+ * Args:
20
+ * module: the default export of the adapter file.
21
+ * name: adapter name, used in diagnostics.
22
+ *
23
+ * Returns:
24
+ * EvidenceAdapter: the validated adapter.
25
+ *
26
+ * Throws:
27
+ * AdapterRegistryError: when any required member is missing or shaped
28
+ * wrong. Optional `baseUrl` (a string) overrides the witness's
29
+ * configured adapter base for this adapter.
30
+ */
31
+ export declare function validateAdapter(module: unknown, name: string): EvidenceAdapter;
32
+ /** Fail-closed adapter-loading error (witness startup aborts). */
33
+ export declare class AdapterRegistryError extends Error {
34
+ constructor(message: string);
35
+ }
36
+ /** The transport the witness hands to adapter `read` calls (GET-only). */
37
+ export declare function makeAdapterContext(baseUrl: string, resourceId: string, get: AdapterContext['get'], headers?: Record<string, string>): AdapterContext;
38
+ //# sourceMappingURL=adapter-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter-registry.d.ts","sourceRoot":"","sources":["../../src/witness/adapter-registry.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElE;;;;;;;;;;;;GAYG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CA6BrF;AAqBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,eAAe,CAkD9E;AAED,kEAAkE;AAClE,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM;CAI5B;AAED,0EAA0E;AAC1E,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,cAAc,CAAC,KAAK,CAAC,EAC1B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,cAAc,CAEhB"}