@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,106 @@
1
+ import type { Location } from '@gate-forge/core';
2
+ /** Default wall-clock bound for one `--list` invocation. */
3
+ export declare const DEFAULT_LIST_TIMEOUT_MS = 60000;
4
+ /** Typed discovery failure: a playwright invocation that could not run
5
+ * or produce parseable output (CLI maps this to exit 2). */
6
+ export declare class TestDiscoveryError extends Error {
7
+ constructor(message: string);
8
+ }
9
+ /** One test instance the native runner enumerated. */
10
+ export interface NativeInstance {
11
+ /** Repo-root-relative posix file path. */
12
+ file: string;
13
+ /** Describe stack + title (no file segment). */
14
+ titlePath: string[];
15
+ /** Last segment of {@link titlePath}. */
16
+ title: string;
17
+ /** Runner project name, e.g. `chromium`. */
18
+ project: string;
19
+ /** The framework's instance id (spec id + project id). */
20
+ frameworkId: string;
21
+ /** Source line/column of the test call (diagnostics). */
22
+ location: Location;
23
+ /** Native expected status (`passed`/`skipped`). */
24
+ expectedStatus: string;
25
+ /** Native annotation types on the instance (e.g. `skip`, `fixme`). */
26
+ annotations: string[];
27
+ }
28
+ /** Outcome of one native list run. */
29
+ export interface NativeListResult {
30
+ /** `discovered` when instances were enumerated, else `unavailable`. */
31
+ status: 'discovered' | 'unavailable';
32
+ /** Single-cause human detail (why unavailable / what ran). */
33
+ detail: string;
34
+ /** Enumerated instances (empty when unavailable). */
35
+ instances: NativeInstance[];
36
+ /** Reporter errors from the JSON document (data, not a throw). */
37
+ errors: string[];
38
+ }
39
+ /** The scanned repo's local playwright CLI locations, in preference order. */
40
+ export declare function localPlaywrightCliCandidates(cwd: string): string[];
41
+ /**
42
+ * Strips every `GATEFORGE_*` variable from the environment for UNTRUSTED
43
+ * child runs: config/test-module enumeration must execute without
44
+ * gateforge signing env (witness keys, run tokens, run state).
45
+ *
46
+ * Args:
47
+ * env: the parent environment.
48
+ *
49
+ * Returns:
50
+ * NodeJS.ProcessEnv: a copy without any `GATEFORGE_*` keys.
51
+ */
52
+ export declare function untrustedEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
53
+ /** Finds the consumer's playwright config at the repo root, if any. */
54
+ export declare function findPlaywrightConfig(cwd: string): string | null;
55
+ /**
56
+ * Enumerates the consumer's playwright tests via official list mode.
57
+ * See the module doc for the trust boundary. The engine's own pinned
58
+ * playwright (pack dependency, 1.58.2) supplies the CLI — no network,
59
+ * no npx resolution from the consumer.
60
+ *
61
+ * Args:
62
+ * options: `cwd` (absolute repo root) and optional `timeoutMs`
63
+ * (default {@link DEFAULT_LIST_TIMEOUT_MS}).
64
+ *
65
+ * Returns:
66
+ * Promise<NativeListResult>: enumerated instances, reporter errors,
67
+ * and an unavailable verdict when no playwright config exists.
68
+ *
69
+ * Throws:
70
+ * TestDiscoveryError: when the child cannot spawn, exceeds the
71
+ * timeout, or stdout is not parseable reporter JSON.
72
+ */
73
+ export declare function listNativePlaywrightTests(options: {
74
+ cwd: string;
75
+ timeoutMs?: number;
76
+ }): Promise<NativeListResult>;
77
+ /** Matching key for reconciliation: file + full title path (no project). */
78
+ export declare function reconciliationKey(file: string, titlePath: readonly string[]): string;
79
+ /**
80
+ * Flattens a junit-style pytest node id into (file, titlePath): the part
81
+ * before the first `::` is the file, the remaining segments the path.
82
+ *
83
+ * Args:
84
+ * nodeId: pytest node id, e.g. `tests/test_x.py::TestA::test_b[param]`.
85
+ *
86
+ * Returns:
87
+ * { file, titlePath }: posix file + class/test title path.
88
+ */
89
+ export declare function splitPytestNodeId(nodeId: string): {
90
+ file: string;
91
+ titlePath: string[];
92
+ };
93
+ /**
94
+ * sha256 hex of one file's bytes (the catalog `sourceDigest`), or null
95
+ * when the file cannot be read — callers turn that into an unresolved
96
+ * row instead of a fabricated digest.
97
+ *
98
+ * Args:
99
+ * cwd: absolute repo root.
100
+ * file: repo-relative posix path.
101
+ *
102
+ * Returns:
103
+ * string | null: 64-char lowercase hex digest, or null when unreadable.
104
+ */
105
+ export declare function fileDigest(cwd: string, file: string): string | null;
106
+ //# sourceMappingURL=reconcile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../../src/discovery/reconcile.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAYjD,4DAA4D;AAC5D,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C;4DAC4D;AAC5D,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAED,sDAAsD;AACtD,MAAM,WAAW,cAAc;IAC7B,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,yCAAyC;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,WAAW,EAAE,MAAM,CAAC;IACpB,yDAAyD;IACzD,QAAQ,EAAE,QAAQ,CAAC;IACnB,mDAAmD;IACnD,cAAc,EAAE,MAAM,CAAC;IACvB,sEAAsE;IACtE,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,sCAAsC;AACtC,MAAM,WAAW,gBAAgB;IAC/B,uEAAuE;IACvE,MAAM,EAAE,YAAY,GAAG,aAAa,CAAC;IACrC,8DAA8D;IAC9D,MAAM,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,kEAAkE;IAClE,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AA0BD,8EAA8E;AAC9E,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAKlE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,CAMtE;AAED,uEAAuE;AACvE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM/D;AAsCD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,yBAAyB,CAAC,OAAO,EAAE;IACvD,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAuG5B;AAED,4EAA4E;AAC5E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAEpF;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,EAAE,CAAA;CAAE,CAKvF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMnE"}
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Native Playwright reconciliation (plan 2026-09-13 phase 2 item 4):
3
+ * enumerates the consumer's tests through the INSTALLED Playwright's
4
+ * official list mode (`playwright test --list --reporter=json`) and
5
+ * reconciles the result with the static scan.
6
+ *
7
+ * TRUST BOUNDARY (plan §3.3, phase 2 item 4): `--list` loads the
8
+ * consumer's playwright config and test modules as UNTRUSTED code —
9
+ * they execute in a child process. This module therefore:
10
+ * - strips every `GATEFORGE_*` variable (witness keys, run tokens, run
11
+ * state) from the child environment, so enumeration never runs with
12
+ * signing material or production credentials;
13
+ * - enforces a finite timeout (the child is killed; a timeout is a
14
+ * typed failure, never a hang or an empty inventory);
15
+ * - treats a failed invocation as a typed error (CLI exit 2), while
16
+ * parseable reporter output (even alongside reporter `errors`, e.g.
17
+ * "No tests found") is DATA for the catalog.
18
+ *
19
+ * Where no playwright config exists, reconciliation is reported
20
+ * unavailable — that is not an error for non-playwright repositories.
21
+ */
22
+ import { spawn } from 'node:child_process';
23
+ import { createHash } from 'node:crypto';
24
+ import { existsSync, readFileSync } from 'node:fs';
25
+ import { createRequire } from 'node:module';
26
+ import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
27
+ /** Config file names checked at the repo root (first match wins). */
28
+ const PLAYWRIGHT_CONFIG_NAMES = [
29
+ 'playwright.config.ts',
30
+ 'playwright.config.mts',
31
+ 'playwright.config.cts',
32
+ 'playwright.config.js',
33
+ 'playwright.config.mjs',
34
+ 'playwright.config.cjs',
35
+ ];
36
+ /** Default wall-clock bound for one `--list` invocation. */
37
+ export const DEFAULT_LIST_TIMEOUT_MS = 60_000;
38
+ /** Typed discovery failure: a playwright invocation that could not run
39
+ * or produce parseable output (CLI maps this to exit 2). */
40
+ export class TestDiscoveryError extends Error {
41
+ constructor(message) {
42
+ super(message);
43
+ this.name = 'TestDiscoveryError';
44
+ }
45
+ }
46
+ /** The playwright CLI of the repo being scanned, else the pack's own. */
47
+ function playwrightCliPath(cwd) {
48
+ // CONSUMER-FIRST resolution: a consumer repo pins its own
49
+ // playwright/@playwright/test version (its config and specs load
50
+ // through it). Running the pack's CLI against a consumer whose local
51
+ // version differs dies with the two-versions-of-@playwright/test
52
+ // conflict — so the scanned repo's own CLI wins when present
53
+ // (consumer migration, E22). The pack's CLI remains the fallback
54
+ // (fixture repos symlink the monorepo node_modules, so they resolve
55
+ // to the same bytes either way).
56
+ if (cwd !== undefined) {
57
+ for (const candidate of localPlaywrightCliCandidates(cwd)) {
58
+ if (existsSync(candidate))
59
+ return candidate;
60
+ }
61
+ }
62
+ const require = createRequire(import.meta.url);
63
+ const pkgJson = require.resolve('playwright/package.json');
64
+ const cli = join(dirname(pkgJson), 'cli.js');
65
+ if (!existsSync(cli)) {
66
+ throw new TestDiscoveryError(`playwright CLI not found at '${cli}' (pack dependency broken)`);
67
+ }
68
+ return cli;
69
+ }
70
+ /** The scanned repo's local playwright CLI locations, in preference order. */
71
+ export function localPlaywrightCliCandidates(cwd) {
72
+ return [
73
+ join(cwd, 'node_modules', 'playwright', 'cli.js'),
74
+ join(cwd, 'node_modules', '@playwright', 'test', 'cli.js'),
75
+ ];
76
+ }
77
+ /**
78
+ * Strips every `GATEFORGE_*` variable from the environment for UNTRUSTED
79
+ * child runs: config/test-module enumeration must execute without
80
+ * gateforge signing env (witness keys, run tokens, run state).
81
+ *
82
+ * Args:
83
+ * env: the parent environment.
84
+ *
85
+ * Returns:
86
+ * NodeJS.ProcessEnv: a copy without any `GATEFORGE_*` keys.
87
+ */
88
+ export function untrustedEnv(env) {
89
+ const child = { ...env };
90
+ for (const key of Object.keys(child)) {
91
+ if (key.startsWith('GATEFORGE_'))
92
+ delete child[key];
93
+ }
94
+ return child;
95
+ }
96
+ /** Finds the consumer's playwright config at the repo root, if any. */
97
+ export function findPlaywrightConfig(cwd) {
98
+ for (const name of PLAYWRIGHT_CONFIG_NAMES) {
99
+ const path = join(cwd, name);
100
+ if (existsSync(path))
101
+ return name;
102
+ }
103
+ return null;
104
+ }
105
+ /** Converts an absolute path to repo-root-relative posix form. */
106
+ function toRepoRelative(cwd, path) {
107
+ const rel = relative(cwd, resolve(cwd, path));
108
+ return rel.split('\\').join('/');
109
+ }
110
+ /**
111
+ * Enumerates the consumer's playwright tests via official list mode.
112
+ * See the module doc for the trust boundary. The engine's own pinned
113
+ * playwright (pack dependency, 1.58.2) supplies the CLI — no network,
114
+ * no npx resolution from the consumer.
115
+ *
116
+ * Args:
117
+ * options: `cwd` (absolute repo root) and optional `timeoutMs`
118
+ * (default {@link DEFAULT_LIST_TIMEOUT_MS}).
119
+ *
120
+ * Returns:
121
+ * Promise<NativeListResult>: enumerated instances, reporter errors,
122
+ * and an unavailable verdict when no playwright config exists.
123
+ *
124
+ * Throws:
125
+ * TestDiscoveryError: when the child cannot spawn, exceeds the
126
+ * timeout, or stdout is not parseable reporter JSON.
127
+ */
128
+ export async function listNativePlaywrightTests(options) {
129
+ const configName = findPlaywrightConfig(options.cwd);
130
+ if (configName === null) {
131
+ return {
132
+ status: 'unavailable',
133
+ detail: 'reconciliation: unavailable — no playwright config (not an error for non-playwright repos)',
134
+ instances: [],
135
+ errors: [],
136
+ };
137
+ }
138
+ const timeoutMs = options.timeoutMs ?? DEFAULT_LIST_TIMEOUT_MS;
139
+ const cli = playwrightCliPath(options.cwd);
140
+ const child = spawn(process.execPath, [cli, 'test', '--list', '--reporter=json'], {
141
+ cwd: options.cwd,
142
+ env: untrustedEnv(process.env),
143
+ stdio: ['ignore', 'pipe', 'pipe'],
144
+ });
145
+ const outcome = await new Promise((settle) => {
146
+ let stdout = '';
147
+ let stderr = '';
148
+ let timedOut = false;
149
+ const timer = setTimeout(() => {
150
+ timedOut = true;
151
+ child.kill('SIGKILL');
152
+ }, timeoutMs);
153
+ child.stdout?.on('data', (chunk) => {
154
+ stdout += chunk.toString('utf8');
155
+ });
156
+ child.stderr?.on('data', (chunk) => {
157
+ stderr += chunk.toString('utf8');
158
+ });
159
+ child.once('error', (error) => {
160
+ clearTimeout(timer);
161
+ settle({ code: null, stdout, stderr, timedOut: false, error });
162
+ });
163
+ child.once('exit', (code) => {
164
+ clearTimeout(timer);
165
+ settle({ code, stdout, stderr, timedOut, error: null });
166
+ });
167
+ });
168
+ if (outcome.error !== null) {
169
+ throw new TestDiscoveryError(`playwright --list failed to run: ${outcome.error.message}`);
170
+ }
171
+ if (outcome.timedOut) {
172
+ throw new TestDiscoveryError(`playwright --list exceeded its ${String(timeoutMs)}ms timeout and was killed (fail closed — never an empty inventory)`);
173
+ }
174
+ let document;
175
+ try {
176
+ document = JSON.parse(outcome.stdout);
177
+ }
178
+ catch {
179
+ throw new TestDiscoveryError(`playwright --list produced unparseable output (exit ${String(outcome.code)}): ` +
180
+ `${(outcome.stderr || outcome.stdout).slice(0, 400)}`);
181
+ }
182
+ const rootDir = document.config?.rootDir !== undefined ? resolve(document.config.rootDir) : options.cwd;
183
+ const instances = [];
184
+ const walkSuites = (suites, describeStack, file) => {
185
+ for (const suite of suites) {
186
+ // Top-level suites carry the file; deeper suites are describes.
187
+ const suiteFile = suite.file !== undefined ? suite.file : file;
188
+ const isFileSuite = file === null && suite.file !== undefined;
189
+ // Titles accumulate only BELOW the file suite (describes). Suites
190
+ // ABOVE it are runner-structural (the unnamed root and the project
191
+ // suite — title '' for an unnamed project): their names are not
192
+ // part of the test identity, and leaking them (newer reporter
193
+ // nestings) broke reconciliation against the static scan.
194
+ const stack = isFileSuite || file === null ? describeStack : [...describeStack, suite.title ?? ''];
195
+ for (const spec of suite.specs ?? []) {
196
+ if (spec.title === undefined || suiteFile === null)
197
+ continue;
198
+ for (const test of spec.tests ?? []) {
199
+ const project = test.projectName ?? '';
200
+ instances.push({
201
+ file: toRepoRelative(options.cwd, isAbsolute(suiteFile) ? suiteFile : join(rootDir, suiteFile)),
202
+ titlePath: [...stack, spec.title],
203
+ title: spec.title,
204
+ project,
205
+ frameworkId: `${spec.id ?? 'unknown'}#${test.projectId ?? project}`,
206
+ location: {
207
+ file: toRepoRelative(options.cwd, isAbsolute(suiteFile) ? suiteFile : join(rootDir, suiteFile)),
208
+ line: spec.line ?? 0,
209
+ col: spec.column ?? 0,
210
+ },
211
+ expectedStatus: test.expectedStatus ?? 'unknown',
212
+ annotations: (test.annotations ?? []).map((annotation) => annotation.type ?? '').filter((type) => type.length > 0),
213
+ });
214
+ }
215
+ }
216
+ walkSuites(suite.suites ?? [], stack, suiteFile);
217
+ }
218
+ };
219
+ walkSuites(document.suites ?? [], [], null);
220
+ const errors = (document.errors ?? []).map((error) => error.message ?? String(error));
221
+ return {
222
+ status: 'discovered',
223
+ detail: `native playwright --list over '${configName}' enumerated ${String(instances.length)} instance(s) as untrusted code (no GATEFORGE_* env)`,
224
+ instances,
225
+ errors,
226
+ };
227
+ }
228
+ /** Matching key for reconciliation: file + full title path (no project). */
229
+ export function reconciliationKey(file, titlePath) {
230
+ return `${file}#${titlePath.join('>')}`;
231
+ }
232
+ /**
233
+ * Flattens a junit-style pytest node id into (file, titlePath): the part
234
+ * before the first `::` is the file, the remaining segments the path.
235
+ *
236
+ * Args:
237
+ * nodeId: pytest node id, e.g. `tests/test_x.py::TestA::test_b[param]`.
238
+ *
239
+ * Returns:
240
+ * { file, titlePath }: posix file + class/test title path.
241
+ */
242
+ export function splitPytestNodeId(nodeId) {
243
+ const segments = nodeId.split('::');
244
+ const file = (segments[0] ?? nodeId).split('\\').join('/');
245
+ const titlePath = segments.slice(1);
246
+ return { file, titlePath: titlePath.length > 0 ? titlePath : [basename(file)] };
247
+ }
248
+ /**
249
+ * sha256 hex of one file's bytes (the catalog `sourceDigest`), or null
250
+ * when the file cannot be read — callers turn that into an unresolved
251
+ * row instead of a fabricated digest.
252
+ *
253
+ * Args:
254
+ * cwd: absolute repo root.
255
+ * file: repo-relative posix path.
256
+ *
257
+ * Returns:
258
+ * string | null: 64-char lowercase hex digest, or null when unreadable.
259
+ */
260
+ export function fileDigest(cwd, file) {
261
+ try {
262
+ return createHash('sha256').update(readFileSync(join(cwd, file))).digest('hex');
263
+ }
264
+ catch {
265
+ return null;
266
+ }
267
+ }
268
+ //# sourceMappingURL=reconcile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../../src/discovery/reconcile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * The supervised runner child's ENVIRONMENT ALLOWLIST (enforcement-
3
+ * review fix 1): `executeSupervisedPlaywright` used to merge
4
+ * `process.env` into the runner child, so `GATEFORGE_WITNESS_VERIFIER_KEY`
5
+ * — the key that authenticates gate receipts and the witness's
6
+ * attestation/supervisor surface — was inherited by UNTRUSTED test code.
7
+ *
8
+ * The child env is now built from an explicit allowlist, never from the
9
+ * ambient environment wholesale:
10
+ * - system basics the runner needs (PATH/HOME, locale, temp dirs);
11
+ * - the supervisor-supplied run variables (`RunnerExecutionEnv.vars` —
12
+ * already sanitized by the CLI);
13
+ * - NON-secret `GATEFORGE_*` run flags from the ambient environment.
14
+ * Every other name — and in particular every secret — is EXCLUDED. The
15
+ * secret names are enumerated in {@link RUNNER_SECRET_ENV} and are
16
+ * refused even when a caller explicitly stuffs them into `vars`
17
+ * (fail closed on the wiring bug instead of leaking).
18
+ *
19
+ * Secret/non-secret classification of every known `GATEFORGE_*` name:
20
+ * - SECRET (never crosses to the runner child): GATEFORGE_WITNESS_VERIFIER_KEY
21
+ * (authenticates gate receipts + the witness supervisor/attestation
22
+ * surface).
23
+ * - NON-SECRET run wiring (allowlisted): GATEFORGE_RUN_TOKEN (the
24
+ * suite's own submission credential by design), GATEFORGE_WITNESS_URL,
25
+ * GATEFORGE_APP_BASE_URL, GATEFORGE_TARGET_BASE_URL,
26
+ * GATEFORGE_TARGET_FINGERPRINT, GATEFORGE_REPORTER_FAIL_RUN.
27
+ * - PARENT-SIDE ONLY (execution-authority fix — the runner child must
28
+ * not address them): GATEFORGE_RUN_ID, GATEFORGE_STATE_DIR,
29
+ * GATEFORGE_OBLIGATIONS, GATEFORGE_OUTCOMES_FILE, GATEFORGE_ADAPTERS_DIR,
30
+ * GATEFORGE_CLASSIFICATIONS, GATEFORGE_ADAPTER_BASE_URL,
31
+ * GATEFORGE_PROXY_TARGET, GATEFORGE_MOUNT_PATH. Run-state paths reach
32
+ * the engine reporter as TRUSTED-CONFIG constructor options (never
33
+ * env), so worker code cannot locate the lifecycle spool or the
34
+ * outcomes document. The legacy `--suite` escape still wires its own
35
+ * env outside this allowlist — it is a dev loop, never the strict gate.
36
+ * - Everything else: not allowlisted → excluded by default.
37
+ */
38
+ /** Typed error for a forbidden runner-child environment. */
39
+ export declare class RunnerEnvError extends Error {
40
+ constructor(message: string);
41
+ }
42
+ /**
43
+ * Environment variable names that are SECRETS and must never reach the
44
+ * runner child through ANY channel (env here; argv, stdin, and state
45
+ * files are excluded by construction elsewhere). Keep this list in sync
46
+ * with the module doc's classification.
47
+ */
48
+ export declare const RUNNER_SECRET_ENV: readonly string[];
49
+ /**
50
+ * Run-state paths that must NEVER reach the runner child through ANY
51
+ * channel (execution-authority fix): worker code that can address the
52
+ * lifecycle spool or the outcomes document can fabricate execution.
53
+ * They reach the engine reporter as trusted-config constructor options
54
+ * instead. A caller that stuffs them into `vars` is a wiring bug and
55
+ * throws (fail closed, same discipline as {@link RUNNER_SECRET_ENV}).
56
+ */
57
+ export declare const RUNNER_PARENT_SIDE_ENV: readonly string[];
58
+ export declare const RUNNER_GATEFORGE_ALLOWLIST: readonly string[];
59
+ /** System basics (path/home/locale/temp) the runner needs to function. */
60
+ export declare const RUNNER_SYSTEM_ALLOWLIST: readonly string[];
61
+ /**
62
+ * Builds the runner child's environment from the ALLOWLIST (never a
63
+ * wholesale `process.env` merge). Precedence: supervisor-supplied
64
+ * `vars` win over the ambient environment; only allowlisted names are
65
+ * copied at all.
66
+ *
67
+ * Args:
68
+ * vars: supervisor-supplied run variables (must be child-safe; a
69
+ * secret name here is a wiring bug and throws).
70
+ * ambient: the parent environment (default `process.env`).
71
+ *
72
+ * Returns:
73
+ * Record<string, string>: the allowlisted child environment.
74
+ *
75
+ * Throws:
76
+ * RunnerEnvError: when `vars` carries a secret name — fail closed on
77
+ * the wiring bug instead of leaking signing material.
78
+ */
79
+ export declare function buildRunnerChildEnv(vars: Readonly<Record<string, string>>, ambient?: NodeJS.ProcessEnv): Record<string, string>;
80
+ //# sourceMappingURL=runner-env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner-env.d.ts","sourceRoot":"","sources":["../../src/discovery/runner-env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,4DAA4D;AAC5D,qBAAa,cAAe,SAAQ,KAAK;gBAC3B,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE,SAAS,MAAM,EAAuC,CAAC;AAEvF;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,EAAE,SAAS,MAAM,EAUnD,CAAC;AACF,eAAO,MAAM,0BAA0B,EAAE,SAAS,MAAM,EAOvD,CAAC;AAEF,0EAA0E;AAC1E,eAAO,MAAM,uBAAuB,EAAE,SAAS,MAAM,EAWpD,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EACtC,OAAO,GAAE,MAAM,CAAC,UAAwB,GACvC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA8BxB"}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * The supervised runner child's ENVIRONMENT ALLOWLIST (enforcement-
3
+ * review fix 1): `executeSupervisedPlaywright` used to merge
4
+ * `process.env` into the runner child, so `GATEFORGE_WITNESS_VERIFIER_KEY`
5
+ * — the key that authenticates gate receipts and the witness's
6
+ * attestation/supervisor surface — was inherited by UNTRUSTED test code.
7
+ *
8
+ * The child env is now built from an explicit allowlist, never from the
9
+ * ambient environment wholesale:
10
+ * - system basics the runner needs (PATH/HOME, locale, temp dirs);
11
+ * - the supervisor-supplied run variables (`RunnerExecutionEnv.vars` —
12
+ * already sanitized by the CLI);
13
+ * - NON-secret `GATEFORGE_*` run flags from the ambient environment.
14
+ * Every other name — and in particular every secret — is EXCLUDED. The
15
+ * secret names are enumerated in {@link RUNNER_SECRET_ENV} and are
16
+ * refused even when a caller explicitly stuffs them into `vars`
17
+ * (fail closed on the wiring bug instead of leaking).
18
+ *
19
+ * Secret/non-secret classification of every known `GATEFORGE_*` name:
20
+ * - SECRET (never crosses to the runner child): GATEFORGE_WITNESS_VERIFIER_KEY
21
+ * (authenticates gate receipts + the witness supervisor/attestation
22
+ * surface).
23
+ * - NON-SECRET run wiring (allowlisted): GATEFORGE_RUN_TOKEN (the
24
+ * suite's own submission credential by design), GATEFORGE_WITNESS_URL,
25
+ * GATEFORGE_APP_BASE_URL, GATEFORGE_TARGET_BASE_URL,
26
+ * GATEFORGE_TARGET_FINGERPRINT, GATEFORGE_REPORTER_FAIL_RUN.
27
+ * - PARENT-SIDE ONLY (execution-authority fix — the runner child must
28
+ * not address them): GATEFORGE_RUN_ID, GATEFORGE_STATE_DIR,
29
+ * GATEFORGE_OBLIGATIONS, GATEFORGE_OUTCOMES_FILE, GATEFORGE_ADAPTERS_DIR,
30
+ * GATEFORGE_CLASSIFICATIONS, GATEFORGE_ADAPTER_BASE_URL,
31
+ * GATEFORGE_PROXY_TARGET, GATEFORGE_MOUNT_PATH. Run-state paths reach
32
+ * the engine reporter as TRUSTED-CONFIG constructor options (never
33
+ * env), so worker code cannot locate the lifecycle spool or the
34
+ * outcomes document. The legacy `--suite` escape still wires its own
35
+ * env outside this allowlist — it is a dev loop, never the strict gate.
36
+ * - Everything else: not allowlisted → excluded by default.
37
+ */
38
+ /** Typed error for a forbidden runner-child environment. */
39
+ export class RunnerEnvError extends Error {
40
+ constructor(message) {
41
+ super(message);
42
+ this.name = 'RunnerEnvError';
43
+ }
44
+ }
45
+ /**
46
+ * Environment variable names that are SECRETS and must never reach the
47
+ * runner child through ANY channel (env here; argv, stdin, and state
48
+ * files are excluded by construction elsewhere). Keep this list in sync
49
+ * with the module doc's classification.
50
+ */
51
+ export const RUNNER_SECRET_ENV = ['GATEFORGE_WITNESS_VERIFIER_KEY'];
52
+ /**
53
+ * Run-state paths that must NEVER reach the runner child through ANY
54
+ * channel (execution-authority fix): worker code that can address the
55
+ * lifecycle spool or the outcomes document can fabricate execution.
56
+ * They reach the engine reporter as trusted-config constructor options
57
+ * instead. A caller that stuffs them into `vars` is a wiring bug and
58
+ * throws (fail closed, same discipline as {@link RUNNER_SECRET_ENV}).
59
+ */
60
+ export const RUNNER_PARENT_SIDE_ENV = [
61
+ 'GATEFORGE_RUN_ID',
62
+ 'GATEFORGE_STATE_DIR',
63
+ 'GATEFORGE_OBLIGATIONS',
64
+ 'GATEFORGE_OUTCOMES_FILE',
65
+ 'GATEFORGE_ADAPTERS_DIR',
66
+ 'GATEFORGE_CLASSIFICATIONS',
67
+ 'GATEFORGE_ADAPTER_BASE_URL',
68
+ 'GATEFORGE_PROXY_TARGET',
69
+ 'GATEFORGE_MOUNT_PATH',
70
+ ];
71
+ export const RUNNER_GATEFORGE_ALLOWLIST = [
72
+ 'GATEFORGE_RUN_TOKEN',
73
+ 'GATEFORGE_WITNESS_URL',
74
+ 'GATEFORGE_APP_BASE_URL',
75
+ 'GATEFORGE_TARGET_BASE_URL',
76
+ 'GATEFORGE_TARGET_FINGERPRINT',
77
+ 'GATEFORGE_REPORTER_FAIL_RUN',
78
+ ];
79
+ /** System basics (path/home/locale/temp) the runner needs to function. */
80
+ export const RUNNER_SYSTEM_ALLOWLIST = [
81
+ 'PATH',
82
+ 'HOME',
83
+ 'LANG',
84
+ 'LC_ALL',
85
+ 'LC_CTYPE',
86
+ 'LC_MESSAGES',
87
+ 'TZ',
88
+ 'TMPDIR',
89
+ 'TEMP',
90
+ 'TMP',
91
+ ];
92
+ /**
93
+ * Builds the runner child's environment from the ALLOWLIST (never a
94
+ * wholesale `process.env` merge). Precedence: supervisor-supplied
95
+ * `vars` win over the ambient environment; only allowlisted names are
96
+ * copied at all.
97
+ *
98
+ * Args:
99
+ * vars: supervisor-supplied run variables (must be child-safe; a
100
+ * secret name here is a wiring bug and throws).
101
+ * ambient: the parent environment (default `process.env`).
102
+ *
103
+ * Returns:
104
+ * Record<string, string>: the allowlisted child environment.
105
+ *
106
+ * Throws:
107
+ * RunnerEnvError: when `vars` carries a secret name — fail closed on
108
+ * the wiring bug instead of leaking signing material.
109
+ */
110
+ export function buildRunnerChildEnv(vars, ambient = process.env) {
111
+ for (const secret of RUNNER_SECRET_ENV) {
112
+ if (vars[secret] !== undefined) {
113
+ throw new RunnerEnvError(`refusing to pass '${secret}' to the runner child: signing material never reaches ` +
114
+ 'untrusted test code (enforcement-review fix 1)');
115
+ }
116
+ }
117
+ for (const parentSide of RUNNER_PARENT_SIDE_ENV) {
118
+ if (vars[parentSide] !== undefined) {
119
+ throw new RunnerEnvError(`refusing to pass '${parentSide}' to the runner child: run-state paths stay parent-side ` +
120
+ 'so worker code cannot address the lifecycle spool or outcomes document ' +
121
+ '(execution-authority fix; the engine reporter receives them as trusted-config options)');
122
+ }
123
+ }
124
+ const child = {};
125
+ for (const name of [...RUNNER_SYSTEM_ALLOWLIST, ...RUNNER_GATEFORGE_ALLOWLIST]) {
126
+ const value = vars[name] ?? ambient[name];
127
+ if (value !== undefined && value !== '')
128
+ child[name] = value;
129
+ }
130
+ // Supervisor vars that are not GATEFORGE-known (future flags) still
131
+ // pass through — they arrive from trusted supervision, and the secret
132
+ // and parent-side checks above already ran over ALL of vars.
133
+ for (const [name, value] of Object.entries(vars)) {
134
+ if (child[name] === undefined && value !== '')
135
+ child[name] = value;
136
+ }
137
+ return child;
138
+ }
139
+ //# sourceMappingURL=runner-env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner-env.js","sourceRoot":"","sources":["../../src/discovery/runner-env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,4DAA4D;AAC5D,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAsB,CAAC,gCAAgC,CAAC,CAAC;AAEvF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAsB;IACvD,kBAAkB;IAClB,qBAAqB;IACrB,uBAAuB;IACvB,yBAAyB;IACzB,wBAAwB;IACxB,2BAA2B;IAC3B,4BAA4B;IAC5B,wBAAwB;IACxB,sBAAsB;CACvB,CAAC;AACF,MAAM,CAAC,MAAM,0BAA0B,GAAsB;IAC3D,qBAAqB;IACrB,uBAAuB;IACvB,wBAAwB;IACxB,2BAA2B;IAC3B,8BAA8B;IAC9B,6BAA6B;CAC9B,CAAC;AAEF,0EAA0E;AAC1E,MAAM,CAAC,MAAM,uBAAuB,GAAsB;IACxD,MAAM;IACN,MAAM;IACN,MAAM;IACN,QAAQ;IACR,UAAU;IACV,aAAa;IACb,IAAI;IACJ,QAAQ;IACR,MAAM;IACN,KAAK;CACN,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAAsC,EACtC,UAA6B,OAAO,CAAC,GAAG;IAExC,KAAK,MAAM,MAAM,IAAI,iBAAiB,EAAE,CAAC;QACvC,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,SAAS,EAAE,CAAC;YAC/B,MAAM,IAAI,cAAc,CACtB,qBAAqB,MAAM,wDAAwD;gBACjF,gDAAgD,CACnD,CAAC;QACJ,CAAC;IACH,CAAC;IACD,KAAK,MAAM,UAAU,IAAI,sBAAsB,EAAE,CAAC;QAChD,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,SAAS,EAAE,CAAC;YACnC,MAAM,IAAI,cAAc,CACtB,qBAAqB,UAAU,0DAA0D;gBACvF,yEAAyE;gBACzE,wFAAwF,CAC3F,CAAC;QACJ,CAAC;IACH,CAAC;IACD,MAAM,KAAK,GAA2B,EAAE,CAAC;IACzC,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,uBAAuB,EAAE,GAAG,0BAA0B,CAAC,EAAE,CAAC;QAC/E,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE;YAAE,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;IAC/D,CAAC;IACD,oEAAoE;IACpE,sEAAsE;IACtE,6DAA6D;IAC7D,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACjD,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE;YAAE,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;IACrE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}