@sun-asterisk/sungen 3.2.9 → 3.2.10-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/dist/capabilities/discover.d.ts.map +1 -1
  2. package/dist/capabilities/discover.js +5 -2
  3. package/dist/capabilities/discover.js.map +1 -1
  4. package/dist/capabilities/registry.d.ts +9 -0
  5. package/dist/capabilities/registry.d.ts.map +1 -1
  6. package/dist/capabilities/registry.js +4 -0
  7. package/dist/capabilities/registry.js.map +1 -1
  8. package/dist/cli/commands/capability.d.ts.map +1 -1
  9. package/dist/cli/commands/capability.js +44 -2
  10. package/dist/cli/commands/capability.js.map +1 -1
  11. package/dist/cli/commands/delivery.d.ts.map +1 -1
  12. package/dist/cli/commands/delivery.js +15 -5
  13. package/dist/cli/commands/delivery.js.map +1 -1
  14. package/dist/cli/commands/generate.d.ts.map +1 -1
  15. package/dist/cli/commands/generate.js +16 -0
  16. package/dist/cli/commands/generate.js.map +1 -1
  17. package/dist/exporters/spec-parser.d.ts.map +1 -1
  18. package/dist/exporters/spec-parser.js +16 -5
  19. package/dist/exporters/spec-parser.js.map +1 -1
  20. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/alert-accept-action.hbs +1 -1
  21. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/alert-dismiss-action.hbs +1 -1
  22. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/alert-fill-action.hbs +1 -1
  23. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/capture-row-column.hbs +2 -0
  24. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/click-element-with-text.hbs +1 -1
  25. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/click-with-alert-action.hbs +2 -0
  26. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/recipe-sequence.hbs +3 -0
  27. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row-nth.hbs +2 -0
  28. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/unknown-element-action.hbs +1 -1
  29. package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/route-assertion.hbs +3 -0
  30. package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-for-element.hbs +1 -1
  31. package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-table-refresh.hbs +3 -0
  32. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/has-text-value.hbs +1 -0
  33. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +1 -1
  34. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +1 -1
  35. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-blur.hbs +1 -0
  36. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-click-text.hbs +3 -0
  37. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-click.hbs +1 -0
  38. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-expect-text.hbs +1 -0
  39. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-expect-value.hbs +1 -0
  40. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-for-each.hbs +5 -0
  41. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-hover.hbs +1 -0
  42. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-press.hbs +1 -0
  43. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-scroll-into-view.hbs +1 -0
  44. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-type.hbs +1 -0
  45. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-wait-for.hbs +1 -0
  46. package/dist/generators/test-generator/app-profile.d.ts +89 -0
  47. package/dist/generators/test-generator/app-profile.d.ts.map +1 -0
  48. package/dist/generators/test-generator/app-profile.js +157 -0
  49. package/dist/generators/test-generator/app-profile.js.map +1 -0
  50. package/dist/generators/test-generator/code-generator.d.ts +4 -0
  51. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  52. package/dist/generators/test-generator/code-generator.js +7 -0
  53. package/dist/generators/test-generator/code-generator.js.map +1 -1
  54. package/dist/generators/test-generator/diagnostics.d.ts +36 -0
  55. package/dist/generators/test-generator/diagnostics.d.ts.map +1 -0
  56. package/dist/generators/test-generator/diagnostics.js +44 -0
  57. package/dist/generators/test-generator/diagnostics.js.map +1 -0
  58. package/dist/generators/test-generator/patterns/types.d.ts +12 -0
  59. package/dist/generators/test-generator/patterns/types.d.ts.map +1 -1
  60. package/dist/generators/test-generator/recipe-expander.d.ts +32 -0
  61. package/dist/generators/test-generator/recipe-expander.d.ts.map +1 -0
  62. package/dist/generators/test-generator/recipe-expander.js +118 -0
  63. package/dist/generators/test-generator/recipe-expander.js.map +1 -0
  64. package/dist/generators/test-generator/step-mapper.d.ts +17 -0
  65. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  66. package/dist/generators/test-generator/step-mapper.js +70 -0
  67. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  68. package/dist/generators/test-generator/template-engine.d.ts +5 -1
  69. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  70. package/dist/generators/test-generator/template-engine.js +27 -3
  71. package/dist/generators/test-generator/template-engine.js.map +1 -1
  72. package/dist/generators/test-generator/utils/selector-resolver.d.ts +10 -1
  73. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  74. package/dist/generators/test-generator/utils/selector-resolver.js +32 -0
  75. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  76. package/dist/harness/capability.d.ts +1 -0
  77. package/dist/harness/capability.d.ts.map +1 -1
  78. package/dist/harness/capability.js.map +1 -1
  79. package/dist/harness/catalog/drivers.yaml +2 -1
  80. package/dist/harness/query-catalog.d.ts +32 -2
  81. package/dist/harness/query-catalog.d.ts.map +1 -1
  82. package/dist/harness/query-catalog.js +0 -0
  83. package/dist/harness/query-catalog.js.map +1 -1
  84. package/dist/index.d.ts +5 -2
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.js +5 -1
  87. package/dist/index.js.map +1 -1
  88. package/dist/orchestrator/templates/ai-src/commands/create-data-test.md +99 -0
  89. package/dist/orchestrator/templates/ai-src/commands/create-test.md +3 -0
  90. package/dist/orchestrator/templates/ai-src/commands/run-test.md +1 -0
  91. package/dist/orchestrator/templates/ai-src/config/claude.md +3 -1
  92. package/dist/orchestrator/templates/ai-src/config/copilot.md +3 -1
  93. package/dist/orchestrator/templates/ai-src/skills/sungen-data-factory/SKILL.md +116 -0
  94. package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +12 -1
  95. package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +43 -8
  96. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +22 -0
  97. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +14 -1
  98. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +1 -0
  99. package/dist/orchestrator/templates/specs-db.d.ts +201 -11
  100. package/dist/orchestrator/templates/specs-db.d.ts.map +1 -1
  101. package/dist/orchestrator/templates/specs-db.js +440 -91
  102. package/dist/orchestrator/templates/specs-db.js.map +1 -1
  103. package/dist/orchestrator/templates/specs-db.ts +463 -79
  104. package/dist/orchestrator/templates/specs-test-data.ts +39 -3
  105. package/package.json +8 -4
  106. package/src/capabilities/discover.ts +5 -2
  107. package/src/capabilities/registry.ts +13 -0
  108. package/src/cli/commands/capability.ts +40 -2
  109. package/src/cli/commands/delivery.ts +13 -5
  110. package/src/cli/commands/generate.ts +15 -0
  111. package/src/exporters/spec-parser.ts +17 -5
  112. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/alert-accept-action.hbs +1 -1
  113. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/alert-dismiss-action.hbs +1 -1
  114. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/alert-fill-action.hbs +1 -1
  115. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/capture-row-column.hbs +2 -0
  116. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/click-element-with-text.hbs +1 -1
  117. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/click-with-alert-action.hbs +2 -0
  118. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/recipe-sequence.hbs +3 -0
  119. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row-nth.hbs +2 -0
  120. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/unknown-element-action.hbs +1 -1
  121. package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/route-assertion.hbs +3 -0
  122. package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-for-element.hbs +1 -1
  123. package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-table-refresh.hbs +3 -0
  124. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/has-text-value.hbs +1 -0
  125. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +1 -1
  126. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +1 -1
  127. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-blur.hbs +1 -0
  128. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-click-text.hbs +3 -0
  129. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-click.hbs +1 -0
  130. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-expect-text.hbs +1 -0
  131. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-expect-value.hbs +1 -0
  132. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-for-each.hbs +5 -0
  133. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-hover.hbs +1 -0
  134. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-press.hbs +1 -0
  135. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-scroll-into-view.hbs +1 -0
  136. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-type.hbs +1 -0
  137. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-wait-for.hbs +1 -0
  138. package/src/generators/test-generator/app-profile.ts +185 -0
  139. package/src/generators/test-generator/code-generator.ts +9 -0
  140. package/src/generators/test-generator/diagnostics.ts +51 -0
  141. package/src/generators/test-generator/patterns/types.ts +12 -0
  142. package/src/generators/test-generator/recipe-expander.ts +129 -0
  143. package/src/generators/test-generator/step-mapper.ts +73 -0
  144. package/src/generators/test-generator/template-engine.ts +29 -3
  145. package/src/generators/test-generator/utils/selector-resolver.ts +49 -2
  146. package/src/harness/capability.ts +1 -0
  147. package/src/harness/catalog/drivers.yaml +2 -1
  148. package/src/harness/query-catalog.ts +0 -0
  149. package/src/index.ts +5 -2
  150. package/src/orchestrator/templates/ai-src/commands/create-data-test.md +99 -0
  151. package/src/orchestrator/templates/ai-src/commands/create-test.md +3 -0
  152. package/src/orchestrator/templates/ai-src/commands/run-test.md +1 -0
  153. package/src/orchestrator/templates/ai-src/config/claude.md +3 -1
  154. package/src/orchestrator/templates/ai-src/config/copilot.md +3 -1
  155. package/src/orchestrator/templates/ai-src/skills/sungen-data-factory/SKILL.md +116 -0
  156. package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +12 -1
  157. package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +43 -8
  158. package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +22 -0
  159. package/src/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +14 -1
  160. package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +1 -0
  161. package/src/orchestrator/templates/specs-db.ts +463 -79
  162. package/src/orchestrator/templates/specs-test-data.ts +39 -3
