@gate-forge/pack-playwright 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -0
- package/dist/constants.d.ts +13 -140
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +13 -148
- package/dist/constants.js.map +1 -1
- package/dist/cypress/plugin.d.ts +53 -0
- package/dist/cypress/plugin.d.ts.map +1 -0
- package/dist/cypress/plugin.js +369 -0
- package/dist/cypress/plugin.js.map +1 -0
- package/dist/cypress/spec-scan.d.ts +39 -0
- package/dist/cypress/spec-scan.d.ts.map +1 -0
- package/dist/cypress/spec-scan.js +301 -0
- package/dist/cypress/spec-scan.js.map +1 -0
- package/dist/diagnosis.d.ts +39 -0
- package/dist/diagnosis.d.ts.map +1 -0
- package/dist/diagnosis.js +63 -0
- package/dist/diagnosis.js.map +1 -0
- package/dist/discovery/adapter-projection.d.ts +13 -0
- package/dist/discovery/adapter-projection.d.ts.map +1 -0
- package/dist/discovery/adapter-projection.js +84 -0
- package/dist/discovery/adapter-projection.js.map +1 -0
- package/dist/discovery/config-locations.d.ts +5 -0
- package/dist/discovery/config-locations.d.ts.map +1 -0
- package/dist/discovery/config-locations.js +19 -0
- package/dist/discovery/config-locations.js.map +1 -0
- package/dist/discovery/cypress-runner-adapter.d.ts +112 -0
- package/dist/discovery/cypress-runner-adapter.d.ts.map +1 -0
- package/dist/discovery/cypress-runner-adapter.js +501 -0
- package/dist/discovery/cypress-runner-adapter.js.map +1 -0
- package/dist/discovery/discover.d.ts +53 -1
- package/dist/discovery/discover.d.ts.map +1 -1
- package/dist/discovery/discover.js +100 -3
- package/dist/discovery/discover.js.map +1 -1
- package/dist/discovery/git-ignore.d.ts +13 -0
- package/dist/discovery/git-ignore.d.ts.map +1 -0
- package/dist/discovery/git-ignore.js +62 -0
- package/dist/discovery/git-ignore.js.map +1 -0
- package/dist/discovery/index.d.ts +9 -0
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/index.js +9 -0
- package/dist/discovery/index.js.map +1 -1
- package/dist/discovery/playwright-runner-adapter.d.ts +108 -0
- package/dist/discovery/playwright-runner-adapter.d.ts.map +1 -0
- package/dist/discovery/playwright-runner-adapter.js +185 -0
- package/dist/discovery/playwright-runner-adapter.js.map +1 -0
- package/dist/discovery/prepare-barrier.d.ts +522 -0
- package/dist/discovery/prepare-barrier.d.ts.map +1 -0
- package/dist/discovery/prepare-barrier.js +739 -0
- package/dist/discovery/prepare-barrier.js.map +1 -0
- package/dist/discovery/pytest-adapter.d.ts.map +1 -1
- package/dist/discovery/pytest-adapter.js +6 -2
- package/dist/discovery/pytest-adapter.js.map +1 -1
- package/dist/discovery/pytest-runner-adapter.d.ts +124 -0
- package/dist/discovery/pytest-runner-adapter.d.ts.map +1 -0
- package/dist/discovery/pytest-runner-adapter.js +454 -0
- package/dist/discovery/pytest-runner-adapter.js.map +1 -0
- package/dist/discovery/reconcile.d.ts +80 -11
- package/dist/discovery/reconcile.d.ts.map +1 -1
- package/dist/discovery/reconcile.js +395 -78
- package/dist/discovery/reconcile.js.map +1 -1
- package/dist/discovery/runner-env.d.ts +88 -2
- package/dist/discovery/runner-env.d.ts.map +1 -1
- package/dist/discovery/runner-env.js +142 -4
- package/dist/discovery/runner-env.js.map +1 -1
- package/dist/discovery/static-discovery.d.ts +38 -3
- package/dist/discovery/static-discovery.d.ts.map +1 -1
- package/dist/discovery/static-discovery.js +454 -15
- package/dist/discovery/static-discovery.js.map +1 -1
- package/dist/discovery/supervised-run.d.ts +199 -10
- package/dist/discovery/supervised-run.d.ts.map +1 -1
- package/dist/discovery/supervised-run.js +350 -39
- package/dist/discovery/supervised-run.js.map +1 -1
- package/dist/discovery/trusted-config.d.ts +180 -6
- package/dist/discovery/trusted-config.d.ts.map +1 -1
- package/dist/discovery/trusted-config.js +279 -10
- package/dist/discovery/trusted-config.js.map +1 -1
- package/dist/discovery/vitest-runner-adapter.d.ts +172 -0
- package/dist/discovery/vitest-runner-adapter.d.ts.map +1 -0
- package/dist/discovery/vitest-runner-adapter.js +607 -0
- package/dist/discovery/vitest-runner-adapter.js.map +1 -0
- package/dist/fixture/consumer-runner.d.ts +4 -0
- package/dist/fixture/consumer-runner.d.ts.map +1 -0
- package/dist/fixture/consumer-runner.js +38 -0
- package/dist/fixture/consumer-runner.js.map +1 -0
- package/dist/fixture/evidence.d.ts +27 -0
- package/dist/fixture/evidence.d.ts.map +1 -1
- package/dist/fixture/evidence.js +12 -0
- package/dist/fixture/evidence.js.map +1 -1
- package/dist/fixture/fixture.d.ts +2 -1
- package/dist/fixture/fixture.d.ts.map +1 -1
- package/dist/fixture/fixture.js +1 -17
- package/dist/fixture/fixture.js.map +1 -1
- package/dist/fixture/witness-client.d.ts +6 -174
- package/dist/fixture/witness-client.d.ts.map +1 -1
- package/dist/fixture/witness-client.js +6 -267
- package/dist/fixture/witness-client.js.map +1 -1
- package/dist/index.d.ts +9 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/json.d.ts +6 -12
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +6 -17
- package/dist/json.js.map +1 -1
- package/dist/reporter/project-graph-reporter.d.ts +92 -0
- package/dist/reporter/project-graph-reporter.d.ts.map +1 -0
- package/dist/reporter/project-graph-reporter.js +92 -0
- package/dist/reporter/project-graph-reporter.js.map +1 -0
- package/dist/reporter/reporter.cjs.map +1 -1
- package/dist/reporter/reporter.d.ts +93 -4
- package/dist/reporter/reporter.d.ts.map +1 -1
- package/dist/reporter/reporter.js +174 -18
- package/dist/reporter/reporter.js.map +1 -1
- package/dist/runner-claims.d.ts +13 -0
- package/dist/runner-claims.d.ts.map +1 -0
- package/dist/runner-claims.js +46 -0
- package/dist/runner-claims.js.map +1 -0
- package/dist/runner-resolution.d.ts +3 -0
- package/dist/runner-resolution.d.ts.map +1 -0
- package/dist/runner-resolution.js +14 -0
- package/dist/runner-resolution.js.map +1 -0
- package/dist/supervisor/client.d.ts +23 -1
- package/dist/supervisor/client.d.ts.map +1 -1
- package/dist/supervisor/client.js +49 -0
- package/dist/supervisor/client.js.map +1 -1
- package/dist/supervisor/drain.d.ts +65 -0
- package/dist/supervisor/drain.d.ts.map +1 -1
- package/dist/supervisor/drain.js +353 -9
- package/dist/supervisor/drain.js.map +1 -1
- package/dist/supervisor/index.d.ts +7 -3
- package/dist/supervisor/index.d.ts.map +1 -1
- package/dist/supervisor/index.js +7 -3
- package/dist/supervisor/index.js.map +1 -1
- package/dist/supervisor/spool.d.ts +65 -2
- package/dist/supervisor/spool.d.ts.map +1 -1
- package/dist/supervisor/spool.js +47 -0
- package/dist/supervisor/spool.js.map +1 -1
- package/dist/surface.d.ts +6 -207
- package/dist/surface.d.ts.map +1 -1
- package/dist/surface.js +6 -264
- package/dist/surface.js.map +1 -1
- package/dist/vitest/index.d.ts +23 -0
- package/dist/vitest/index.d.ts.map +1 -0
- package/dist/vitest/index.js +356 -0
- package/dist/vitest/index.js.map +1 -0
- package/dist/vitest/reporter.d.ts +83 -0
- package/dist/vitest/reporter.d.ts.map +1 -0
- package/dist/vitest/reporter.js +211 -0
- package/dist/vitest/reporter.js.map +1 -0
- package/dist/vitest/worker-slot.d.ts +36 -0
- package/dist/vitest/worker-slot.d.ts.map +1 -0
- package/dist/vitest/worker-slot.js +51 -0
- package/dist/vitest/worker-slot.js.map +1 -0
- package/dist/witness/adapter-registry.d.ts +6 -50
- package/dist/witness/adapter-registry.d.ts.map +1 -1
- package/dist/witness/adapter-registry.js +6 -257
- package/dist/witness/adapter-registry.js.map +1 -1
- package/dist/witness/behavior-request.d.ts +6 -76
- package/dist/witness/behavior-request.d.ts.map +1 -1
- package/dist/witness/behavior-request.js +6 -273
- package/dist/witness/behavior-request.js.map +1 -1
- package/dist/witness/behavior.d.ts +6 -48
- package/dist/witness/behavior.d.ts.map +1 -1
- package/dist/witness/behavior.js +6 -114
- package/dist/witness/behavior.js.map +1 -1
- package/dist/witness/bin.d.ts +6 -17
- package/dist/witness/bin.d.ts.map +1 -1
- package/dist/witness/bin.js +6 -168
- package/dist/witness/bin.js.map +1 -1
- package/dist/witness/browser.d.ts +14 -206
- package/dist/witness/browser.d.ts.map +1 -1
- package/dist/witness/browser.js +15 -654
- package/dist/witness/browser.js.map +1 -1
- package/dist/witness/classifications.d.ts +6 -38
- package/dist/witness/classifications.d.ts.map +1 -1
- package/dist/witness/classifications.js +6 -83
- package/dist/witness/classifications.js.map +1 -1
- package/dist/witness/env-attestation.d.ts +6 -94
- package/dist/witness/env-attestation.d.ts.map +1 -1
- package/dist/witness/env-attestation.js +6 -198
- package/dist/witness/env-attestation.js.map +1 -1
- package/dist/witness/fixture-provider.d.ts +6 -82
- package/dist/witness/fixture-provider.d.ts.map +1 -1
- package/dist/witness/fixture-provider.js +6 -105
- package/dist/witness/fixture-provider.js.map +1 -1
- package/dist/witness/loopback-pins.d.ts +6 -75
- package/dist/witness/loopback-pins.d.ts.map +1 -1
- package/dist/witness/loopback-pins.js +6 -240
- package/dist/witness/loopback-pins.js.map +1 -1
- package/dist/witness/server.d.ts +8 -26
- package/dist/witness/server.d.ts.map +1 -1
- package/dist/witness/server.js +24 -3761
- package/dist/witness/server.js.map +1 -1
- package/dist/witness/types.d.ts +6 -882
- package/dist/witness/types.d.ts.map +1 -1
- package/dist/witness/types.js +9 -1
- package/dist/witness/types.js.map +1 -1
- package/examples/overlay-proof.spec.js +75 -0
- package/package.json +8 -2
- package/python/__pycache__/gateforge_persistence_intents.cpython-312.pyc +0 -0
- package/python/__pycache__/gateforge_persistence_intents.cpython-314.pyc +0 -0
- package/python/gateforge_pytest_plugin.py +462 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project-graph reporter: the ONLY trustworthy source of a Playwright
|
|
3
|
+
* config's per-project `dependencies`.
|
|
4
|
+
*
|
|
5
|
+
* The json reporter Gateforge enumerates with does NOT carry the edge:
|
|
6
|
+
* `config.projects[]` there is rebuilt field by field (outputDir,
|
|
7
|
+
* repeatEach, retries, metadata, id, name, testDir, testIgnore,
|
|
8
|
+
* testMatch, timeout) and `dependencies` is simply absent — verified
|
|
9
|
+
* against the pack's pinned playwright 1.58.2 and against a consumer's
|
|
10
|
+
* own 1.62.1. Reading the edge from the consumer config file instead
|
|
11
|
+
* would mean parsing arbitrary candidate code, which the trusted
|
|
12
|
+
* supervision boundary exists to avoid.
|
|
13
|
+
*
|
|
14
|
+
* So the enumeration adds this ENGINE-OWNED reporter next to the json
|
|
15
|
+
* one (`--reporter=json,<this file>`). The runner hands every reporter
|
|
16
|
+
* the resolved FullConfig in `onConfigure` — the same object the runner
|
|
17
|
+
* itself schedules from — so the graph is the runner's own truth, not
|
|
18
|
+
* the candidate's claim about it.
|
|
19
|
+
*
|
|
20
|
+
* It records the project graph and, alongside it, the RESOLVED
|
|
21
|
+
* `use.storageState` of each project — but only when that value is a
|
|
22
|
+
* plain string path. That is DATA the runner already resolved from the
|
|
23
|
+
* consumer config (a file location), not consumer code, and it is what
|
|
24
|
+
* the standard auth pattern needs: a `setup` project signs in and saves
|
|
25
|
+
* `playwright/.auth/user.json`, and the dependent project is the one
|
|
26
|
+
* that must be handed that file. Every other project option is
|
|
27
|
+
* consumer configuration a supervised run deliberately does not honor,
|
|
28
|
+
* so none of it crosses this boundary — including a `storageState`
|
|
29
|
+
* given as an inline `{cookies, origins}` document, which is a value
|
|
30
|
+
* this boundary does not carry.
|
|
31
|
+
*
|
|
32
|
+
* The output path arrives in the environment, exactly like the json
|
|
33
|
+
* reporter's own `PLAYWRIGHT_JSON_OUTPUT_FILE`: the reporter list a CLI
|
|
34
|
+
* accepts cannot carry per-reporter options, and this reporter runs in
|
|
35
|
+
* the ENUMERATION child (`--list`, no test ever executes), never in the
|
|
36
|
+
* supervised run whose run-state paths must stay out of worker hands.
|
|
37
|
+
*/
|
|
38
|
+
import { writeFileSync } from 'node:fs';
|
|
39
|
+
/** Environment variable naming the graph document to write. */
|
|
40
|
+
export const PROJECT_GRAPH_PATH_ENV = 'PLAYWRIGHT_GATEFORGE_PROJECT_GRAPH_FILE';
|
|
41
|
+
/**
|
|
42
|
+
* The Playwright reporter that records the resolved project dependency
|
|
43
|
+
* graph and the storage states its projects declare. Written for the
|
|
44
|
+
* v2 reporter protocol (`version()` returning `'v2'`), whose
|
|
45
|
+
* `onConfigure` receives the resolved full config.
|
|
46
|
+
*/
|
|
47
|
+
export class ProjectGraphReporter {
|
|
48
|
+
/** Opts into the v2 reporter protocol (config arrives in onConfigure). */
|
|
49
|
+
version() {
|
|
50
|
+
return 'v2';
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Records the graph, and the declared storage states, the RUNNER
|
|
54
|
+
* resolved.
|
|
55
|
+
*
|
|
56
|
+
* @param config: the resolved full config, exactly as the runner has it.
|
|
57
|
+
*/
|
|
58
|
+
onConfigure(config) {
|
|
59
|
+
const graphPath = process.env[PROJECT_GRAPH_PATH_ENV];
|
|
60
|
+
if (graphPath === undefined || graphPath.length === 0)
|
|
61
|
+
return;
|
|
62
|
+
// Null-prototype: a project NAME is candidate data and `__proto__` is
|
|
63
|
+
// a legal one, so a map keyed by it must not inherit anything.
|
|
64
|
+
const projectDependencies = Object.create(null);
|
|
65
|
+
const projectStorageStates = Object.create(null);
|
|
66
|
+
for (const project of config.projects ?? []) {
|
|
67
|
+
if (typeof project.name !== 'string' || project.name.length === 0)
|
|
68
|
+
continue;
|
|
69
|
+
projectDependencies[project.name] = [
|
|
70
|
+
...new Set((project.dependencies ?? []).filter((name) => typeof name === 'string' && name.length > 0)),
|
|
71
|
+
].sort();
|
|
72
|
+
// Only a plain path string crosses. An inline `{cookies, origins}`
|
|
73
|
+
// document is a value this boundary does not carry, and a
|
|
74
|
+
// function-valued state is consumer code, not data. Every STRING
|
|
75
|
+
// crosses verbatim — an empty one included: a project that asked
|
|
76
|
+
// for no state at all is a refusal the planner owes, not a
|
|
77
|
+
// silently-dropped declaration that would run logged out.
|
|
78
|
+
const storageState = project.use?.storageState;
|
|
79
|
+
if (typeof storageState === 'string') {
|
|
80
|
+
projectStorageStates[project.name] = storageState;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const document = {
|
|
84
|
+
schemaVersion: 2,
|
|
85
|
+
projectDependencies,
|
|
86
|
+
...(Object.keys(projectStorageStates).length > 0 ? { projectStorageStates } : {}),
|
|
87
|
+
};
|
|
88
|
+
writeFileSync(graphPath, `${JSON.stringify(document)}\n`, 'utf8');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export default ProjectGraphReporter;
|
|
92
|
+
//# sourceMappingURL=project-graph-reporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-graph-reporter.js","sourceRoot":"","sources":["../../src/reporter/project-graph-reporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAExC,+DAA+D;AAC/D,MAAM,CAAC,MAAM,sBAAsB,GAAG,yCAAyC,CAAC;AAqChF;;;;;GAKG;AACH,MAAM,OAAO,oBAAoB;IAC/B,0EAA0E;IACnE,OAAO;QACZ,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;OAKG;IACI,WAAW,CAAC,MAAwB;QACzC,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;QACtD,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC9D,sEAAsE;QACtE,+DAA+D;QAC/D,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAA6B,CAAC;QAC5E,MAAM,oBAAoB,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAA2B,CAAC;QAC3E,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;YAC5C,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAS;YAC5E,mBAAmB,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG;gBAClC,GAAG,IAAI,GAAG,CACR,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,MAAM,CACjC,CAAC,IAAI,EAAkB,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CACtE,CACF;aACF,CAAC,IAAI,EAAE,CAAC;YACT,mEAAmE;YACnE,0DAA0D;YAC1D,iEAAiE;YACjE,iEAAiE;YACjE,2DAA2D;YAC3D,0DAA0D;YAC1D,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,EAAE,YAAY,CAAC;YAC/C,IAAI,OAAO,YAAY,KAAK,QAAQ,EAAE,CAAC;gBACrC,oBAAoB,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC;YACpD,CAAC;QACH,CAAC;QACD,MAAM,QAAQ,GAAyB;YACrC,aAAa,EAAE,CAAC;YAChB,mBAAmB;YACnB,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,oBAAoB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAClF,CAAC;QACF,aAAa,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACpE,CAAC;CACF;AAED,eAAe,oBAAoB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reporter.cjs","sourceRoot":"","sources":["../../src/reporter/reporter.cts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;;
|
|
1
|
+
{"version":3,"file":"reporter.cjs","sourceRoot":"","sources":["../../src/reporter/reporter.cts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
+
import { type SerializedFailure } from '../diagnosis.js';
|
|
1
2
|
import { type LedgerRow } from './ledger.js';
|
|
3
|
+
/**
|
|
4
|
+
* The scope the CLI graded, from the run-scope view it writes before the
|
|
5
|
+
* suite starts. A run that graded a SELECTION (a named test list, a
|
|
6
|
+
* changed slice) observed no repository-wide debt, so the reporter must
|
|
7
|
+
* not verdict on it.
|
|
8
|
+
*/
|
|
9
|
+
export type ReporterRunScope = 'full' | 'changed' | 'named';
|
|
2
10
|
/** Minimal reporter-side test shape (Playwright's TestCase in practice). */
|
|
3
11
|
export interface ReporterTest {
|
|
4
12
|
id: string;
|
|
@@ -47,11 +55,13 @@ export interface ReporterSuiteLike {
|
|
|
47
55
|
* Args:
|
|
48
56
|
* rows: the per-claim ledger rows.
|
|
49
57
|
* unclaimedObligations: count of run obligations with no claim row.
|
|
58
|
+
* scope: the scope the CLI graded (a selection run never verdicts
|
|
59
|
+
* repository debt it did not observe).
|
|
50
60
|
*
|
|
51
61
|
* Returns:
|
|
52
62
|
* string: the aggregate summary line.
|
|
53
63
|
*/
|
|
54
|
-
export declare function gateSummaryLine(rows: readonly LedgerRow[], unclaimedObligations: number): string;
|
|
64
|
+
export declare function gateSummaryLine(rows: readonly LedgerRow[], unclaimedObligations: number, scope?: ReporterRunScope): string;
|
|
55
65
|
/** Constructor options for the gateforge reporter (trusted-config path). */
|
|
56
66
|
export interface GateforgeReporterOptions {
|
|
57
67
|
/** Run-state dir override (precedence over GATEFORGE_STATE_DIR env). */
|
|
@@ -62,10 +72,45 @@ export interface GateforgeReporterOptions {
|
|
|
62
72
|
outcomesPath?: string;
|
|
63
73
|
/** Obligations document path override (precedence over GATEFORGE_OBLIGATIONS env). */
|
|
64
74
|
obligationsPath?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Absolute CANDIDATE ROOT every reported file identity is relative
|
|
77
|
+
* to. A supervised run hands it as trusted CONSTRUCTOR DATA because
|
|
78
|
+
* the runner child does not run from there: it runs from the
|
|
79
|
+
* selected native config directory, exactly as enumeration did and as
|
|
80
|
+
* the project's own relative paths require. The identities the
|
|
81
|
+
* supervisor registered — and every claim injection keyed by them —
|
|
82
|
+
* are repo-relative, so anchoring them at the child's cwd would
|
|
83
|
+
* rename `frontend/tests/x.spec.ts` to `tests/x.spec.ts`, miss every
|
|
84
|
+
* claim injection and fail every session binding closed.
|
|
85
|
+
*
|
|
86
|
+
* Absent (the standalone reporter a project runs itself, wired only
|
|
87
|
+
* through the environment) means the process cwd, which IS the honest
|
|
88
|
+
* root for that case.
|
|
89
|
+
*/
|
|
90
|
+
candidateRoot?: string;
|
|
91
|
+
/**
|
|
92
|
+
* Absolute path of the engine's own generated preparation-freeze
|
|
93
|
+
* controller spec, when the trusted CLI armed the global native
|
|
94
|
+
* freeze. A test whose location file IS this file is the engine's
|
|
95
|
+
* control, not a candidate case: it produces no lifecycle event, no
|
|
96
|
+
* outcome row and no claim, so the native count and the registered
|
|
97
|
+
* expected set speak about the candidate's own tests only.
|
|
98
|
+
*
|
|
99
|
+
* The comparison is on the resolved absolute path (never a title, a
|
|
100
|
+
* basename or a project-name prefix), so a candidate test that merely
|
|
101
|
+
* borrows the controller's name — or lives beside it — is never
|
|
102
|
+
* excluded. The path arrives already symlink-resolved: the spec lives
|
|
103
|
+
* in a private per-run temporary directory (`/tmp` is commonly a
|
|
104
|
+
* symlink), and the runner reports it under the `testDir` the config
|
|
105
|
+
* was handed, so both sides must name the same physical file. Absent
|
|
106
|
+
* means every reported test is a candidate case, exactly as before.
|
|
107
|
+
*/
|
|
108
|
+
controlSpecPath?: string;
|
|
65
109
|
}
|
|
66
110
|
/**
|
|
67
|
-
* The gateforge reporter.
|
|
68
|
-
*
|
|
111
|
+
* The gateforge reporter. Its constructor signature is the Playwright
|
|
112
|
+
* reporter contract (`(options: object)`); every path and the identity
|
|
113
|
+
* root arrive as options.
|
|
69
114
|
*/
|
|
70
115
|
export declare class GateforgeReporter {
|
|
71
116
|
private readonly rows;
|
|
@@ -81,8 +126,23 @@ export declare class GateforgeReporter {
|
|
|
81
126
|
private readonly runnerErrors;
|
|
82
127
|
/** The lifecycle spool file (null when the run has no state dir). */
|
|
83
128
|
private readonly spoolFile;
|
|
129
|
+
/**
|
|
130
|
+
* The candidate root file identities are relative to, when the
|
|
131
|
+
* trusted supervisor supplied one; null means the process cwd (the
|
|
132
|
+
* standalone reporter's own honest root).
|
|
133
|
+
*/
|
|
134
|
+
private readonly candidateRoot;
|
|
84
135
|
/** Resolved run-state paths (options win, env is the legacy fallback). */
|
|
85
136
|
private readonly resolved;
|
|
137
|
+
/**
|
|
138
|
+
* Absolute path of the engine's own generated preparation-freeze
|
|
139
|
+
* controller spec, or null when this run armed no freeze. A test
|
|
140
|
+
* located exactly at this path is the engine's control: it reaches
|
|
141
|
+
* neither the lifecycle spool, nor the outcomes document, nor the
|
|
142
|
+
* claim registry, so the native count and the registered expected set
|
|
143
|
+
* speak about the candidate's own tests only.
|
|
144
|
+
*/
|
|
145
|
+
private readonly controlSpecPath;
|
|
86
146
|
constructor(options?: GateforgeReporterOptions);
|
|
87
147
|
/**
|
|
88
148
|
* Lifecycle spool write (enforcement-review fix 3): announces the
|
|
@@ -105,12 +165,20 @@ export declare class GateforgeReporter {
|
|
|
105
165
|
status: string;
|
|
106
166
|
workerIndex?: number;
|
|
107
167
|
retry?: number;
|
|
168
|
+
duration?: number;
|
|
169
|
+
errors?: SerializedFailure[];
|
|
108
170
|
}): void;
|
|
109
171
|
/** Playswright `onEnd`: seal the run-level outcome + write artifacts. */
|
|
110
172
|
onEnd(result?: {
|
|
111
173
|
status?: string;
|
|
112
174
|
}): Promise<void>;
|
|
113
175
|
/** Builds non-authoritative counts from the runner events and ledger.
|
|
176
|
+
*
|
|
177
|
+
* The debt here is the LEGACY, reporter-observable total only
|
|
178
|
+
* (blocking claims + every unclaimed obligation, baselined or not).
|
|
179
|
+
* How much of it the adopted baseline forgave is NOT derivable here —
|
|
180
|
+
* the reporter never loads the baseline, the waivers or the graded
|
|
181
|
+
* scope — so the gate owns that number alone.
|
|
114
182
|
*
|
|
115
183
|
* Args:
|
|
116
184
|
* ledger: selected claimed-obligation results from the real engine.
|
|
@@ -150,8 +218,29 @@ export declare class GateforgeReporter {
|
|
|
150
218
|
* source location (its parent is root/project or absent).
|
|
151
219
|
*/
|
|
152
220
|
private isDescribeBoundary;
|
|
153
|
-
/**
|
|
221
|
+
/**
|
|
222
|
+
* Repo-relative posix file of a test (null when unknown), relative to
|
|
223
|
+
* the CANDIDATE ROOT the supervisor handed the reporter — never to
|
|
224
|
+
* the runner child's cwd, which for a nested project is its config
|
|
225
|
+
* directory and would report every identity one directory short.
|
|
226
|
+
*/
|
|
154
227
|
private repoRelativeOf;
|
|
228
|
+
/**
|
|
229
|
+
* Whether this test IS the engine's own freeze controller.
|
|
230
|
+
*
|
|
231
|
+
* The answer is the resolved ABSOLUTE location file against the
|
|
232
|
+
* resolved absolute control path the trusted CLI pinned — nothing
|
|
233
|
+
* else. A test title, a project name, a directory or a basename is
|
|
234
|
+
* never a sufficient identity, so a candidate test that happens to be
|
|
235
|
+
* called `gateforge global preparation freeze`, or that sits beside
|
|
236
|
+
* the control file, is still a candidate case.
|
|
237
|
+
*
|
|
238
|
+
* @param test: the reported test.
|
|
239
|
+
*
|
|
240
|
+
* @returns
|
|
241
|
+
* boolean: true only for the pinned control file itself.
|
|
242
|
+
*/
|
|
243
|
+
private isFreezeControl;
|
|
155
244
|
/** Native annotation claims of one test (`{type: 'gateforge'}`). */
|
|
156
245
|
private annotationClaimsOf;
|
|
157
246
|
/** Injection-key lookup for one test: `<file>#<titlePath.join('>')>`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../../src/reporter/reporter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../../src/reporter/reporter.ts"],"names":[],"mappings":"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"}
|
|
@@ -52,14 +52,33 @@
|
|
|
52
52
|
* construction; a missing drain leaves no sessions open, and the
|
|
53
53
|
* witness rejects every submission fail-closed.
|
|
54
54
|
*/
|
|
55
|
-
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
56
|
-
import { join, relative, sep } from 'node:path';
|
|
55
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
56
|
+
import { join, relative, resolve, sep } from 'node:path';
|
|
57
57
|
import { canonicalOf } from '../json.js';
|
|
58
58
|
import { CLAIM_ANNOTATION_TYPE, CLAIM_INJECTIONS_FILE, ENV_OBLIGATIONS, ENV_OUTCOMES_FILE, ENV_REPORTER_FAIL_RUN, ENV_RUN_ID, ENV_STATE_DIR, ENV_WITNESS_URL, } from '../constants.js';
|
|
59
59
|
import { appendSpoolEvent, spoolPathFor } from '../supervisor/spool.js';
|
|
60
|
+
import { failureDiagnosisOf } from '../diagnosis.js';
|
|
60
61
|
import { WitnessClient } from '../fixture/witness-client.js';
|
|
61
62
|
import { resolveWitnessUrl } from '../fixture/witness-client.js';
|
|
62
63
|
import { claimOf, isBlocking, ledgerRowFor, parseObligationsDocument, } from './ledger.js';
|
|
64
|
+
/** Reads a state-dir JSON document, or null when it is absent or unusable. */
|
|
65
|
+
function readStateView(stateDir, name) {
|
|
66
|
+
try {
|
|
67
|
+
return JSON.parse(readFileSync(join(stateDir, name), 'utf8'));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The scope the CLI graded for this run. Absent or unreadable means a
|
|
75
|
+
* whole-repository run — the conservative reading, since anything
|
|
76
|
+
* narrower means the reporter observed no repository-wide debt.
|
|
77
|
+
*/
|
|
78
|
+
function runScopeOf(stateDir) {
|
|
79
|
+
const scope = readStateView(stateDir, 'run-scope.json')?.scope;
|
|
80
|
+
return scope === 'named' || scope === 'changed' ? scope : 'full';
|
|
81
|
+
}
|
|
63
82
|
/**
|
|
64
83
|
* Computes the honest aggregate line (plan Phase 4 item 7, §2 row): the
|
|
65
84
|
* reporter NEVER prints an overall gate pass based only on claimed rows
|
|
@@ -69,16 +88,26 @@ import { claimOf, isBlocking, ledgerRowFor, parseObligationsDocument, } from './
|
|
|
69
88
|
* Args:
|
|
70
89
|
* rows: the per-claim ledger rows.
|
|
71
90
|
* unclaimedObligations: count of run obligations with no claim row.
|
|
91
|
+
* scope: the scope the CLI graded (a selection run never verdicts
|
|
92
|
+
* repository debt it did not observe).
|
|
72
93
|
*
|
|
73
94
|
* Returns:
|
|
74
95
|
* string: the aggregate summary line.
|
|
75
96
|
*/
|
|
76
|
-
export function gateSummaryLine(rows, unclaimedObligations) {
|
|
97
|
+
export function gateSummaryLine(rows, unclaimedObligations, scope = 'full') {
|
|
77
98
|
const blocking = rows.filter((row) => isBlocking(row.verdict));
|
|
78
99
|
const authority = 'final gate result: the gateforge CLI (test-gates/check), never this reporter';
|
|
79
100
|
if (blocking.length > 0) {
|
|
80
101
|
return `GATEFORGE GATE: FAIL (${String(blocking.length)}/${String(rows.length)} claimed obligations not satisfied; ${authority})`;
|
|
81
102
|
}
|
|
103
|
+
if (unclaimedObligations > 0 && scope !== 'full') {
|
|
104
|
+
// A selection run graded its own claims and nothing else. Printing
|
|
105
|
+
// "NOT PASSED" here would state a verdict about debt the run never
|
|
106
|
+
// observed, and it would contradict the CLI exit code printed
|
|
107
|
+
// seconds later — two verdicts, one run, one of them wrong.
|
|
108
|
+
return (`GATEFORGE GATE: SELECTION (${String(rows.length)} satisfied, 0 blocking; ` +
|
|
109
|
+
`repository verdict not graded here — ${authority})`);
|
|
110
|
+
}
|
|
82
111
|
if (unclaimedObligations > 0) {
|
|
83
112
|
return (`GATEFORGE GATE: NOT PASSED (${String(rows.length)} claimed obligation(s) satisfied, ` +
|
|
84
113
|
`${String(unclaimedObligations)} unclaimed obligation(s) still block; ${authority})`);
|
|
@@ -89,8 +118,9 @@ export function gateSummaryLine(rows, unclaimedObligations) {
|
|
|
89
118
|
return `GATEFORGE GATE: PASS (${String(rows.length)}/${String(rows.length)} claimed obligations satisfied; ${authority})`;
|
|
90
119
|
}
|
|
91
120
|
/**
|
|
92
|
-
* The gateforge reporter.
|
|
93
|
-
*
|
|
121
|
+
* The gateforge reporter. Its constructor signature is the Playwright
|
|
122
|
+
* reporter contract (`(options: object)`); every path and the identity
|
|
123
|
+
* root arrive as options.
|
|
94
124
|
*/
|
|
95
125
|
export class GateforgeReporter {
|
|
96
126
|
rows = [];
|
|
@@ -106,8 +136,23 @@ export class GateforgeReporter {
|
|
|
106
136
|
runnerErrors = [];
|
|
107
137
|
/** The lifecycle spool file (null when the run has no state dir). */
|
|
108
138
|
spoolFile;
|
|
139
|
+
/**
|
|
140
|
+
* The candidate root file identities are relative to, when the
|
|
141
|
+
* trusted supervisor supplied one; null means the process cwd (the
|
|
142
|
+
* standalone reporter's own honest root).
|
|
143
|
+
*/
|
|
144
|
+
candidateRoot;
|
|
109
145
|
/** Resolved run-state paths (options win, env is the legacy fallback). */
|
|
110
146
|
resolved;
|
|
147
|
+
/**
|
|
148
|
+
* Absolute path of the engine's own generated preparation-freeze
|
|
149
|
+
* controller spec, or null when this run armed no freeze. A test
|
|
150
|
+
* located exactly at this path is the engine's control: it reaches
|
|
151
|
+
* neither the lifecycle spool, nor the outcomes document, nor the
|
|
152
|
+
* claim registry, so the native count and the registered expected set
|
|
153
|
+
* speak about the candidate's own tests only.
|
|
154
|
+
*/
|
|
155
|
+
controlSpecPath;
|
|
111
156
|
constructor(options = {}) {
|
|
112
157
|
const stateDir = options.stateDir ?? process.env[ENV_STATE_DIR];
|
|
113
158
|
const runId = options.runId ?? process.env[ENV_RUN_ID];
|
|
@@ -119,6 +164,18 @@ export class GateforgeReporter {
|
|
|
119
164
|
outcomesPath: outcomesPath !== undefined && outcomesPath !== '' ? outcomesPath : null,
|
|
120
165
|
obligationsPath: obligationsPath !== undefined && obligationsPath !== '' ? obligationsPath : null,
|
|
121
166
|
};
|
|
167
|
+
this.candidateRoot =
|
|
168
|
+
options.candidateRoot !== undefined && options.candidateRoot.length > 0
|
|
169
|
+
? resolve(options.candidateRoot)
|
|
170
|
+
: null;
|
|
171
|
+
// Resolved once, in this process, from TRUSTED CONSTRUCTOR DATA: the
|
|
172
|
+
// exact control file the CLI pinned before the run. A candidate test
|
|
173
|
+
// can never name it, and one that merely borrows the controller's
|
|
174
|
+
// title or lives in the same directory is untouched.
|
|
175
|
+
this.controlSpecPath =
|
|
176
|
+
options.controlSpecPath !== undefined && options.controlSpecPath.length > 0
|
|
177
|
+
? resolve(options.controlSpecPath)
|
|
178
|
+
: null;
|
|
122
179
|
const wired = (process.env[ENV_WITNESS_URL] ?? '') !== '' ||
|
|
123
180
|
(this.resolved.stateDir ?? '') !== '';
|
|
124
181
|
if (!wired) {
|
|
@@ -145,6 +202,12 @@ export class GateforgeReporter {
|
|
|
145
202
|
* the dispatch latency (the fixture's session resolve waits up to 5s).
|
|
146
203
|
*/
|
|
147
204
|
onTestBegin(test, result) {
|
|
205
|
+
// The engine's own freeze controller is NOT a candidate case: it
|
|
206
|
+
// opens no session, so the registered expected set never has to
|
|
207
|
+
// contain it and the drain never tries. The exclusion is by the
|
|
208
|
+
// pinned absolute control file, never by title or project name.
|
|
209
|
+
if (this.isFreezeControl(test))
|
|
210
|
+
return;
|
|
148
211
|
if (this.spoolFile === null)
|
|
149
212
|
return;
|
|
150
213
|
const workerIndex = typeof result.workerIndex === 'number' ? result.workerIndex : 0;
|
|
@@ -165,10 +228,56 @@ export class GateforgeReporter {
|
|
|
165
228
|
}
|
|
166
229
|
/** Collects claims + test identity + the outcome row at test end (synchronous). */
|
|
167
230
|
onTestEnd(test, result) {
|
|
168
|
-
//
|
|
169
|
-
//
|
|
231
|
+
// Same absolute-file exclusion as onTestBegin: the engine's own
|
|
232
|
+
// controller contributes NO outcome row, so it never enters the
|
|
233
|
+
// runner-outcomes document supervision compares against the planned
|
|
234
|
+
// expected set, nor the selected-test counts it prints. A refused
|
|
235
|
+
// release still fails the run honestly: the runner's own final status
|
|
236
|
+
// becomes 'failed', the drain records the refusal as a conflict, and
|
|
237
|
+
// the sealed receipt is never written.
|
|
238
|
+
if (this.isFreezeControl(test))
|
|
239
|
+
return;
|
|
170
240
|
const titlePath = this.titlePathOf(test);
|
|
171
241
|
const file = this.repoRelativeOf(test);
|
|
242
|
+
const finishedAt = new Date().toISOString();
|
|
243
|
+
if (this.resolved.stateDir !== null && typeof result.duration === 'number') {
|
|
244
|
+
const diagnosticsDir = join(this.resolved.stateDir, 'diagnostics');
|
|
245
|
+
mkdirSync(diagnosticsDir, { recursive: true });
|
|
246
|
+
appendFileSync(join(diagnosticsDir, 'test-timing.jsonl'), `${JSON.stringify({
|
|
247
|
+
testId: test.id,
|
|
248
|
+
file: file ?? '',
|
|
249
|
+
titlePath,
|
|
250
|
+
status: result.status,
|
|
251
|
+
durationMs: result.duration,
|
|
252
|
+
finishedAt,
|
|
253
|
+
})}\n`, 'utf8');
|
|
254
|
+
}
|
|
255
|
+
if (result.status === 'timedOut' &&
|
|
256
|
+
typeof result.duration === 'number' &&
|
|
257
|
+
this.resolved.stateDir !== null) {
|
|
258
|
+
const timingPath = join(this.resolved.stateDir, 'diagnostics', 'adapter-timing.jsonl');
|
|
259
|
+
let witnessDurationMs = 0;
|
|
260
|
+
if (existsSync(timingPath)) {
|
|
261
|
+
for (const line of readFileSync(timingPath, 'utf8').split(/\r?\n/).filter(Boolean)) {
|
|
262
|
+
try {
|
|
263
|
+
const timing = JSON.parse(line);
|
|
264
|
+
if (timing.testId === test.id &&
|
|
265
|
+
timing.runId === this.resolved.runId &&
|
|
266
|
+
typeof timing.durationMs === 'number') {
|
|
267
|
+
witnessDurationMs += timing.durationMs;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const appDurationMs = Math.max(0, result.duration - witnessDurationMs);
|
|
276
|
+
console.warn(`[gateforge] timeout split for ${test.id}: app/runner ${appDurationMs}ms, ` +
|
|
277
|
+
`witness adapter ${witnessDurationMs}ms`);
|
|
278
|
+
}
|
|
279
|
+
// Phase 4: capture the outcome row for trusted runner supervision
|
|
280
|
+
// (every test, claimed or not — the expected set includes them all).
|
|
172
281
|
this.runnerOutcomes.push({
|
|
173
282
|
testId: test.id,
|
|
174
283
|
...(file !== null ? { file } : { file: '' }),
|
|
@@ -191,6 +300,10 @@ export class GateforgeReporter {
|
|
|
191
300
|
project: this.projectOf(test),
|
|
192
301
|
outcome: result.status,
|
|
193
302
|
attempt: (typeof result.retry === 'number' ? result.retry : 0) + 1,
|
|
303
|
+
// Additive diagnosis for a non-passing test: the CI progress
|
|
304
|
+
// stream and the failures artifact are the only readers, and the
|
|
305
|
+
// CLI screens the message before printing or writing it.
|
|
306
|
+
...(result.status === 'passed' ? {} : (failureDiagnosisOf(result.errors) ?? {})),
|
|
194
307
|
});
|
|
195
308
|
}
|
|
196
309
|
// Claims: native annotations keep working unchanged; Phase 4 adds
|
|
@@ -256,11 +369,12 @@ export class GateforgeReporter {
|
|
|
256
369
|
const httpRoutes = readHttpRoutes(stateDir);
|
|
257
370
|
const ledger = this.ledgerRows(obligations, classifications, records, now, httpRoutes);
|
|
258
371
|
writeJson(stateDir, 'ledger.json', ledger);
|
|
259
|
-
const
|
|
260
|
-
?
|
|
372
|
+
const unclaimedIds = obligations === null
|
|
373
|
+
? new Set()
|
|
261
374
|
: new Set(obligations.obligations
|
|
262
375
|
.filter((entry) => !this.rows.some((row) => row.claims.includes(entry.id)))
|
|
263
|
-
.map((entry) => entry.id))
|
|
376
|
+
.map((entry) => entry.id));
|
|
377
|
+
const unclaimed = unclaimedIds.size;
|
|
264
378
|
const summary = this.runSummary(ledger, obligations, unclaimed);
|
|
265
379
|
writeJson(stateDir, 'run-summary.json', summary);
|
|
266
380
|
console.log(`selected tests: ${String(summary.selectedTests.passed)} passed, ${String(summary.selectedTests.failed)} failed ` +
|
|
@@ -268,9 +382,11 @@ export class GateforgeReporter {
|
|
|
268
382
|
`expected failures: ${String(summary.selectedTests.expectedFailures)})`);
|
|
269
383
|
console.log(`selected claims: ${String(summary.selectedClaims.satisfied)} satisfied, ${String(summary.selectedClaims.blocking)} blocking ` +
|
|
270
384
|
`(selected: ${String(summary.selectedClaims.selected)}, waived: ${String(summary.selectedClaims.waived)})`);
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
385
|
+
// Debt is the gate's to grade: the reporter cannot see the waivers,
|
|
386
|
+
// the scope or the adopted baseline it graded against, so it names
|
|
387
|
+
// no debt count (one run, one number).
|
|
388
|
+
console.log('repository debt: graded by gateforge after the run');
|
|
389
|
+
this.printLedger(ledger, unclaimed, runScopeOf(stateDir));
|
|
274
390
|
this.printRegistryMismatches(obligations, records);
|
|
275
391
|
const blocking = ledger.some((row) => isBlocking(row.verdict));
|
|
276
392
|
if (blocking && process.env[ENV_REPORTER_FAIL_RUN] === '1') {
|
|
@@ -283,6 +399,12 @@ export class GateforgeReporter {
|
|
|
283
399
|
}
|
|
284
400
|
}
|
|
285
401
|
/** Builds non-authoritative counts from the runner events and ledger.
|
|
402
|
+
*
|
|
403
|
+
* The debt here is the LEGACY, reporter-observable total only
|
|
404
|
+
* (blocking claims + every unclaimed obligation, baselined or not).
|
|
405
|
+
* How much of it the adopted baseline forgave is NOT derivable here —
|
|
406
|
+
* the reporter never loads the baseline, the waivers or the graded
|
|
407
|
+
* scope — so the gate owns that number alone.
|
|
286
408
|
*
|
|
287
409
|
* Args:
|
|
288
410
|
* ledger: selected claimed-obligation results from the real engine.
|
|
@@ -307,6 +429,10 @@ export class GateforgeReporter {
|
|
|
307
429
|
const satisfied = ledger.filter((row) => row.verdict === 'satisfied').length;
|
|
308
430
|
const blockingClaims = ledger.filter((row) => isBlocking(row.verdict));
|
|
309
431
|
const waived = ledger.filter((row) => row.verdict === 'waived').length;
|
|
432
|
+
const distinctBlockingClaims = new Set(blockingClaims.map((row) => row.claim)).size;
|
|
433
|
+
// No debt split is computed here: the reporter cannot know which
|
|
434
|
+
// obligations the adopted baseline forgave, and a guessed split
|
|
435
|
+
// contradicts the gate line printed seconds later.
|
|
310
436
|
return {
|
|
311
437
|
schemaVersion: 1,
|
|
312
438
|
selectedTests: { selected: outcomes.length, passed, failed, skipped, expectedFailures },
|
|
@@ -314,7 +440,9 @@ export class GateforgeReporter {
|
|
|
314
440
|
repositoryDebt: {
|
|
315
441
|
obligations: obligations?.obligations.length ?? 0,
|
|
316
442
|
unclaimed,
|
|
317
|
-
|
|
443
|
+
// Legacy total, unchanged: blocking claims + every unclaimed
|
|
444
|
+
// obligation, baselined or not.
|
|
445
|
+
blocking: distinctBlockingClaims + unclaimed,
|
|
318
446
|
},
|
|
319
447
|
};
|
|
320
448
|
}
|
|
@@ -388,14 +516,42 @@ export class GateforgeReporter {
|
|
|
388
516
|
return parent.type !== 'describe';
|
|
389
517
|
return parent.location === undefined || parent.location === null;
|
|
390
518
|
}
|
|
391
|
-
/**
|
|
519
|
+
/**
|
|
520
|
+
* Repo-relative posix file of a test (null when unknown), relative to
|
|
521
|
+
* the CANDIDATE ROOT the supervisor handed the reporter — never to
|
|
522
|
+
* the runner child's cwd, which for a nested project is its config
|
|
523
|
+
* directory and would report every identity one directory short.
|
|
524
|
+
*/
|
|
392
525
|
repoRelativeOf(test) {
|
|
393
526
|
if (test.location?.file === undefined || test.location.file === null)
|
|
394
527
|
return null;
|
|
395
528
|
const raw = test.location.file;
|
|
396
529
|
if (raw.length === 0)
|
|
397
530
|
return null;
|
|
398
|
-
return relative(process.cwd(), raw).split(sep).join('/');
|
|
531
|
+
return relative(this.candidateRoot ?? process.cwd(), raw).split(sep).join('/');
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Whether this test IS the engine's own freeze controller.
|
|
535
|
+
*
|
|
536
|
+
* The answer is the resolved ABSOLUTE location file against the
|
|
537
|
+
* resolved absolute control path the trusted CLI pinned — nothing
|
|
538
|
+
* else. A test title, a project name, a directory or a basename is
|
|
539
|
+
* never a sufficient identity, so a candidate test that happens to be
|
|
540
|
+
* called `gateforge global preparation freeze`, or that sits beside
|
|
541
|
+
* the control file, is still a candidate case.
|
|
542
|
+
*
|
|
543
|
+
* @param test: the reported test.
|
|
544
|
+
*
|
|
545
|
+
* @returns
|
|
546
|
+
* boolean: true only for the pinned control file itself.
|
|
547
|
+
*/
|
|
548
|
+
isFreezeControl(test) {
|
|
549
|
+
if (this.controlSpecPath === null)
|
|
550
|
+
return false;
|
|
551
|
+
const file = test.location?.file;
|
|
552
|
+
if (typeof file !== 'string' || file.length === 0)
|
|
553
|
+
return false;
|
|
554
|
+
return resolve(file) === this.controlSpecPath;
|
|
399
555
|
}
|
|
400
556
|
/** Native annotation claims of one test (`{type: 'gateforge'}`). */
|
|
401
557
|
annotationClaimsOf(test) {
|
|
@@ -549,7 +705,7 @@ export class GateforgeReporter {
|
|
|
549
705
|
* obligations block; the authoritative CLI remains the only final
|
|
550
706
|
* gate result.
|
|
551
707
|
*/
|
|
552
|
-
printLedger(rows, unclaimedObligations) {
|
|
708
|
+
printLedger(rows, unclaimedObligations, scope) {
|
|
553
709
|
const width = Math.max('CLAIMED OBLIGATION'.length, ...rows.map((row) => row.claim.length));
|
|
554
710
|
console.log('\n=== GATEFORGE VERDICTS ===');
|
|
555
711
|
if (rows.length === 0) {
|
|
@@ -564,7 +720,7 @@ export class GateforgeReporter {
|
|
|
564
720
|
console.log(` - records: ${row.recordIds.join(', ')}`);
|
|
565
721
|
}
|
|
566
722
|
}
|
|
567
|
-
console.log(gateSummaryLine(rows, unclaimedObligations));
|
|
723
|
+
console.log(gateSummaryLine(rows, unclaimedObligations, scope));
|
|
568
724
|
console.log('==========================\n');
|
|
569
725
|
}
|
|
570
726
|
/** GF-24 observability: obligations nobody claimed + records nobody claimed. */
|