@sun-asterisk/sungen 3.2.16-beta.4 → 3.2.16-beta.5
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/exporters/matrix/build.d.ts +14 -2
- package/dist/exporters/matrix/build.d.ts.map +1 -1
- package/dist/exporters/matrix/build.js +55 -20
- package/dist/exporters/matrix/build.js.map +1 -1
- package/dist/exporters/matrix/gates.js +16 -1
- package/dist/exporters/matrix/gates.js.map +1 -1
- package/dist/exporters/matrix/map-loader.d.ts.map +1 -1
- package/dist/exporters/matrix/map-loader.js +2 -0
- package/dist/exporters/matrix/map-loader.js.map +1 -1
- package/dist/exporters/matrix/render-csv.d.ts.map +1 -1
- package/dist/exporters/matrix/render-csv.js +8 -10
- package/dist/exporters/matrix/render-csv.js.map +1 -1
- package/dist/exporters/matrix/render-xlsx.d.ts +14 -0
- package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -1
- package/dist/exporters/matrix/render-xlsx.js +62 -42
- package/dist/exporters/matrix/render-xlsx.js.map +1 -1
- package/dist/exporters/matrix/types.d.ts +18 -2
- package/dist/exporters/matrix/types.d.ts.map +1 -1
- package/dist/exporters/matrix/types.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/commands/delivery.md +12 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +12 -6
- package/package.json +3 -3
- package/src/exporters/matrix/build.ts +56 -17
- package/src/exporters/matrix/gates.ts +16 -1
- package/src/exporters/matrix/map-loader.ts +2 -0
- package/src/exporters/matrix/render-csv.ts +9 -11
- package/src/exporters/matrix/render-xlsx.ts +59 -43
- package/src/exporters/matrix/types.ts +19 -3
- package/src/orchestrator/templates/ai-src/commands/delivery.md +12 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +12 -6
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* and result), so downstream pipelines can re-derive both views from one file.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import { itemModeLabel, itemResultLabel } from './render-xlsx';
|
|
8
|
+
import { variantState } from './build';
|
|
9
|
+
import { coverageSummary, itemModeLabel, itemResultLabel } from './render-xlsx';
|
|
10
10
|
import { MatrixModel } from './types';
|
|
11
11
|
|
|
12
12
|
const HEADERS = [
|
|
@@ -35,9 +35,6 @@ export function renderMatrixCsv(model: MatrixModel): string {
|
|
|
35
35
|
lines.push(HEADERS.map(esc).join(','));
|
|
36
36
|
|
|
37
37
|
for (const item of model.items) {
|
|
38
|
-
const triggerTexts = new Set(item.variants.map((v) => v.trigger.join('\n')));
|
|
39
|
-
const triggerIsCommon = triggerTexts.size === 1;
|
|
40
|
-
const commonPre = new Set(item.precondition);
|
|
41
38
|
lines.push([
|
|
42
39
|
'item',
|
|
43
40
|
item.id,
|
|
@@ -46,9 +43,9 @@ export function renderMatrixCsv(model: MatrixModel): string {
|
|
|
46
43
|
item.category,
|
|
47
44
|
item.priority,
|
|
48
45
|
item.precondition.join('\n'),
|
|
49
|
-
|
|
50
|
-
//
|
|
51
|
-
|
|
46
|
+
coverageSummary(item),
|
|
47
|
+
// Shared leading steps only; each variant row carries its remaining steps.
|
|
48
|
+
item.trigger.join('\n'),
|
|
52
49
|
item.oracle,
|
|
53
50
|
itemModeLabel(item),
|
|
54
51
|
item.traces.join(' '),
|
|
@@ -69,13 +66,14 @@ export function renderMatrixCsv(model: MatrixModel): string {
|
|
|
69
66
|
v.condition,
|
|
70
67
|
'', '',
|
|
71
68
|
// Precondition DELTA — variant-specific setup only (review GAP-03).
|
|
72
|
-
v.
|
|
69
|
+
(item.preconditionDeltas[v.ref] ?? []).join('\n'),
|
|
73
70
|
v.data.join('\n'),
|
|
74
|
-
|
|
71
|
+
// Steps after the parent's shared prefix only.
|
|
72
|
+
(item.triggerDeltas[v.ref] ?? []).join('\n'),
|
|
75
73
|
expected,
|
|
76
74
|
v.mode === 'manual' ? 'Manual' : 'Auto',
|
|
77
75
|
v.traces.join(' '),
|
|
78
|
-
|
|
76
|
+
variantState(v),
|
|
79
77
|
isoDate(v.result?.startTime),
|
|
80
78
|
v.result?.error ? String(v.result.error).slice(0, 200) : '',
|
|
81
79
|
'',
|
|
@@ -27,8 +27,8 @@ import {
|
|
|
27
27
|
BLACK,
|
|
28
28
|
LAVENDER,
|
|
29
29
|
} from '../xlsx-report-builder';
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
30
|
+
import { variantState } from './build';
|
|
31
|
+
import { DeliveryItem, MatrixModel, MapCategory } from './types';
|
|
32
32
|
|
|
33
33
|
const CATEGORY_ORDER: MapCategory[] = ['normal', 'abnormal', 'security', 'nfr'];
|
|
34
34
|
const SOFT_BLUE = { argb: 'FFDCE6F1' };
|
|
@@ -63,22 +63,21 @@ export function itemModeLabel(item: DeliveryItem): string {
|
|
|
63
63
|
return item.mode === 'manual' ? 'Manual' : 'Auto';
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Parent roll-up label. It must NEVER be exactly one of the variant state words
|
|
68
|
+
* (`Passed`/`Failed`/`Blocked`/`Pending`/`N/A`) — the summary band counts the
|
|
69
|
+
* Result column with exact-match COUNTIF over parent AND child rows, so a parent
|
|
70
|
+
* reading plain "Pending" was counted as an extra pending variant (109 instead
|
|
71
|
+
* of 77 on st-login).
|
|
72
|
+
*/
|
|
66
73
|
export function itemResultLabel(item: DeliveryItem): string {
|
|
67
|
-
const { passed, failed, blocked,
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
not_run: 'Pending',
|
|
75
|
-
};
|
|
76
|
-
return map[item.result];
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function variantResultLabel(v: CoverageVariant): string {
|
|
80
|
-
if (!v.result) return 'Pending';
|
|
81
|
-
return statusToTestResult(v.result.status);
|
|
74
|
+
const { passed, failed, blocked, na } = item.resultCounts;
|
|
75
|
+
const eff = item.variants.length - na;
|
|
76
|
+
const suffix = na > 0 ? ` · ${na} N/A` : '';
|
|
77
|
+
if (item.result === 'na') return 'All N/A';
|
|
78
|
+
if (item.result === 'failed') return `${passed}/${eff} Passed · ${failed} Failed${suffix}`;
|
|
79
|
+
if (item.result === 'blocked') return `${passed}/${eff} Passed · ${blocked} Blocked${suffix}`;
|
|
80
|
+
return `${passed}/${eff} Passed${suffix}`;
|
|
82
81
|
}
|
|
83
82
|
|
|
84
83
|
/** ISO date (2026-08-04) — unambiguous across locales (review §9). */
|
|
@@ -89,10 +88,24 @@ function isoDate(startTime: string | undefined): string {
|
|
|
89
88
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
90
89
|
}
|
|
91
90
|
|
|
92
|
-
function numbered(lines: string[]): string {
|
|
91
|
+
function numbered(lines: string[], start = 1): string {
|
|
93
92
|
if (lines.length === 0) return '';
|
|
94
|
-
if (lines.length === 1) return lines[0];
|
|
95
|
-
return lines.map((l, i) => `${
|
|
93
|
+
if (lines.length === 1 && start === 1) return lines[0];
|
|
94
|
+
return lines.map((l, i) => `${start + i}. ${l}`).join('\n');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Compact coverage summary for the parent row. A declared `dimensions:` digest
|
|
99
|
+
* wins ("15 variants — required ×3 · format ×9 …"); otherwise a single variant
|
|
100
|
+
* shows its condition and a group falls back to its variant REFS. Concatenating
|
|
101
|
+
* every variant's full title turned this cell into an 800-character paragraph.
|
|
102
|
+
*/
|
|
103
|
+
export function coverageSummary(item: DeliveryItem): string {
|
|
104
|
+
const n = item.variants.length;
|
|
105
|
+
const head = `${n} variant${n === 1 ? '' : 's'}:`;
|
|
106
|
+
if (item.dimensions) return `${head} ${item.dimensions}`;
|
|
107
|
+
if (n === 1) return `${head} ${item.variants[0].condition}`;
|
|
108
|
+
return `${head} ${item.variants.map((v) => v.ref).join(' · ')}`;
|
|
96
109
|
}
|
|
97
110
|
|
|
98
111
|
/** Expected cell = observable outcomes; the HOW moves under a separate heading. */
|
|
@@ -154,11 +167,16 @@ function addMatrixSheet(wb: ExcelJS.Workbook, model: MatrixModel, sungenVersion?
|
|
|
154
167
|
// Summary band (row 6) — items vs variants stay separate metrics by design (I8).
|
|
155
168
|
// The execution counters are LIVE formulas over the variant Result cells, so
|
|
156
169
|
// hand-entered results update the totals (review GAP-06). Variant cells hold the
|
|
157
|
-
// exact state words
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
|
|
170
|
+
// exact state words and parent cells NEVER do (itemResultLabel + the roll-up
|
|
171
|
+
// formula always compose a "n/m Passed …" label), so exact-match COUNTIF over
|
|
172
|
+
// the whole column counts variants only.
|
|
173
|
+
const sum = (k: 'passed' | 'failed' | 'blocked' | 'notRun' | 'na'): number =>
|
|
174
|
+
model.items.reduce((a, i) => a + i.resultCounts[k], 0);
|
|
175
|
+
const passed = sum('passed');
|
|
176
|
+
const failed = sum('failed');
|
|
177
|
+
const blocked = sum('blocked');
|
|
178
|
+
const notRun = sum('notRun');
|
|
179
|
+
const na = sum('na');
|
|
162
180
|
const RANGE = 'M9:M10000';
|
|
163
181
|
const band = ws.getRow(6);
|
|
164
182
|
const bandCells: Array<[string, ExcelJS.CellValue]> = [
|
|
@@ -166,9 +184,9 @@ function addMatrixSheet(wb: ExcelJS.Workbook, model: MatrixModel, sungenVersion?
|
|
|
166
184
|
['variants', `Coverage variants: ${model.manifest.variantCount}`],
|
|
167
185
|
['passed', { formula: `"Passed: "&COUNTIF(${RANGE},"Passed")`, result: `Passed: ${passed}` } as ExcelJS.CellValue],
|
|
168
186
|
['failed', { formula: `"Failed: "&COUNTIF(${RANGE},"Failed")`, result: `Failed: ${failed}` } as ExcelJS.CellValue],
|
|
169
|
-
['blocked', { formula: `"Blocked: "&COUNTIF(${RANGE},"Blocked")`, result:
|
|
187
|
+
['blocked', { formula: `"Blocked: "&COUNTIF(${RANGE},"Blocked")`, result: `Blocked: ${blocked}` } as ExcelJS.CellValue],
|
|
170
188
|
['pending', { formula: `"Pending: "&COUNTIF(${RANGE},"Pending")`, result: `Pending: ${notRun}` } as ExcelJS.CellValue],
|
|
171
|
-
['na', { formula: `"N/A: "&COUNTIF(${RANGE},"N/A")`, result:
|
|
189
|
+
['na', { formula: `"N/A: "&COUNTIF(${RANGE},"N/A")`, result: `N/A: ${na}` } as ExcelJS.CellValue],
|
|
172
190
|
];
|
|
173
191
|
bandCells.forEach(([, v], i) => {
|
|
174
192
|
const c = band.getCell(2 + i);
|
|
@@ -192,25 +210,23 @@ function addMatrixSheet(wb: ExcelJS.Workbook, model: MatrixModel, sungenVersion?
|
|
|
192
210
|
// item lives ONCE on the parent — a child repeats only what distinguishes it.
|
|
193
211
|
// The child's Expected Result is always kept: it is the precise source
|
|
194
212
|
// oracle (the semantic anchor), not a repetition of the parent's summary.
|
|
195
|
-
const commonPre = new Set(item.precondition);
|
|
196
|
-
const triggerTextsAll = new Set(item.variants.map((v) => v.trigger.join('\n')));
|
|
197
|
-
const triggerIsCommon = triggerTextsAll.size === 1;
|
|
198
213
|
for (const v of item.variants) {
|
|
199
214
|
const vr = ws.getRow(rowIdx++);
|
|
200
215
|
vr.outlineLevel = 1;
|
|
201
|
-
const preDelta = v.precondition.filter((line) => !commonPre.has(line));
|
|
202
216
|
const vValues: ExcelJS.CellValue[] = [
|
|
203
217
|
v.ref,
|
|
204
218
|
'',
|
|
205
219
|
v.condition,
|
|
206
220
|
'', '',
|
|
207
|
-
numbered(
|
|
221
|
+
numbered(item.preconditionDeltas[v.ref] ?? []),
|
|
208
222
|
numbered(v.data),
|
|
209
|
-
|
|
223
|
+
// Only the steps after the parent's shared prefix, numbered so the
|
|
224
|
+
// parent's steps + these read as one continuous procedure.
|
|
225
|
+
numbered(item.triggerDeltas[v.ref] ?? [], item.trigger.length + 1),
|
|
210
226
|
expectedWithVerification(v.oracle, v.verification),
|
|
211
227
|
v.mode === 'manual' ? `Manual${v.manualReason ? ` (${v.manualReason})` : ''}` : 'Auto',
|
|
212
228
|
v.traces.join(', '),
|
|
213
|
-
|
|
229
|
+
variantState(v),
|
|
214
230
|
isoDate(v.result?.startTime),
|
|
215
231
|
'',
|
|
216
232
|
v.result?.error ? String(v.result.error).slice(0, 200) : '',
|
|
@@ -227,8 +243,7 @@ function addMatrixSheet(wb: ExcelJS.Workbook, model: MatrixModel, sungenVersion?
|
|
|
227
243
|
// --- Parent row: only values that are genuinely COMMON to the whole item
|
|
228
244
|
// (review M-04); everything variant-specific stays on the sub-rows.
|
|
229
245
|
const pr = ws.getRow(parentRowIdx);
|
|
230
|
-
const
|
|
231
|
-
const coverage = `${item.variants.length} variant(s): ${item.variants.map((v) => v.condition).join(' · ')}`;
|
|
246
|
+
const coverage = coverageSummary(item);
|
|
232
247
|
|
|
233
248
|
// Live roll-up derived from the child Result cells — precedence
|
|
234
249
|
// failed → blocked → pending → partial → passed. N/A variants leave the
|
|
@@ -236,12 +251,13 @@ function addMatrixSheet(wb: ExcelJS.Workbook, model: MatrixModel, sungenVersion?
|
|
|
236
251
|
const rng = `M${firstChild}:M${lastChild}`;
|
|
237
252
|
const n = item.variants.length;
|
|
238
253
|
const EFF = `(${n}-COUNTIF(${rng},"N/A"))`;
|
|
254
|
+
const NA = `IF(COUNTIF(${rng},"N/A")>0," · "&COUNTIF(${rng},"N/A")&" N/A","")`;
|
|
255
|
+
// Mirrors itemResultLabel exactly — and never yields a bare state word.
|
|
239
256
|
const rollUpFormula =
|
|
240
|
-
`IF(COUNTIF(${rng},"Failed")>0,COUNTIF(${rng},"Passed")&"/"&${EFF}&" Passed · "&COUNTIF(${rng},"Failed")&" Failed",` +
|
|
241
|
-
`IF(COUNTIF(${rng},"Blocked")>0,COUNTIF(${rng},"Passed")&"/"&${EFF}&" Passed · "&COUNTIF(${rng},"Blocked")&" Blocked",` +
|
|
242
|
-
`IF(${EFF}=0,"N/A",` +
|
|
243
|
-
`
|
|
244
|
-
`IF(COUNTIF(${rng},"Passed")=0,"Pending",COUNTIF(${rng},"Passed")&"/"&${EFF}&" Passed · pending")))))`;
|
|
257
|
+
`IF(COUNTIF(${rng},"Failed")>0,COUNTIF(${rng},"Passed")&"/"&${EFF}&" Passed · "&COUNTIF(${rng},"Failed")&" Failed"&${NA},` +
|
|
258
|
+
`IF(COUNTIF(${rng},"Blocked")>0,COUNTIF(${rng},"Passed")&"/"&${EFF}&" Passed · "&COUNTIF(${rng},"Blocked")&" Blocked"&${NA},` +
|
|
259
|
+
`IF(${EFF}=0,"All N/A",` +
|
|
260
|
+
`COUNTIF(${rng},"Passed")&"/"&${EFF}&" Passed"&${NA})))`;
|
|
245
261
|
|
|
246
262
|
const values: ExcelJS.CellValue[] = [
|
|
247
263
|
item.id,
|
|
@@ -251,8 +267,8 @@ function addMatrixSheet(wb: ExcelJS.Workbook, model: MatrixModel, sungenVersion?
|
|
|
251
267
|
item.priority,
|
|
252
268
|
numbered(item.precondition),
|
|
253
269
|
coverage,
|
|
254
|
-
//
|
|
255
|
-
|
|
270
|
+
// The shared leading steps; each variant's remaining steps sit on its sub-row.
|
|
271
|
+
numbered(item.trigger),
|
|
256
272
|
item.oracle,
|
|
257
273
|
itemModeLabel(item),
|
|
258
274
|
item.traces.join(', '),
|
|
@@ -29,6 +29,10 @@ export interface MapGroup {
|
|
|
29
29
|
oracle: string;
|
|
30
30
|
category: MapCategory;
|
|
31
31
|
review: ReviewState;
|
|
32
|
+
/** Optional short digest of the coverage dimensions this item varies over
|
|
33
|
+
* (e.g. "required ×3 · format ×9 · full-width ×2"). Rendered on the parent row
|
|
34
|
+
* instead of concatenating every variant's title — the compact review view. */
|
|
35
|
+
dimensions?: string;
|
|
32
36
|
/** Variant refs: `VP-ID` (all @cases rows when the scenario has a dataset) or `VP-ID#label`. */
|
|
33
37
|
variants: string[];
|
|
34
38
|
}
|
|
@@ -118,7 +122,11 @@ export interface CoverageVariant {
|
|
|
118
122
|
result?: PlaywrightResult;
|
|
119
123
|
}
|
|
120
124
|
|
|
121
|
-
export type ItemResult = 'passed' | 'failed' | 'blocked' | 'partial' | 'not_run';
|
|
125
|
+
export type ItemResult = 'passed' | 'failed' | 'blocked' | 'partial' | 'not_run' | 'na';
|
|
126
|
+
|
|
127
|
+
/** The exact word rendered in a variant's Result cell — the single vocabulary the
|
|
128
|
+
* parent's roll-up formula counts (COUNTIF is exact-match). */
|
|
129
|
+
export type VariantState = 'Passed' | 'Failed' | 'Blocked' | 'Pending' | 'N/A';
|
|
122
130
|
|
|
123
131
|
/** One summarized row in the matrix — a group of variants sharing the signature. */
|
|
124
132
|
export interface DeliveryItem {
|
|
@@ -126,6 +134,8 @@ export interface DeliveryItem {
|
|
|
126
134
|
target: string;
|
|
127
135
|
intent: string;
|
|
128
136
|
oracle: string;
|
|
137
|
+
/** Short coverage-dimension digest from the map (compact parent view). */
|
|
138
|
+
dimensions?: string;
|
|
129
139
|
category: MapCategory;
|
|
130
140
|
review: ReviewState;
|
|
131
141
|
/** Highest priority among the variants (per-variant priorities stay on the sub-rows). */
|
|
@@ -136,13 +146,19 @@ export interface DeliveryItem {
|
|
|
136
146
|
layers: MatrixLayer[];
|
|
137
147
|
/** Union of variant traces (exact per-variant traces stay on the variants). */
|
|
138
148
|
traces: string[];
|
|
149
|
+
/** Preconditions shared by EVERY variant (the intersection) — written once here. */
|
|
139
150
|
precondition: string[];
|
|
140
|
-
/**
|
|
151
|
+
/** Leading trigger steps shared by every variant — written once here; each
|
|
152
|
+
* variant renders only its remaining steps (`triggerDeltas`). */
|
|
141
153
|
trigger: string[];
|
|
142
154
|
variants: CoverageVariant[];
|
|
155
|
+
/** variant ref → its precondition lines that are not on the parent. */
|
|
156
|
+
preconditionDeltas: Record<string, string[]>;
|
|
157
|
+
/** variant ref → its trigger steps after the shared prefix. */
|
|
158
|
+
triggerDeltas: Record<string, string[]>;
|
|
143
159
|
/** Derived roll-up — never entered independently (Gate F). */
|
|
144
160
|
result: ItemResult;
|
|
145
|
-
resultCounts: { passed: number; failed: number; blocked: number; notRun: number };
|
|
161
|
+
resultCounts: { passed: number; failed: number; blocked: number; notRun: number; na: number };
|
|
146
162
|
}
|
|
147
163
|
|
|
148
164
|
export interface MatrixDisposition {
|
|
@@ -60,6 +60,7 @@ groups:
|
|
|
60
60
|
target: login.email # ONE target: field/component dot-path, flow phrase, or METHOD /path
|
|
61
61
|
intent: <one behavior/rule this item verifies>
|
|
62
62
|
oracle: <the shared observable Pass/Fail statement>
|
|
63
|
+
dimensions: violated rule — required ×3 · format ×10 · full-width ×2 # see below
|
|
63
64
|
category: normal | abnormal | security | nfr
|
|
64
65
|
review: proposed # ALWAYS proposed — only QA approval flips it
|
|
65
66
|
variants: [VP-VAL-001-B, VP-VAL-001-S] # VP-ids; bare id on a @cases scenario = all its rows
|
|
@@ -94,6 +95,17 @@ determined** (its oracle *family*, not its exact message).
|
|
|
94
95
|
disposition). Data-setup blocks (`@manual:data-setup`) → `excluded`; SPEC-GAP placeholders →
|
|
95
96
|
`blocked`.
|
|
96
97
|
|
|
98
|
+
**`dimensions:` — the compact coverage digest (required for items with >3 variants).**
|
|
99
|
+
This one short line is what the collapsed parent row shows instead of listing every variant, so a
|
|
100
|
+
reviewer sees *which dimensions* the item covers without expanding it. Name the dimension, then the
|
|
101
|
+
branches with counts:
|
|
102
|
+
- `violated rule — required ×3 · format ×10 · full-width ×2`
|
|
103
|
+
- `account state — wrong password · unregistered · locked · soft-deleted`
|
|
104
|
+
- `submission method — Login button · Enter in Password · Enter in Email`
|
|
105
|
+
|
|
106
|
+
Keep it ≤120 chars (Gate W warns). **YAML caveat:** a bare `: ` inside the value breaks the parse —
|
|
107
|
+
use ` — ` as the label separator (as above) or quote the whole string.
|
|
108
|
+
|
|
97
109
|
**Wording rules for `intent`/`oracle` (customer-facing — Gate W lints these):**
|
|
98
110
|
- Plain product language, present simple, ~10–20 words, one behavior:
|
|
99
111
|
"A user can sign in with valid credentials and is redirected to the Jobs page."
|
|
@@ -63,12 +63,18 @@ result/evidence entry). Sub-rows are **delta-only**: knowledge common to the who
|
|
|
63
63
|
execution fields. The parent never carries placeholder text — when triggers differ the cell is
|
|
64
64
|
simply empty and the sub-rows carry them.
|
|
65
65
|
|
|
66
|
-
Parent preconditions are the **intersection** of the variants'
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
Parent preconditions are the **intersection** of the variants' and the parent's Action holds the
|
|
67
|
+
**shared leading steps**; each variant renders only its remaining steps, numbered to continue the
|
|
68
|
+
parent's list — so a shared prefix is written once, not repeated on every child. The parent's
|
|
69
|
+
Coverage cell is the map's short `dimensions:` digest (or the variant refs when none is declared),
|
|
70
|
+
never a concatenation of variant titles.
|
|
71
|
+
|
|
72
|
+
Variant Result cells hold exactly one of `Passed`/`Failed`/`Blocked`/`Pending`/`N/A` (dropdown) —
|
|
73
|
+
that vocabulary is the contract: the parent Result and the summary band are **live Excel formulas**
|
|
74
|
+
counting those words, so a parent label is always a composed `n/m Passed …` string (`All N/A` when
|
|
75
|
+
every variant is N/A) and never a bare state word. **`N/A` leaves the denominator.** Evidence and
|
|
76
|
+
Defect ID are separate columns; ID + Target frozen; ISO dates; landscape print with repeated
|
|
77
|
+
header rows.
|
|
72
78
|
|
|
73
79
|
`Coverage` sheet — requirement coverage table (every FR/TR/NFR id with an explicit status),
|
|
74
80
|
target × category grid with explicit `—` gaps, dispositions, manifest. CSV mirrors the same model
|