@nurix/etna 0.19.55 → 0.19.57

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 (84) hide show
  1. package/data/harness-index.json +1 -1
  2. package/data/harnesses/apollo/CLAUDE.md +29 -30
  3. package/data/harnesses/apollo/agents/bookkeeper.md +1 -1
  4. package/data/harnesses/apollo/agents/change-review.md +1 -1
  5. package/data/harnesses/apollo/agents/lld-mode.md +1 -1
  6. package/data/harnesses/apollo/agents/maintenance.md +1 -1
  7. package/data/harnesses/apollo/agents/performance-review.md +1 -1
  8. package/data/harnesses/apollo/agents/retro.md +1 -1
  9. package/data/harnesses/apollo/harness.json +2 -2
  10. package/data/harnesses/apollo/hooks/approved-packages.md +42 -0
  11. package/data/harnesses/apollo/hooks/compliance.mjs +53 -6
  12. package/data/harnesses/apollo/rules/changelog.md +7 -1
  13. package/data/harnesses/apollo/rules/cloudflare.md +167 -0
  14. package/data/harnesses/apollo/rules/database.md +3 -127
  15. package/data/harnesses/apollo/rules/frontend.md +2 -4
  16. package/data/harnesses/apollo/rules/general.md +20 -42
  17. package/data/harnesses/apollo/rules/nomenclature.md +2 -6
  18. package/data/harnesses/apollo/rules/pnpm.md +2 -2
  19. package/data/harnesses/apollo/rules/testing.md +44 -3
  20. package/data/harnesses/apollo/skills/documentation/references/instruction-style.md +1 -0
  21. package/data/harnesses/apollo/skills/documentation/templates/plan.md +4 -4
  22. package/data/harnesses/apollo/skills/schema-design/SKILL.md +141 -0
  23. package/data/harnesses/apollo/skills/test/SKILL.md +104 -0
  24. package/data/harnesses/apollo/skills/testing/SKILL.md +100 -29
  25. package/data/harnesses/code/CLAUDE.md +29 -30
  26. package/data/harnesses/code/agents/bookkeeper.md +1 -1
  27. package/data/harnesses/code/agents/change-review.md +1 -1
  28. package/data/harnesses/code/agents/lld-mode.md +1 -1
  29. package/data/harnesses/code/agents/maintenance.md +1 -1
  30. package/data/harnesses/code/agents/performance-review.md +1 -1
  31. package/data/harnesses/code/agents/retro.md +1 -1
  32. package/data/harnesses/code/harness.json +2 -2
  33. package/data/harnesses/code/hooks/approved-packages.md +42 -0
  34. package/data/harnesses/code/hooks/compliance.mjs +53 -6
  35. package/data/harnesses/code/rules/changelog.md +7 -1
  36. package/data/harnesses/code/rules/cloudflare.md +167 -0
  37. package/data/harnesses/code/rules/database.md +3 -127
  38. package/data/harnesses/code/rules/frontend.md +2 -4
  39. package/data/harnesses/code/rules/general.md +20 -42
  40. package/data/harnesses/code/rules/nomenclature.md +2 -6
  41. package/data/harnesses/code/rules/pnpm.md +2 -2
  42. package/data/harnesses/code/rules/testing.md +44 -3
  43. package/data/harnesses/code/skills/documentation/references/instruction-style.md +1 -0
  44. package/data/harnesses/code/skills/documentation/templates/plan.md +4 -4
  45. package/data/harnesses/code/skills/schema-design/SKILL.md +141 -0
  46. package/data/harnesses/code/skills/test/SKILL.md +104 -0
  47. package/data/harnesses/code/skills/testing/SKILL.md +100 -29
  48. package/data/harnesses/delivery/CLAUDE.md +13 -1
  49. package/data/harnesses/delivery/agents/lld-mode.md +1 -1
  50. package/data/harnesses/delivery/agents/maintenance.md +1 -1
  51. package/data/harnesses/delivery/agents/retro.md +1 -1
  52. package/data/harnesses/delivery/harness.json +2 -2
  53. package/data/harnesses/delivery/rules/changelog.md +7 -1
  54. package/data/harnesses/delivery/rules/nomenclature.md +2 -6
  55. package/data/harnesses/delivery/skills/documentation/references/instruction-style.md +1 -0
  56. package/data/harnesses/delivery/skills/documentation/templates/plan.md +4 -4
  57. package/data/harnesses/discover/CLAUDE.md +14 -1
  58. package/data/harnesses/discover/harness.json +1 -1
  59. package/data/harnesses/discussion/CLAUDE.md +4 -2
  60. package/data/harnesses/discussion/harness.json +1 -1
  61. package/data/harnesses/etna/CLAUDE.md +14 -2
  62. package/data/harnesses/etna/agents/change-review.md +1 -1
  63. package/data/harnesses/etna/agents/lld-mode.md +1 -1
  64. package/data/harnesses/etna/agents/maintenance.md +1 -1
  65. package/data/harnesses/etna/agents/performance-review.md +1 -1
  66. package/data/harnesses/etna/agents/retro.md +1 -1
  67. package/data/harnesses/etna/harness.json +2 -2
  68. package/data/harnesses/etna/rules/changelog.md +7 -1
  69. package/data/harnesses/etna/rules/general.md +20 -42
  70. package/data/harnesses/etna/rules/nomenclature.md +2 -6
  71. package/data/harnesses/etna/rules/testing.md +44 -3
  72. package/data/harnesses/etna/skills/documentation/references/instruction-style.md +1 -0
  73. package/data/harnesses/etna/skills/documentation/templates/plan.md +4 -4
  74. package/data/harnesses/hr/CLAUDE.md +8 -3
  75. package/data/harnesses/hr/harness.json +1 -1
  76. package/data/harnesses/kb-agi/CLAUDE.md +8 -2
  77. package/data/harnesses/kb-agi/harness.json +1 -1
  78. package/package.json +2 -2
  79. package/data/harnesses/apollo/rules/express.md +0 -33
  80. package/data/harnesses/apollo/rules/verification.md +0 -67
  81. package/data/harnesses/apollo/skills/test-plan/SKILL.md +0 -85
  82. package/data/harnesses/code/rules/express.md +0 -33
  83. package/data/harnesses/code/rules/verification.md +0 -67
  84. package/data/harnesses/code/skills/test-plan/SKILL.md +0 -85
@@ -1,9 +1,44 @@
1
- # Testing
1
+ # Testing & Verification
2
2
 
