groundwork-method 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/bin/groundwork.js +86 -17
  3. package/dist/src/generators/system-test-runner/generator.js +52 -4
  4. package/dist/src/generators/system-test-runner/generator.js.map +1 -1
  5. package/package.json +1 -1
  6. package/src/docs/principles/design/usability-and-ux.md +11 -0
  7. package/src/docs/principles/foundations/testing.md +32 -6
  8. package/src/docs/principles/index.md +2 -1
  9. package/src/docs/principles/quality/observability.md +2 -2
  10. package/src/engineer-skills/groundwork-electron-engineer/SKILL.md +6 -1
  11. package/src/engineer-skills/groundwork-electron-engineer/references/documentation.md +126 -0
  12. package/src/engineer-skills/groundwork-electron-engineer/references/observability.md +37 -0
  13. package/src/engineer-skills/groundwork-electron-engineer/references/performance-and-reliability.md +80 -0
  14. package/src/engineer-skills/groundwork-electron-engineer/references/testing-and-smoke.md +22 -0
  15. package/src/engineer-skills/groundwork-electron-engineer/sync-anchor.md +12 -4
  16. package/src/engineer-skills/groundwork-flutter-engineer/SKILL.md +7 -1
  17. package/src/engineer-skills/groundwork-flutter-engineer/references/documentation.md +122 -0
  18. package/src/engineer-skills/groundwork-flutter-engineer/references/observability.md +37 -0
  19. package/src/engineer-skills/groundwork-flutter-engineer/references/performance-and-reliability.md +100 -0
  20. package/src/engineer-skills/groundwork-flutter-engineer/references/security.md +96 -0
  21. package/src/engineer-skills/groundwork-flutter-engineer/references/testing.md +25 -0
  22. package/src/engineer-skills/groundwork-flutter-engineer/sync-anchor.md +13 -4
  23. package/src/engineer-skills/groundwork-go-engineer/SKILL.md +5 -2
  24. package/src/engineer-skills/groundwork-go-engineer/references/documentation.md +130 -0
  25. package/src/engineer-skills/groundwork-go-engineer/references/testing.md +63 -1
  26. package/src/engineer-skills/groundwork-go-engineer/sync-anchor.md +13 -4
  27. package/src/engineer-skills/groundwork-nextjs-engineer/SKILL.md +6 -1
  28. package/src/engineer-skills/groundwork-nextjs-engineer/references/accessibility.md +111 -0
  29. package/src/engineer-skills/groundwork-nextjs-engineer/references/observability.md +48 -0
  30. package/src/engineer-skills/groundwork-nextjs-engineer/references/security.md +131 -0
  31. package/src/engineer-skills/groundwork-nextjs-engineer/references/testing.md +59 -1
  32. package/src/engineer-skills/groundwork-nextjs-engineer/references/ux-principles.md +1 -49
  33. package/src/engineer-skills/groundwork-nextjs-engineer/sync-anchor.md +10 -3
  34. package/src/engineer-skills/groundwork-python-engineer/SKILL.md +5 -2
  35. package/src/engineer-skills/groundwork-python-engineer/references/security.md +148 -0
  36. package/src/engineer-skills/groundwork-python-engineer/references/testing.md +40 -1
  37. package/src/engineer-skills/groundwork-python-engineer/sync-anchor.md +11 -4
  38. package/src/generators/electron-app/docs/principles/stack/electron/index.md +2 -0
  39. package/src/generators/electron-app/files/tests/smoke/app.spec.ts.template +73 -8
  40. package/src/generators/flutter-app/docs/principles/stack/flutter/testing.md +14 -2
  41. package/src/generators/flutter-app/files/integration_test/app_test.dart.template +46 -12
  42. package/src/generators/go-microservice/docs/principles/stack/go/testing.md +17 -1
  43. package/src/generators/python-microservice/docs/principles/stack/python/testing.md +41 -0
  44. package/src/generators/system-test-runner/NATIVE-CHECK-CONTRACT.md +20 -0
  45. package/src/generators/system-test-runner/files/tests/system/test_render_smoke.py.template +30 -0
  46. package/src/generators/system-test-runner/generator.ts +58 -4
  47. package/src/generators/workspace-dev-cli/cli-src/dist/dev-bundle.js +1 -1
  48. package/src/hidden-skills/code-intelligence.md +6 -0
  49. package/src/hidden-skills/groundwork-architect/SKILL.md +1 -1
  50. package/src/hidden-skills/groundwork-architect/sync-anchor.md +2 -2
  51. package/src/hidden-skills/groundwork-bet/briefs/acceptance-auditor.md +68 -0
  52. package/src/hidden-skills/groundwork-bet/briefs/blind-reviewer.md +56 -0
  53. package/src/hidden-skills/groundwork-bet/briefs/coverage-auditor.md +95 -0
  54. package/src/hidden-skills/groundwork-bet/briefs/edge-case-tracer.md +64 -0
  55. package/src/hidden-skills/groundwork-bet/briefs/experience-auditor.md +83 -0
  56. package/src/hidden-skills/groundwork-bet/briefs/slice-worker.md +92 -26
  57. package/src/hidden-skills/groundwork-bet/instructions.md +4 -4
  58. package/src/hidden-skills/groundwork-bet/templates/bet-progress-test.md +16 -27
  59. package/src/hidden-skills/groundwork-bet/templates/change-proposal.md +1 -1
  60. package/src/hidden-skills/groundwork-bet/templates/decomposition/milestone-index.md +12 -16
  61. package/src/hidden-skills/groundwork-bet/templates/decomposition/slice.md +4 -8
  62. package/src/hidden-skills/groundwork-bet/templates/technical-design/03-api-design.md +1 -1
  63. package/src/hidden-skills/groundwork-bet/workflows/01-discovery.md +3 -1
  64. package/src/hidden-skills/groundwork-bet/workflows/02-design.md +11 -1
  65. package/src/hidden-skills/groundwork-bet/workflows/03-decomposition.md +60 -64
  66. package/src/hidden-skills/groundwork-bet/workflows/04-delivery.md +75 -42
  67. package/src/hidden-skills/groundwork-bet/workflows/05-validation.md +18 -7
  68. package/src/hidden-skills/groundwork-designer/sync-anchor.md +1 -1
  69. package/src/hidden-skills/groundwork-persona/instructions.md +11 -0
  70. package/src/hidden-skills/groundwork-review/checklists/implementation-readiness.md +1 -0
