@sun-asterisk/sungen 3.2.23 → 3.2.24-beta.2
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/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +40 -8
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/dashboard/templates/index.html +1 -1
- package/dist/exporters/csv-exporter.d.ts.map +1 -1
- package/dist/exporters/csv-exporter.js +3 -1
- package/dist/exporters/csv-exporter.js.map +1 -1
- package/dist/exporters/feature-parser.d.ts +7 -0
- package/dist/exporters/feature-parser.d.ts.map +1 -1
- package/dist/exporters/feature-parser.js +27 -0
- package/dist/exporters/feature-parser.js.map +1 -1
- package/dist/exporters/xlsx-report-builder.d.ts.map +1 -1
- package/dist/exporters/xlsx-report-builder.js +3 -1
- package/dist/exporters/xlsx-report-builder.js.map +1 -1
- package/dist/harness/audit.d.ts +22 -1
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +122 -13
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/flow-contract.d.ts +104 -0
- package/dist/harness/flow-contract.d.ts.map +1 -1
- package/dist/harness/flow-contract.js +199 -12
- package/dist/harness/flow-contract.js.map +1 -1
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +22 -3
- package/dist/harness/parse.js.map +1 -1
- package/dist/harness/quality-gates.d.ts +2 -1
- package/dist/harness/quality-gates.d.ts.map +1 -1
- package/dist/harness/quality-gates.js +47 -5
- package/dist/harness/quality-gates.js.map +1 -1
- package/dist/harness/sensors.d.ts.map +1 -1
- package/dist/harness/sensors.js +48 -5
- package/dist/harness/sensors.js.map +1 -1
- package/dist/harness/spec-coverage.d.ts +20 -0
- package/dist/harness/spec-coverage.d.ts.map +1 -1
- package/dist/harness/spec-coverage.js +35 -0
- package/dist/harness/spec-coverage.js.map +1 -1
- package/dist/harness/viewpoint-ledger.d.ts +4 -0
- package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
- package/dist/harness/viewpoint-ledger.js +59 -1
- package/dist/harness/viewpoint-ledger.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/commands/create-test.md +28 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +98 -5
- package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/group-e-identity.md +1 -1
- package/dist/orchestrator/test-data-guide.d.ts.map +1 -1
- package/dist/orchestrator/test-data-guide.js +8 -0
- package/dist/orchestrator/test-data-guide.js.map +1 -1
- package/package.json +3 -3
- package/src/cli/commands/audit.ts +37 -8
- package/src/dashboard/templates/index.html +1 -1
- package/src/exporters/csv-exporter.ts +3 -1
- package/src/exporters/feature-parser.ts +22 -0
- package/src/exporters/xlsx-report-builder.ts +3 -1
- package/src/harness/audit.ts +131 -17
- package/src/harness/flow-contract.ts +258 -12
- package/src/harness/parse.ts +22 -3
- package/src/harness/quality-gates.ts +48 -5
- package/src/harness/sensors.ts +47 -4
- package/src/harness/spec-coverage.ts +38 -0
- package/src/harness/viewpoint-ledger.ts +58 -1
- package/src/orchestrator/templates/ai-src/commands/create-test.md +28 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +98 -5
- package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/group-e-identity.md +1 -1
- package/src/orchestrator/test-data-guide.ts +8 -0
|
@@ -23,6 +23,19 @@ export interface LedgerResult {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
const ID_RE = /\b([A-Z]{1,5}\d{0,2}(?:[.\-][A-Za-z0-9]+)*-?\d{0,3})\b/; // VP0.Title, VP7-002, MS-HP-001, TV-01
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Lines that DECLARE something about the checklist rather than being a checklist item —
|
|
29
|
+
* atomizing them manufactures gaps that can never be closed (QA field report: 4 of 6
|
|
30
|
+
* "missing items" were a `qa/bug-reports` placeholder plus three Priority-Viewpoints
|
|
31
|
+
* rows whose third cell is the PROSE REASON for a priority, not a claim to test).
|
|
32
|
+
*/
|
|
33
|
+
// "None on file yet …", "N/A", "TBD", "Do not invent …" — an explicit statement that there
|
|
34
|
+
// is nothing here. A gap report built from these tells the author to test the absence.
|
|
35
|
+
const PLACEHOLDER_ITEM = /^(none\b|n\/a\b|tbd\b|no known\b|nothing\b|do not invent\b|-+$)/i;
|
|
36
|
+
/** A priority-DECLARATION row: `| VP-LOGIC | High | <reason prose> |`. The category id and its
|
|
37
|
+
* priority are consumed by the traceability + balance axes; the reason is rationale. */
|
|
38
|
+
const PRIORITY_ROW = /^(?:VP|FL)[A-Z0-9._-]*\s+—\s+(?:critical|high|medium|normal|low|deferred)\b/i;
|
|
26
39
|
const GENERIC = new Set(['display', 'shown', 'value', 'field', 'input', 'page', 'screen', 'button', 'link', 'text', 'check', 'verify', 'should', 'with', 'when', 'then', 'user', 'this', 'that', 'each', 'item', 'items']);
|
|
27
40
|
|
|
28
41
|
/** Extract atomic checklist items from a viewpoint file (format-tolerant). */
|
|
@@ -42,11 +55,13 @@ export function parseViewpointItems(viewpointPath: string): { id?: string; text:
|
|
|
42
55
|
else if (line.startsWith('|')) { // table data row
|
|
43
56
|
if (/^\|[\s|:-]+\|?$/.test(line)) continue; // separator
|
|
44
57
|
const cells = line.split('|').map((c) => c.trim()).filter(Boolean);
|
|
45
|
-
if (/^(vp|id|viewpoint|priority|reason|no\.?|category|item|trigger|#|pattern|applicable|notes|field|constraint|code|description|status)$/i.test(cells[0] || '')) continue; // header
|
|
58
|
+
if (/^(vp|id|viewpoint|priority|reason|no\.?|category|item|trigger|#|pattern|applicable|notes|field|constraint|code|description|status|step|flow|ref|level|question|screen|actor|branches from|own steps|component|thành phần|trường|bước)$/i.test(cells[0] || '')) continue; // header
|
|
46
59
|
text = cells.join(' — ');
|
|
47
60
|
} else continue;
|
|
48
61
|
text = text.replace(/[*`]/g, '').trim();
|
|
49
62
|
if (!text) continue;
|
|
63
|
+
if (PLACEHOLDER_ITEM.test(text)) continue; // "None on file yet" is not a claim
|
|
64
|
+
if (PRIORITY_ROW.test(text)) continue; // priority declaration, not a checklist item
|
|
50
65
|
const idM = text.match(ID_RE);
|
|
51
66
|
const id = idM && /\d/.test(idM[1]) ? idM[1] : undefined; // require a digit so prose words aren't IDs
|
|
52
67
|
const words = (text.toLowerCase().match(/[a-z][a-z-]{3,}/g) || []).filter((w) => !GENERIC.has(w));
|
|
@@ -79,3 +94,45 @@ export function viewpointLedger(viewpointPath: string, scenarios: ScenarioInfo[]
|
|
|
79
94
|
|
|
80
95
|
return { hasViewpoint: true, total: items.length, covered, ratio: items.length ? covered / items.length : 1, missing };
|
|
81
96
|
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* A viewpoint item that names a BROWSER-LEVEL gesture, met by an in-app control instead.
|
|
100
|
+
*
|
|
101
|
+
* "Browser back button from Basic Info Confirmation to Basic Info — values must be restored"
|
|
102
|
+
* was answered with `User click [Confirmation:Back] button`. Those are different mechanisms: an
|
|
103
|
+
* app button runs the screen's own re-hydrate logic, browser history does not, and a defect
|
|
104
|
+
* that only appears on history navigation lives exactly in the gap. The ledger counted the item
|
|
105
|
+
* covered because the words matched, so the substitution was invisible (#595).
|
|
106
|
+
*
|
|
107
|
+
* The language has no reload/back step at all (#593), so the honest outcome is a `@manual`
|
|
108
|
+
* deferral naming the gesture — not a silent swap.
|
|
109
|
+
*/
|
|
110
|
+
const BROWSER_GESTURES: Array<{ gesture: string; re: RegExp; appSubstitute: RegExp }> = [
|
|
111
|
+
{ gesture: 'browser back', re: /\bbrowser(?:'s)?[ -]?(?:back|history)\b|\bhistory[ -]back\b/i,
|
|
112
|
+
appSubstitute: /\bclick \[[^\]]*back[^\]]*\]/i },
|
|
113
|
+
{ gesture: 'browser reload/refresh', re: /\b(?:page |browser )?(?:reload|refresh)(?:ing|ed|es)?\b/i,
|
|
114
|
+
appSubstitute: /\bis on \[[^\]]+\] page\b/i },
|
|
115
|
+
{ gesture: 'closing and reopening the tab', re: /\bclos(?:e|ing) (?:and reopen\w*\s*)?the tab\b|\breopen\w* the tab\b/i,
|
|
116
|
+
appSubstitute: /\bis on \[[^\]]+\] page\b/i },
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
export function browserGestureSubstitutions(
|
|
120
|
+
viewpointPath: string, scenarios: ScenarioInfo[],
|
|
121
|
+
): Array<{ gesture: string; item: string }> {
|
|
122
|
+
const out: Array<{ gesture: string; item: string }> = [];
|
|
123
|
+
// Judged PER SCENARIO, not over the whole file: a @manual scenario elsewhere that merely
|
|
124
|
+
// mentions the gesture in passing (explaining a tooling limit) silenced the check for an
|
|
125
|
+
// item that an automated scenario had quietly substituted.
|
|
126
|
+
const deferred = scenarios.filter((s) => s.manual).map((s) => s.haystack);
|
|
127
|
+
const automated = scenarios.filter((s) => !s.manual).map((s) => s.haystack);
|
|
128
|
+
for (const item of parseViewpointItems(viewpointPath)) {
|
|
129
|
+
for (const g of BROWSER_GESTURES) {
|
|
130
|
+
if (!g.re.test(item.text)) continue;
|
|
131
|
+
// A @manual scenario whose own subject IS the gesture is the honest answer.
|
|
132
|
+
if (deferred.some((h) => g.re.test(h))) break;
|
|
133
|
+
if (automated.some((h) => g.appSubstitute.test(h))) out.push({ gesture: g.gesture, item: item.text });
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
@@ -30,6 +30,34 @@ If `spec_figma.md` exists OR the user provides a Figma URL for the PAT flow:
|
|
|
30
30
|
**Input**: Screen or flow name (e.g., `/sungen-create-test admin-users`).
|
|
31
31
|
{{/cap}}
|
|
32
32
|
|
|
33
|
+
## ⛔ HARD RULE — in flow mode, AUTHOR the flow contract before generating
|
|
34
|
+
|
|
35
|
+
If `requirements/flow-contract.yaml` is missing, **write it first** — never cite a contract you
|
|
36
|
+
did not create, and never generate scenarios without one. Without it the flow is scored as a
|
|
37
|
+
generic screen (page-type themes that do not fit a journey), and the audit reports
|
|
38
|
+
`FLOW-CONTRACT-MISSING`.
|
|
39
|
+
|
|
40
|
+
It needs the use-case declaration ONCE — actor · trigger · goal · precondition · `outcome` —
|
|
41
|
+
plus **both** postconditions: `successGuarantee` (true when the goal is reached) and
|
|
42
|
+
`minimalGuarantee` (what must still hold when the journey FAILS). Exception flows assert against
|
|
43
|
+
the second one; with no `minimalGuarantee` they can only prove "an error appeared"
|
|
44
|
+
(`FLOW-GUARANTEE-MISSING`). Then declare `phases:` — `[BF, AF, EF]` for a use-case decomposition
|
|
45
|
+
(the first phase is the Basic Flow). Then enumerate the use case's flows with the step × risk
|
|
46
|
+
matrix in the `sungen-tc-generation` skill and **write them into the contract's `flows:`
|
|
47
|
+
inventory**, each with a `status:` (`covered` / `deferred` / `pending-clarification` /
|
|
48
|
+
`out-of-scope`) and a `reason:` for anything but `covered`. Without the inventory, coverage can
|
|
49
|
+
only be measured per phase — and a phase counts as covered as soon as ONE scenario carries it, so
|
|
50
|
+
a fifteen-flow use case scores 100% on three scenarios (`FLOW-INVENTORY-MISSING`, and the top mark
|
|
51
|
+
is withheld).
|
|
52
|
+
|
|
53
|
+
A risk the specs do NOT settle is `pending-clarification` with the question written down — never a
|
|
54
|
+
scenario asserting invented behaviour, and never a silent omission. If the flow's requirements are
|
|
55
|
+
restated from screen specs the project does not hold, say so: the audit reports
|
|
56
|
+
`SPEC-RESTATED-UNVERIFIED` because `specFR 100%` over a hand-copied list certifies the copy, not
|
|
57
|
+
the source.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
33
61
|
## Platform detection (do this FIRST)
|
|
34
62
|
|
|
35
63
|
Read `qa/capabilities.yaml` and check the `platform` field — **and the verification scope** (`verification:` if set, else derived from `enabled`: `ui` always; `api`/`db` only if that driver is on). This is the project's recorded **test type**: an **E2E/UI-only** project (no `api`/`db`) must NOT get `@api`/`@query`/`@requires:api|db` verification unless the test-viewpoint explicitly asks for it — keep oracles UI-observable, and cap any in-scope API/DB-in-E2E verification at the **≤20% band** (see `sungen-tc-generation` → "Respect the project's verification scope"). `sungen audit` flags `VERIFICATION-OUT-OF-SCOPE`.
|
|
@@ -636,10 +636,21 @@ those namespaces are the **System INTEGRATION** group — keep their oracles abo
|
|
|
636
636
|
|
|
637
637
|
**Read `requirements/flow-contract.yaml` FIRST — it is the flow's boundary and the yardstick
|
|
638
638
|
`sungen audit` scores the flow against** (`flowCoverage` axis = journey phases HP/ER/EH automated;
|
|
639
|
-
`FLOW-OUTCOME-UNPROVEN`; `FLOW-SCOPE-CREEP`).
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
639
|
+
`FLOW-OUTCOME-UNPROVEN`; `FLOW-SCOPE-CREEP`). **A filled contract is an INPUT — never rewrite it
|
|
640
|
+
to match your output** (same rule as `test-viewpoint.md`).
|
|
641
|
+
|
|
642
|
+
> **HARD RULE — the contract must be AUTHORED, never merely cited.** If
|
|
643
|
+
> `requirements/flow-contract.yaml` is absent, WRITE it (with the user, via the boundary checklist
|
|
644
|
+
> in `add-flow`: one business goal · clear trigger · ONE observable outcome valuable to the actor ·
|
|
645
|
+
> name = "Verb + outcome") **before** generating scenarios. Citing a contract you did not create
|
|
646
|
+
> leaves the flow scored as a generic screen — `flowCoverage` never applies, `coverage` falls back
|
|
647
|
+
> to page-type themes that do not fit a journey, and the audit reports `FLOW-CONTRACT-MISSING`.
|
|
648
|
+
> A contract also needs **both** guarantees, not just the happy one:
|
|
649
|
+
>
|
|
650
|
+
> - `successGuarantee` — everything true once the goal IS reached (what the Basic Flow proves).
|
|
651
|
+
> - `minimalGuarantee` — what must still hold when the journey **FAILS** (no duplicate record, no
|
|
652
|
+
> mail on a rejected submit, no half-written state). Without it an Exception Flow has nothing to
|
|
653
|
+
> assert against beyond "an error appeared", and `FLOW-GUARANTEE-MISSING` is reported.
|
|
643
654
|
|
|
644
655
|
| Aspect | Screen | Flow |
|
|
645
656
|
|---|---|---|
|
|
@@ -647,7 +658,7 @@ never rewrite it to match your output** (same rule as `test-viewpoint.md`).
|
|
|
647
658
|
| Selector format | `[Element]` | `[Screen:Element]` (namespaced) |
|
|
648
659
|
| Test data keys | `{{variable}}` | `{{phase.variable}}` |
|
|
649
660
|
| Feature tag | `@auto` / `@smoke` etc. | `@flow` (required) |
|
|
650
|
-
| Scenario ids | `VP-<CATEGORY>-NNN` | `FL-<PHASE>-NNN` — phases
|
|
661
|
+
| Scenario ids | `VP-<CATEGORY>-NNN` | `FL-<PHASE>-NNN` — use-case phases `BF`/`AF0n`/`EF0n` (see below), or the flat `HP` (happy path) / `ER` (error recovery) / `EH` (guards) / `UI` for a short single-outcome flow |
|
|
651
662
|
|
|
652
663
|
**Scenarios to generate — every phase demanded by the contract, automated:**
|
|
653
664
|
|
|
@@ -659,6 +670,88 @@ never rewrite it to match your output** (same rule as `test-viewpoint.md`).
|
|
|
659
670
|
| Cross-screen handoff | After every screen transition, assert the CARRIED state on the new screen (the added product's name in the cart, the email echoed on the sent screen). | blind tails cap `businessDepth` (`FLOW-HANDOFF-SHALLOW`) |
|
|
660
671
|
| Stateful regression (when `stateful:` declared) | Count/quantity proof · teardown (remove → empty) · multi-source add. | missing dims cap `businessDepth` (`FLOW-DEPTH`) |
|
|
661
672
|
|
|
673
|
+
### Use-case decomposition — how a flow becomes 15 scenarios instead of 3
|
|
674
|
+
|
|
675
|
+
`HP`/`ER`/`EH` are phase *buckets*; they do not tell you how many flows a use case HAS. The
|
|
676
|
+
use-case framework does, and it is the default decomposition for a multi-screen journey:
|
|
677
|
+
|
|
678
|
+
**1 Use Case = 1 Basic Flow + N Alternate Flows + N Exception Flows.**
|
|
679
|
+
|
|
680
|
+
| Phase | Meaning | Test |
|
|
681
|
+
|---|---|---|
|
|
682
|
+
| `BF` Basic Flow | **Exactly one.** The path where nothing goes wrong, end to end. | Reaches `outcome.assertion` and satisfies `successGuarantee`. |
|
|
683
|
+
| `AF` Alternate Flow | A **designed-for branch** — the user does something else legitimate. NOT an error. | Its own outcome: either re-enters the BF at a named step, or ends elsewhere on purpose. |
|
|
684
|
+
| `EF` Exception Flow | **Blocked.** The goal is not reached. | Asserts `minimalGuarantee` — what must STILL be true now that this failed. |
|
|
685
|
+
|
|
686
|
+
`AF` is the bucket most generators skip entirely, and it is where the interesting bugs live
|
|
687
|
+
(going back and finding the form re-hydrated, two tabs, an escape hatch out of the journey).
|
|
688
|
+
Use `FL-BF-NNN` / `FL-AF0n-NNN` / `FL-EF0n-NNN` ids — `sungen audit` buckets them (BF→business
|
|
689
|
+
core, AF→behaviour, EF→validation/security) and traces them to the viewpoint's `FL-BF`/`FL-AF`/
|
|
690
|
+
`FL-EF` priority rows. Declare `phases: [BF, AF, EF]` in the contract; the FIRST phase is the
|
|
691
|
+
Basic Flow and is the one that must reach `outcome`.
|
|
692
|
+
|
|
693
|
+
**Declare the flow INVENTORY, not just the phases.** `phases: [BF, AF, EF]` says which
|
|
694
|
+
vocabulary the suite uses; it cannot say how many flows the use case has — a phase counts as
|
|
695
|
+
covered the moment ONE scenario carries it, so a fifteen-flow use case with three scenarios
|
|
696
|
+
reported full phase coverage while a reviewer counting flows read it as a third done. List every
|
|
697
|
+
flow in the contract, and give each one a status:
|
|
698
|
+
|
|
699
|
+
```yaml
|
|
700
|
+
flows:
|
|
701
|
+
- id: BF
|
|
702
|
+
outcome: "The successGuarantee — verified, recorded, signed in"
|
|
703
|
+
status: covered
|
|
704
|
+
- id: AF02
|
|
705
|
+
branchFrom: "BF step 4, before submitting"
|
|
706
|
+
outcome: "Re-enters BF at step 4 with the buffer restored"
|
|
707
|
+
status: covered
|
|
708
|
+
- id: EF08
|
|
709
|
+
branchFrom: "BF step 1, the control tapped twice"
|
|
710
|
+
outcome: "One request, one record"
|
|
711
|
+
status: pending-clarification # the spec is silent — ASK, never assume a guard
|
|
712
|
+
reason: "The guard lives in ST_AUTH_001's spec, which this project does not hold."
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
Four statuses, and **silence is not one of them**: `covered` · `deferred` · `pending-clarification`
|
|
716
|
+
(the behaviour is not agreed yet, so no scenario can be right) · `out-of-scope` (another suite owns
|
|
717
|
+
it). Anything but `covered` needs a `reason:` — a deferral nobody can audit is the same as a
|
|
718
|
+
missing flow. `sungen audit` then measures `flowCoverage` per DECLARED FLOW and reports
|
|
719
|
+
`FLOW-UNCOVERED` (declared, nobody wrote it), `FLOW-UNDECLARED` (a scenario claiming a flow id the
|
|
720
|
+
inventory never declares), `FLOW-STATUS-UNREASONED` and `FLOW-INVENTORY-MISSING`.
|
|
721
|
+
|
|
722
|
+
**One flow, one id — a viewpoint is not a flow.** Several scenarios may prove one flow: give them
|
|
723
|
+
the same flow id and different sequence numbers (`VP-VAL-EF01-001`, `VP-VAL-EF01-002`), never a
|
|
724
|
+
fresh flow id per assertion. Two shapes the audit reports as `FLOW-PHASE-MISFILED`: an `EF` that
|
|
725
|
+
reaches the outcome with nothing failing (that is a success-path postcondition, so it belongs to
|
|
726
|
+
the BF), and an `AF` with no branch point (it walks the basic path asserting extra content — also
|
|
727
|
+
the BF's). Both inflate the flow count while adding no branch coverage.
|
|
728
|
+
|
|
729
|
+
**Declare once, then declare only the differences.** Actor · Trigger · Goal · Precondition ·
|
|
730
|
+
`successGuarantee` · `minimalGuarantee` are use-case-level: they live in `flow-contract.yaml`
|
|
731
|
+
and are never repeated per flow. Each flow in `test-viewpoint.md` then states only three things:
|
|
732
|
+
**where it branches from · its own steps · its own outcome.** Repeating the precondition in
|
|
733
|
+
fifteen rows is noise; the branch point is the information.
|
|
734
|
+
|
|
735
|
+
**Enumerate flows with the step × risk matrix.** Walk every BF step and ask each column. A hit
|
|
736
|
+
with a basis in the spec becomes a flow; a hit with no basis becomes an **open question** — never
|
|
737
|
+
an invented behaviour:
|
|
738
|
+
|
|
739
|
+
| Risk family | The question at this step |
|
|
740
|
+
|---|---|
|
|
741
|
+
| Double submit | Is the control tapped twice guarded? One request, one record? (Spec silent → open question, not an assumed guard.) |
|
|
742
|
+
| Client-side buffer | Is state held client-side between this step and the next? Then test **both** directions — it survives a legitimate back, and it is **LOST** on reload. A suite proving only the surviving side has tested half the mechanism (`CONTINUITY-ONE-SIDED`). |
|
|
743
|
+
| Concurrency | Two tabs / two devices at this step — whose state wins? |
|
|
744
|
+
| Server error | This step's submit fails server-side: what is kept, what is retryable, is anything half-written? |
|
|
745
|
+
| Abandonment / TTL | The user stops here. Does the partial state lapse, expire, or linger forever? |
|
|
746
|
+
| Direct access | This step's URL opened cold, without the preceding state → refused, and the screen never renders. |
|
|
747
|
+
| Auth transition | Where exactly does the session begin? Before that point the actor is unauthenticated — prove it. |
|
|
748
|
+
| Escape hatch | Is there a documented way OUT of the journey here (back to top, cancel)? That is an `AF`, and its outcome is "goal deliberately not reached". |
|
|
749
|
+
|
|
750
|
+
**Do NOT re-derive field validation.** Per-field equivalence and boundary values belong to the
|
|
751
|
+
OWNING SCREEN's suite. A flow takes representative inputs (see the system-test rule above). State
|
|
752
|
+
that exclusion in the viewpoint's Design Decisions — and if the screen suite does not exist, that
|
|
753
|
+
is a gap to REPORT, not to quietly absorb.
|
|
754
|
+
|
|
662
755
|
**Boundary discipline while generating:** every scenario must serve the contract's goal. A scenario
|
|
663
756
|
that never touches `outcome.screen` and is not a guard (`EH`) or error-recovery (`ER`) belongs in a
|
|
664
757
|
DIFFERENT flow — propose the split instead of writing it here (`FLOW-SCOPE-CREEP` will flag it).
|
|
@@ -107,7 +107,7 @@ See `SKILL.md` for the 4 Viewpoints, Shared Checks, and Security Tag Rules.
|
|
|
107
107
|
|
|
108
108
|
**[VP-VAL] Edge cases**
|
|
109
109
|
|
|
110
|
-
- [@normal] Register with a sub-address email (user+tag@
|
|
110
|
+
- [@normal] Register with a sub-address email (user+tag@sun-asterisk.com) → treated as a unique email, creation succeeds
|
|
111
111
|
- [@low] Browser autofill fills the fields → the form receives the correct values, no conflict with a custom input component
|
|
112
112
|
|
|
113
113
|
---
|
|
@@ -50,6 +50,14 @@ payload \`<script>alert(1)</script>\`; the exact expected error message from the
|
|
|
50
50
|
**Self-check:** *"If I swapped this for another value of the same kind, would the
|
|
51
51
|
test still mean the same thing?"* → yes = INVENTED.
|
|
52
52
|
|
|
53
|
+
**Email domains — never a live mailbox provider.** An invented address still gets
|
|
54
|
+
mail sent to it: a registration or password-reset test submits it and the system
|
|
55
|
+
dispatches for real. \`@gmail.com\`, \`@yahoo.com\`, \`@outlook.com\` and friends all
|
|
56
|
+
resolve, so that mail reaches whoever owns the address — a stranger, or someone's
|
|
57
|
+
personal account. Use the project domain \`@sun-asterisk.com\`, or \`@example.com\`
|
|
58
|
+
(RFC 2606 — reserved, guaranteed never to deliver). Make each run unique with
|
|
59
|
+
\`{{$timestamp}}\` in the local part: \`"signup+{{\$timestamp}}@sun-asterisk.com"\`.
|
|
60
|
+
|
|
53
61
|
## 2) ENV-BOUND — real records of the test environment (not secret)
|
|
54
62
|
|
|
55
63
|
**What it is:** the value must EXIST in the environment for the test to work — a
|