3
- Test technology and placement — framework per stack, suite layout, mocking policy; **when** a test is written and how work is proven is [`verification.md`](verification.md), not here. Framework choice is **never** ad hoc and never the user's problem mid-task: detect the stack, use its row; if the repo already has a runner, that runner wins — **never** introduce a second framework beside it.
3
+ **When** work is proven and **which technology** proves it — the standing constraints that bind on every turn. The full lifecycle procedure — deriving a plan's Test Obligations, placing tests in strata, executing the verification gate — is the `testing` skill, invoked at those moments; this rule never restates it.
4
+
5
+ ## Two regimes
6
+
7
+ **Deferral is a property of plans, not of code.** A plan carries a Test Obligations ledger and a gate that discharges it; nothing else does, so nothing else may defer.
8
+
9
+ | Regime | During the work | Proven |
10
+ | --- | --- | --- |
11
+ | **Plan-driven build** | No new test files. Code only — the loop stays fast | At the verification gate, before `Status: Complete` |
12
+ | **Bug fix** — a defect in shipped behavior | The regression test ships with the fix | Immediately — it fails before the change, passes after |
13
+ | **Ad-hoc / non-plan change** | The test ships with the change **when it alters promised behavior** — layout, copy, and config tweaks owe none | Immediately, same discipline as a bug fix |
14
+
15
+ - **A bug fix cannot defer** — "fails before, passes after" is provable afterward only by reverting to the pre-fix ref, a cost nobody pays; capture the property at fix time or lose it.
16
+ - **Ad-hoc work cannot defer — there is no ledger to defer into.** A non-plan change that alters no promised behavior — a pure refactor, a rename — owes no test.
17
+
18
+ ## During the loop
19
+
20
+ Rules for the plan regime between the plan gate and the verification gate:
21
+
22
+ - **No new test files** — obligations wait in the plan's ledger (written at the plan gate by the `testing` skill).
23
+ - **Existing tests broken by the plan's intended behavior change are updated to the new contract before any mid-plan commit** — the shared branch never carries a red suite, and an update is never a weakening (§ Discipline).
24
+ - **A defect in the plan's own in-flux code is the loop iterating** — no test, no ledger row. **A pre-existing bug in shipped behavior is the bug-fix regime even mid-plan**: fix it with its regression test now — deferring it to the gate loses "fails before" — or send it to the backlog via Out-of-Scope Discovery. Never fix silently.
25
+ - **The ledger is amendable while the plan is open** — the gate discharges whatever it holds at gate time.
26
+ - **A mid-plan commit is a checkpoint, not a verification claim.**
27
+
28
+ ## The gate
29
+
30
+ **A discrete, user-triggered step.** When implementation is complete, report it and offer the gate — never run it unbidden, and never flip `Status: Complete` without it. An explicit user acceptance of finished plan work — "ship it", "commit", "looks good" — counts as the trigger; open the gate without asking again. A plan with outstanding obligations stays open, whatever the code does. The procedure — checklist, drive-then-pin ladder, row discharge, what earns a durable test — is the `testing` skill (gate mode).
31
+
32
+ - **Precondition: the ledger exists.** A plan with no Test Obligations section is blocked at the gate — run the `testing` skill (plan-gate mode) on it first; an empty ledger passes vacuously and the gap surfaces only after the file is frozen.
33
+ - **The verdict is never delegated** (the root policy file § Delegation), and where browser work is main-session-only (its § Runtime Notes), the driving is too. Fan-out may prepare fixtures or draft test bodies; the driving and the verdict stay inline.
34
+ - **The gate needs no stronger tier than the session default** — a standard-tier session suffices whatever tier built the plan; the plan and its ledger carry all the state the gate needs, so gating in a fresh session loses nothing.
35
+ - **Report what happened, including failures.** A gate that reports green while a suite is red is worse than no gate; state the failing test and its output.
36
+ - **Abandonment does not erase debt.** Before `Status: Abandoned` freezes a plan whose code was committed, convert its undischarged rows into backlog items; uncommitted code owes nothing.
4
37
 
5
38
  ## Frameworks by stack
6
39
 
40
+ Framework choice is **never** ad hoc and never the user's problem mid-task: detect the stack, use its row; if the repo already has a runner, that runner wins — **never** introduce a second framework beside it.
41
+
7
42
  | Stack | Unit / integration | Component | E2E | HTTP mocking | Don't use |
8
43
  | --- | --- | --- | --- | --- | --- |
9
44
  | Node + Vite / React (web) | `vitest` | `@testing-library/react` (+ `@testing-library/user-event`, `happy-dom`) | `@playwright/test` | `msw` | `jest`, `mocha`, `jasmine`, `cypress`, `enzyme` |
@@ -12,7 +47,7 @@ Test technology and placement — framework per stack, suite layout, mocking pol
12
47
  | Cloudflare Workers | `vitest` + `@cloudflare/vitest-pool-workers` | — | `@playwright/test` against `wrangler dev` | `msw` | `jest` |
13
48
  | Rust | `cargo test` (built-in) | — | — | — | — |
14
49
 
15
- Test packages install at `latest` — they sit outside [`general.md`](general.md)'s pinned table. Fake data comes from `@ngneat/falso` — **never** `@faker-js/faker` / `chance` (its row there).
50
+ Test packages install at `latest` — outside the approved-packages pin set. Fake data comes from `@ngneat/falso` — **never** `@faker-js/faker` / `chance`.
16
51
 
17
52
  ## Conventions
18
53
 