@@ -0,0 +1,96 @@
1
+ # Security
2
+
3
+ ## Table of Contents
4
+ - [The Posture](#the-posture)
5
+ - [No Secrets in the Binary](#no-secrets-in-the-binary)
6
+ - [Secure Storage for Tokens](#secure-storage-for-tokens)
7
+ - [Transport and Certificate Pinning](#transport-and-certificate-pinning)
8
+ - [Deep Link and Intent Validation](#deep-link-and-intent-validation)
9
+ - [Biometric and Auth-Token Handling](#biometric-and-auth-token-handling)
10
+ - [Obfuscation's Real Limits](#obfuscations-real-limits)
11
+ - [Security Review Checklist](#security-review-checklist)
12
+
13
+ ---
14
+
15
+ ## The Posture
16
+
17
+ A shipped mobile binary runs on a device the attacker owns. It can be pulled off the device, decompiled, patched, and run under a debugger or a proxy. So the client is **not** a trust boundary — it is hostile territory you are deploying into. Every security guarantee that matters is enforced server-side, behind the gateway (`references/data-and-contracts.md` → The Seam); the client's job is to handle credentials carefully and fail safe, not to keep secrets or enforce rules. This file is the Flutter idiom of the framework security canon (`docs/principles/quality/security.md`); when this file and the canon disagree, the canon wins and this file is the one to fix.
18
+
19
+ The recurring mistake is treating the app as trusted because it is "your" code. It stops being yours the moment it ships.
20
+
21
+ ## No Secrets in the Binary
22
+
23
+ There is no secure place in the app bundle for a secret. API keys, signing keys, and shared credentials compiled into Dart — or passed via `--dart-define`, or baked into a `.env` asset — are all recoverable from the binary. The config seam already states this: no `.env` files baked in, no secrets in `--dart-define` (`references/data-and-contracts.md` → Configuration).
24
+
25
+ - A capability that needs a secret (a third-party API key, a server-to-server credential) lives **server-side**, behind a core endpoint the app calls with its user session. The surface never embeds a provider key — one owner per capability, keys stay server-side.
26
+ - `--dart-define` is for non-secret configuration only: the gateway base URL, build flavour, feature toggles.
27
+ - If the app appears to need a secret to call a third party directly, that call belongs on the server; route it through the core.
28
+
29
+ ## Secure Storage for Tokens
30
+
31
+ Session and refresh tokens go in the platform keystore via `flutter_secure_storage`, which is backed by the iOS **Keychain** and the Android **Keystore**/EncryptedSharedPreferences. `SharedPreferences` and plain files are world-readable on a rooted/jailbroken device — they are never used for anything sensitive.
32
+
33
+ ```dart
34
+ const _storage = FlutterSecureStorage(
35
+ aOptions: AndroidOptions(encryptedSharedPreferences: true),
36
+ iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock_this_device),
37
+ );
38
+
39
+ Future<void> saveSession(String token) =>
40
+ _storage.write(key: 'session_token', value: token);
41
+ ```
42
+
43
+ - Tokens read out of secure storage are injected as a header by a single dio interceptor (`references/data-and-contracts.md` → dio Conventions), not stored in widget state or passed through constructors.
44
+ - `first_unlock_this_device` keeps the credential off device backups and off other devices; widen it only with a recorded reason.
45
+ - Clear secure storage on sign-out and on detected token invalidation — a lingering token is a credential waiting to be lifted.
46
+
47
+ ## Transport and Certificate Pinning
48
+
49
+ All traffic is HTTPS; the dio client rejects plaintext. For an app handling sensitive data, pin the gateway's certificate (or its public-key/SPKI hash) so a user-installed or malicious root CA cannot transparently proxy the connection.
50
+
51
+ ```dart
52
+ (dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () {
53
+ final client = HttpClient();
54
+ client.badCertificateCallback = (cert, host, port) =>
55
+ _spkiSha256(cert) == _pinnedSpkiHash; // pin to the key, not the leaf cert
56
+ return client;
57
+ };
58
+ ```
59
+
60
+ - Pin to the SPKI hash, not a specific leaf certificate, so routine certificate renewal does not brick the app; ship at least one backup pin for rotation.
61
+ - Pinning raises the bar against interception but is bypassable on a fully compromised device — it is defence in depth, not a substitute for server-side authorization.
62
+
63
+ ## Deep Link and Intent Validation
64
+
65
+ Every `GoRoute` is deep-linkable, and a deep link is untrusted input from another app or a web page (`references/navigation.md` → Deep Links). Route parameters arriving cold are hostile until proven otherwise.
66
+
67
+ - Validate and parse path/query parameters in the view model that receives them; never assume a deep-linked id exists, belongs to the user, or is well-formed. The server authorizes the fetch — the client renders the result or an error state.
68
+ - Guard-sensitive destinations are protected by the central `redirect` (`references/navigation.md` → Auth Guards), so a deep link cannot skip authentication. Confirm new protected routes are covered by the guard, not by a per-screen check.
69
+ - Treat any token or credential arriving *in* a deep link (an OAuth callback) as single-use, validated server-side, and never logged.
70
+
71
+ ## Biometric and Auth-Token Handling
72
+
73
+ Biometric unlock (`local_auth`) gates access to a credential already held in the keystore; it does not authenticate the user to the server. The server trusts the session token, not the fingerprint.
74
+
75
+ - Use biometrics to unlock or re-confirm before a sensitive action, then present the keystore-held token to the gateway. The biometric check is a local gate, not a replacement for a verified session.
76
+ - Token refresh runs through the dio interceptor against the auth provider; the app does not mint, sign, or verify tokens itself. Auth is boring technology — `docs/principles/system-design/identity-and-access.md`.
77
+ - On biometric failure or lockout, fall back to full re-authentication, never to an unguarded path.
78
+
79
+ ## Obfuscation's Real Limits
80
+
81
+ `flutter build --obfuscate --split-debug-info` renames symbols and raises the cost of reverse engineering. It is a speed bump, not a control: it does not encrypt logic, hide strings reliably, or protect anything the earlier sections cover. An obfuscated binary still hands a determined attacker every embedded secret and every client-side check.
82
+
83
+ Ship it for the marginal friction, and depend on it for nothing. The actual protections are: no secrets in the binary, tokens in the keystore, TLS pinning, and authorization on the server.
84
+
85
+ ## Security Review Checklist
86
+
87
+ For any PR touching auth, secure storage, the dio client, deep-link routes, or build config:
88
+
89
+ - [ ] No secret, key, or credential embedded in Dart, assets, or `--dart-define`
90
+ - [ ] Tokens and credentials in `flutter_secure_storage` — never `SharedPreferences` or plain files
91
+ - [ ] Secure storage cleared on sign-out and token invalidation
92
+ - [ ] Transport is HTTPS only; pinning present (with a backup pin) for sensitive apps
93
+ - [ ] Deep-link parameters validated in the view model; protected routes covered by the central guard
94
+ - [ ] No client-side check standing in for a server-side authorization decision
95
+ - [ ] Biometrics gate a keystore credential — they do not authenticate to the server
96
+ - [ ] Obfuscation enabled, and relied on for nothing
@@ -25,6 +25,10 @@
25
25
 
26
26
  Pick the **cheapest tier that can carry the assertion**. If a widget test can prove it, an integration test that proves it is waste.
27
27
 
28
+ This taxonomy is the Flutter idiom of the framework testing canon (`docs/principles/foundations/testing.md`): widget tests are the fat middle that the canon's honeycomb puts the weight on, unit tests are the thin solitary layer, and `integration_test` is the few-end-to-end top. When this file and the canon disagree, the canon wins and this file is the one to fix.
29
+
30
+ Above all of these sits the front-door proof the canon now demands: an `integration_test` harness driving the real shipping app the way a user does — end to end against the real backend, not a fake gateway — because widget tests that each pass against fakes can still assemble into an app that does nothing on the real data path. And the fake-needs-a-real-test rule follows from it: a mock repository or fixture standing in for a real stage is a debt that some integration test of the real producer must pay. Seeded inputs are fine; faking the work in the middle with nothing real behind it is a green light wired to nothing. See `docs/principles/foundations/testing.md`.
31
+
28
32
  ## The Prove-Once Rule
29
33
 
30
34
  Capability behaviour is proven once, headless, at the core's contract. The surface suite proves three things only:
@@ -124,6 +128,18 @@ Goldens guard **design-system-level components** (the token-projected theme made
124
128
  - **iOS is a local-only / device-farm lane (Firebase Test Lab, Codemagic), never a CI gate** — it needs macOS runners and hands, and putting it in the gate breaks the headless loop.
125
129
  - A runner without the Flutter SDK reports the tier **skipped-with-reason, never silently green**.
126
130
 
131
+ ## Assertion Quality — Mutation Testing and Its Absence
132
+
133
+ The canon's assertion-quality read-out is mutation testing — inject a fault, confirm a test fails. Dart has **no production-grade mutation tool** (the existing packages are experimental and unmaintained), so that automated read-out is not available here. The discipline it enforces is carried by review instead: fakes over mocks (a stub-and-verify mock asserts the call, not the outcome), assert on semantics and visible text rather than widget types, and cover error and empty states — the failure modes mutation testing would otherwise catch. When a dense pure-Dart algorithm genuinely warrants it, a hand-run experimental tool is a spot check, never a gate.
134
+
135
+ ## Generate the Inputs You Can't Enumerate
136
+
137
+ Example-based tests check the cases you thought of (canon principle 7). The generative surface on a Flutter client is narrow but real: a **dense, pure-Dart unit** with an invariant — a mapper round-trip (`fromJson ∘ toJson = id`), a date/currency formatter, a validator that must never throw — can state the property and let the framework generate counterexamples. `glados` is the Dart property-based option; it is niche, so reach for it only where a genuine invariant lives in pure logic, never for widget trees. The service-boundary generative tools the canon names — Schemathesis, coverage-guided fuzzing — do not apply to a client: the gateway's contract is fuzzed once at the capability core, and re-running it from the surface duplicates a proof that already exists (the Prove-Once Rule).
138
+
139
+ ## Naming Tests by Behaviour
140
+
141
+ A test name must let an engineer form a hypothesis from the failure log alone. State the observable behaviour and the condition — `'placing an order shows the confirmation'`, not `'OrderView test'`. Names that describe what the user sees survive refactors and double as living documentation; names that describe the widget under test convey nothing the file tree doesn't.
142
+
127
143
  ## Test Commands
128
144
 
129
145
  | Command | Purpose |
@@ -133,3 +149,12 @@ Goldens guard **design-system-level components** (the token-projected theme made
133
149
  | `npx nx run <app>:test-integration` | integration_test against a device/emulator |
134
150
  | `flutter test test/home_view_test.dart` | single file |
135
151
  | `flutter test --name 'refresh'` | by test name |
152
+
153
+ ## Bet Slice Rollout — the permanent tests a slice owes
154
+
155
+ When a bet slice's progress tests go green, the slice rolls out permanent coverage before it closes (bet workflow, Delivery). The bet-progress tests prove the capability once and are archived; these stay. The Prove-Once Rule governs the whole rollout — surface tests prove wiring, rendering, and interaction; they never re-prove a business rule the capability core already owns.
156
+
157
+ - **Widget tests (always).** The bulk of the slice's coverage: pump each View the slice delivered inside a `ProviderScope` with fake repositories, assert through semantics and visible text, and cover its error and empty states — not just the happy path.
158
+ - **Unit tests (when logic earned them).** Pure-Dart tests for branching logic the slice introduced in a view model, mapper, or repository — through `ProviderContainer`, testing the real Notifier. Plumbing does not earn one; the widget test already covers it.
159
+ - **Golden tests (when the slice touched a design-system component).** A new or changed token-projected component extends the alchemist goldens; screen-level goldens do not — they churn on every copy change.
160
+ - **Integration / Patrol (only when the journey or the OS boundary is new).** A new critical journey earns one happy-path `integration_test`; a flow that newly leaves Flutter for the OS earns Patrol. Trace assertions do not apply — a Flutter client emits no OpenTelemetry traces, so there is no span surface to assert on.
@@ -1,8 +1,10 @@
1
1
  # Sync Anchor
2
2
 
3
- This file pins the principle files this skill embeds. When any listed file
4
- changes, this skill must be reviewed in the same commit. CI verifies the
5
- hashes match.
3
+ This file pins the principle files this skill embeds both the per-stack
4
+ Flutter idiom docs and the cross-cutting central canon this skill distils. When
5
+ any listed file changes, this skill must be reviewed in the same commit (and the
6
+ matching per-stack idiom doc reconciled to the canon). CI verifies the hashes
7
+ match.
6
8
 
7
9
  | Principle file | SHA-256 | Last reviewed |
8
10
  |---|---|---|
@@ -10,6 +12,13 @@ hashes match.
10
12
  | src/generators/flutter-app/docs/principles/stack/flutter/architecture.md | ac10c2c87da358157973ebbfe07657491d68a248b946b00697fdc5e18f3af596 | 2026-06-12 |
11
13
  | src/generators/flutter-app/docs/principles/stack/flutter/state-management.md | a690a3476453cb8ed0af0d3f48566d8ba6a2d508ac1ef782fd27bbffd2268994 | 2026-06-12 |
12
14
  | src/generators/flutter-app/docs/principles/stack/flutter/widgets-and-composition.md | b45c55220f14a7886837c4d4159b33febceed772db32a7efb277e7dba00512e8 | 2026-06-12 |
13
- | src/generators/flutter-app/docs/principles/stack/flutter/testing.md | 5aec7d7c5300cf6f4c4f4b07809ff02010505cf71329f7dc5b766de39b97685e | 2026-06-12 |
15
+ | src/generators/flutter-app/docs/principles/stack/flutter/testing.md | e337a0e1c4a6c5502f69745b0a4e0be35dd3303fbf11ed1f2f3688e93f16ed4f | 2026-06-27 |
14
16
  | src/generators/flutter-app/docs/principles/stack/flutter/platform-channels.md | 6b5a54dcb8b55433b7175cf715a6a3abf03c86c317f2037af6155a131691cfb2 | 2026-06-12 |
15
17
  | src/generators/flutter-app/docs/principles/stack/flutter/releases-and-distribution.md | 70ecdca2be6d8476359dbc2e72e3510157b49db0f5512cded76e0e3f19bed46f | 2026-06-12 |
18
+ | src/docs/principles/foundations/testing.md | 205ac40d4c643e7b61cf1e4295df8a7b8b46dcd7c81b857aa8c642ea353f62ef | 2026-06-27 |
19
+ | src/docs/principles/quality/observability.md | 8aa60e213ba03e989c93263153e3a1ac10b2336f6d0360c394f473660d565a0b | 2026-06-26 |
20
+ | src/docs/principles/quality/security.md | 61157d97677142737ec537954dc5aaad7a04012cc8a3dcc855e2d324287fdc64 | 2026-06-26 |
21
+ | src/docs/principles/quality/performance.md | 18b6d3391c57d97342068f9f1da732b24de4221489d0459bb6ad8900fac0a02e | 2026-06-26 |
22
+ | src/docs/principles/quality/reliability.md | 9c9788504e0963458667d2727c3fc2359776108be593a2efc6603f6470002252 | 2026-06-26 |
23
+ | src/docs/principles/quality/accessibility.md | f921e7bf6256bc105b127b841d0a30af8a70ad1ddd7632d492589f052e6501b2 | 2026-06-26 |
24
+ | src/docs/principles/foundations/documentation.md | 8b576072eaf4970f1251b560781e3e755c864a7920faa599b2834c921cbb8734 | 2026-06-26 |
@@ -20,10 +20,11 @@ Go backend execution router for service repositories. Durable engineering guidan
20
20
  ## Operating Contract
21
21
 
22
22
  1. Load reference docs from `references/` for architectural and implementation guidance. Treat the current repository's code, specs, and generated contracts as the source of truth for naming, structure, and behavior.
23
- 2. Inspect the current repository before naming packages, commands, import paths, schemas, or generated files.
23
+ 2. Orient with the repo map and Serena before reading widely (see Required First Checks) — find the hubs, then navigate by symbol. Inspect the current repository before naming packages, commands, import paths, schemas, or generated files.
24
24
  3. Load the smallest reference set that explains the task. Add more context only when the task crosses a boundary.
25
25
  4. Preserve the service's dependency direction and public contracts. Code implements OpenAPI, database migrations, event schemas, and documented architecture — it does not invent them.
26
- 5. Coordinate with adjacent skills when another skill owns the primary decision surface.
26
+ 5. Treat observability as part of the contract, not an afterthought: a critical path emits an unbroken trace, and a missing span is a defect. Route durable engineering policy to the canonical docs (`docs/principles/stack/go/`, and the cross-cutting canon under `docs/principles/quality/` and `docs/principles/foundations/`) rather than restating it in code comments or this skill.
27
+ 6. Coordinate with adjacent skills when another skill owns the primary decision surface.
27
28
 
28
29
  ---
29
30
 
@@ -39,6 +40,7 @@ Before non-trivial Go implementation or review work:
39
40
 
40
41
  | Check | Why |
41
42
  |---|---|
43
+ | **Orient with the repo map + Serena** — refresh `npx groundwork-method repo-map`, read its `centrality` ranking to find the hubs, then navigate them with Serena (`get_symbols_overview` / `find_symbol` / `find_referencing_symbols`) | A blind file crawl misses the structure the map already computed; symbol navigation and reference-aware edits beat grep-and-read. Fall back to ordinary reads only when these are unavailable |
42
44
  | Service package layout and nearby examples for the touched layer | Prevents inventing structure that already has a convention |
43
45
  | `go.mod` for Go and dependency versions | Avoids version-specific advice that contradicts the project |
44
46
  | OpenAPI spec (if HTTP behavior changes) | HTTP contracts are generated — code must match the spec |
@@ -67,6 +69,7 @@ Load only the rows relevant to the current task. Reference files are in the skil
67
69
  | Tests, quality gates, coverage strategy, flake triage | `testing.md` |
68
70
  | Code quality, naming, simplicity, deletion | `code-craft-security.md` |
69
71
  | Security, auth, secrets, input validation, supply chain | `code-craft-security.md` |
72
+ | Doc comments, naming-as-documentation, godoc, comment-is-a-smell | `documentation.md` |
70
73
 
71
74
  ---
72
75
 
@@ -0,0 +1,130 @@
1
+ # Documentation
2
+
3
+ Go ships its documentation discipline in the toolchain: `go doc` and pkg.go.dev render doc comments, `gofmt` normalises them, `go vet` flags malformed ones. The language was designed so that well-named code needs little prose around it. Lean on that.
4
+
5
+ ## Hierarchy
6
+
7
+ Structure documents more reliably than comments. A comment is a promise no compiler checks; when the code changes, the comment silently lies. Documentation priority — the foundations principle (`docs/principles/foundations/documentation.md`) written the Go way:
8
+
9
+ 1. **Types and signatures** — the compiler rejects incorrect types. Zero drift risk.
10
+ 2. **Naming** — self-documenting identifiers and small interfaces. Refactor before you comment.
11
+ 3. **Error context** — `fmt.Errorf("doing X: %w", err)` strings document the failure path and are exercised by every call site.
12
+ 4. **Test names** — `TestReserve_OutOfStock_ReturnsConflict` is executable documentation verified by CI.
13
+ 5. **Doc comments on exported API** — rendered by godoc; written only when the signature cannot carry the contract.
14
+ 6. **Inline "why" comments** — last resort for a genuinely non-obvious decision.
15
+
16
+ Levels 1–4 are verified by tooling. Levels 5–6 are human promises that drift. Minimise them.
17
+
18
+ ## Doc Comments
19
+
20
+ A doc comment is the sentence directly above a declaration, and it begins with the name of the thing it documents — `go doc` and pkg.go.dev parse that convention, and `go vet`'s doc checks rely on it.
21
+
22
+ ```go
23
+ // Reserve holds stock for an order until the reservation expires.
24
+ // It returns ErrOutOfStock when the requested quantity is unavailable.
25
+ func (s *Service) Reserve(ctx context.Context, orderID string, qty int) error
26
+ ```
27
+
28
+ Document the **exported** surface — the package's contract with its callers. Unexported helpers are read with their implementation in view; a doc comment there is usually redundant with the code below it.
29
+
30
+ State the contract the signature cannot: the error conditions a caller must branch on, side effects invisible in the return type, the units or invariants of a parameter. Do not restate the signature in prose.
31
+
32
+ ```go
33
+ // BAD — restates the signature
34
+ // GetOrder gets an order by id and returns it.
35
+ func GetOrder(ctx context.Context, id string) (*Order, error)
36
+
37
+ // GOOD — skip it; the name + types already say this
38
+ func GetOrder(ctx context.Context, id string) (*Order, error)
39
+ ```
40
+
41
+ ## Package Documentation
42
+
43
+ A package earns a doc comment when its name does not convey its purpose, its boundaries, or how its pieces fit. Put it in a dedicated `doc.go` so it survives file churn:
44
+
45
+ ```go
46
+ // Package inventory tracks stock levels and reservations.
47
+ //
48
+ // A reservation is a hold with a TTL; a hold that expires returns its
49
+ // quantity to available stock. Callers reserve, then either commit or
50
+ // release — an abandoned hold is reclaimed by the sweeper, not the caller.
51
+ package inventory
52
+ ```
53
+
54
+ The comment orients a reader before they open a single file. A package whose name and exported identifiers already explain it (`httpclient`, `postgres`) needs no `doc.go`.
55
+
56
+ ## Names and Interfaces Are the Documentation
57
+
58
+ The cheapest documentation is a name that makes the comment unnecessary. Go's conventions push this hard, and the service standards bake them in (`references/go-services.md`): small interfaces defined by their consumer, concrete types returned, no stuttering (`inventory.Service`, not `inventory.InventoryService`).
59
+
60
+ A one-to-three-method interface documents a capability by its shape:
61
+
62
+ ```go
63
+ // The name and the single method are the whole contract.
64
+ type ReservationStore interface {
65
+ Save(ctx context.Context, r Reservation) error
66
+ }
67
+ ```
68
+
69
+ A wide interface needs a comment to explain what it is *for* — which is the signal it is doing too much. Narrow it, and the comment disappears.
70
+
71
+ ## Error Messages Are Documentation
72
+
73
+ A Go error string is read far more often than any doc comment — it surfaces in logs, traces, and incident timelines. Treat it as documentation of the failure path. Wrap with context that names the operation, so the chain reads as a trace:
74
+
75
+ ```go
76
+ if err := store.Save(ctx, r); err != nil {
77
+ return fmt.Errorf("reserving stock for order %s: %w", orderID, err)
78
+ }
79
+ ```
80
+
81
+ Lowercase, no trailing punctuation, no "failed to" prefix — the convention that lets wraps compose cleanly (`reserving stock for order 7: writing row: connection refused`). Sentinel and structured errors document the branches a caller is expected to take; define them where that caller lives (`references/go-services.md`).
82
+
83
+ ## Inline Comments
84
+
85
+ Inline comments explain **why**, never **what**. The code already says what it does; the comment captures the reason the next reader cannot recover from the code alone.
86
+
87
+ ```go
88
+ // Sweep every 30s: shorter churns the DB, longer lets expired holds
89
+ // starve available stock past the SLA. Tuned against load test #214.
90
+ ticker := time.NewTicker(30 * time.Second)
91
+ ```
92
+
93
+ A comment that narrates the mechanics is noise — the reader can see the loop.
94
+
95
+ ```go
96
+ // BAD — narrates the obvious
97
+ // loop over the items and sum the quantities
98
+ for _, item := range items {
99
+ total += item.Qty
100
+ }
101
+ ```
102
+
103
+ ## A Comment Is Often a Smell
104
+
105
+ When you reach for a comment to explain *what* a block does, the code is asking to be refactored. The comment is debt; the fix is in the code:
106
+
107
+ - A comment explaining a variable → rename the variable.
108
+ - A comment heading a block → extract a function whose name is that comment.
109
+ - A comment decoding a boolean argument (`Process(data, true) // skip cache`) → introduce a named type or option.
110
+ - A comment listing what a function does in three parts → the function does three things; split it.
111
+
112
+ Delete the comment and fix the name. The refactor cannot drift; the comment can.
113
+
114
+ ## In-Code Markers
115
+
116
+ ```go
117
+ // TODO(bob): batch these writes once the store supports it. Issue #231.
118
+ // FIXME(carol): retry storms under partition; needs a circuit breaker. Issue #245.
119
+ // HACK(dave): upstream returns 200 with an error body; inspect payload until fixed.
120
+ ```
121
+
122
+ Always include `(username)` and an issue reference. A marker without one will never be resolved.
123
+
124
+ ## What NOT to Document
125
+
126
+ - Self-evident exported functions where the name and types tell the whole story.
127
+ - Unexported helpers read in context with their callers.
128
+ - `Args`/`Returns`-style prose that duplicates the signature — Go has no such convention; the types are the parameter docs.
129
+ - Struct fields whose name and type are clear (`CreatedAt time.Time`); comment only a non-obvious unit or invariant.
130
+ - Generated code (protobuf, mocks) — never hand-edit comments into it.
@@ -1,5 +1,11 @@
1
1
  # Testing
2
2
 
3
+ ## The Model: Honeycomb, Not Pyramid
4
+
5
+ The default shape is the test honeycomb: a fat middle of sociable service-perimeter tests, a thin layer of solitary unit tests, a few end-to-end checks on top. Testcontainers starts real Postgres in seconds, so the old excuse for mocking the database is gone — and a mock-heavy suite passes while production breaks. This is the stack idiom of the framework testing canon (`docs/principles/foundations/testing.md`); when this file and the canon disagree, the canon wins and this file is the one to fix.
6
+
7
+ Above the honeycomb sits one proof the tiers below cannot give you: drive the real running service through its real front door — the HTTP/gRPC API a consumer actually calls — end to end on the real pipeline. Service tests that each pass behind a stubbed or faked dependency can still assemble into a product that does nothing on the real path, because the seams between them were never wired. And every fake or fixture standing in for a real stage is a debt: some test must drive the real producer of that data, or the fixture is a green light wired to nothing (canon, `docs/principles/foundations/testing.md`). Seeding the inputs is fine; faking the work in the middle is the violation.
8
+
3
9
  ## Testing Tiers
4
10
 
5
11
  ### Tier 1 — Service Perimeter Tests (Default)
@@ -120,6 +126,61 @@ Tests are risk-weighted assertions about production behaviour — not boxes tick
120
126
  5. **Risk-based depth.** Score modules with Impact × Complexity × Change-frequency before deciding test depth.
121
127
  6. **Tests are part of the change.** A PR without tests is incomplete.
122
128
 
129
+ ## Trace Assertions
130
+
131
+ Observability is a test surface (principle 3): a critical-path request must emit an unbroken trace, and a missing span is a test failure, not an instrumentation TODO. The mechanism is an **in-memory span exporter** from the OTel SDK — no external tooling, and the durable approach now that the dedicated trace-test tools have gone dormant.
132
+
133
+ ```go
134
+ import (
135
+ sdktrace "go.opentelemetry.io/otel/sdk/trace"
136
+ "go.opentelemetry.io/otel/sdk/trace/tracetest"
137
+ )
138
+
139
+ func TestCreateEntity_EmitsTrace(t *testing.T) {
140
+ exporter := tracetest.NewInMemoryExporter()
141
+ tp := sdktrace.NewTracerProvider(sdktrace.WithSyncer(exporter))
142
+ otel.SetTracerProvider(tp)
143
+ t.Cleanup(func() { _ = tp.Shutdown(context.Background()) })
144
+
145
+ // exercise the real handler (Tier 1 setup) ...
146
+
147
+ spans := exporter.GetSpans().Snapshots()
148
+ names := spanNames(spans)
149
+ require.Contains(t, names, "POST /entities") // the entry span exists
150
+ require.Contains(t, names, "db.insert") // the work span exists
151
+ // assert the DB span is a descendant of the entry span — the trace is connected
152
+ }
153
+ ```
154
+
155
+ Assert what the contract promises — the spans that must exist on the journey, that the trace stays connected across hops, and the attributes a dashboard or SLO query depends on. Pinning the exact span tree and every attribute couples the test to implementation and trains the team to delete it.
156
+
157
+ ## Mutation Testing — the assertion-quality read-out
158
+
159
+ A fat service-perimeter test drives many branches through one HTTP call and can *execute* them all while only asserting on the response body. Mutation testing is the one instrument that proves the suite checks what it runs: inject a fault, confirm a test fails, and a surviving mutant is a line you cover but do not check. Treat it as a **signal, never a gate** — run it on the high-risk modules the risk matrix flags and on changed code only.
160
+
161
+ Go's mutation tooling is immature: `gremlins` is pre-1.0 and slow on large packages, and `go-mutesting` is effectively unmaintained. So here it stays a deliberate, hand-run spot check on a dense package under active change (`gremlins unleash ./internal/pricing`), not a CI expectation. Where it surfaces a surviving mutant on changed high-risk code, that is the missing assertion to add — the same read-out catches AI-generated tests whose oracle was lifted from the implementation.
162
+
163
+ ## Generate the Inputs You Can't Enumerate
164
+
165
+ Example-based tests check the cases you thought of; the bugs live in the cases you didn't (canon principle 7). Two generative surfaces apply in Go, both highest-leverage on the **dense, boundary-poor** logic that earns Tier 2 unit tests:
166
+
167
+ - **Property-based tests** for code with an invariant that holds across a large input space — a round-trip (`Decode(Encode(x)) == x`), a parser that must never panic, a pricing calculation with an algebraic law, a state machine that must preserve a constraint. State the property and let the framework generate and shrink counterexamples. Reach for `pgregory.net/rapid` (modern, shrinks well) over stdlib `testing/quick`; one property covers an infinity of examples, and most caught faults surface on a single generated input.
168
+
169
+ ```go
170
+ func TestEncodeDecode_RoundTrips(t *testing.T) {
171
+ rapid.Check(t, func(t *rapid.T) {
172
+ order := genOrder().Draw(t, "order") // a rapid generator for the domain type
173
+ got, err := Decode(Encode(order))
174
+ require.NoError(t, err)
175
+ require.Equal(t, order, got)
176
+ })
177
+ }
178
+ ```
179
+
180
+ - **Native fuzzing** (`go test -fuzz`) at the byte boundary — parsers, decoders, anything that ingests untrusted input. Coverage-guided, first-class in the toolchain since Go 1.18, and a failing input is saved under `testdata/fuzz/` as a permanent regression seed. Run a fuzz target in a bounded CI lane (`-fuzztime=30s`) on changed parsers, not unbounded on every PR.
181
+
182
+ The cost is authoring — a meaningful property needs a real invariant and a generator — so reach for these where invariants are real, not everywhere. Where the input space is small or there is no invariant, a table-driven unit test is the right tool.
183
+
123
184
  ## Anti-Patterns
124
185
 
125
186
  - **Mocking the database.** Test against a real schema.
@@ -131,9 +192,10 @@ Tests are risk-weighted assertions about production behaviour — not boxes tick
131
192
 
132
193
  ## Bet Slice Rollout — the permanent tests a slice owes
133
194
 
134
- When a bet slice's progress tests go green, the slice rolls out permanent coverage before it closes (bet workflow, Delivery step 5). The bet-progress tests prove the capability once and are archived; these stay.
195
+ When a bet slice's progress tests go green, the slice-worker rolls out permanent coverage as part of the same slice, before the driver reviews it (bet workflow, Delivery). The bet-progress tests prove the capability once and are archived; these stay.
135
196
 
136
197
  - **Service perimeter test (always).** One Tier 1 test per capability the slice delivered, exercising the real handler against real Postgres — this is the honeycomb wall that survives refactors.
137
198
  - **Unit tests (when logic earned them).** Pure-function tests for branching business logic the slice introduced — state machines, pricing rules, parsers. CRUD plumbing does not earn unit tests; the perimeter test already covers it.
138
199
  - **Property-based tests (when invariants exist).** A slice that introduced an invariant — serialization round-trips, idempotent handlers, commutative merges — pins it with a property test (`testing/quick` or rapid), because example-based tests sample invariants instead of stating them.
200
+ - **Critical-path trace assertions (when the slice added an observable path).** A slice that introduced a handler or background job whose trace a dashboard or SLO depends on pins it with an in-memory-exporter test: the entry and work spans exist and the trace stays connected. A missing span is a test failure, not an instrumentation TODO.
139
201
  - **Contract conformance (when the slice changed an API).** The served OpenAPI must match the promoted spec in `docs/architecture/api/<service>/openapi.yaml`; the generated system suite checks this — the slice's job is to keep the spec promotion current, not to hand-write the check.
@@ -1,11 +1,20 @@
1
1
  # Sync Anchor
2
2
 
3
- This file pins the principle files this skill embeds. When any listed file
4
- changes, this skill must be reviewed in the same commit. CI verifies the
5
- hashes match.
3
+ This file pins the principle files this skill embeds both the per-stack Go
4
+ idiom docs and the cross-cutting central canon this skill distils. When any
5
+ listed file changes, this skill must be reviewed in the same commit (and the
6
+ matching per-stack idiom doc reconciled to the canon). CI verifies the hashes
7
+ match.
6
8
 
7
9
  | Principle file | SHA-256 | Last reviewed |
8
10
  |---|---|---|
9
11
  | src/generators/go-microservice/docs/principles/stack/go/index.md | 5404a872df986823ca05107485ec6e22952c25b39882e44ed82316c0044f0973 | 2026-06-19 |
10
12
  | src/generators/go-microservice/docs/principles/stack/go/concurrency.md | e63a90b46f85ad63f0e1535f105106b9f30bff58e0270e9dc8d4b8f91951e0ca | 2026-05-26 |
11
- | src/generators/go-microservice/docs/principles/stack/go/testing.md | 5df1715b9fa10ff50cb3f18348a7a67dbef70cff80d7d77aefef2d6f1dc5da4c | 2026-05-26 |
13
+ | src/generators/go-microservice/docs/principles/stack/go/testing.md | fd3561fbcb1c79bbd219ff0e76b8958898b0159daacef41bcf59092613773064 | 2026-06-26 |
14
+ | src/docs/principles/foundations/testing.md | 205ac40d4c643e7b61cf1e4295df8a7b8b46dcd7c81b857aa8c642ea353f62ef | 2026-06-27 |
15
+ | src/docs/principles/quality/observability.md | 8aa60e213ba03e989c93263153e3a1ac10b2336f6d0360c394f473660d565a0b | 2026-06-26 |
16
+ | src/docs/principles/quality/security.md | 61157d97677142737ec537954dc5aaad7a04012cc8a3dcc855e2d324287fdc64 | 2026-06-26 |
17
+ | src/docs/principles/foundations/code-craft.md | 55aa79dffada43c86e546ead89b07578dddb6a9ec8a7dba15034e3628b3e9d38 | 2026-06-26 |
18
+ | src/docs/principles/quality/performance.md | 18b6d3391c57d97342068f9f1da732b24de4221489d0459bb6ad8900fac0a02e | 2026-06-26 |
19
+ | src/docs/principles/quality/reliability.md | 9c9788504e0963458667d2727c3fc2359776108be593a2efc6603f6470002252 | 2026-06-26 |
20
+ | src/docs/principles/foundations/documentation.md | 8b576072eaf4970f1251b560781e3e755c864a7920faa599b2834c921cbb8734 | 2026-06-26 |
@@ -41,7 +41,7 @@ GroundWork gives you a deterministic **repo map** (`npx groundwork-method repo-m
41
41
 
42
42
  ## How to Use This Skill
43
43
 
44
- Match the user's task to the smallest relevant reference set. Most tasks touch one or two references.
44
+ **Orient first.** On any non-trivial task, refresh the repo map (`npx groundwork-method repo-map`), read its `centrality` ranking to find the hubs, and navigate them with Serena before reading widely (see Code intelligence above) — this is the first step, not optional; fall back to ordinary reads only when those tools are unavailable. Then match the user's task to the smallest relevant reference set. Most tasks touch one or two references.
45
45
 
46
46
  | Topic | Reference | Load When |
47
47
  |-------|-----------|-----------|
@@ -55,6 +55,9 @@ Match the user's task to the smallest relevant reference set. Most tasks touch o
55
55
  | Tailwind & Styling | `references/tailwind-and-styling.md` | Tailwind v4 mechanics, consuming projected tokens, theming, dark mode, responsive design. |
56
56
  | Visual Language | `references/visual-language.md` | Consuming the design system: colour/type/spacing/elevation/surface technique and the projected token + surface utilities. |
57
57
  | UX Principles | `references/ux-principles.md` | Interaction patterns, progressive disclosure, feedback, empty states. |
58
+ | Accessibility | `references/accessibility.md` | Semantic HTML, ARIA discipline, keyboard/focus, WCAG AA contrast, accessible forms, `jest-axe`. |
59
+ | Security | `references/security.md` | XSS, CSRF, auth/session, the `NEXT_PUBLIC` secret boundary, Server Action validation, CSP, SSRF on server fetches. |
60
+ | Observability | `references/observability.md` | Server spans via `instrumentation.ts`, client Web Vitals/RUM, error reporting, PII discipline. |
58
61
  | Testing | `references/testing.md` | Component tests, integration tests, accessibility testing, test utilities. |
59
62
  | Performance & Deployment | `references/performance-and-deployment.md` | Bundle analysis, lazy loading, image optimization, build configuration. |
60
63
  | Documentation | `references/documentation.md` | Component documentation, Storybook patterns, inline docs. |
@@ -68,6 +71,8 @@ Match the user's task to the smallest relevant reference set. Most tasks touch o
68
71
  - **Form work** → Load `references/mutations-and-forms.md`. Verify Zod schema patterns.
69
72
  - **Styling/theming** → Load `references/tailwind-and-styling.md` and `references/visual-language.md`. Check design guide.
70
73
  - **Performance issues** → Load `references/performance-and-deployment.md`. Profile before optimizing.
74
+ - **Security / auth / session work** → Load `references/security.md`. Check the server/client boundary and the `NEXT_PUBLIC` secret line.
75
+ - **Instrumentation / telemetry** → Load `references/observability.md`. Distinguish server spans from client RUM.
71
76
 
72
77
  ## Safety Gates
73
78
 
@@ -0,0 +1,111 @@
1
+ # Accessibility
2
+
3
+ ## Table of Contents
4
+ - [The Baseline](#the-baseline)
5
+ - [Semantic HTML First, ARIA Last](#semantic-html-first-aria-last)
6
+ - [Keyboard & Focus](#keyboard--focus)
7
+ - [Contrast & Colour](#contrast--colour)
8
+ - [Accessible Forms](#accessible-forms)
9
+ - [Motion](#motion)
10
+ - [Accessibility as the Test Seam](#accessibility-as-the-test-seam)
11
+ - [Review Checklist](#review-checklist)
12
+
13
+ ---
14
+
15
+ ## The Baseline
16
+
17
+ Accessibility is a merge gate, not a backlog item. The baseline for every surface: native semantic elements over `div` soup, every interactive element keyboard-reachable with a visible focus ring, WCAG AA contrast on text and UI boundaries, programmatic labels on every form field, and motion that honours `prefers-reduced-motion`. An accessibility failure blocks the slice the way a failing test does. The standard is WCAG 2.2 AA — see `docs/principles/quality/accessibility.md`.
18
+
19
+ ## Semantic HTML First, ARIA Last
20
+
21
+ The first rule of ARIA is that no ARIA is better than bad ARIA — a misapplied `role` silently overrides the native semantics that already worked. Reach for the native element before the attribute:
22
+
23
+ - Use `<button>`, `<a>`, `<nav>`, `<main>`, `<header>`, `<footer>`, `<article>`, `<section>`. Never a `<div onClick>` for a control — it is invisible to keyboard and assistive tech.
24
+ - Headings form an outline: one `<h1>` per page, no skipped levels.
25
+ - Reach for ARIA only when HTML cannot express the semantics, and then wire up every state and key handler by hand — an ARIA reimplementation is correct only if complete.
26
+
27
+ When naming, associate visible text first; fall back to `aria-label` only when there is no on-screen text to reference. Icon-only buttons need an explicit label:
28
+
29
+ ```tsx
30
+ <button onClick={onClose} aria-label="Close dialog">
31
+ <X size={20} aria-hidden />
32
+ </button>
33
+ ```
34
+
35
+ ## Keyboard & Focus
36
+
37
+ Every journey completes without a pointer. Tab order follows reading order, there are no keyboard traps, and focus is always visible.
38
+
39
+ - Never delete the focus indicator for aesthetics. Style `:focus-visible` so a clear ring shows for keyboard users without firing on mouse clicks:
40
+
41
+ ```css
42
+ :focus-visible {
43
+ outline: 2px solid var(--color-accent);
44
+ outline-offset: 2px;
45
+ }
46
+ ```
47
+
48
+ - Composite widgets (menus, tabs, grids) are a single tab stop, then arrow-key navigation inside via roving `tabindex` — a 30-item menu is one tab stop, not thirty.
49
+ - Modals and overlays manage focus: trap focus inside while open, return focus to the trigger on close, and mark them with `role="dialog"` and `aria-modal`. Otherwise keyboard users are stranded behind the overlay.
50
+
51
+ ## Contrast & Colour
52
+
53
+ Contrast is measured against rendered colour, not eyeballed. Body text meets **4.5:1**, large text (18px+, or 14px+ bold) meets **3:1**, and UI component boundaries and meaningful graphics meet **3:1**. Consume the design-system role tokens (see `references/visual-language.md`) so audited pairs stay paired; a hand-mixed colour or opacity hack silently breaks contrast and is a review finding. Verify in both themes.
54
+
55
+ Colour is never the only signal. Pair every colour-coded state with a second cue — icon, label, or position:
56
+
57
+ ```tsx
58
+ <span className="text-success flex items-center gap-1">
59
+ <CheckCircle size={16} aria-hidden />
60
+ Completed
61
+ </span>
62
+ ```
63
+
64
+ ## Accessible Forms
65
+
66
+ Every field carries a programmatic label — a real `<label htmlFor>`, never a placeholder standing in for one, because the placeholder vanishes on input.
67
+
68
+ - Errors are announced, not just coloured. Render the message in a `role="alert"` region and tie it to the field with `aria-describedby`; set `aria-invalid` on the failed input.
69
+ - A failed submit keeps every entered value, marks each field inline, and moves focus to the first error.
70
+ - Use the correct input `type` (`email`, `tel`, `url`) so the right keyboard and validation apply.
71
+
72
+ ## Motion
73
+
74
+ Animations honour `prefers-reduced-motion`. For users with vestibular conditions, unrequested motion is an accessibility failure, not decoration. The reduced-motion path still communicates the state change — it drops the movement, not the meaning:
75
+
76
+ ```css
77
+ @media (prefers-reduced-motion: reduce) {
78
+ *, *::before, *::after {
79
+ animation-duration: 0.01ms !important;
80
+ transition-duration: 0.01ms !important;
81
+ }
82
+ }
83
+ ```
84
+
85
+ ## Accessibility as the Test Seam
86
+
87
+ The accessible query is also the test query: Testing Library finds by `getByRole`, `getByLabelText`, and visible text, so **inaccessible UI is untestable UI**. A control with no role and no accessible name is one a screen reader cannot address and a test cannot reach — when a test falls back to `getByTestId` because nothing semantic exists, treat it as the accessibility defect it is and fix the markup.
88
+
89
+ Automated checks cover the mechanical layer only. Run `jest-axe` on every component that renders interactive elements (see `references/testing.md`), and gate it in CI:
90
+
91
+ ```tsx
92
+ it('has no accessibility violations', async () => {
93
+ const { container } = render(<Component />);
94
+ expect(await axe(container)).toHaveNoViolations();
95
+ });
96
+ ```
97
+
98
+ Axe catches roughly a third of WCAG criteria — it cannot judge whether alt text is meaningful or focus order makes sense. A new journey also earns a manual keyboard walk.
99
+
100
+ ## Review Checklist
101
+
102
+ - [ ] Native semantic element used; no `div`/`span` as a control.
103
+ - [ ] Icon-only buttons carry `aria-label`; ARIA used only where HTML can't express it.
104
+ - [ ] Every journey completes by keyboard; focus order matches reading order.
105
+ - [ ] `:focus-visible` ring present on all interactive elements.
106
+ - [ ] Modals trap and restore focus.
107
+ - [ ] Contrast meets AA via theme tokens — no hand-mixed colours.
108
+ - [ ] No state communicated by colour alone.
109
+ - [ ] Every form field has a programmatic label; errors announced via `role="alert"`.
110
+ - [ ] Motion respects `prefers-reduced-motion`.
111
+ - [ ] `jest-axe` scan clean; queries find by role/label, not test IDs.