@sun-asterisk/sungen 3.2.21-beta.1 → 3.2.21-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/delivery.d.ts +3 -0
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +8 -0
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/cli/commands/inspect.d.ts +41 -0
- package/dist/cli/commands/inspect.d.ts.map +1 -0
- package/dist/cli/commands/inspect.js +134 -0
- package/dist/cli/commands/inspect.js.map +1 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/exporters/matrix/build.d.ts +10 -0
- package/dist/exporters/matrix/build.d.ts.map +1 -1
- package/dist/exporters/matrix/build.js +38 -0
- package/dist/exporters/matrix/build.js.map +1 -1
- package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -1
- package/dist/exporters/matrix/render-xlsx.js +29 -1
- package/dist/exporters/matrix/render-xlsx.js.map +1 -1
- package/dist/exporters/matrix/types.d.ts +8 -0
- package/dist/exporters/matrix/types.d.ts.map +1 -1
- package/dist/exporters/matrix/types.js.map +1 -1
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +22 -1
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/flow-contract.d.ts +16 -0
- package/dist/harness/flow-contract.d.ts.map +1 -1
- package/dist/harness/flow-contract.js +26 -2
- package/dist/harness/flow-contract.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/commands/add-flow.md +10 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +25 -0
- package/package.json +3 -3
- package/src/cli/commands/delivery.ts +8 -2
- package/src/cli/commands/inspect.ts +128 -0
- package/src/cli/index.ts +2 -0
- package/src/exporters/matrix/build.ts +40 -0
- package/src/exporters/matrix/render-xlsx.ts +30 -1
- package/src/exporters/matrix/types.ts +8 -0
- package/src/harness/audit.ts +22 -1
- package/src/harness/flow-contract.ts +34 -2
- package/src/orchestrator/templates/ai-src/commands/add-flow.md +10 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +25 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flow-contract.js","sourceRoot":"","sources":["../../src/harness/flow-contract.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"flow-contract.js","sourceRoot":"","sources":["../../src/harness/flow-contract.ts"],"names":[],"mappings":"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"}
|
|
@@ -88,6 +88,11 @@ qa/flows/${input:flow}/
|
|
|
88
88
|
|
|
89
89
|
### 1a. Define the flow's BOUNDARY, then its screens
|
|
90
90
|
|
|
91
|
+
> QA teams often call this level **System Test** — same thing: one fully-integrated business
|
|
92
|
+
> journey verified against the spec. Use whichever name the team knows; the boundary rules
|
|
93
|
+
> below are the ISTQB system-test design rules.
|
|
94
|
+
|
|
95
|
+
|
|
91
96
|
A flow is the **smallest complete business action chain**: one clear trigger ending in ONE
|
|
92
97
|
observable, valuable outcome. Before asking for screens, walk this checklist with the user —
|
|
93
98
|
if 1, 3 or 8 fails, propose SPLITTING into separate flows:
|
|
@@ -127,6 +132,11 @@ outcome:
|
|
|
127
132
|
value: "The customer has paid; the shop has a new order"
|
|
128
133
|
phases: [HP, ER, EH] # journey phases (default); add UI only if the flow owns UI states
|
|
129
134
|
stateful: cart # the mutated collection, if any — enables regression-depth dims
|
|
135
|
+
golden: true # optional — release-critical: Final Inspection expects @golden scenarios here
|
|
136
|
+
external: # optional — legs owned by another team/vendor (System INTEGRATION Testing)
|
|
137
|
+
- name: payment-gateway
|
|
138
|
+
owner: vendor-x
|
|
139
|
+
screens: [payment] # the flow namespaces that leg passes through
|
|
130
140
|
```
|
|
131
141
|
|
|
132
142
|
**A filled contract is an INPUT to generation — never an output.** Like `test-viewpoint.md`,
|
|
@@ -603,6 +603,31 @@ error:
|
|
|
603
603
|
|
|
604
604
|
## Flow Test Generation
|
|
605
605
|
|
|
606
|
+
> **Terminology**: many QA teams call this level **System Test** (ISTQB: verify the fully
|
|
607
|
+
> integrated system against its specified requirements, designed around business
|
|
608
|
+
> processes/use cases). In sungen a *flow* is that system-test suite run through the real UI;
|
|
609
|
+
> its automation then serves as the regression suite on later runs. The names are
|
|
610
|
+
> interchangeable in conversation; the artifacts stay `qa/flows/<name>/`.
|
|
611
|
+
|
|
612
|
+
**Representative, not exhaustive (ISTQB system-test design rule).** A flow case takes ONE
|
|
613
|
+
representative input per branch — one valid value for the main flow, one error value that
|
|
614
|
+
triggers the exception branch and proves the journey recovers. The full equivalence/boundary
|
|
615
|
+
matrix belongs to the OWNING SCREEN (as `@cases` there), never re-run inside the flow: if the
|
|
616
|
+
screen already proved "the field rejects negatives", the flow only proves "the journey survives
|
|
617
|
+
one rejected input". `sungen audit` reports `FLOW-EXHAUSTIVE-INPUTS` when ≥3 same-shape flow
|
|
618
|
+
scenarios differ only in data. **Mocked dependencies**: a flow scenario using `@mock` is
|
|
619
|
+
delivered as *Pass (Mocked)* — verified handling logic, not the real dependency; the delivery's
|
|
620
|
+
External Dependency Tracking table lists it until re-verified real.
|
|
621
|
+
|
|
622
|
+
**Release selection & external legs**: when the contract declares `golden: true`, tag the
|
|
623
|
+
happy-path scenario(s) that prove the outcome with **`@golden`** — that is the set
|
|
624
|
+
`sungen inspect` (Final Inspection, Go/No-Go) runs before a release; `GOLDEN-MISSING` fires
|
|
625
|
+
until it exists, and a @golden scenario that passes only against mocks BLOCKS the release.
|
|
626
|
+
When the contract declares `external:` legs (another team/vendor's system), scenarios touching
|
|
627
|
+
those namespaces are the **System INTEGRATION** group — keep their oracles about the boundary
|
|
628
|
+
(contract/format/handoff), and expect `SIT-MOCK-ONLY` until they are re-verified real.
|
|
629
|
+
|
|
630
|
+
|
|
606
631
|
> **Auto-detect**: if path is `qa/flows/<name>/` → use this section. Skip Steps 1–4 above.
|
|
607
632
|
|
|
608
633
|
**Read `requirements/flow-contract.yaml` FIRST — it is the flow's boundary and the yardstick
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sun-asterisk/sungen",
|
|
3
|
-
"version": "3.2.21-beta.
|
|
3
|
+
"version": "3.2.21-beta.3",
|
|
4
4
|
"description": "Deterministic E2E Test Compiler - Gherkin + Selectors → Playwright tests",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"@babel/types": "^7.28.5",
|
|
40
40
|
"@cucumber/gherkin": "^37.0.0",
|
|
41
41
|
"@cucumber/messages": "^31.0.0",
|
|
42
|
-
"@sungen/driver-data-factory": "3.2.21-beta.
|
|
43
|
-
"@sungen/driver-ui": "3.2.21-beta.
|
|
42
|
+
"@sungen/driver-data-factory": "3.2.21-beta.3",
|
|
43
|
+
"@sungen/driver-ui": "3.2.21-beta.3",
|
|
44
44
|
"chalk": "^5.6.2",
|
|
45
45
|
"commander": "^14.0.2",
|
|
46
46
|
"dotenv": "^17.2.3",
|
|
@@ -100,7 +100,7 @@ function listFeatureTargets(cwd: string, screen: string, kind: UnitKind): Delive
|
|
|
100
100
|
.sort((a, b) => a.featureBaseName.localeCompare(b.featureBaseName));
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
function listAllTargets(cwd: string): DeliveryTarget[] {
|
|
103
|
+
export function listAllTargets(cwd: string): DeliveryTarget[] {
|
|
104
104
|
const targets: DeliveryTarget[] = [];
|
|
105
105
|
const scan = (kind: UnitKind, skip: (n: string) => boolean = () => false) => {
|
|
106
106
|
const root = path.join(cwd, 'qa', qaParent(kind));
|
|
@@ -629,7 +629,7 @@ async function exportTarget(
|
|
|
629
629
|
// Matrix export (Delivery v2 — the default; --legacy keeps the classic workbook)
|
|
630
630
|
// ----------------------------------------------------------------------------
|
|
631
631
|
|
|
632
|
-
function matrixPathsFor(cwd: string, target: DeliveryTarget): MatrixTargetPaths {
|
|
632
|
+
export function matrixPathsFor(cwd: string, target: DeliveryTarget): MatrixTargetPaths {
|
|
633
633
|
const base = qaDir(cwd, target);
|
|
634
634
|
const genBase = generatedDir(cwd, target);
|
|
635
635
|
return {
|
|
@@ -690,6 +690,12 @@ async function exportMatrixTarget(
|
|
|
690
690
|
}
|
|
691
691
|
|
|
692
692
|
log(`${COLOR.bold}${paths.label}${COLOR.reset} — ${model.manifest.itemCount} delivery item(s), ${model.manifest.variantCount} coverage variant(s), state: ${model.manifest.approvalState}`);
|
|
693
|
+
{
|
|
694
|
+
const mocked = model.items.flatMap((it) => it.variants.filter((v) => v.mocked));
|
|
695
|
+
if (mocked.length > 0) {
|
|
696
|
+
log(` ${COLOR.yellow}MOCKED${COLOR.reset}: ${mocked.length} variant(s) verified against mocks (${mocked.slice(0, 4).map((v) => v.ref).join(', ')}${mocked.length > 4 ? ', …' : ''}) — Pass (Mocked) ≠ Pass (Real); see the External Dependency Tracking block on the Coverage sheet.`);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
693
699
|
if (model.perf) {
|
|
694
700
|
const p = model.perf;
|
|
695
701
|
const tone = p.pass ? COLOR.green : COLOR.yellow;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sungen inspect` — Final Inspection: the deterministic Go/No-Go view. (#580 P9)
|
|
3
|
+
*
|
|
4
|
+
* The QA practice this implements: ONCE per release, run only the release-critical
|
|
5
|
+
* (@golden) journeys on the real environment with real (masked) data, on FROZEN code,
|
|
6
|
+
* and answer one question — is this build safe to release? Not a place to find new
|
|
7
|
+
* bugs; a place to refuse a release that is not proven.
|
|
8
|
+
*
|
|
9
|
+
* Every ingredient already exists, this command only reads them:
|
|
10
|
+
* - selection : @golden scenarios (tags pass through to Playwright — `--grep @golden`)
|
|
11
|
+
* - results : the units' Playwright JSON runs (same source delivery reads)
|
|
12
|
+
* - freeze : the approved delivery map's fingerprints — Gate E drift = not frozen
|
|
13
|
+
* - honesty : a @golden variant verified against MOCKS blocks GO — Pass (Mocked)
|
|
14
|
+
* says nothing about the real dependency (External Dependency Tracking)
|
|
15
|
+
* - perf : the qa/perf.yaml verdict, when configured
|
|
16
|
+
*
|
|
17
|
+
* Exit code 0 = GO, 2 = NO-GO — CI-usable as a release gate.
|
|
18
|
+
*/
|
|
19
|
+
import { Command } from 'commander';
|
|
20
|
+
import * as fs from 'fs';
|
|
21
|
+
import { listAllTargets, matrixPathsFor } from './delivery';
|
|
22
|
+
import { loadMatrixModel } from '../../exporters/matrix/export';
|
|
23
|
+
|
|
24
|
+
export interface UnitInspection {
|
|
25
|
+
unit: string;
|
|
26
|
+
approved: boolean;
|
|
27
|
+
frozen: boolean; // no Gate E drift against the approved fingerprints
|
|
28
|
+
goldenTotal: number;
|
|
29
|
+
goldenPassed: number;
|
|
30
|
+
goldenFailed: number;
|
|
31
|
+
goldenPending: number; // golden variants with no run result yet
|
|
32
|
+
goldenMocked: number; // golden variants verified only against mocks
|
|
33
|
+
perfPass: boolean | null; // null = no budget configured / no samples
|
|
34
|
+
reasons: string[]; // what blocks GO for this unit (empty = clean)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ProjectInspection {
|
|
38
|
+
units: UnitInspection[];
|
|
39
|
+
goldenTotal: number;
|
|
40
|
+
verdict: 'GO' | 'NO-GO';
|
|
41
|
+
reasons: string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Pure aggregation over the delivery models — testable without the CLI. */
|
|
45
|
+
export function inspectProject(cwd: string): ProjectInspection {
|
|
46
|
+
const units: UnitInspection[] = [];
|
|
47
|
+
for (const target of listAllTargets(cwd)) {
|
|
48
|
+
const paths = matrixPathsFor(cwd, target);
|
|
49
|
+
if (!fs.existsSync(paths.mapFile)) continue; // no delivery map → not a delivered unit
|
|
50
|
+
const { model } = loadMatrixModel(paths);
|
|
51
|
+
if (!model) continue; // structural map errors surface in delivery
|
|
52
|
+
|
|
53
|
+
const variants = model.items.flatMap((it) => it.variants);
|
|
54
|
+
const golden = variants.filter((v) => v.golden);
|
|
55
|
+
if (golden.length === 0) continue; // Final Inspection reads only the golden set
|
|
56
|
+
|
|
57
|
+
const passed = golden.filter((v) => v.result?.status === 'passed');
|
|
58
|
+
const failed = golden.filter((v) => v.result && ['failed', 'timedOut', 'interrupted'].includes(v.result.status));
|
|
59
|
+
const pending = golden.filter((v) => !v.result);
|
|
60
|
+
const mocked = golden.filter((v) => v.mocked);
|
|
61
|
+
const approved = model.manifest.approvalState === 'approved';
|
|
62
|
+
const frozen = !model.findings.some((f) => f.gate === 'E');
|
|
63
|
+
const perfPass = model.perf ? model.perf.pass : null;
|
|
64
|
+
|
|
65
|
+
const reasons: string[] = [];
|
|
66
|
+
if (!approved) reasons.push('delivery map not approved');
|
|
67
|
+
if (!frozen) reasons.push('sources drifted since approval (not frozen — Gate E)');
|
|
68
|
+
if (failed.length) reasons.push(`${failed.length} golden variant(s) FAILED`);
|
|
69
|
+
if (pending.length) reasons.push(`${pending.length} golden variant(s) have no run result`);
|
|
70
|
+
if (mocked.length) reasons.push(`${mocked.length} golden variant(s) are mock-verified — Pass (Mocked) cannot release`);
|
|
71
|
+
if (perfPass === false) reasons.push('performance budget OVER (qa/perf.yaml)');
|
|
72
|
+
|
|
73
|
+
units.push({
|
|
74
|
+
unit: paths.label ?? target.name,
|
|
75
|
+
approved, frozen,
|
|
76
|
+
goldenTotal: golden.length,
|
|
77
|
+
goldenPassed: passed.length,
|
|
78
|
+
goldenFailed: failed.length,
|
|
79
|
+
goldenPending: pending.length,
|
|
80
|
+
goldenMocked: mocked.length,
|
|
81
|
+
perfPass,
|
|
82
|
+
reasons,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const goldenTotal = units.reduce((n, u) => n + u.goldenTotal, 0);
|
|
87
|
+
const reasons = units.flatMap((u) => u.reasons.map((r) => `${u.unit}: ${r}`));
|
|
88
|
+
if (goldenTotal === 0) reasons.push('no @golden scenario anywhere — nothing proves the release (tag the release-critical journeys)');
|
|
89
|
+
return { units, goldenTotal, verdict: reasons.length === 0 ? 'GO' : 'NO-GO', reasons };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function registerInspectCommand(program: Command): void {
|
|
93
|
+
program
|
|
94
|
+
.command('inspect')
|
|
95
|
+
.description('Final Inspection: Go/No-Go over the @golden set — results, freeze, mocks, perf (exit 2 = NO-GO)')
|
|
96
|
+
.option('--json', 'Output the raw JSON report')
|
|
97
|
+
.action((options) => {
|
|
98
|
+
try {
|
|
99
|
+
const report = inspectProject(process.cwd());
|
|
100
|
+
if (options.json) {
|
|
101
|
+
console.log(JSON.stringify(report, null, 2));
|
|
102
|
+
} else {
|
|
103
|
+
const L = console.log;
|
|
104
|
+
L('');
|
|
105
|
+
L(`━━━ Final Inspection — ${report.goldenTotal} @golden variant(s) across ${report.units.length} unit(s) ━━━`);
|
|
106
|
+
L('');
|
|
107
|
+
for (const u of report.units) {
|
|
108
|
+
const ok = u.reasons.length === 0;
|
|
109
|
+
L(` ${ok ? '✓' : '✗'} ${u.unit} — golden ${u.goldenPassed}/${u.goldenTotal} passed` +
|
|
110
|
+
`${u.goldenMocked ? ` · ${u.goldenMocked} mocked` : ''}` +
|
|
111
|
+
`${u.perfPass === null ? '' : u.perfPass ? ' · perf PASS' : ' · perf OVER'}` +
|
|
112
|
+
` · ${u.approved ? 'approved' : 'NOT approved'} · ${u.frozen ? 'frozen' : 'DRIFTED'}`);
|
|
113
|
+
for (const r of u.reasons) L(` • ${r}`);
|
|
114
|
+
}
|
|
115
|
+
L('');
|
|
116
|
+
L(` Verdict: ${report.verdict === 'GO' ? '✅ GO' : '⛔ NO-GO'}`);
|
|
117
|
+
for (const r of report.units.length === 0 ? report.reasons : (report.verdict === 'NO-GO' ? report.reasons.slice(0, 12) : [])) {
|
|
118
|
+
L(` • ${r}`);
|
|
119
|
+
}
|
|
120
|
+
L('');
|
|
121
|
+
}
|
|
122
|
+
if (report.verdict !== 'GO') process.exitCode = 2;
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
package/src/cli/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { registerFigmaCommand } from './commands/figma';
|
|
|
15
15
|
import { registerAddFlowCommand } from './commands/add-flow';
|
|
16
16
|
import { registerDashboardCommand } from './commands/dashboard';
|
|
17
17
|
import { registerAuditCommand } from './commands/audit';
|
|
18
|
+
import { registerInspectCommand } from './commands/inspect';
|
|
18
19
|
import { registerDepthLintCommand } from './commands/depth-lint';
|
|
19
20
|
import { registerJourneyCommand } from './commands/journey';
|
|
20
21
|
import { registerGateCommand } from './commands/gate';
|
|
@@ -63,6 +64,7 @@ async function main() {
|
|
|
63
64
|
registerAddFlowCommand(program);
|
|
64
65
|
registerDashboardCommand(program);
|
|
65
66
|
registerAuditCommand(program);
|
|
67
|
+
registerInspectCommand(program);
|
|
66
68
|
registerDepthLintCommand(program);
|
|
67
69
|
registerJourneyCommand(program);
|
|
68
70
|
registerGateCommand(program);
|
|
@@ -32,6 +32,8 @@ import {
|
|
|
32
32
|
MAX_VARIANTS_PER_ITEM,
|
|
33
33
|
} from './types';
|
|
34
34
|
import { runGates } from './gates';
|
|
35
|
+
import { capabilityRegistry } from '../../capabilities/registry';
|
|
36
|
+
import { discoverAndRegisterCapabilities } from '../../capabilities/discover';
|
|
35
37
|
|
|
36
38
|
// ---------------------------------------------------------------------------
|
|
37
39
|
// Variant derivation
|
|
@@ -42,6 +44,27 @@ function normalizeShape(text: string): string {
|
|
|
42
44
|
return text.replace(/\{\{[^}]*\}\}/g, '{{*}}').replace(/\s+/g, ' ').trim();
|
|
43
45
|
}
|
|
44
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Is this scenario verified against MOCKED responses, and against which named deps? (#580)
|
|
49
|
+
* The mock capability owns its grammar: prefer the registry's `annotations` + `detectsStep`
|
|
50
|
+
* (driver-mock exports isMockStep — install AND call-count forms); fall back to literal
|
|
51
|
+
* twins of that grammar when the driver is not installed on this machine.
|
|
52
|
+
*/
|
|
53
|
+
export function detectMocked(tags: string[], stepTexts: string[]): { mocked: boolean; mockDeps: string[] } {
|
|
54
|
+
discoverAndRegisterCapabilities();
|
|
55
|
+
const cap = capabilityRegistry.get('mock');
|
|
56
|
+
const tagForms = cap?.annotations ?? ['@mock', '@network'];
|
|
57
|
+
const tagged = tags.some((t) => tagForms.some((a) => t === a || t.startsWith(`${a}:`)));
|
|
58
|
+
const isMockStep = cap?.detectsStep ?? ((t: string) =>
|
|
59
|
+
/\[[^\]]+\]\s+mock\s+is\s+active\b/i.test(t) ||
|
|
60
|
+
/\[[^\]]+\]\s+mock\s+called\s+(?:\{\{[^}]+\}\}|\d+)\s+times?\b/i.test(t));
|
|
61
|
+
const mockDeps = Array.from(new Set(stepTexts
|
|
62
|
+
.filter((t) => isMockStep(t))
|
|
63
|
+
.map((t) => (t.match(/\[([^\]]+)\]\s+mock\b/i) || [])[1])
|
|
64
|
+
.filter((x): x is string => Boolean(x))));
|
|
65
|
+
return { mocked: tagged || mockDeps.length > 0, mockDeps };
|
|
66
|
+
}
|
|
67
|
+
|
|
45
68
|
function deriveLayers(tags: string[]): MatrixLayer[] {
|
|
46
69
|
const layers = new Set<MatrixLayer>(['ui']);
|
|
47
70
|
if (tags.some((t) => t === '@api' || t.startsWith('@api:') || t === '@requires:api')) layers.add('api');
|
|
@@ -178,12 +201,29 @@ export function deriveVariants(inputs: Pick<BuildInputs, 'feature' | 'merged' |
|
|
|
178
201
|
...(apiCallsAreTrigger ? [] : apiRequestLines),
|
|
179
202
|
]));
|
|
180
203
|
|
|
204
|
+
// Mocked-run detection (#580): a pass against a scripted response must never read as a
|
|
205
|
+
// pass against the real dependency. The GRAMMAR authority is the mock capability itself
|
|
206
|
+
// (driver-mock declares `annotations` and exports `detectsStep`) — consult it instead of
|
|
207
|
+
// re-writing its regexes here, so new tag/step forms are picked up automatically. The
|
|
208
|
+
// literal fallback keeps delivery honest on machines WITHOUT the driver installed
|
|
209
|
+
// (delivery reads only feature text + results, it never compiles). Background steps are
|
|
210
|
+
// scanned too — a Background install mocks every scenario in the feature, and the
|
|
211
|
+
// driver's own spec warns that per-scenario tracking misses them.
|
|
212
|
+
const scenarioStepTexts = [
|
|
213
|
+
...inputs.feature.backgroundOrderedSteps.map((st) => st.text),
|
|
214
|
+
...m.feature.orderedSteps.map((st) => st.text),
|
|
215
|
+
];
|
|
216
|
+
const { mocked, mockDeps } = detectMocked(tags, scenarioStepTexts);
|
|
217
|
+
|
|
181
218
|
const base = {
|
|
182
219
|
vpId,
|
|
183
220
|
vpCategory,
|
|
184
221
|
priority: extractPriority(tags),
|
|
185
222
|
mode: mode as 'auto' | 'manual',
|
|
186
223
|
manualReason: manualReason(tags),
|
|
224
|
+
mocked,
|
|
225
|
+
...(mockDeps.length ? { mockDeps } : {}),
|
|
226
|
+
golden: tags.includes('@golden'),
|
|
187
227
|
layers: deriveLayers(tags),
|
|
188
228
|
traces: tags.filter((t) => t.startsWith('@spec:')).map((t) => t.slice('@spec:'.length)),
|
|
189
229
|
runtimeVars: [...runtimeVars],
|
|
@@ -224,7 +224,7 @@ function addMatrixSheet(wb: ExcelJS.Workbook, model: MatrixModel, sungenVersion?
|
|
|
224
224
|
// parent's steps + these read as one continuous procedure.
|
|
225
225
|
numbered(item.triggerDeltas[v.ref] ?? [], item.trigger.length + 1),
|
|
226
226
|
expectedWithVerification(v.oracle, v.verification),
|
|
227
|
-
v.mode === 'manual' ? `Manual${v.manualReason ? ` (${v.manualReason})` : ''}` : 'Auto',
|
|
227
|
+
v.mode === 'manual' ? `Manual${v.manualReason ? ` (${v.manualReason})` : ''}` : (v.mocked ? 'Auto · Mocked' : 'Auto'),
|
|
228
228
|
v.traces.join(', '),
|
|
229
229
|
variantState(v),
|
|
230
230
|
isoDate(v.result?.startTime),
|
|
@@ -330,6 +330,35 @@ function addCoverageSheet(wb: ExcelJS.Workbook, model: MatrixModel, sungenVersio
|
|
|
330
330
|
rowIdx += 2;
|
|
331
331
|
}
|
|
332
332
|
|
|
333
|
+
// --- External Dependency Tracking (#580, ISTQB system-test practice): every case that
|
|
334
|
+
// ran against a MOCK is listed centrally. Its Result stays in the canonical vocabulary
|
|
335
|
+
// (the roll-up formulas depend on it); THIS table is where "Pass (Mocked) ≠ Pass (Real)"
|
|
336
|
+
// is visible — a case verified only against mocks is not Done until re-verified real.
|
|
337
|
+
{
|
|
338
|
+
const mockedVars = model.items.flatMap((it) => it.variants.filter((v) => v.mocked).map((v) => ({ it, v })));
|
|
339
|
+
if (mockedVars.length > 0) {
|
|
340
|
+
dataCell(ws.getRow(rowIdx).getCell(2), 'External Dependency Tracking — mock-verified cases (Pass here = Pass (Mocked), not Pass (Real))', { bold: true });
|
|
341
|
+
rowIdx++;
|
|
342
|
+
const hd = ws.getRow(rowIdx);
|
|
343
|
+
dataCell(hd.getCell(2), 'Variant');
|
|
344
|
+
dataCell(hd.getCell(3), 'Mocked dependency');
|
|
345
|
+
dataCell(hd.getCell(4), 'Delivery item');
|
|
346
|
+
dataCell(hd.getCell(5), 'Result');
|
|
347
|
+
dataCell(hd.getCell(8), 'Real-environment re-verify');
|
|
348
|
+
rowIdx++;
|
|
349
|
+
for (const { it, v } of mockedVars) {
|
|
350
|
+
const r = ws.getRow(rowIdx);
|
|
351
|
+
dataCell(r.getCell(2), v.ref);
|
|
352
|
+
dataCell(r.getCell(3), (v.mockDeps ?? []).join(', ') || '(scenario-level @mock)');
|
|
353
|
+
dataCell(r.getCell(4), it.id);
|
|
354
|
+
dataCell(r.getCell(5), variantState(v));
|
|
355
|
+
dataCell(r.getCell(8), '');
|
|
356
|
+
rowIdx++;
|
|
357
|
+
}
|
|
358
|
+
rowIdx++;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
333
362
|
// --- Requirement coverage (review §6): every requirement id gets ONE explicit status.
|
|
334
363
|
if (model.requirements.length > 0) {
|
|
335
364
|
dataCell(ws.getRow(rowIdx).getCell(2), 'Requirement coverage — every id has an explicit status', { bold: true });
|
|
@@ -92,6 +92,14 @@ export interface CoverageVariant {
|
|
|
92
92
|
/** 'auto' | 'manual'; manual keeps its @manual:Mx reason. */
|
|
93
93
|
mode: 'auto' | 'manual';
|
|
94
94
|
manualReason?: string;
|
|
95
|
+
/** The scenario runs against MOCKED network responses (@mock / `[x] mock is active`) —
|
|
96
|
+
* a pass verifies the handling logic, not the real dependency (Pass (Mocked) ≠ Pass (Real),
|
|
97
|
+
* #580: ISTQB external-dependency tracking). */
|
|
98
|
+
mocked?: boolean;
|
|
99
|
+
/** Named mocked dependencies (`[name] mock is active` refs), for the tracking table. */
|
|
100
|
+
mockDeps?: string[];
|
|
101
|
+
/** Release-critical: tagged @golden — the Final Inspection set (`sungen inspect`, #580 P9). */
|
|
102
|
+
golden?: boolean;
|
|
95
103
|
layers: MatrixLayer[];
|
|
96
104
|
/** @spec:* requirement traces. */
|
|
97
105
|
traces: string[];
|
package/src/harness/audit.ts
CHANGED
|
@@ -169,7 +169,7 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
169
169
|
const isUiFlowUnit = /^flows\//.test(catalogScreenName);
|
|
170
170
|
const flowQ: FlowQualityResult = isUiFlowUnit
|
|
171
171
|
? flowQuality(screenDir, scenarios)
|
|
172
|
-
: { hasContract: false, errors: [], outcomeProven: false, outcomeManualOnly: false, offGoal: [], offGoalRatio: 0, offGoalCategories: [], phases: [], phaseRatio: 1, handoffs: { total: 0, asserted: 0, ratio: 1 } };
|
|
172
|
+
: { hasContract: false, errors: [], outcomeProven: false, outcomeManualOnly: false, offGoal: [], offGoalRatio: 0, offGoalCategories: [], phases: [], phaseRatio: 1, handoffs: { total: 0, asserted: 0, ratio: 1 }, sit: [] };
|
|
173
173
|
const gateCatalog = (flowQ.hasContract && !declaredPageType(viewpointText, catalog))
|
|
174
174
|
? { ...catalog, page_types: {} } as Catalog
|
|
175
175
|
: catalog;
|
|
@@ -351,6 +351,15 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
351
351
|
for (const ph of flowQ.phases.filter((p) => !p.covered || !p.automated)) {
|
|
352
352
|
findings.push(`FLOW-PHASE-${ph.phase}-MISSING: journey phase ${ph.phase} (${ph.phase === 'HP' ? 'happy path proving the outcome' : ph.phase === 'ER' ? 'error recovery — validation must not trap the journey' : 'guards — direct access / back / refresh'}) is ${ph.covered ? 'covered only by @manual' : 'not covered'} → it does not count toward flowCoverage until automated.`);
|
|
353
353
|
}
|
|
354
|
+
// #580 P10 — the QA matrix keeps System INTEGRATION cases (external-system legs)
|
|
355
|
+
// separate, and holds their exit criteria open while they are mock-verified only.
|
|
356
|
+
for (const x of flowQ.sit.filter((v) => v.mockOnly).slice(0, 4)) {
|
|
357
|
+
findings.push(`SIT-MOCK-ONLY: "${x.name}" touches the external system "${x.external}" but is verified only against mocks — Pass (Mocked) says nothing about the real vendor behaviour. Re-verify on the real dependency before calling it Done (the delivery's External Dependency Tracking table holds the checklist).`);
|
|
358
|
+
}
|
|
359
|
+
// #580 P9 — a release-critical flow must carry its Final-Inspection selection.
|
|
360
|
+
if (flowQ.contract!.golden && !/@golden\b/i.test(featureText)) {
|
|
361
|
+
findings.push(`GOLDEN-MISSING: the contract declares \`golden: true\` (release-critical) but no scenario is tagged @golden — Final Inspection (\`sungen inspect\`) will have nothing to run for this flow. Tag the happy-path scenario(s) that prove the outcome.`);
|
|
362
|
+
}
|
|
354
363
|
if (flowQ.handoffs.total > 0 && flowQ.handoffs.ratio < 0.7) {
|
|
355
364
|
findings.push(`FLOW-HANDOFF-SHALLOW: only ${flowQ.handoffs.asserted}/${flowQ.handoffs.total} cross-screen transitions are followed by an assertion in the new screen — carried state (the thing a flow exists to verify) is unproven at the handoff. businessDepth is capped until asserted.`);
|
|
356
365
|
}
|
|
@@ -500,6 +509,18 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
500
509
|
for (const d of ownership.duplicates.slice(0, 6)) {
|
|
501
510
|
findings.push(`DUPLICATE-FLOW-OWNERSHIP: "${d.scenario}" has the same shape as a scenario in flow "${d.flow}" — keep one owner (screen-local vs flow); the other should only reference/set up.`);
|
|
502
511
|
}
|
|
512
|
+
// #580 — the flow→screen half of the ownership rule (ISTQB system-test design:
|
|
513
|
+
// representative, not exhaustive). A flow's cases take ONE representative input per
|
|
514
|
+
// branch — one valid value on the main flow, one error value that triggers the
|
|
515
|
+
// exception flow. An EP/data-family cluster (≥3 same-shape scenarios differing only
|
|
516
|
+
// in data) inside a FLOW is the screen's validation matrix being re-run where it
|
|
517
|
+
// does not belong: the screen already proved the field rejects each class; the flow
|
|
518
|
+
// only has to prove the journey survives one of them. Advisory — never a gate.
|
|
519
|
+
if (isUiFlow) {
|
|
520
|
+
for (const c of duplicates.clusters.filter((x) => !x.sameDataLikely && x.scenarios.length >= 3).slice(0, 4)) {
|
|
521
|
+
findings.push(`FLOW-EXHAUSTIVE-INPUTS: ${c.scenarios.length} same-shape scenarios differ only in data (${c.scenarios.slice(0, 3).join(' | ')}${c.scenarios.length > 3 ? ' …' : ''}) — a system-test flow uses REPRESENTATIVE inputs (one valid + one that triggers the exception branch); the full EP/boundary matrix belongs to the owning screen (or a @cases dataset there). Keep one representative per branch here.`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
503
524
|
for (const u of unsourced.slice(0, 6)) {
|
|
504
525
|
findings.push(`UNSOURCEABLE-SCENARIO: "${u}" doesn't trace to any FR / viewpoint item — link it to a source, or tag it @exploration (not part of the official suite).`);
|
|
505
526
|
}
|
|
@@ -40,6 +40,11 @@ export interface FlowContract {
|
|
|
40
40
|
/** The mutated collection (cart, order, application …) — enables regression dims. */
|
|
41
41
|
stateful?: string;
|
|
42
42
|
budgets?: Record<string, number>;
|
|
43
|
+
/** External-system legs (owned by another team/vendor) — ISTQB System INTEGRATION Testing
|
|
44
|
+
* scope (#580 P10). Scenarios touching these namespaces are classified SIT. */
|
|
45
|
+
external?: Array<{ name: string; owner?: string; screens: string[] }>;
|
|
46
|
+
/** This flow is release-critical: Final Inspection expects a @golden scenario here (P9). */
|
|
47
|
+
golden?: boolean;
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
export interface FlowQualityResult {
|
|
@@ -60,6 +65,9 @@ export interface FlowQualityResult {
|
|
|
60
65
|
phaseRatio: number;
|
|
61
66
|
/** Cross-namespace transitions followed by an assertion / all transitions. */
|
|
62
67
|
handoffs: { total: number; asserted: number; ratio: number };
|
|
68
|
+
/** ISTQB SIT classification (#580 P10): scenarios whose steps touch an external-system
|
|
69
|
+
* namespace declared in the contract's `external:` block. */
|
|
70
|
+
sit: Array<{ name: string; external: string; mockOnly: boolean }>;
|
|
63
71
|
}
|
|
64
72
|
|
|
65
73
|
const DEFAULT_PHASES = ['HP', 'ER', 'EH'];
|
|
@@ -100,6 +108,16 @@ export function loadFlowContract(unitDir: string): { contract: FlowContract | nu
|
|
|
100
108
|
phases,
|
|
101
109
|
stateful: raw.stateful !== undefined ? String(raw.stateful).toLowerCase() : undefined,
|
|
102
110
|
budgets: (raw.budgets && typeof raw.budgets === 'object') ? raw.budgets as Record<string, number> : undefined,
|
|
111
|
+
external: Array.isArray(raw.external)
|
|
112
|
+
? (raw.external as Array<Record<string, unknown>>)
|
|
113
|
+
.filter((e) => e && typeof e === 'object' && e.name)
|
|
114
|
+
.map((e) => ({
|
|
115
|
+
name: String(e.name),
|
|
116
|
+
owner: e.owner !== undefined ? String(e.owner) : undefined,
|
|
117
|
+
screens: Array.isArray(e.screens) ? (e.screens as unknown[]).map((x) => String(x).toLowerCase()) : [],
|
|
118
|
+
}))
|
|
119
|
+
: undefined,
|
|
120
|
+
golden: raw.golden === true,
|
|
103
121
|
},
|
|
104
122
|
errors: [],
|
|
105
123
|
};
|
|
@@ -139,7 +157,7 @@ export function flowQuality(unitDir: string, scenarios: ScenarioInfo[]): FlowQua
|
|
|
139
157
|
const neutral: FlowQualityResult = {
|
|
140
158
|
hasContract: false, errors, outcomeProven: false, outcomeManualOnly: false,
|
|
141
159
|
offGoal: [], offGoalRatio: 0, offGoalCategories: [],
|
|
142
|
-
phases: [], phaseRatio: 1, handoffs: { total: 0, asserted: 0, ratio: 1 },
|
|
160
|
+
phases: [], phaseRatio: 1, handoffs: { total: 0, asserted: 0, ratio: 1 }, sit: [],
|
|
143
161
|
};
|
|
144
162
|
if (!contract) return neutral;
|
|
145
163
|
|
|
@@ -202,12 +220,26 @@ export function flowQuality(unitDir: string, scenarios: ScenarioInfo[]): FlowQua
|
|
|
202
220
|
}
|
|
203
221
|
const handoffs = { total, asserted, ratio: total ? asserted / total : 1 };
|
|
204
222
|
|
|
223
|
+
// --- SIT classification (#580 P10): a scenario whose steps touch an external-system
|
|
224
|
+
// namespace belongs to the System INTEGRATION group the QA matrix keeps separate. When
|
|
225
|
+
// that scenario is also verified only against mocks, its pass says nothing about the
|
|
226
|
+
// real vendor behaviour — the exit criteria the QA doc holds open (Pass (Mocked)).
|
|
227
|
+
const MOCK_STEP = /\[[^\]]+\]\s+mock\s+(?:is\s+active|called)\b/i;
|
|
228
|
+
const sit: FlowQualityResult['sit'] = [];
|
|
229
|
+
for (const leg of contract.external ?? []) {
|
|
230
|
+
const legScreens = new Set(leg.screens);
|
|
231
|
+
for (const s of scenarios) {
|
|
232
|
+
if (!namespacesInOrder(s).some((ns) => legScreens.has(ns))) continue;
|
|
233
|
+
sit.push({ name: s.name.slice(0, 80), external: leg.name, mockOnly: MOCK_STEP.test(s.stepsText) });
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
205
237
|
return {
|
|
206
238
|
hasContract: true, contract, errors: [],
|
|
207
239
|
outcomeProven, outcomeManualOnly,
|
|
208
240
|
offGoal: offGoalScenarios.map((s) => s.name.slice(0, 80)),
|
|
209
241
|
offGoalRatio, offGoalCategories,
|
|
210
|
-
phases, phaseRatio, handoffs,
|
|
242
|
+
phases, phaseRatio, handoffs, sit,
|
|
211
243
|
};
|
|
212
244
|
}
|
|
213
245
|
|
|
@@ -88,6 +88,11 @@ qa/flows/${input:flow}/
|
|
|
88
88
|
|
|
89
89
|
### 1a. Define the flow's BOUNDARY, then its screens
|
|
90
90
|
|
|
91
|
+
> QA teams often call this level **System Test** — same thing: one fully-integrated business
|
|
92
|
+
> journey verified against the spec. Use whichever name the team knows; the boundary rules
|
|
93
|
+
> below are the ISTQB system-test design rules.
|
|
94
|
+
|
|
95
|
+
|
|
91
96
|
A flow is the **smallest complete business action chain**: one clear trigger ending in ONE
|
|
92
97
|
observable, valuable outcome. Before asking for screens, walk this checklist with the user —
|
|
93
98
|
if 1, 3 or 8 fails, propose SPLITTING into separate flows:
|
|
@@ -127,6 +132,11 @@ outcome:
|
|
|
127
132
|
value: "The customer has paid; the shop has a new order"
|
|
128
133
|
phases: [HP, ER, EH] # journey phases (default); add UI only if the flow owns UI states
|
|
129
134
|
stateful: cart # the mutated collection, if any — enables regression-depth dims
|
|
135
|
+
golden: true # optional — release-critical: Final Inspection expects @golden scenarios here
|
|
136
|
+
external: # optional — legs owned by another team/vendor (System INTEGRATION Testing)
|
|
137
|
+
- name: payment-gateway
|
|
138
|
+
owner: vendor-x
|
|
139
|
+
screens: [payment] # the flow namespaces that leg passes through
|
|
130
140
|
```
|
|
131
141
|
|
|
132
142
|
**A filled contract is an INPUT to generation — never an output.** Like `test-viewpoint.md`,
|
|
@@ -603,6 +603,31 @@ error:
|
|
|
603
603
|
|
|
604
604
|
## Flow Test Generation
|
|
605
605
|
|
|
606
|
+
> **Terminology**: many QA teams call this level **System Test** (ISTQB: verify the fully
|
|
607
|
+
> integrated system against its specified requirements, designed around business
|
|
608
|
+
> processes/use cases). In sungen a *flow* is that system-test suite run through the real UI;
|
|
609
|
+
> its automation then serves as the regression suite on later runs. The names are
|
|
610
|
+
> interchangeable in conversation; the artifacts stay `qa/flows/<name>/`.
|
|
611
|
+
|
|
612
|
+
**Representative, not exhaustive (ISTQB system-test design rule).** A flow case takes ONE
|
|
613
|
+
representative input per branch — one valid value for the main flow, one error value that
|
|
614
|
+
triggers the exception branch and proves the journey recovers. The full equivalence/boundary
|
|
615
|
+
matrix belongs to the OWNING SCREEN (as `@cases` there), never re-run inside the flow: if the
|
|
616
|
+
screen already proved "the field rejects negatives", the flow only proves "the journey survives
|
|
617
|
+
one rejected input". `sungen audit` reports `FLOW-EXHAUSTIVE-INPUTS` when ≥3 same-shape flow
|
|
618
|
+
scenarios differ only in data. **Mocked dependencies**: a flow scenario using `@mock` is
|
|
619
|
+
delivered as *Pass (Mocked)* — verified handling logic, not the real dependency; the delivery's
|
|
620
|
+
External Dependency Tracking table lists it until re-verified real.
|
|
621
|
+
|
|
622
|
+
**Release selection & external legs**: when the contract declares `golden: true`, tag the
|
|
623
|
+
happy-path scenario(s) that prove the outcome with **`@golden`** — that is the set
|
|
624
|
+
`sungen inspect` (Final Inspection, Go/No-Go) runs before a release; `GOLDEN-MISSING` fires
|
|
625
|
+
until it exists, and a @golden scenario that passes only against mocks BLOCKS the release.
|
|
626
|
+
When the contract declares `external:` legs (another team/vendor's system), scenarios touching
|
|
627
|
+
those namespaces are the **System INTEGRATION** group — keep their oracles about the boundary
|
|
628
|
+
(contract/format/handoff), and expect `SIT-MOCK-ONLY` until they are re-verified real.
|
|
629
|
+
|
|
630
|
+
|
|
606
631
|
> **Auto-detect**: if path is `qa/flows/<name>/` → use this section. Skip Steps 1–4 above.
|
|
607
632
|
|
|
608
633
|
**Read `requirements/flow-contract.yaml` FIRST — it is the flow's boundary and the yardstick
|