@@ -21,3 +56,9 @@ Test packages install at `latest` — they sit outside [`general.md`](general.md
21
56
  - **Unit and E2E trees stay separate.** Vitest/Jest suites in `tests/`; Playwright/Maestro suites in `e2e/` with their own config — **never** mixed into the unit runner's glob.
22
57
  - **No test doubles for what you own.** Prefer real instances — in-memory SQLite for drizzle, the actual Express app via `supertest` — over mocks; mock **only** true externals (network via `msw`, time via the runner's fake timers).
23
58
  - **Determinism.** No real network, no real time, no shared mutable state between tests; every test **must** pass alone and in any order.
59
+
60
+ ## Discipline
61
+
62
+ - **Never weaken, skip, or delete a test to mask an unexplained failure.** When a plan or LLD intentionally changes behavior, updating the affected tests to the new contract is part of that change.
63
+ - **A behavior that is compiler-guarded owes no test** — if it cannot ship green-but-broken, a test adds cost and no safety.
64
+ - **Throwaway checks are not tests.** Temporary scripts written to drive a change live in scratch space and are deleted — never committed into the test tree.
@@ -24,6 +24,7 @@ Policy files (`CLAUDE.md`, rules) are declarative — assertion bullets and tabl
24
24
  ## Routing
25
25
 
26
26
  - **Link, don't copy — one canonical source per topic.** `CLAUDE.md` is a **router, not a manual**: per workflow, when to enter, what to dispatch, where the output lands — mechanics live in the agent or skill file, which loads only when it runs.
27
+ - **A rule never references a sibling rule.** Rules co-load, so pointing at one from another routes a reader to text already in front of them — noise that also drifts the moment either file moves. State what the rule owns, self-contained; the only outward links a rule carries are to skills and agents, whose bodies do *not* co-load. Routing downward from `CLAUDE.md` to a rule is fine — that is the router doing its job.
27
28
  - **Cut generic engineering advice.** Agents already know how to code; keep only what is true of *this* stack and not derivable by looking at it.
28
29
  - **Keep load-bearing rules even when project-specific.** Genericizing is for reference material, not guardrails — deleting a hard rule removes a constraint the agent will otherwise violate.
29
30
 
@@ -1,10 +1,10 @@
1
1
  # Plan (docs/history/plans/YYYY-MM-DD-feature-name.md)
2
2
 
3
- Record. Use for: every implementation plan. Plans are created here directly with `Status: Draft`; after approval the plan gates (`boundary`, then `test-plan`) run against the still-`Draft` file, the status flips to `Status: In Progress` at the first implementation step, and the file freezes at `Status: Complete` or `Status: Abandoned`. Once frozen, never edit — write a new dated plan if scope changes. The plan records the journey; the truth it implemented lives on in the feature doc and any AD it touched.
3
+ Record. Use for: every implementation plan. Plans are created here directly with `Status: Draft`; after approval the plan gates (`boundary`, then `testing` plan-gate mode) run against the still-`Draft` file, the status flips to `Status: In Progress` at the first implementation step, and the file freezes at `Status: Complete` or `Status: Abandoned`. Once frozen, never edit — write a new dated plan if scope changes. The plan records the journey; the truth it implemented lives on in the feature doc and any AD it touched.
4
4
 
5
- The `boundary` skill parses the **Proposed Changes** sections to check boundary impact before execution, and the `test-plan` skill writes a **Test Obligations** section into the plan at the same gate. Keep that structure stable — section headings and bullet shape are load-bearing for the parsers, and the Test Obligations table is the ledger the verification gate discharges before the plan may reach `Status: Complete`.
5
+ The `boundary` skill parses the **Proposed Changes** sections to check boundary impact before execution, and the `testing` skill (plan-gate mode) writes a **Test Obligations** section into the plan at the same gate. Keep that structure stable — section headings and bullet shape are load-bearing for the parsers, and the Test Obligations table is the ledger the verification gate discharges before the plan may reach `Status: Complete`.
6
6
 
7
- The **`Gates:`** line is machine-read: the compliance hook greps exactly the forms below, and implementation edits are blocked while a segment reads `pending`. Each gate skill rewrites only its own segment when it runs — `boundary` to `Boundary: passed YYYY-MM-DD` or `Boundary: blocked YYYY-MM-DD`; `test-plan` to `Test-plan: written YYYY-MM-DD (N rows)`. Never hand-edit the line to skip a gate.
7
+ The **`Gates:`** line is machine-read: the compliance hook greps exactly the forms below, and implementation edits are blocked while a segment reads `pending`. Each gate skill rewrites only its own segment when it runs — `boundary` to `Boundary: passed YYYY-MM-DD` or `Boundary: blocked YYYY-MM-DD`; `testing` (plan-gate mode) to `Test-plan: written YYYY-MM-DD (N rows)` — the segment label stays `Test-plan`, greppable. Never hand-edit the line to skip a gate.
8
8
 
9
9
  ```
10
10
  # {Feature Name}
@@ -52,7 +52,7 @@ Why this work is happening *now*. Link to the spec for *what* we're building; th
52
52
 
53
53
  ## Test Obligations
54
54
 
55
- <!-- Written by the `test-plan` skill at the plan gate (after `boundary`). One row per
55
+ <!-- Written by the `testing` skill (plan-gate mode) at the plan gate (after `boundary`). One row per
56
56
  promised behavior: layer · target file · oracle · fixtures. Amendable while the
57
57
  plan is open; discharged at the verification gate. Do not hand-author. -->
58
58
 
@@ -0,0 +1,141 @@
1
+ ---
2
+ name: schema-design
3
+ description: "Use when designing or altering a relational schema — a new table, column, index, junction, migration, or seed — or reviewing one. Owns the design doctrine: what earns a table, where a fact lives (state column vs. sequence log vs. audit log), scope columns over parallel tables, junction = M:N only, FK policy including append-only tables, uniqueness with nullable columns, vocabulary tables, copying rows between scopes. Naming and column types stay in the `database.md` rule. Triggers on 'new table', 'schema', 'migration', 'add a column', 'junction table', 'foreign key', 'audit log', 'versioning'."
4
+ ---
5
+
6
+ # Schema Design — the doctrine
7
+
8
+ You decide **what earns a table and where a fact lives** — for every Nurix relational schema, engine-agnostic (written for SQLite / Cloudflare D1 + Drizzle; a Postgres project keeps the same names and semantics with native types). Naming, the `id`/`slug`/`label` vocabulary, and column types are the `database.md` rule and are never restated here — this skill owns the design arguments consulted at schema-change time.
9
+
10
+ Apply the sections below to the change in hand; when reviewing, report violations with the section that names them.
11
+
12
+ ## What earns a table
13
+
14
+ A table is the most expensive home for data — a migration to change its shape, a join to read, a mirror to keep in sync. Default to **not** a table — a concept earns one only with at least one property in the left column.
15
+
16
+ | A table is earned by… | Lacking all of these, the data belongs in… |
17
+ | -------------------------------------------------------------------------- | ------------------------------------------------------- |
18
+ | identity minted at **runtime** — a session, an upload | — |
19
+ | variation **per tenant / user** — a workspace's theme | — |
20
+ | **independent addressing** — queried, indexed, or FK'd in isolation | a **JSON column** on its parent row |
21
+ | **concurrency-safe** writes — racing writers must not clobber a shared blob | — |
22
+ | a **DB-enforced invariant** — immutability trigger, unique, atomic boundary | — |
23
+ | dev-authored, published on deploy, identical for every tenant — plan tiers | **config / a constants file**, validated at build |
24
+ | exactly recomputable from other rows — a tenant's usage total | a **cache** (KV / edge), recomputed on demand |
25
+ | process state dead once the request completes — a retry count | a **log or workflow engine**, never the domain DB |
26
+ | owned by another system-of-record — the payment processor's invoice | a **reference column** — store the id, not the data |
27
+
28
+ Each right-column route is one test: process state — would a product feature break if this vanished the instant the request finished?; derived output — exactly recomputable from other rows + code?; un-addressed detail — is the row ever `SELECT`ed / `JOIN`ed / FK'd alone?; another system's record — does a specialized service (chat, telemetry, object store) already own it?
29
+
30
+ - **Static vocabulary or a fixed graph → config, not a table** — HTTP status labels, a fixed onboarding funnel. Surface it in code as a constant/type; reifying it into tables plus a seed makes the file and the tables a two-copy sync problem. The refined boundary is § Vocabulary / lookup tables.
31
+ - **History → one immutable log + a mutable head.** Never shatter versioning into per-entity `*_revisions` / `*_revision_sections` tables — snapshot the coherent set into one append-only versions table; current state is a projection of the newest entry. A table that is just "old rows of another table" collapses into the snapshot.
32
+ - **A surviving table names the invariant it buys** — content-addressed immutable rows buy reproducibility, a monotonic sequence buys ordering/sync, an atomic write boundary buys multi-row atomicity. Drop the requirement and the table drops with it.
33
+
34
+ Table count tracks **requirements, not domain richness** — a rich domain is not entitled to a table per noun.
35
+
36
+ ## State, rules, and history
37
+
38
+ **One fact has one home, chosen by what kind of fact it is.** Confusing these produces the most common redundant table in a schema — a queue whose rows restate state the entity already carries.
39
+
40
+ | The fact | Home | Example |
41
+ | --- | --- | --- |
42
+ | What the entity **is now** | a **column on the entity's own row** | `documents.state = 'blocked'` |
43
+ | What may be **done** about that state | **code** — the transition map, the UI's affordances | which inputs unblock a document |
44
+ | What **was done**, and by whom | the **append-only audit log** | an `audit_log` row: `document.override_applied` |
45
+ | What a consumer must **replay in order** | the **sequence log** | an `app_events` row keyed `(app_id, sequence)` |
46
+
47
+ - **State becomes a column the moment anything selects on it.** `WHERE state = 'blocked'` takes an index; `WHERE json_extract(metadata, '$.state') = 'blocked'` scans the table and is invisible to every schema check. JSON keeps only what § What earns a table leaves it — detail nothing addresses alone.
48
+ - **A pending-work table mirroring row state is the anti-pattern** — `*_actions`, `*_inbox`, `*_flags`. The tell: resolving one of its rows also updates the entity, so two rows answer one question and are free to disagree. The *open question* is a column beside the state (at most one per row ⇒ a column, § A junction spells many-to-many); the *answer given* is an audit-log row.
49
+ - **Legal transitions live in code, never as rows** — a `status_transitions` table is a state machine the database cannot enforce and the code must re-implement anyway to use.
50
+
51
+ **The two logs are not substitutes**, and no one table serves both:
52
+
53
+ | | Sequence log | Audit log |
54
+ | --- | --- | --- |
55
+ | Answers | what happened here, **in order** | who did what, **and whether it succeeded** |
56
+ | Ordinal | a **gapless per-entity `sequence`**, allocated from a counter | none — nothing replays it |
57
+ | Scope | one entity, always | any — **including none**: a sign-up predates every tenant |
58
+ | Records | only what **happened** | **attempts too** — `denied`, `failure` |
59
+ | Read by | cursors replaying forward | investigations, at any age |
60
+
61
+ The bottom two rows are why neither can hold the other's rows: an event at a scope with no entity has no sequence to take, and a refused attempt in a replay stream would have consumers apply something that never happened.
62
+
63
+ ### The audit log's own shape
64
+
65
+ **An audit log outlives every row it names, so it stores what it needs to stay readable without them.** Both consequences are missing from most first drafts:
66
+
67
+ - **Denormalize the display at write time** — `actor_label`, `target_label` beside `actor_id` / `target_id`. Ids alone leave the log intact but unreadable once the person or record is deleted, which is the one thing it exists to survive.
68
+ - **Record the attempt, not the success** — `outcome` (`success | failure | denied`) plus a `reason`. Refused sign-ins and permission denials are most of what anyone opens an audit log for; without the column they cannot be written at all.
69
+ - **Keep the columns product-neutral** — `actor_*` · `action` · `outcome` · `target_*` · `scope_path` · `channel` · `source_ip` · `correlation_id` · `changes_json` serve any domain. Per-level tenancy columns tie the log to today's model; a materialized `scope_path` (`workspace:abc/app:def`) makes "everything under this tenant" one prefix scan and survives a new level appearing — at the price of enforcing the hierarchy in code rather than a `CHECK`.
70
+
71
+ ## Scope, not parallel tables
72
+
73
+ One entity that exists at several scopes is **one table with a nullable scope column** — never one table per scope, and never one table shared across entity families.
74
+
75
+ - **A table shared by several families carries the union of their columns** — one `settings` table for users, apps, and workspaces. Almost nothing can be `NOT NULL`, the payload lands in JSON beyond every constraint, index, and FK, and a new family widens a table other features already depend on. One table per family.
76
+ - **Two tables declared to have "the same columns" are two schemas maintained by hand** — the drift is silent, surfacing as an incomplete copy, never as an error. Collapse them and discriminate by column.
77
+ - **The scope column is the discriminator** (`app_id IS NULL` = parent scope, set = child scope). Copying a scope is then `INSERT … SELECT` over one table with one column rewritten, so source and target cannot disagree about shape.
78
+ - **A nullable owner column is how a row exists before its owner does** — a pending enrolment or unclaimed record is creatable before the owner is known, which is what lets a `pending_*` / `*_requests` table collapse into the real one.
79
+ - **A reserved row can own the shipped defaults.** A fixed-id "system" parent makes the defaults and a tenant's copy the same kind of row, through the same code. It must then be excluded from every tenant-scoped query — state that invariant where the id is defined.
80
+
81
+ ## Copying rows between scopes
82
+
83
+ - **A copied junction row is already correct with no remapping** — its endpoints are `slug` references (§ Keys & the shared vocabulary), and a slug means the same thing in every scope. Rewriting ids through a source→copy map is the most error-prone step in a multi-level copy, and a partial failure leaves wiring pointed into another scope rather than raising.
84
+ - **Provenance is a soft reference — no FK.** `source_id` must outlive its referent; an FK would either block the source's deletion or erase the lineage.
85
+ - **Store the source's version at copy time** — `version` + `source_version` make "is an upgrade available" a comparison of two stored values, not a content diff.
86
+ - **A copy is a row, never a live read.** Nothing at the child scope reads the parent's row at runtime; propagation happens only through an explicit re-copy, so a local edit is never silently overwritten.
87
+
88
+ ## A junction spells many-to-many — nothing else
89
+
90
+ **A `_map` table is the schema's notation for M:N. Using it for 1:M is a category error** — the junction's own uniqueness admits many parents per child, so the "one parent" rule stops being enforced the moment it is written this way. Cardinality decides between the only two shapes:
91
+
92
+ | Relationship | Shape |
93
+ | --- | --- |
94
+ | **1:M** — the child has exactly one parent | a **reference column on the child** (`agents.routing_slug`), plus an index |
95
+ | **M:N** — both sides have many | a **`<a>_<b>_map` junction** with a composite unique |
96
+
97
+ - **The test is one question about the child**: can this row ever belong to two parents at once? No ⇒ column. A junction "in case it becomes M:N later" ships the unenforced model today to buy a migration you may never run.
98
+ - **A junction for a 1:M also** makes the association a row that can go missing independently of the child — a child can exist correctly while its wiring does not — and forces a join onto the most common read in the system.
99
+ - **A junction carrying only its two endpoints and timestamps is the tell** — a real M:N junction usually earns its own columns (a role, an order, a status); one that never does is a 1:M waiting to be collapsed into a column.
100
+ - **Collapsing deletes a copy step** — the association travels inside the row it belongs to: no second pass to remap, no copy landing with its wiring half-applied.
101
+ - **A pair already recorded elsewhere needs no table at all.** If an append-only log stores `(parent, child)` on every write, the association table is `SELECT DISTINCT` over that log — a cache free to disagree with the log it derives from (§ What earns a table).
102
+
103
+ ## Foreign keys into and out of append-only tables
104
+
105
+ **An append-only table can afford no `ON DELETE` action at all, so its references to mutable tables are soft — no FK.** Every option is closed to it:
106
+
107
+ | Action | Why it fails on an append-only table |
108
+ | --- | --- |
109
+ | `SET NULL` | It is an **UPDATE**. An append-only trigger aborts it, so the parent's deletion fails — with an error naming the trigger, not the constraint. |
110
+ | `CASCADE` | Deletes history to delete a person. The log exists precisely to outlive its subjects. |
111
+ | `NO ACTION` | Makes anyone who ever appeared in the log undeletable — the hub defect above. |
112
+
113
+ - **`SET NULL` is the trap** — it reads like a safe relaxation of `NO ACTION` and passes review as one.
114
+ - **Record the actor as a plain column** — the same soft-reference treatment provenance gets (`source_id`, `thread_id`). The log keeps naming who acted after the account is gone, which is what an audit trail is for.
115
+ - **`CASCADE` from the append-only side's own parent is still fine** — a cascade is a DELETE, so a `BEFORE UPDATE` trigger never sees it: an App's log dying with the App is legitimate, distinct from erasing a log to delete a person.
116
+ - **State the exemption where it exists.** A catalog whose rows must not disappear under a referencing row (a service definition, a vocabulary) keeps `NO ACTION` deliberately — that guarantee is worth one line saying so.
117
+
118
+ ## Uniqueness with a nullable column
119
+
120
+ **`UNIQUE (a, b)` does not constrain rows where `b` is `NULL`** — SQL treats every `NULL` as distinct, so a composite unique over a nullable discriminator silently permits exactly the duplicates it was introduced to keep apart. Write one partial index per scope instead:
121
+
122
+ ```sql
123
+ CREATE UNIQUE INDEX <t>_parent_slug_unique ON <t> (parent_id, slug) WHERE child_id IS NULL;
124
+ CREATE UNIQUE INDEX <t>_child_slug_unique ON <t> (child_id, slug) WHERE child_id IS NOT NULL;
125
+ ```
126
+
127
+ **Prove a uniqueness claim by inserting the duplicate it forbids** — a schema review cannot tell a firing index from a silent one by reading.
128
+
129
+ ## Vocabulary / lookup tables
130
+
131
+ A controlled vocabulary earns a **table** only when a user row FKs into it and reads its `label` / `description` at runtime — a service catalog rendered in a picker; static dev-authored config nobody references by FK stays **config** (§ What earns a table) — a `doc_type` value list. Enumerable is not tabular, and a `type`-style column validated against the constants file beats an FK to a seeded mirror. An earned table has a fixed shape: `id` (UUID) · `slug` (UNIQUE) · `label` · optional `description` · `status` · timestamps.
132
+
133
+ - **Seeds are generated, never hand-written** — the source of truth is a CSV or a shared package; a generator emits the `*.seed.sql` with UUIDv5 ids and an idempotent `ON CONFLICT(slug)` upsert that preserves live counts / admin-tuned columns. ❌ Hand-editing seed SQL — regenerate it.
134
+
135
+ ## Run
136
+
137
+ 1. **Route every new fact through § What earns a table** — default to *not* a table; name the left-column property that earns one, or the right-column home it takes instead.
138
+ 2. **Classify every fact by § State, rules, and history** — now-state → entity column; rules → code; what-was-done → audit log; replayable → sequence log.
139
+ 3. **Check cardinality before any junction** (§ A junction spells many-to-many) and every FK's `ON DELETE` (§ Keys, § Append-only).
140
+ 4. **Prove uniqueness claims by inserting the forbidden duplicate** — a schema review cannot tell a firing index from a silent one by reading.
141
+ 5. **Report as a findings list** — one line per violation: the section, the table/column, the fix.
@@ -0,0 +1,104 @@
1
+ ---
2
+ name: test
3
+ description: Test Runner using Chrome DevTools Protocol (CDP). Run UI tests with Claude-in-Chrome browser automation. Triggers include: "run my tests", "run the test suite", "execute tests", "test this code", "check if tests pass".
4
+ argument-hint: [test-name]
5
+ ---
6
+
7
+ # Test Runner (CDP)
8
+
9
+ Run UI tests using Claude-in-Chrome browser automation.
10
+
11
+ ## Prerequisites
12
+
13
+ Complete these steps before executing test assertions:
14
+
15
+ ### 1. Load Credentials
16
+
17
+ Read `tests/.env` for: `TEST_USER_EMAIL`, `TEST_USER_PASSWORD`, `TEST_BASE_URL`, `TEST_APP_ID`
18
+
19
+ - `TEST_USER_EMAIL` / `TEST_USER_PASSWORD`: credentials used to log in during browser setup.
20
+ - `TEST_BASE_URL`: the root URL the browser will navigate to (e.g. `http://localhost:3000`).
21
+ - `TEST_APP_ID` _(optional)_: an application or workspace identifier passed to tests that need to target a specific app instance. When unset, tests run against the default app/workspace returned by the base URL.
22
+
23
+ If the file is missing, instruct user to copy `tests/.env.example` to `tests/.env`.
24
+
25
+ ### 2. Connect to Chrome
26
+
27
+ ```
28
+ mcp__claude-in-chrome__tabs_context_mcp with createIfEmpty: true
29
+ ```
30
+
31
+ If connection fails: Install Claude-in-Chrome extension, ensure Chrome is running.
32
+
33
+ ### 3. Setup Browser
34
+
35
+ ```
36
+ mcp__claude-in-chrome__tabs_create_mcp
37
+ ```
38
+
39
+ Navigate to `${TEST_BASE_URL}`. Login if required using credentials from step 1.
40
+
41
+ ### 4. Verify Ready State
42
+
43
+ Confirm main dashboard is visible before proceeding.
44
+
45
+ ## Execution
46
+
47
+ 1. **Locate test file**: `.github/docs/tests/<test-name>.md`
48
+ 2. **Validate exists**: If not found, list available tests in `.github/docs/tests/`. If that directory does not exist, search for common test patterns: `*.test.*`, `*.spec.*`, `__tests__/`, `test/`, `tests/` directories, or check `package.json` for a `test` script to determine how tests should be run.
49
+ 3. **Create results file**: `tests/claude-runs/<test-name>-<YYYY-MM-DD-HHmmss>.md`
50
+ 4. **Execute sequentially**: Update checkboxes as you go
51
+ 5. **Save results**: Write completed file with summary header
52
+
53
+ ## Checkbox Markers
54
+
55
+ | Marker | Meaning |
56
+ | ------ | ----------------------------------- |
57
+ | `[x]` | Passed |
58
+ | `[!]` | Failed - add `**FAILED**: <reason>` |
59
+ | `[-]` | Skipped |
60
+ | `[ ]` | Not run |
61
+
62
+ ## Results Template
63
+
64
+ ```markdown
65
+ # Test Results: <Test Name>
66
+
67
+ ## Execution Summary
68
+
69
+ | Property | Value |
70
+ | ----------- | ------------------------------- |
71
+ | Test File | `<path>` |
72
+ | Executed At | `<YYYY-MM-DD HH:mm:ss>` |
73
+ | Status | `PASSED` / `FAILED` / `PARTIAL` |
74
+
75
+ ## Results Overview
76
+
77
+ | Metric | Count |
78
+ | ---------------- | ----- |
79
+ | Total Assertions | X |
80
+ | Passed | X |
81
+ | Failed | X |
82
+ | Skipped | X |
83
+
84
+ ## Failed Assertions Summary
85
+
86
+ - Section X.X: <failure description>
87
+
88
+ ---
89
+
90
+ ## Test: <Original Title>
91
+
92
+ ... (test content with updated checkboxes)
93
+ ```
94
+
95
+ ## Error Handling
96
+
97
+ - **Network errors**: Retry once, then mark failed
98
+ - **Timeout**: Mark failed with timeout note
99
+ - **Element not found**: Screenshot, mark failed, continue
100
+ - **Browser disconnect**: Stop, save partial results, inform user
101
+
102
+ ## Screenshots
103
+
104
+ Take screenshots: before each major section, on failures, after completion.
@@ -1,15 +1,69 @@
1
1
  ---
2
2
  name: testing
3
- description: "Use when deciding where tests live — designing a package's test architecture, laying out a new suite, placing a new test, or judging whether an existing test is at the right altitude. Layered test strategy: derives which ownership layers an application has, what each layer owns and may assert, and the technology class per layer. Use it whenever a testing strategy, test pyramid, coverage plan, or 'should this be a unit/integration/e2e test' question comes up, even if the word 'layer' is never said. Triggers on 'testing strategy', 'test architecture', 'test layers', 'where should this test live'."
3
+ description: "The test-lifecycle skill — three modes. Plan-gate mode: after `boundary` passes, derives the tests a plan owes and writes the Test Obligations ledger into the plan (a row per behavior: layer, target file, oracle, fixtures); writes no test files. Placement mode: layered test strategy — which ownership strata the application has, where a test lives and what it may assert; use for any testing-strategy, test-architecture, test-pyramid, or 'unit/integration/e2e?' question, even if 'layer' is never said. Gate mode: executes the verification gate — derives the behavioral checklist, drives the real surface (browser automation, the real CLI, HTTP; the `/test` runner where wired), pins durable behaviors, discharges every ledger row. Triggers on 'test plan', 'testing strategy', 'test architecture', 'test layers', 'where should this test live', 'run the verification gate'."
4
4
  ---
5
5
 
6
- # Layered Test Strategy
6
+ # Testing — Ledger, Layers, Gate
7
7
 
8
- You decide **where a test lives and what it may assert**. Not *when* tests are written — that is `.claude/rules/verification.md` — and not *which framework* — that is `.claude/rules/testing.md`'s per-stack table. This skill owns the layer model those two rules leave open: the strata, their responsibilities, and the derivation of which strata a given application has.
8
+ One skill owns the test lifecycle's three procedural moments; the standing constraints (regimes, deferral, gate preconditions) are `.claude/rules/testing.md` and are never restated here. Pick the mode from the moment:
9
9
 
10
- **The litmus is "where can this defect originate?"** A test lives with the only layer that can produce the defect it catches. "The 409 response carries the current body" can only break in a route handler — service-contract layer. "The buffer the user typed survives the 409" can only break in component state — component layer. Same feature, two owners, two tests, zero overlap. Placement is decided by this question alone, never by which surface the author happened to be looking at — testing a feature wherever you first *see* it is what makes suites flat, duplicative, and multiplicative in cost.
10
+ | Moment | Mode |
11
+ | --- | --- |
12
+ | Plan gate, after `boundary` passes | **Plan-gate** — derive what the plan owes |
13
+ | A strategy / placement / altitude question, or laying out a suite | **Placement** — derive the strata, place the tests |
14
+ | The user triggers the verification gate | **Gate** — drive, pin, discharge |
15
+
16
+ ---
17
+
18
+ ## Plan-gate mode — the Test Obligations ledger
19
+
20
+ You derive the tests an implementation plan owes and pin them into the plan as an explicit, reviewable manifest — at the same gate `boundary` runs, ordered second. You never write a test file; you write the **ledger gate mode discharges**, so the gate proves a checklist instead of a vibe.
21
+
22
+ **The litmus is "which promise, at which altitude, proven by what?"** A plan promises behavior; each promise earns exactly one test at the cheapest layer that can actually prove it. Spend your reading deciding the *layer* and the *oracle*, not on prose. Writing tests *before* the code exists fabricates implementation coupling or leaves the suite red for the plan's duration; writing them *during* the build loop taxes every iteration — so the plan gate emits obligations, the loop stays free of test files, and gate mode discharges the manifest in one pass.
23
+
24
+ ### Contract sources
25
+
26
+ - **The plan's Proposed Changes** — each file/entity/API change is a behavior that may owe a test.
27
+ - **The owning LLD** (`docs/features/{feature}.md`, via the ownership index) — every obligation names the LLD promise it proves; the LLD is referenced, never restated. This one-directional chain (LLD → manifest → tests + Completion checklist) is what stops the artifacts drifting.
28
+ - **`.claude/rules/testing.md`** — frameworks and canonical placement per stack; target paths come from there, never invented. The repo's existing runner always wins.
29
+ - **`boundary`'s output** — a HIGH/CRITICAL finding is itself a behavior owed a regression test. Fold boundary's amendments first, then read the amended plan.
30
+
31
+ ### Layer assignment
32
+
33
+ Assign each behavior the **cheapest layer that can prove it**, then name the oracle. The ledger altitudes are `unit` / `component` / `integration` / `e2e` (strata mapping: § Bridge below); **integration is the default for server behavior** — a route/handler against a real in-memory datastore via the app instance. Error paths, races, and idempotency are obligations too, and belong at **integration** with injected faults (network mock, fake timers, forced constraint) — never e2e.
34
+
35
+ **The e2e altitude gate:** an e2e obligation is earned **only** when the behavior genuinely spans processes no single-process test can exercise together. Density is one scenario per cross-process journey, not per feature — prefer **"extend scenario `<name>`"** over **"new scenario"**. **Refuse to emit any e2e obligation while the project's e2e smoke gate is not wired in CI** — emit a **blocked** row naming the missing gate instead.
36
+
37
+ ### Run (plan-gate)
38
+
39
+ **Input:** a plan path (given, or the newest in `docs/history/plans/`), boundary-checked with amendments folded; skipped on a boundary BLOCK. For a non-plan diff, the hunks stand in for Proposed Changes but the obligations are paid **immediately** — only a plan has a ledger to defer into.
40
+
41
+ 1. **Build the behavior list** from Proposed Changes + the LLD's promises + HIGH/CRITICAL boundary findings. Note compiler-guarded behaviors — they owe **no** test.
42
+ 2. **Assign layer + oracle** per behavior; cheapest proving layer wins.
43
+ 3. **Map to a target file** per `testing.md`; name fixtures/factories and fault injection; use the repo-standard fake-data source.
44
+ 4. **Apply the e2e altitude gate**; downgrade everything else to integration.
45
+ 5. **Emit the Test Obligations section into the plan** (an executor dispatched with only the plan sees nothing else), plus a one-line chat summary:
46
+
47
+ ```
48
+ ## Test Obligations
11
49
 
12
- ## The stack
50
+ | # | Behavior (LLD promise) | Layer | Target file | Oracle | Fixtures / faults |
51
+ | - | ---------------------- | ----- | ----------- | ------ | ----------------- |
52
+
53
+ Coverage: {N} behaviors · {N} integration · {N} component · {N} unit · {N} e2e ({N} extend, {N} new)
54
+ Completion checklist: docs/tests/{feature}.md (derived at the gate, not now)
55
+ ```
56
+
57
+ A behavior with no obligation is stated too, with the reason — "none — compiler-guarded" reads as *checked*; an empty cell reads as *not analyzed*. A short manifest is valid — do not pad.
58
+ 6. **Record the write in the plan's `Gates:` line**, rewriting only your segment to exactly `Test-plan: written YYYY-MM-DD (N rows)` — the compliance hook greps this exact form. Add the line if the plan predates the format.
59
+
60
+ ---
61
+
62
+ ## Placement mode — layered test strategy
63
+
64
+ You decide **where a test lives and what it may assert**. **The litmus is "where can this defect originate?"** A test lives with the only layer that can produce the defect it catches. "The 409 response carries the current body" can only break in a route handler — service-contract layer. "The buffer the user typed survives the 409" can only break in component state — component layer. Same feature, two owners, two tests, zero overlap. Placement is decided by this question alone — testing a feature wherever you first *see* it is what makes suites flat, duplicative, and multiplicative in cost.
65
+
66
+ ### The stack
13
67
 
14
68
  The maximum form, for a full-stack application. Each layer names what only it can break, the oracle that proves it, and the technology **class** — the concrete framework comes from `testing.md`'s stack table, never from here.
15
69
 
@@ -24,11 +78,11 @@ The maximum form, for a full-stack application. Each layer names what only it ca
24
78
  | 7 | **Component / surface** | Interaction orchestration — what a user action changes on screen: buffer lifecycle, optimistic states, error surfaces | Rendered output a user can observe; network answered by layer 4 | DOM testing-library class + interception |
25
79
  | 8 | **System smoke** | Composition only — the layers are actually stacked: the build serves, auth flows end to end, one write round-trips | A real browser or real binary against the really-running app | Browser/process driver class; 3–5 journeys, zero variants |
26
80
 
27
- Layer 4 is the hinge. Every layer ≥5 runs on the assumption that layers 1–3 are green — and the artifact is what makes that assumption *enforceable* rather than aspirational: when the provider's response type changes, the fixture stops compiling, and every consumer test fails at typecheck before it runs. A consumer-side test that hand-writes a wire response has smuggled unverified layer-3 knowledge upward; route it through the artifact instead.
81
+ Layer 4 is the hinge. Every layer ≥5 runs on the assumption that layers 1–3 are green — and the artifact makes that assumption *enforceable*: when the provider's response type changes, the fixture stops compiling, and every consumer test fails at typecheck before it runs. A consumer-side test that hand-writes a wire response has smuggled unverified layer-3 knowledge upward; route it through the artifact.
28
82
 
29
- ## How many layers — derived, never chosen
83
+ ### How many layers — derived, never chosen
30
84
 
31
- The count is not a taste decision. **A layer exists iff the application has the boundary that can originate that layer's defects; an absent boundary makes the layer vanish** — it never merges its responsibilities into a neighbor. Merging is the failure mode: a team that "simplifies" layers 3 and 7 together has reinvented the flat feature test, where every UI assertion silently re-verifies the API beneath it and cost goes multiplicative again.
85
+ **A layer exists iff the application has the boundary that can originate that layer's defects; an absent boundary makes the layer vanish** — it never merges into a neighbor. Merging is the failure mode: "simplifying" layers 3 and 7 together reinvents the flat feature test, where every UI assertion silently re-verifies the API beneath it and cost goes multiplicative again.
32
86
 
33
87
  | Application shape | Strata present |
34
88
  | --- | --- |
@@ -40,19 +94,17 @@ The count is not a taste decision. **A layer exists iff the application has the
40
94
 
41
95
  Derive the stack first, then place tests: a placement argument that starts "this feels like an integration test" has skipped the derivation.
42
96
 
43
- ## Placement discipline
97
+ ### Placement discipline
44
98
 
45
99
  - **An assertion provable at two layers is misplaced — it belongs at the lower one.** The duplication check is mechanical, not judgment.
46
- - **Behavior variants sink to the cheapest layer that owns them** — almost always 2 or 6, where a variant costs one function call. A variant at layer 3 pays wire tax on a domain test; a variant at layer 8 is a placement failure below, by definition.
47
- - **One representative path upward.** Layer 3 keeps one happy path per route to prove wiring into layer 2, plus exhaustive coverage of *contract edges only* (auth refusals, not-found indistinguishability, projection/withholding). Layer 8 keeps 3–5 journeys total.
48
- - **External systems enter as modeled fakes, not spies.** A fake implements the port's semantics so tests assert resulting state ("the branch gained two commits, the first holds the earlier body"); a spy only proves methods were called, which passes just as happily with the bytes swapped.
49
- - **Failure localization is the payoff to protect.** One defect should turn exactly one layer red, at its owner. When a single change reddens tests across several layers, at most one of those tests was correctly placed — treat the rest as findings.
100
+ - **Behavior variants sink to the cheapest layer that owns them** — almost always 2 or 6. A variant at layer 3 pays wire tax on a domain test; a variant at layer 8 is a placement failure below, by definition.
101
+ - **One representative path upward.** Layer 3 keeps one happy path per route plus exhaustive coverage of *contract edges only*; layer 8 keeps 3–5 journeys total.
102
+ - **External systems enter as modeled fakes, not spies.** A fake implements the port's semantics so tests assert resulting state; a spy only proves methods were called, which passes just as happily with the bytes swapped.
103
+ - **Failure localization is the payoff to protect.** One defect turns exactly one layer red, at its owner. When a single change reddens several layers, at most one of those tests was correctly placed — treat the rest as findings.
50
104
 
51
- Why this is the cheap strategy at the horizon: layering makes cost **additive** — a new API behavior costs one layer-2 test and maybe a layer-3 edge, zero frontend tests; a new UI state costs one layer-7 test, zero backend tests. A flat suite pays N×M, because every top-layer path implicitly re-verifies every behavior beneath it.
105
+ Layering makes cost **additive** — a new API behavior costs one layer-2 test and maybe a layer-3 edge, zero frontend tests; a new UI state costs one layer-7 test, zero backend tests. A flat suite pays N×M, because every top-layer path implicitly re-verifies every behavior beneath it.
52
106
 
53
- ## The tree
54
-
55
- The directory layout follows the strata, so a test's home states its layer and the runner split is mechanical, not per-file judgment:
107
+ ### The tree
56
108
 
57
109
  ```
58
110
  tests/
@@ -68,13 +120,13 @@ e2e/ L8 — its own config, never mixed into the unit runner's glob
68
120
  ```
69
121
 
70
122
  - **A stratum the app lacks gets no directory** — an empty `component/` in a backend service is a lie about the app's shape.
71
- - **One shared glob list, imported by both runner configs** — the infrastructure-bound runner *includes* `schema|domain|routes`, the unit runner *excludes* them. Two hand-maintained lists drift; one imported constant cannot.
72
- - **Which concrete runner, framework, and DOM environment fill these slots is `testing.md`'s per-stack table** — this skill names the slot, never the brand, so the two files cannot disagree.
73
- - **Contract modules the provider binds** (the layer-4 source types) live in the app's shared source zone — importable by both provider and consumer builds — not in the test tree; `tests/contract/` holds only the fixtures and fakes compiled from them.
123
+ - **One shared glob list, imported by both runner configs** — two hand-maintained lists drift; one imported constant cannot.
124
+ - **Concrete runner, framework, and DOM environment come from `testing.md`'s per-stack table** — this skill names the slot, never the brand.
125
+ - **Contract modules the provider binds** (the layer-4 source types) live in the app's shared source zone — importable by both sides; `tests/contract/` holds only the compiled fixtures and fakes.
74
126
 
75
- ## Bridge to the obligation ledger
127
+ ### Bridge to the obligation ledger
76
128
 
77
- The `test-plan` skill's ledger assigns each obligation a runner altitude — `unit` / `component` / `integration` / `e2e`. Those are coarse names for where a test *runs*; the strata here say what it may *assert*. The mapping:
129
+ The ledger's altitudes are coarse names for where a test *runs*; the strata say what it may *assert*:
78
130
 
79
131
  | Ledger altitude | Strata it covers |
80
132
  | --- | --- |
@@ -83,13 +135,32 @@ The `test-plan` skill's ledger assigns each obligation a runner altitude — `un
83
135
  | component | 7 |
84
136
  | e2e | 8 |
85
137
 
86
- When writing or discharging a ledger row, name the stratum, not just the altitude — "integration" alone leaves open whether the row asserts a schema invariant, a business rule, or an envelope, and those are three different owners with three different oracles.
138
+ When writing or discharging a ledger row, name the stratum, not just the altitude — "integration" alone leaves open whether the row asserts a schema invariant, a business rule, or an envelope: three owners, three oracles.
139
+
140
+ ### Run (placement)
141
+
142
+ 1. **Derive the stack** from the application's real boundaries (storage? published wire? separate client? DOM?).
143
+ 2. **Place each behavior** by the litmus; name stratum, oracle, technology class; take the concrete framework and file placement from `testing.md`.
144
+ 3. **Flag violations, don't just place** — assertions provable lower, variants above 2/6, hand-written wire fixtures above layer 4, spies where fakes should model state, layer-8 variants.
145
+ 4. **Report as a stratum map** — one line per stratum: exists/absent (why), what lives there, what is misplaced and where it moves. For a plan, feed the strata into the ledger's rows.
146
+
147
+ ---
148
+
149
+ ## Gate mode — discharge the ledger
150
+
151
+ Runs only when the user triggers the verification gate (constraints — preconditions, verdict-inline, tier, honesty — are `.claude/rules/testing.md` § The gate). **Drive the real thing first, then pin what must not regress**: driving finds the defects that matter to a user, and shows which behaviors are worth the permanent cost of a test.
87
152
 
88
- ## Run
153
+ 1. **Write the checklist** — derive or update `docs/tests/{feature}.md` from the ledger and the LLD's promises (the `documentation` skill's `templates/behavioral-checklist.md`; one `**Assert**` per observable). Derived before the driving it is the script; derived after, it is merely a report with nothing to trace tests back to.
154
+ 2. **Drive the real surface** — browser automation for web UI (the `/test` runner skill where the repo wires it), the actual CLI binary, HTTP against the running worker. **A failing checklist assertion is never softened to make a run green** — record the defect, send it to the backlog. Where the runtime offers no browser automation, emit the browser-verification checklist for the user to run; their report is the rung's verdict and `Status: Complete` waits for it.
155
+ 3. **Pin the durable behaviors** as suite tests per `testing.md`, naming each test's stratum (§ Bridge). A test is a permanent maintenance cost, bought deliberately:
89
156
 
90
- Given a package, a plan, or a "where does this test go?" question:
157
+ | Earns a suite test | Verified by driving alone |
158
+ | --- | --- |
159
+ | A promise an LLD makes about the feature's contract | Layout, spacing, copy — restated by a test, not protected by one |
160
+ | A bug that occurred once — its regression test | A one-off migration or backfill's output |
161
+ | An error path, race, or idempotency guarantee | Behavior a type or the compiler already makes unshippable |
91
162
 
92
- 1. **Derive the stack.** From the application's shape (its real boundaries: storage? published wire? separate client? DOM?), list which strata exist. This takes minutes and settles every later argument.
93
- 2. **Place each behavior.** Apply the litmus per behavior: which stratum is the only origin of this defect? Name the stratum, the oracle, and the technology class; take the concrete framework and file placement from `testing.md`.
94
- 3. **Flag violations, don't just place.** When reviewing an existing suite or plan: assertions provable lower, variants above layer 2/6, hand-written wire fixtures above layer 4, spies where a fake should model state, and any layer-8 test carrying a behavior variant.
95
- 4. **Report as a stratum map** — one line per stratum: exists/absent (and why), what currently lives there, what is misplaced and where it should move. For a plan, feed the strata into the ledger's rows instead.
163
+ A driven check is evidence, not an artifact — it proves the change works today and leaves nothing behind; that is why this rung exists.
164
+ 4. **Run the affected suite**, plus the project's e2e smoke gate where one is wired.
165
+ 5. **Discharge every ledger row into exactly one outcome**: **satisfied** (test green, or driven check passed), **owes nothing** (with the reason — compiler-guarded, covered elsewhere), or **blocked** (missing infrastructure named, row converted to a backlog item). A row left unexamined blocks the gate.
166
+ 6. **Report results honestly, including failures**; record which test now guards each checklist section (`Pinned:` traces). Completion steps — freezing the plan, the changelog entry — are the root policy file's, not yours.
@@ -102,6 +102,18 @@ Work surfaced outside the current plan's scope: append to `docs/history/backlog.
102
102
 
103
103
  ---
104
104
 
105
+ ## Delegation
106
+
107
+ The single policy for sub-agent dispatch — when to fan out, at what tier, and how far to trust the result. Where the `code` harness leaves tier choice to session judgment, dispatch here runs through **The Routing Policy** above — the table is the first stop for every dispatch, not session judgment. No dispatch mechanism ⇒ inline.
108
+
109
+ - **Delegate to keep bulk out of the main context.** Wide file reads, sweeps, broad research, audits, fixture prep — work whose raw tool output the main session doesn't need — defaults to a sub-agent: output a sub-agent reads dies with it, while output read inline is re-read and re-billed on every later turn. The hand-off is self-contained (the sub-agent sees none of the conversation), so it must still cost less than the work; run independent delegations in parallel.
110
+ - **Small or judgment work stays inline.** A task finishable in a handful of tool calls is faster done than briefed. **Never delegate judgment by reflex** — a second opinion is for a high-stakes call or an explicit request.
111
+ - **A delegated result is a report, not verifiable ground truth** — verify load-bearing results directly.
112
+ - **Tiers run through the routing table, never session judgment** — mechanical fan-out routes to economy, judgment-heavy delegation to standard, frontier only for the ship gate, adversarial probing, or an explicit user-requested second opinion (§ The Routing Policy); the table's own guardrails (❌ Frontier fan-out, ❌ Unevidenced promotion) bind every dispatch. Tier → model mapping lives only in `.claude/rules/model-tiers.md`.
113
+ - **An explicit user directive about tier, cost, or delegation overrides this section**, subject to the routing table's evidence requirement for promotion (the `escalate` skill records it).
114
+
115
+ ---
116
+
105
117
  ## Guardrails & Defaults
106
118
 
107
119
  ❌ prohibitions hold unless the user explicitly asks otherwise; ✅ and ⚠️ items are standing policy:
@@ -109,7 +121,7 @@ Work surfaced outside the current plan's scope: append to `docs/history/backlog.
109
121
  - ❌ **Dependencies** — never upgrade frameworks, libraries, or dependencies.
110
122
  - ❌ **Branches & Worktrees [override]** — never self-create git branches or worktrees; commit straight to the checked-out branch. Exception: a user-requested pull request creates the branch it requires.
111
123
  - ❌ **Protected Files** — if `docs/operations/protected-files.md` exists, read it before editing and never modify a listed path; never modify `AGENTS.md` or that file itself.
112
- - ✅ **Fan-out is a tool, not a default** — but where the `code` harness leaves tier choice to session judgment, here dispatch runs through the routing table. Delegation questions are routing questions: consult the table first.
124
+ - ✅ **Delegation** — when to fan out, tiers, and trust are § Delegation.
113
125
  - ⚠️ **Plans** — created in `docs/history/plans/`; frozen at `Complete`/`Abandoned`, never edited after.
114
126
  - ⚠️ **Tool Issues** — report unexpected CLI/MCP tool behavior to the user with the exact tool, command, and error.
115
127
 
@@ -2,7 +2,7 @@
2
2
  name: lld-mode
3
3
  description: LLD researcher for Feature and Product workflows. Use to explore the codebase and gather the context needed for writing a feature LLD (low-level design — the feature doc in docs/features/). Returns research findings — does not write files.
4
4
  tools: Read, Glob, Grep, WebSearch, WebFetch
5
- model: inherit
5
+ model: opus
6
6
  skills:
7
7
  - documentation
8
8
  ---
@@ -2,7 +2,7 @@
2
2
  name: maintenance
3
3
  description: The single docs steward. Default behavior is the docs-vs-code coherence audit — detects and fixes docs drifting from code (stale docs, broken ownership, unowned source paths, architecture/backlog drift). An additive **baseline mode** layers structural reconcile on top: when the repo's `harness-version` marker is missing or behind, it converges `docs/` to the installed harness shape (scaffold the standard tree, fold dissolved-shape artifacts forward, stamp the version, propose modules) before running the audit. Mode is auto-selected from the marker. Run weekly, after major changes, or to baseline/upgrade a repo. Cross-cutting contract analysis is the `boundary` skill (`check` for plans, `scan` for diffs) — it derives contracts dynamically from source; there are no persisted boundary docs to audit. Triggers on "run maintenance", "docs health", "baseline this repo", "reconcile the harness", "upgrade the harness", "is this repo on the current harness", "/maintenance".
4
4
  tools: Read, Glob, Grep, Edit, Write, Bash
5
- model: inherit
5
+ model: opus
6
6
  ---
7
7
 
8
8
  # Maintenance Agent