@@ -18,14 +18,16 @@ You generate 3 files for sungen — a Gherkin compiler that produces Playwright
18
18
  | `sungen-delivery` | Export Gherkin + Playwright results → CSV test case deliverable |
19
19
  | `sungen-capture` | Acquire visual/design context — one skill, 4 modes: figma-mcp (Dev Mode MCP), figma-pat (URL → spec_figma.md), live (Playwright MCP scan), local (images in `requirements/ui/`) |
20
20
  | `sungen-locale` | Bootstrap i18n — audit selectors, detect locale switch mechanism, generate test-data overlay |
21
+ | `sungen-data-factory` | Standardized test-data — field-type/security catalog, 4-source method, `.overwrite`, blind-spot prompts |
21
22
 
22
- ## Workflow (7 AI commands)
23
+ ## Workflow (8 AI commands)
23
24
 
24
25
  | Command | What it does |
25
26
  |---|---|
26
27
  | `/sungen-add-screen <name> <path>` | Scaffold `qa/screens/<name>/` directories |
27
28
  | `/sungen-add-flow <name> [--path <url>]` | Scaffold `qa/flows/<name>/` directories for E2E cross-screen testing |
28
29
  | `/sungen-create-test <name>` | Generate `.feature` + `test-data.yaml` (auto-detects screen or flow) |
30
+ | `/sungen-create-data-test <name>` | Generate standardized test-data (valid/boundary/invalid + CHK trace) from the Data Factory catalog; no name = all units (asks to confirm) |
29
31
  | `/sungen-review <name>` | Score syntax, coverage, viewpoint quality (auto-detects screen or flow) |
30
32
  | `/sungen-run-test <name>` | Generate `selectors.yaml`, compile, run, auto-fix (auto-detects screen or flow) |
31
33
  | `/sungen-delivery [name...]` | Export test cases → CSV for QA delivery (all screens if no arg) |
@@ -0,0 +1,116 @@
1
+ ---
2
+ name: sungen-data-factory
3
+ description: 'Use when create-data-test (or create-test) needs standardized TEST DATA — valid/boundary/invalid values for input fields with CHK-* traceability. Drives the Data Factory catalog + the 4-source method. Invoke after reading spec.md, when authoring the field-map or filling test-data.'
4
+ user-invocable: false
5
+ ---
6
+
7
+ ## What this skill does
8
+
9
+ Turn a screen/flow's fields into **standardized test data** — not guessed values. You author a
10
+ **field-map**; the deterministic `sungen data gen` expands it from the Data Factory **catalog**
11
+ (field-type + security + technique library) into valid / boundary / invalid values, each carrying a
12
+ `CHK-*` id that traces to the spec. Your judgment goes into *reading the spec correctly* and
13
+ *answering blind spots* — never into inventing values the catalog or the QA should own.
14
+
15
+ Data Factory is **bundled with core** (runtime-free) — no `capability add` needed; the `sungen data …`
16
+ CLI is available out of the box.
17
+
18
+ ## The 4-source method (phuong-phap-tao-test-data.md)
19
+
20
+ Never invent values from memory. Cross-reference four sources — each covers the previous one's gap:
21
+
22
+ 1. **Spec validate rules** — required, format, min/max, business rules, and the **error/message code**
23
+ per rule. Defines the legal/illegal boundary. (Says the *law*, not every way to *break* it.)
24
+ 2. **Field-type catalog** — the accumulated "how this type breaks" knowledge (email/password/phone/
25
+ number/…). This is the Data Factory `common/` — it fills the "spec doesn't list every break" gap.
26
+ 3. **Design technique** — Boundary Value Analysis / Equivalence Partitioning / Decision Table / State
27
+ Transition — reduces the rule + candidates to a *minimal-but-sufficient* representative set.
28
+ 4. **DB mapping** — unique constraints, cross-entity dependency, real backend limits → precondition
29
+ data, not just a single input value.
30
+
31
+ Then classify every value into **Valid / Boundary / Invalid** (each invalid tagged with its error
32
+ code) and give it a `CHK-*` id for traceability.
33
+
34
+ ## The field-map — `qa/data-factory/<name>.fields.yaml`
35
+
36
+ Your main artifact. One entry per input field; the generator expands it:
37
+
38
+ ```yaml
39
+ screen: login # or flow: <name>
40
+ fields:
41
+ - name: email
42
+ type: email # a catalog field-type (see list below)
43
+ required: true
44
+ constraints: { maxLocal: 64, maxTotal: 254 } # the project's REAL limits from spec
45
+ errorMap: { M13: E1042, M02: E1001 } # catalog placeholder code → project's real code
46
+ - name: password
47
+ type: password
48
+ constraints: { minLength: 8, maxLength: 32 }
49
+ ```
50
+
51
+ **Catalog field-types** (P1): `email` · `password` · `phone` · `string` · `number-integer` ·
52
+ `number-float` · `currency` · `postcode` · `katakana` (JP). Security payload banks auto-append to a
53
+ field's invalid group via the type's `uses_security` (`sqli`, `xss`, `idor`, `broken-auth`, `upload`,
54
+ `input-validation`).
55
+
56
+ Rules:
57
+ - Pick the closest `type`; put the project's real numbers in `constraints` (they override the
58
+ catalog defaults).
59
+ - Map every `errorRef` placeholder (`M13`, `M39`, …) to the spec's real code via `errorMap`. Leave
60
+ one unmapped **only** if the spec has none — the lint flags it as an open point to confirm.
61
+ - **Complex forms:** a **group** field takes nested **`fields:`** (recursive; no `type` on the group → renders a nested object like `address.city`). A field whose rule depends on another takes **`dependsOn: { field, value }`** — recorded as a `_depends_on` note (the generator doesn't resolve the runtime condition; weave it into the scenario/`@cases`).
62
+ - Set **`unique: true`** on a field that must be fresh every run (register email/username, anything
63
+ that would clash with a record a previous run left behind). Its valid value then carries a runtime
64
+ `{{$timestamp}}` (email → `user+{{$timestamp}}@…`) so each run is unique — no cross-run cache
65
+ (which would make tests order-dependent/flaky). Prefer this over caching prior-run values. For
66
+ reusing a value produced earlier **within the same run** (register → then log in with it), bind it
67
+ via the scenario (`@dataFactory` recipe / `testData.bind`), not a cross-run cache.
68
+
69
+ ## No input fields → nothing to generate
70
+
71
+ Data Factory standardizes **field-level input data**. If a unit has no user-input fields or
72
+ parameters — a pure navigation, listing, or capture-compare flow — there is nothing to generate.
73
+ Report *"no input fields → nothing to generate"* and skip it; do **not** write an empty field-map.
74
+ Those scenarios are covered by the data create-test already produced, not by field-level test data.
75
+ (This is a correct outcome, not a gap — don't force a field-map to make the unit "processed".)
76
+
77
+ ## Blind spots — ask, never fabricate
78
+
79
+ If a field's type is **not** in the catalog — a business enum, a cross-entity constraint, a
80
+ project-specific data model — do **not** guess. Ask the QA (via `AskUserQuestion`) for: (1) the valid
81
+ set, (2) invalid / non-existent values, (3) the error code when wrong, (4) dependencies on other
82
+ fields. Record it in `qa/data-factory/project/<entity>.yaml` so it becomes reusable project catalog.
83
+ This is the core discipline: *common is never enough; when blind, think and ask — don't invent.*
84
+
85
+ ## Customizing the catalog — `.overwrite` (survives `sungen update`)
86
+
87
+ The shipped `common/` is a floor. Project rules go in `qa/data-factory/common.overwrite.yaml`,
88
+ deep-merged over the common (**later-wins**, keyed by `type` + data-point `id`): same `id` replaces,
89
+ new `id` appends, `constraints` merge key-by-key. `sungen update` refreshes `common/` but never
90
+ touches your overlay. If an update makes an overlay entry stale (references a bank/constraint that no
91
+ longer exists), the loader **warns you to prune it** — it never edits your file. Prefer a targeted
92
+ `errorMap`/`constraints` in the field-map for per-screen tweaks; use `.overwrite` only for rules that
93
+ apply project-wide.
94
+
95
+ ## Bulk-import fixtures (CSV)
96
+
97
+ For CSV/Excel import or bulk-upload testing, `sungen data fixture --screen <name> [--rows N] [--invalid]`
98
+ turns the field-map into a fixture FILE under `qa/fixtures/`: `<name>.csv` (N valid rows; nested groups
99
+ → dotted headers; `unique` fields row-indexed) that should import cleanly, and — with `--invalid` —
100
+ `<name>.invalid.csv` (one seeded-bad row per field + a `_violation` column) for row-level rejection
101
+ tests. Reference it from an `@api` upload (`files:`) or a UI file input.
102
+
103
+ ## Verify before done — the phuong-phap §6 checklist
104
+
105
+ Run `sungen data lint` and clear it: every required field has **≥1 valid**, **boundary present**
106
+ (exact min/max, not only over-boundary) for bounded types, **≥1 invalid per rule** (not one generic
107
+ "wrong" value for many rules), no `UNRESOLVED` values, error codes mapped or explicitly left open.
108
+ Then `sungen data gen` writes the standardized `test-data/<name>.yaml`. Do not hand-edit the
109
+ generated values — change the field-map or `.overwrite` and re-run (determinism: same input → same
110
+ data).
111
+
112
+ ## Weaving into scenarios
113
+
114
+ When a `.feature` exists, express invalid/boundary sets as data-driven `Scenario Outline` + `Examples`
115
+ (`@cases`) referencing the values — see `sungen-gherkin-syntax`. This keeps one logic + many values
116
+ instead of copy-pasted scenarios, and each row carries its `CHK-*` id for traceability.
@@ -78,9 +78,20 @@ needs any of these, it is a **finding for QA** — surface it in the run summary
78
78
  | strict mode violation | Multiple elements match | Add `nth: 0`, `exact: true`, or more specific `name` |
79
79
  | Timeout / not found | Element doesn't exist or name wrong | Re-snapshot → copy exact accessible name. Check iframe/dialog scope |
80
80
  | Element is not an input | Wrong element type targeted | Change `type` or `value` to match actual element |
81
- | not a select | Custom dropdown, not native `<select>` | Set `variant: 'custom'` |
81
+ | not a select | Custom dropdown, not native `<select>` | Set the widget variant: `variant: radix-select`/`antd-select` (built-in recipes), a custom recipe name from `qa/app.yaml`, or project-wide `widgets.select` in `qa/app.yaml` |
82
82
  | Frame not found | iframe selector wrong or doesn't exist | Fix `frame` value, verify iframe in snapshot |
83
83
 
84
+ ### Compile diagnostics (`sungen generate` output) → fix BEFORE running
85
+
86
+ | Code | Meaning | Fix |
87
+ |---|---|---|
88
+ | `SG-W001` | Step matched no pattern — compiled to a no-op `// TODO` (the test runs weaker than it reads) | Rephrase to a documented pattern (`sungen-gherkin-syntax`) or tag `@manual` with a reason |
89
+ | `SG-W002` | A step's `{{value}}` was parsed but never used in the generated code | The step form isn't the one you think — check the pattern's exact syntax |
90
+ | `SG-W003` | `{ name }` filter defaulted from the Gherkin reference on a name-from-content role (toast/alert/status) | Add explicit `name:` or opt out with `name: null` |
91
+ | `SG-W005` | `qa/app.yaml` unknown key / unknown preset / unparseable | Fix the key, or `extends: native\|radix\|antd` |
92
+ | `SG-W006` | `qa/app.<env>.yaml` found — unsupported by design | Describe the app once in `qa/app.yaml`; env differences belong in test-data overlays |
93
+ | `SG-W007` | Browser-alert handler step written AFTER an action — listener registers too late, the dialog auto-dismisses silently | Use the compound form `click [X] button and accept [OK] alert` (preferred), or move the alert step before the trigger |
94
+
84
95
  ### Assertion errors → apply the Source-of-truth gate above FIRST
85
96
 
86
97
  > The "Fix" column below applies **only when the expected value was wrong relative to `spec.md`** (a test defect). If the app's value contradicts the spec, the row is a **candidate bug → report it, do not edit the expected to match live**. Never weaken `toHaveText`→`toContainText` just to pass.
@@ -50,16 +50,41 @@ User expand | collapse [T] row
50
50
  ### Alert / Keyboard / Wait / Scroll
51
51
 
52
52
  ```
53
- User click [OK | Cancel] alert
53
+ User click [T] button and accept [OK] alert # PREFERRED (v3.3): natural order,
54
+ User click [T] button and dismiss [Cancel] alert # compiler registers the listener first
55
+ User click [OK | Cancel] alert # two-step form: must come BEFORE the trigger
54
56
  User fill [T] alert with {{v}}
55
57
  User see [message text] alert
56
58
  User press Escape key | Enter on [T] field
57
- User wait for N seconds | [T] dialog | [T] dialog is STATE | [T] page
59
+ User wait for N seconds | [T] page
60
+ User wait for [T] TYPE is visible | hidden | enabled | disabled # ANY reference (v3.3)
61
+ User wait for [T] TYPE with {{v}} # until it shows the value
62
+ User wait for [T] table to refresh # filter/search/pagination round-trip (v3.3)
58
63
  User scroll to [T] section
59
64
  User switch to [T] frame | [main] frame
60
65
  ```
61
66
 
62
- > Alert steps must appear BEFORE the action that triggers the dialog.
67
+ > **Browser alerts (native `window.confirm/alert/prompt` only):** prefer the compound form
68
+ > it reads in the user's real order and declares the trigger↔dialog causality in one step.
69
+ > The two-step form stays valid but the alert step must appear BEFORE the triggering action
70
+ > (a late `page.once('dialog')` lets the dialog auto-dismiss silently — compile warning
71
+ > `SG-W007`). Custom confirm MODALS (ARIA dialogs — most modern apps) are ordinary buttons:
72
+ > write normal click steps, no alert step at all.
73
+ > State-based waits (`is hidden`, `with {{v}}`, `table to refresh`) are legitimate; time-based
74
+ > `wait for N seconds` stays a last resort. `table to refresh` watches the app's loading
75
+ > indicator (`qa/app.yaml` `feedback.loading.indicator`, default `[aria-busy="true"]`).
76
+
77
+ ### Positional table rows (v3.3)
78
+
79
+ ```
80
+ User remember [Col] column in [T] table row {{n}} as {{var}} # read a cell by POSITION
81
+ User click [Act] button in [T] table row {{n}} # act on the SAME row
82
+ User click [Act] button in [T] table row 2 # literal row number
83
+ ```
84
+
85
+ Row numbers are 1-based data rows (header not counted; override via `qa/app.yaml`
86
+ `conventions.table`). Use positional rows when you must READ then ACT on the same row —
87
+ two content-filtered queries can hit different rows if the table re-renders in between.
63
88
 
64
89
  ### Assertions (8 patterns → determines Playwright assertion)
65
90
 
@@ -118,14 +143,24 @@ Scenario: ...
118
143
 
119
144
  Path access on a bound result: `{{q.count}}`/`{{q.length}}`, `{{q.first.col}}`, `{{q.last.col}}`, `{{q[2].col}}`, `{{q.col}}` (= first row's col). `expect A is B` also supports `is at least` / `is at most` / `is not`. Tier-2 declarative (trivial inline, no catalog): `User see [<table>] row where [<col>] is {{v}} [has [<col2>] = "x"]`, `… no row where …`, `… count is {{n}}`. Full grammar + catalog/datasource/secret rules → **Advanced → Database** doc. Only emit DB steps when the project has a `database/` catalog / `datasources.yaml`.
120
145
 
146
+ ### NoSQL engines (Cosmos, MongoDB, DynamoDB) — natively supported
147
+
148
+ Beyond the three SQL engines, the Data Driver has native NoSQL engines. Declarative steps (`User see [table] row where …`, `… no row …`, `… count is …`) work across ALL of them; only the raw `@query` catalog shape differs per engine:
149
+
150
+ - **`engine: cosmos`** (Azure Cosmos DB, NoSQL API) — SQL dialect. Author `sql:` catalog entries against the mandatory `c` alias (`SELECT * FROM c WHERE c.<field> = :p`); named params bind automatically. Config: `endpoint`+`key` (or a connection-string `url`), `database`, `container` (default when no `[table]`). Read-only by construction (the query API can't mutate).
151
+ - **`engine: mongodb`** — no SQL. Author a `find:` (`{collection, filter, projection?, sort?, limit?}`) or `pipeline:` (`{collection, stages: […]}`) catalog entry; `:param` placeholders bind at runtime. `$out`/`$merge`/`$where`/`$function`/`$accumulator`/`mapReduce` are refused. Config: a `mongodb://` `url`. Read-only = a server-side read role.
152
+ - **`engine: dynamodb`** — raw `@query` is PartiQL SELECT (`sql: SELECT … FROM "table" WHERE pk = :p`). Declarative steps route GetItem/Query only for partition-/sort-key filters via a `keySchema:` registry; a **non-key filter is refused** (use a raw PartiQL SELECT — never a full-table Scan). Config: `region` + `keySchema` (credentials from the AWS env chain). Read-only = IAM `PartiQLSelect`-only.
153
+
154
+ Full config + catalog examples → **Advanced → Database** doc.
155
+
121
156
  ### Unsupported DB engine — fallback (ask, don't improvise)
122
157
 
123
- Direct SQL verification (`@query`, the Data Driver) only supports these engines: **`{postgres, sqlite, mysql}`**. When a datasource's `engine` (or the DB the user describes) is **outside this set** — e.g. Cosmos NoSQL, MongoDB, Cassandra, DynamoDB:
158
+ Direct SQL verification (`@query`, the Data Driver) covers these SQL engines: **`{postgres, sqlite, mysql}`** — plus the three native NoSQL engines above (Cosmos, MongoDB, DynamoDB). When a datasource's `engine` (or the DB the user describes) is **outside all of those** — e.g. Cassandra, Oracle, Neo4j, a proprietary store:
124
159
 
125
160
  1. **Do not** attempt a direct connect. **Do not** improvise a verification method.
126
161
  2. Present a fixed `AskUserQuestion` with exactly these 3 branches (always these, no others invented):
127
162
  - **@api** — verify state via the project's internal API (route to the API driver).
128
- - **@manual** — emit a `@manual` step + manual-check guidance (e.g. Cosmos Data Explorer, `az cosmosdb`, the vendor CLI).
163
+ - **@manual** — emit a `@manual` step + manual-check guidance (e.g. the vendor console / CLI — `cqlsh` for Cassandra, `cypher-shell` for Neo4j).
129
164
  - **Proxy / read-replica SQL** — if the user can supply a SQL-compatible endpoint (including Azure Cosmos DB **for PostgreSQL** / Citus), treat it as `engine: postgres` (or `mysql`) and use the normal Data Driver path above.
130
165
  3. **Recommend, don't decide.** Check the project for signals and put the best-fit branch **first**, tagged **"(Recommended)"** with a one-line reason:
131
166
  - a project API driver / `qa/api/` catalog / `PEERCONNE_URL`-style internal API exists → **@api** first;
@@ -138,11 +173,11 @@ Direct SQL verification (`@query`, the Data Driver) only supports these engines:
138
173
  Example:
139
174
  ```
140
175
  AskUserQuestion:
141
- question: "This datasource's engine (cosmos-nosql) isn't in {postgres, sqlite, mysql} — direct SQL connect isn't supported. How should DB state be verified?"
176
+ question: "This datasource's engine (cassandra) isn't a supported Data Driver engine ({postgres, sqlite, mysql} + Cosmos/MongoDB/DynamoDB) — direct connect isn't supported. How should DB state be verified?"
142
177
  options:
143
178
  - "@api — verify via the project's internal API (Recommended: qa/api/ catalog found)"
144
- - "@manual — emit a @manual step + Cosmos Data Explorer / az cosmosdb check"
145
- - "Proxy / read-replica SQL — supply a SQL-compatible endpoint (e.g. Cosmos DB for PostgreSQL) → engine: postgres"
179
+ - "@manual — emit a @manual step + cqlsh check"
180
+ - "Proxy / read-replica SQL — supply a SQL-compatible endpoint → engine: postgres"
146
181
  - "Other — describe your own verification approach"
147
182
  ```
148
183
 
@@ -83,6 +83,13 @@ When running Phase 0 for a **flow** (`qa/flows/<name>/`), check existing screen
83
83
  - If any mismatch → write an explicit YAML entry using the real DOM name. Do not leave a mismatch to be caught at runtime.
84
84
  - **Typical mismatch cases**: Gherkin uses English label (`[Submit]`) but app displays Vietnamese (`"Gửi"`); placeholder is descriptive (`"Nhập email của bạn"`) not a bare field name (`"Email"`); button text includes an icon glyph before/after the word.
85
85
  7. **Substring ambiguity check**: for each `role` + `name` selector, check if any other element in the snapshot has a name that **contains** this name as a substring (e.g., `"Đăng ký"` vs `"Đăng ký bằng Google"`). If yes → add `exact: true` to prevent strict mode violation at runtime.
86
+ 7b. **Widget & profile fingerprinting** (App Profile, v3.3): while the snapshot is open, identify the UI library and non-standard widget behavior, and record it so the compiler stops guessing:
87
+ - **Fingerprints** (check DOM classes/attributes): `.ant-select`/`.ant-picker` → antd · `[data-radix-*]` / shadcn portals → radix · `.MuiSelect-*`/`.MuiPickersDay` → MUI · `.select2-container` → select2 · plain `<select>` → native.
88
+ - **Project-wide** conclusion (the whole app uses one library) → write/update `qa/app.yaml`: `extends: radix|antd|native`. **Element-level** outlier (one widget differs from the rest) → set `variant:` on that selector entry only (e.g. `variant: native`, `variant: antd-select`).
89
+ - Custom widget nobody's preset covers (in-house datepicker etc.) → author a recipe in `qa/app.yaml` `recipes:` (closed primitives: click/type/press/waitFor/clickText/expectText/expectValue/forEach) and bind it per element with `variant: <recipe-name>`.
90
+ - Toast/alert named from content (no aria-label) → `feedback.toast: { role: status, named_by: content }` + `name: null` on the selector entries.
91
+ - Loading indicator for `wait for [T] table to refresh` → `feedback.loading.indicator` (skip if the app sets `aria-busy`).
92
+ - **Every qa/app.yaml edit MUST carry provenance** in `detection:`: who/when (`detected_by`) and the observed evidence per key (`evidence:`). A profile without evidence is a guess a human cannot review.
86
93
  8. **Merge, don't overwrite**: preserve the page selector and any user-authored entries in `selectors.yaml`. Only add missing keys.
87
94
  - **Type-collision check** (see `sungen-selector-keys` § Collision rule): before writing a base key, check whether it already exists with a *different* `type`. If so, give **both** entries a `--type` suffix instead of clobbering or sharing the base key — a base-key entry of the wrong type gets picked up by any step referencing that label. Watch `page` especially: a `[X] page` step reads `value` as a URL, so if `X` already holds a button (`value: 'button'`) the assertion compiles to `toHaveURL(/button/)`. Write `X--page` (URL from the live page / feature `Path:`) alongside `X--button`.
88
95
  9. **Show summary + confirm**: list the keys that will be added, ask the user to approve, then write the file.
@@ -301,6 +308,18 @@ Only when `test-results/` screenshots are insufficient:
301
308
 
302
309
  Never use `browser_evaluate` to inject or read cookies (misses httpOnly). For auth, use Phase 0.5 or `sungen makeauth`.
303
310
 
311
+ ### Step 2b: Selector sanity probe — suspect the compiler/profile BEFORE the app
312
+
313
+ For every failing locator, while the live page is in the failing state, count matches before attributing the failure to server load, timing, or flakiness:
314
+
315
+ 1. **Probe the exact generated locator** (copy it from the `.spec.ts`): count = 0 while the element is clearly on screen → the locator is wrong, not the app.
316
+ 2. **Peel one constraint at a time and re-count**:
317
+ - drop the `{ name }` filter → now found? → invented/wrong accessible name → fix `name:` or set `name: null` (name-from-content roles; matches compile warning `SG-W003`)
318
+ - drop the dialog/parent scope → now found? → portal-rendered element → check `qa/app.yaml` `layers.portal_roles` / remove a wrong `scope: 'dialog'`
319
+ - drop `exact`/text filter → now found? → text mismatch (NBSP, trailing space, i18n) → fix the value or use `{{variable}}`
320
+ 3. **Repair at the right altitude**: a per-element mistake → fix that `selectors.yaml` entry; a pattern repeated across elements (every toast, every dropdown) → fix `qa/app.yaml` once (with `detection.evidence`) so every screen inherits the fix. **Never rewrite the `.feature` to dodge a selector problem** — intent stays, identity/behavior get repaired.
321
+ 4. Also read the generate output: `SG-W001/W002/W003` diagnostics fired at compile time usually name the broken step before you run anything.
322
+
304
323
  ### Step 3: Fix Broken Selectors
305
324
 
306
325
  Selector priority (use first applicable):
@@ -367,6 +386,9 @@ Quick reference for the most frequent production failures:
367
386
  | `No element found` on `[X] field` | Field has no placeholder, or placeholder ≠ X | Write explicit YAML: `type: label, value: "Actual label"` or `type: placeholder, value: "Actual placeholder"` |
368
387
  | `No element found` on `[X] text` / `message` | Visible text differs from Gherkin label, or text is dynamic | Write explicit YAML or use `{{variable}}` for dynamic content |
369
388
  | `strict mode violation` | Multiple elements match the same name/text | Add `exact: true` to YAML entry, or add `nth` |
389
+ | `strict mode violation` on `… with {{v}}` clicks (prefix-sharing options: "Văn phòng Hà Nội 1/2/…") | The `{{v}}` filter is a substring match by default | Add `exact: true` — since 3.3 it also anchors the `.filter({ hasText })` of action steps (`/^value$/`), not just role-name matching |
390
+ | Toast/alert/spinner assertion times out but `[role="status"]` exists on the live page (`SG-W003` at generate time) | Entry has no `name:` → compiler defaulted `{ name: '<Gherkin ref>' }`, but status/alert names derive from CONTENT and never equal the reference | Add `name: null` to the entry (match by role only). Do NOT switch to `type: locator` |
391
+ | Option/menu click inside a dialog times out, but the SAME locator works without dialog scope | Portal-rendered layer (Radix/MUI/antd) lives outside the dialog's DOM subtree | Since 3.3 the compiler page-roots portal roles (option/listbox/menu/menuitem/tooltip) automatically; for a listbox genuinely inside the dialog add `scope: 'dialog'` to the entry. Check `qa/app.yaml` `layers.portal_roles` (preset `native` disables the exemption) |
370
392
  | `toBeVisible` timeout on dynamic content | Snapshot was taken while page was still loading | Wait for spinner/skeleton to clear before snapshotting; add `browser_wait_for` |
371
393
  | All tests fail with page navigate error | Page selector URL wrong or baseURL mismatch | Re-check `playwright.config.ts` `baseURL` and page selector `value` path |
372
394
  | Auth redirect on every test | `specs/.auth/<role>.json` missing or expired | Run Phase 0.5 to capture fresh session |
@@ -239,7 +239,20 @@ If no YAML key exists, the resolver infers from the Gherkin element type:
239
239
 
240
240
  **Special note on `[X] field`**: `getByPlaceholder('X')` only works when (1) the field has a placeholder attribute AND (2) the placeholder text equals X. For fields without placeholders (floating labels, aria-label), write explicit YAML: `type: label, value: "Actual label text"`.
241
241
 
242
- **Only add a YAML entry when** auto-infer cannot work: DOM name differs from Gherkin label, need `testid`, need `nth`, need `exact: true`, or the field type requires explicit config.
242
+ **Only add a YAML entry when** auto-infer cannot work: DOM name differs from Gherkin label, need `testid`, need `nth`, need `exact: true`, need `name: null`, or the field type requires explicit config.
243
+
244
+ ### `name: null` — name-from-content roles (toast, alert, spinner)
245
+
246
+ For a `type: role` entry **without** `name:`, the compiler defaults the accessible-name filter to the Gherkin `[Reference]` text. That is right for buttons/links — and **always wrong** for roles whose accessible name derives from their CONTENT (`status`, `alert`, `log`, `marquee`, `timer`): a toast's name is its message text, never the string "Success Toast". The compiler warns (`SG-W003`). Opt out explicitly:
247
+
248
+ ```yaml
249
+ success toast:
250
+ type: 'role'
251
+ value: 'status'
252
+ name: null # deliberate: match by role only, no name filter
253
+ ```
254
+
255
+ Use this for every toast/alert/spinner content assertion. Do NOT fall back to `type: locator, value: '[role="status"]'` — `name: null` keeps role semantics.
243
256
 
244
257
  ### Types requiring YAML entry (no auto-infer)
245
258
 
@@ -6,6 +6,7 @@ user-invocable: false
6
6
 
7
7
  ## ⚠️ Gotchas — read before generating
8
8
 
9
+ - **Field-level test-data follows the Data Factory standard — don't hand-invent values.** When the unit has input fields, the field values (valid / boundary / invalid + error codes) come from the **`sungen-data-factory`** catalog via a field-map + `sungen data gen` (create-test step 5.3), not free-form guessing. Weave the standardized boundary/invalid sets into `@cases` with `CHK-*` trace. This skill still owns scenario structure, viewpoints, and non-field oracles.
9
10
  - **Write incrementally — never emit the whole suite in one response.** Build the `.feature` in batches via successive `Write`/`Edit` (≈10–15 scenarios per call). For **Full coverage**, write tier-by-tier: `Write` Tier 1 → `Edit` append Tier 2 → `Edit` append Tier 3.
10
11
  → One huge `Write` can exceed the model's output-token cap → `API Error: Claude's response exceeded the N output token maximum`. Single-pass full coverage only fits when `CLAUDE_CODE_MAX_OUTPUT_TOKENS ≥ 64000`; otherwise batch. Batching also lets the audit/reviewer run per batch — higher quality.
11
12
 
@@ -1,36 +1,226 @@
1
1
  /**
2
- * `build()` always emits PG-style `$1..$n` placeholders; mysql2/better-sqlite3 both bind with `?`.
3
- * Only real placeholders are rewritten a `$1` sitting inside a single-quoted string literal (e.g.
4
- * a catalog query `WHERE label = '$1 off'`) is left untouched. SQL escapes an inner quote by doubling
5
- * it (`''`), which the simple in-string toggle handles (an even number of quotes returns to the same state).
2
+ * `build()` always emits PG-style `$1..$n` placeholders; mysql2/better-sqlite3 both bind with
3
+ * positional `?` (left-to-right). Rewriting `$n`→`?` must therefore ALSO reorder the params to the
4
+ * textual placeholder order: an author-written catalog query `WHERE a = $2 AND b = $1` becomes
5
+ * `a = ? AND b = ?`, so the params must be bound `[params[1], params[0]]` — otherwise the values
6
+ * silently swap and the query returns wrong rows. A repeated `$k` duplicates its value at each `?`.
7
+ * Postgres binds `$n` natively, so it is returned untouched.
8
+ *
9
+ * Only real placeholders are rewritten — a `$1` inside a single-quoted string literal (e.g.
10
+ * `WHERE label = '$1 off'`) is left untouched. SQL escapes an inner quote by doubling it (`''`),
11
+ * which the simple in-string toggle handles (an even number of quotes returns to the same state).
12
+ *
13
+ * DynamoDB PartiQL binds the same positional `?` as mysql2/sqlite, so it shares this path — the
14
+ * reorder is essential there too (an author-written PartiQL `WHERE sk = $2 AND pk = $1`).
6
15
  */
7
- export declare function rewritePlaceholders(engine: string, sql: string): string;
16
+ export declare function rewritePlaceholders(engine: string, sql: string, params: any[]): {
17
+ sql: string;
18
+ params: any[];
19
+ };
20
+ /**
21
+ * Engine contract — the assertion surface is engine-agnostic; each engine owns its own dialect so
22
+ * a NoSQL store (Cosmos / MongoDB / DynamoDB) can back the same declarative steps as SQL.
23
+ * findRows(table, filter, limit) — rows matching an equality filter, capped at `limit`
24
+ * countRows(table, filter) — count of rows matching an equality filter
25
+ * runQuery(label, sql, params) — a raw catalog query (read-only). SQL dialects (incl. Cosmos SQL
26
+ * and DynamoDB PartiQL) take a SQL string; MongoDB's non-SQL
27
+ * find/pipeline shapes are dispatched separately via fetchQueryShape.
28
+ */
29
+ interface Engine {
30
+ findRows(table: string, filter: Record<string, any>, limit: number): Promise<any[]>;
31
+ countRows(table: string, filter: Record<string, any>): Promise<number>;
32
+ runQuery(label: string, sql: string, params: any[]): Promise<any[]>;
33
+ runShape?(label: string, shape: ShapeQuery, params: Record<string, any>): Promise<any[]>;
34
+ }
35
+ /** A MongoDB catalog entry's runtime shape: exactly one of find/pipeline, with `:param` placeholders bound at runtime. */
36
+ type ShapeQuery = {
37
+ find?: {
38
+ collection: string;
39
+ filter?: Record<string, any>;
40
+ projection?: Record<string, any>;
41
+ sort?: Record<string, any>;
42
+ limit?: number;
43
+ };
44
+ pipeline?: {
45
+ collection: string;
46
+ stages: any[];
47
+ };
48
+ };
49
+ /** Minimal shape of an @azure/cosmos container the engine uses — kept local so the SDK stays a lazy dep. */
50
+ type CosmosContainer = {
51
+ items: {
52
+ query(spec: {
53
+ query: string;
54
+ parameters: Array<{
55
+ name: string;
56
+ value: any;
57
+ }>;
58
+ }, opts?: any): {
59
+ fetchAll(): Promise<{
60
+ resources: any[];
61
+ }>;
62
+ };
63
+ };
64
+ };
65
+ /**
66
+ * Azure Cosmos DB (NoSQL API) engine. The Cosmos query API is read-only by construction — `.query()`
67
+ * only ever reads; mutations require item-level APIs this engine never calls — so the SELECT guard is
68
+ * defense-in-depth. Cosmos binds NAMED params (`@p`), so it is immune to the positional-`?` reorder
69
+ * hazard. A step/catalog `[table]` selects the container; `SELECT * FROM c` uses the mandatory `c` alias.
70
+ */
71
+ export declare class CosmosEngine implements Engine {
72
+ private getContainer;
73
+ private opts;
74
+ constructor(getContainer: (name?: string) => CosmosContainer, opts: {
75
+ timeoutMs: number;
76
+ maxRows: number;
77
+ });
78
+ private query;
79
+ /** Build `c.<col> = @p<i>` equality predicates over the mandatory `c` alias. Columns are identifier-checked. */
80
+ private whereFor;
81
+ findRows(table: string, filter: Record<string, any>, limit: number): Promise<any[]>;
82
+ countRows(table: string, filter: Record<string, any>): Promise<number>;
83
+ runQuery(label: string, sql: string, params: any[]): Promise<any[]>;
84
+ }
85
+ /**
86
+ * Rewrite compiled positional `$n` placeholders to Cosmos NAMED params (`@pN`) and collect the
87
+ * matching parameter list. A `$n` inside a single-quoted string literal is left untouched (same
88
+ * in-string toggle as the SQL rewrite); a repeated `$n` reuses its one `@pN` entry.
89
+ */
90
+ export declare function cosmosBindParams(sql: string, params: any[]): {
91
+ query: string;
92
+ parameters: Array<{
93
+ name: string;
94
+ value: any;
95
+ }>;
96
+ };
97
+ /** Minimal shapes of the mongodb driver objects the engine uses — kept local so the SDK stays a lazy dep. */
98
+ type MongoCursor = {
99
+ project(p: any): MongoCursor;
100
+ sort(s: any): MongoCursor;
101
+ limit(n: number): MongoCursor;
102
+ maxTimeMS(t: number): MongoCursor;
103
+ toArray(): Promise<any[]>;
104
+ };
105
+ type MongoCollection = {
106
+ find(filter: any): MongoCursor;
107
+ countDocuments(filter: any, opts?: any): Promise<number>;
108
+ aggregate(pipeline: any[], opts?: any): {
109
+ toArray(): Promise<any[]>;
110
+ };
111
+ };
112
+ /**
113
+ * MongoDB engine. There is no SQL — declarative steps map to native find()/countDocuments(), and the
114
+ * raw `@query` path takes a find/pipeline shape (via fetchQueryShape → runShape) rather than a SQL
115
+ * string. Type-strict matching (`{age:30}` ≠ `{age:"30"}`) is why DB params bind via raw() (native
116
+ * type), not the stringifying get(). Read-only is enforced first by the server-side read role, with
117
+ * the operator blocklist as in-driver defense-in-depth. Per-query `maxTimeMS` + row cap bound cost.
118
+ */
119
+ export declare class MongoEngine implements Engine {
120
+ private getCollection;
121
+ private opts;
122
+ constructor(getCollection: (name: string) => MongoCollection, opts: {
123
+ timeoutMs: number;
124
+ maxRows: number;
125
+ });
126
+ findRows(table: string, filter: Record<string, any>, limit: number): Promise<any[]>;
127
+ countRows(table: string, filter: Record<string, any>): Promise<number>;
128
+ runQuery(label: string): Promise<any[]>;
129
+ runShape(label: string, shape: ShapeQuery, params: Record<string, any>): Promise<any[]>;
130
+ }
131
+ /** The three read-only DynamoDB operations the engine needs, over the auto-marshalling Document client. */
132
+ type DynamoOps = {
133
+ executeStatement(input: {
134
+ Statement: string;
135
+ Parameters?: any[];
136
+ Limit?: number;
137
+ NextToken?: string;
138
+ }, signal?: AbortSignal): Promise<{
139
+ Items?: any[];
140
+ NextToken?: string;
141
+ }>;
142
+ getItem(input: {
143
+ TableName: string;
144
+ Key: Record<string, any>;
145
+ }, signal?: AbortSignal): Promise<{
146
+ Item?: any;
147
+ }>;
148
+ query(input: {
149
+ TableName: string;
150
+ KeyConditionExpression: string;
151
+ ExpressionAttributeNames: Record<string, string>;
152
+ ExpressionAttributeValues: Record<string, any>;
153
+ Select?: string;
154
+ Limit?: number;
155
+ ExclusiveStartKey?: Record<string, any>;
156
+ }, signal?: AbortSignal): Promise<{
157
+ Items?: any[];
158
+ Count?: number;
159
+ LastEvaluatedKey?: Record<string, any>;
160
+ }>;
161
+ };
162
+ type KeySchema = Record<string, {
163
+ partitionKey: string;
164
+ sortKey?: string | null;
165
+ }>;
166
+ /**
167
+ * DynamoDB engine. The clean primary path is raw `@query` PartiQL SELECT (positional `?`, reordered
168
+ * via the shared placeholder rewrite). Declarative assertRow/count is structurally constrained by
169
+ * DynamoDB's key model: it routes GetItem/Query only for pk / pk+sk filters (via the `keySchema`
170
+ * registry) and REFUSES a non-key filter rather than falling back to an unaffordable full-table Scan.
171
+ * Read-only is primarily IAM (`dynamodb:PartiQLSelect` only); the SELECT guard is defense-in-depth.
172
+ */
173
+ export declare class DynamoEngine implements Engine {
174
+ private ops;
175
+ private keySchema;
176
+ private opts;
177
+ constructor(ops: DynamoOps, keySchema: KeySchema, opts: {
178
+ timeoutMs: number;
179
+ maxRows: number;
180
+ });
181
+ private withTimeout;
182
+ /** Decide how a flat equality filter maps to a key-based access pattern, or refuse it. */
183
+ private route;
184
+ findRows(table: string, filter: Record<string, any>, limit: number): Promise<any[]>;
185
+ countRows(table: string, filter: Record<string, any>): Promise<number>;
186
+ runQuery(label: string, sql: string, params: any[]): Promise<any[]>;
187
+ }
8
188
  declare class DataSource {
9
189
  private configs;
10
190
  private engines;
11
191
  private tunnels;
12
192
  private mysqlPools;
193
+ private mongoClients;
13
194
  private cfg;
14
195
  private engine;
15
- /** Close open MySQL pools + SSH tunnels (optional explicit teardown; tunnels are unref'd so the process exits regardless). */
196
+ /** Build the lazy driver-call closure for a SQL engine (pg/mysql/sqlite), incl. the optional SSH-tunnel fallback. */
197
+ private sqlExec;
198
+ /** Build the Cosmos engine — connect via endpoint+key or a connection-string url; resolve containers by name. */
199
+ private cosmosEngine;
200
+ /** Build the MongoDB engine — one reused client per datasource; the db comes from `database` or the URI default. */
201
+ private mongoEngine;
202
+ /** Build the DynamoDB engine — region + credentials from the AWS env chain; wraps the Document client's three read ops. */
203
+ private dynamoEngine;
204
+ /** Close open MySQL pools + Mongo clients + SSH tunnels (optional explicit teardown; tunnels are unref'd so the process exits regardless). */
16
205
  close(): void;
17
- private build;
18
206
  /** A row matching `filter` must exist; if `expected` given, assert those columns on the first match. */
19
207
  assertRow(table: string, filter: Record<string, any>, expected?: Record<string, any>, datasource?: string): Promise<void>;
20
208
  /** No row matching `filter` may exist. */
21
209
  assertNoRow(table: string, filter: Record<string, any>, datasource?: string): Promise<void>;
22
210
  /** Exactly `count` rows must match `filter`. */
23
211
  assertCount(table: string, filter: Record<string, any>, count: number, datasource?: string): Promise<void>;
24
- /** Rewrites $1/$2 placeholders to `?` for engines that don't support PG-style positional params. */
25
- private sqlFor;
26
- /** Read-only guard (second layer): a named query must be a single SELECT/WITH statement. */
27
- private assertSelectOnly;
28
212
  /**
29
213
  * Run a catalog query (read-only) and return its rows. The result is bound to a `{{name}}`
30
214
  * variable via `testData.bind(...)`, so the scenario asserts on it with `expect …` steps and
31
215
  * path access (`{{name.count}}`, `{{name.first.col}}`, `{{name[2].col}}`).
32
216
  */
33
217
  fetchQuery(label: string, sql: string, params: any[], datasource?: string): Promise<any[]>;
218
+ /**
219
+ * Run a MongoDB find/pipeline catalog query (read-only) and return its documents. Same binding
220
+ * contract as fetchQuery, but the query is a structured shape (not SQL) with `:param` placeholders
221
+ * bound from `params` at runtime. Refused on an engine that has no non-SQL shape support.
222
+ */
223
+ fetchQueryShape(label: string, shape: ShapeQuery, params: Record<string, any>, datasource?: string): Promise<any[]>;
34
224
  }
35
225
  export declare const db: DataSource;
36
226
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"specs-db.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-db.ts"],"names":[],"mappings":"AA8HA;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAiBvE;AAID,cAAM,UAAU;IACd,OAAO,CAAC,OAAO,CAAiD;IAChE,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,OAAO,CAAoC;IACnD,OAAO,CAAC,UAAU,CAA2C;IAE7D,OAAO,CAAC,GAAG;YAQG,MAAM;IAkEpB,8HAA8H;IAC9H,KAAK,IAAI,IAAI;IAOb,OAAO,CAAC,KAAK;IAOb,wGAAwG;IAClG,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAc/H,0CAA0C;IACpC,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOjG,gDAAgD;IAC1C,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUhH,oGAAoG;IACpG,OAAO,CAAC,MAAM;IAKd,4FAA4F;IAC5F,OAAO,CAAC,gBAAgB;IASxB;;;;OAIG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;CAKjG;AAMD,eAAO,MAAM,EAAE,YAAmB,CAAC"}
1
+ {"version":3,"file":"specs-db.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-db.ts"],"names":[],"mappings":"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"}