@nurix/etna 0.19.66 → 0.19.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/etna.js +10 -1
- package/data/harness-index.json +1 -5
- package/data/harnesses/code/CLAUDE.md +2 -0
- package/data/harnesses/code/agents/maintenance.md +3 -1
- package/data/harnesses/code/harness.json +7 -2
- package/data/harnesses/code/hooks/compliance.mjs +1 -0
- package/data/harnesses/code/hooks/lib/frozen.mjs +3 -0
- package/data/harnesses/code/hooks/lib/ownership.mjs +3 -1
- package/data/harnesses/code/skills/documentation/scripts/doc-lint.mjs +1 -1
- package/data/harnesses/delivery/agents/maintenance.md +3 -1
- package/data/harnesses/delivery/harness.json +2 -2
- package/data/harnesses/delivery/skills/documentation/scripts/doc-lint.mjs +1 -1
- package/data/harnesses/etna/agents/maintenance.md +3 -1
- package/data/harnesses/etna/harness.json +2 -2
- package/data/harnesses/etna/skills/documentation/scripts/doc-lint.mjs +1 -1
- package/lib/install/materialize.mjs +51 -57
- package/package.json +2 -2
- package/data/harnesses/apollo/.mcp.json +0 -8
- package/data/harnesses/apollo/.prettierignore +0 -29
- package/data/harnesses/apollo/.prettierrc.json +0 -3
- package/data/harnesses/apollo/.vscode/settings.json +0 -19
- package/data/harnesses/apollo/CLAUDE.md +0 -162
- package/data/harnesses/apollo/agents/bookkeeper.md +0 -28
- package/data/harnesses/apollo/agents/brainstorm.md +0 -144
- package/data/harnesses/apollo/agents/change-review.md +0 -251
- package/data/harnesses/apollo/agents/lld-mode.md +0 -58
- package/data/harnesses/apollo/agents/maintenance.md +0 -234
- package/data/harnesses/apollo/agents/performance-review.md +0 -223
- package/data/harnesses/apollo/agents/retro.md +0 -46
- package/data/harnesses/apollo/codex/agents/bookkeeper.toml +0 -26
- package/data/harnesses/apollo/codex/agents/brainstorm.toml +0 -142
- package/data/harnesses/apollo/codex/agents/change-review.toml +0 -249
- package/data/harnesses/apollo/codex/agents/lld-mode.toml +0 -52
- package/data/harnesses/apollo/codex/agents/maintenance.toml +0 -228
- package/data/harnesses/apollo/codex/agents/performance-review.toml +0 -222
- package/data/harnesses/apollo/codex/agents/retro.toml +0 -45
- package/data/harnesses/apollo/harness.json +0 -19
- package/data/harnesses/apollo/hooks/approved-packages.md +0 -44
- package/data/harnesses/apollo/hooks/awareness.mjs +0 -405
- package/data/harnesses/apollo/hooks/check-bookkeeping-clean.mjs +0 -19
- package/data/harnesses/apollo/hooks/check-changelog-presence.mjs +0 -57
- package/data/harnesses/apollo/hooks/check-completion-integrity.mjs +0 -71
- package/data/harnesses/apollo/hooks/check-ownership.mjs +0 -22
- package/data/harnesses/apollo/hooks/compliance.mjs +0 -457
- package/data/harnesses/apollo/hooks/hooks.json +0 -23
- package/data/harnesses/apollo/hooks/lib/frozen.mjs +0 -58
- package/data/harnesses/apollo/hooks/lib/ownership.mjs +0 -242
- package/data/harnesses/apollo/hooks/lib/state.mjs +0 -170
- package/data/harnesses/apollo/rules/changelog.md +0 -27
- package/data/harnesses/apollo/rules/database.md +0 -34
- package/data/harnesses/apollo/rules/frontend.md +0 -30
- package/data/harnesses/apollo/rules/general.md +0 -40
- package/data/harnesses/apollo/rules/nomenclature.md +0 -30
- package/data/harnesses/apollo/rules/pnpm.md +0 -57
- package/data/harnesses/apollo/rules/testing.md +0 -64
- package/data/harnesses/apollo/skills/boundary/SKILL.md +0 -75
- package/data/harnesses/apollo/skills/boundary/references/index.md +0 -16
- package/data/harnesses/apollo/skills/boundary/references/mechanisms.md +0 -99
- package/data/harnesses/apollo/skills/boundary/references/packs/async-messaging.md +0 -81
- package/data/harnesses/apollo/skills/boundary/references/packs/auth.md +0 -93
- package/data/harnesses/apollo/skills/boundary/references/packs/build-config-runtime.md +0 -93
- package/data/harnesses/apollo/skills/boundary/references/packs/cloudflare.md +0 -93
- package/data/harnesses/apollo/skills/boundary/references/packs/frontend.md +0 -93
- package/data/harnesses/apollo/skills/boundary/references/packs/generic-tail.md +0 -55
- package/data/harnesses/apollo/skills/boundary/references/packs/persistence.md +0 -93
- package/data/harnesses/apollo/skills/boundary/references/packs/wire-api.md +0 -87
- package/data/harnesses/apollo/skills/brainstorm/README.md +0 -56
- package/data/harnesses/apollo/skills/brainstorm/SKILL.md +0 -173
- package/data/harnesses/apollo/skills/brainstorm/references/doctrine.md +0 -98
- package/data/harnesses/apollo/skills/brainstorm/references/harvest.md +0 -86
- package/data/harnesses/apollo/skills/brainstorm/references/session-schema.md +0 -186
- package/data/harnesses/apollo/skills/brainstorm/scripts/detector.mjs +0 -460
- package/data/harnesses/apollo/skills/design/SKILL.md +0 -121
- package/data/harnesses/apollo/skills/design/components/charts.md +0 -76
- package/data/harnesses/apollo/skills/design/components/collections.md +0 -132
- package/data/harnesses/apollo/skills/design/components/feedback.md +0 -47
- package/data/harnesses/apollo/skills/design/components/hooks.md +0 -35
- package/data/harnesses/apollo/skills/design/components/index.md +0 -143
- package/data/harnesses/apollo/skills/design/components/inputs.md +0 -327
- package/data/harnesses/apollo/skills/design/components/layout.md +0 -52
- package/data/harnesses/apollo/skills/design/components/media.md +0 -66
- package/data/harnesses/apollo/skills/design/components/motion.md +0 -82
- package/data/harnesses/apollo/skills/design/components/navigation.md +0 -29
- package/data/harnesses/apollo/skills/design/components/overlays.md +0 -72
- package/data/harnesses/apollo/skills/design/components/setup.md +0 -40
- package/data/harnesses/apollo/skills/design/components/typography.md +0 -46
- package/data/harnesses/apollo/skills/design/components/utils.md +0 -37
- package/data/harnesses/apollo/skills/design/data/blueprints/browse.md +0 -38
- package/data/harnesses/apollo/skills/design/data/blueprints/creation-flow.md +0 -38
- package/data/harnesses/apollo/skills/design/data/blueprints/dashboard.md +0 -38
- package/data/harnesses/apollo/skills/design/data/blueprints/grid.md +0 -38
- package/data/harnesses/apollo/skills/design/data/blueprints/queue.md +0 -38
- package/data/harnesses/apollo/skills/design/data/blueprints/record-detail.md +0 -38
- package/data/harnesses/apollo/skills/design/data/blueprints/settings.md +0 -38
- package/data/harnesses/apollo/skills/design/data/charts/census.csv +0 -58
- package/data/harnesses/apollo/skills/design/data/checklists/browse.md +0 -21
- package/data/harnesses/apollo/skills/design/data/checklists/creation-flow.md +0 -20
- package/data/harnesses/apollo/skills/design/data/checklists/dashboard.md +0 -21
- package/data/harnesses/apollo/skills/design/data/checklists/grid.md +0 -21
- package/data/harnesses/apollo/skills/design/data/checklists/queue.md +0 -21
- package/data/harnesses/apollo/skills/design/data/checklists/record-detail.md +0 -21
- package/data/harnesses/apollo/skills/design/data/checklists/settings.md +0 -21
- package/data/harnesses/apollo/skills/design/data/chrome/board-column.md +0 -68
- package/data/harnesses/apollo/skills/design/data/chrome/data-table.md +0 -63
- package/data/harnesses/apollo/skills/design/data/chrome/side-panel.md +0 -60
- package/data/harnesses/apollo/skills/design/data/chrome/sidebar.md +0 -78
- package/data/harnesses/apollo/skills/design/data/chrome/stat-cards.md +0 -62
- package/data/harnesses/apollo/skills/design/data/chrome/topbar.md +0 -65
- package/data/harnesses/apollo/skills/design/data/elevation/ant-design-directional-shadow.md +0 -19
- package/data/harnesses/apollo/skills/design/data/elevation/apple-liquid-glass-material.md +0 -18
- package/data/harnesses/apollo/skills/design/data/elevation/atlassian-surface-shadow-pairing.md +0 -20
- package/data/harnesses/apollo/skills/design/data/elevation/fluent-2-key-ambient-ramp.md +0 -20
- package/data/harnesses/apollo/skills/design/data/elevation/github-primer-sparing-shadow.md +0 -19
- package/data/harnesses/apollo/skills/design/data/elevation/ibm-carbon-layer-and-border.md +0 -19
- package/data/harnesses/apollo/skills/design/data/elevation/linear-dark-surface-ladder.md +0 -17
- package/data/harnesses/apollo/skills/design/data/elevation/material-2-key-ambient-dp-ramp.md +0 -19
- package/data/harnesses/apollo/skills/design/data/elevation/material-3-tonal-elevation.md +0 -21
- package/data/harnesses/apollo/skills/design/data/elevation/neobrutalist-hard-offset.md +0 -19
- package/data/harnesses/apollo/skills/design/data/elevation/neumorphic-soft-dual-shadow.md +0 -18
- package/data/harnesses/apollo/skills/design/data/elevation/notion-warm-minimal-hairline.md +0 -17
- package/data/harnesses/apollo/skills/design/data/elevation/polaris-elevation-inset-bevel.md +0 -21
- package/data/harnesses/apollo/skills/design/data/elevation/radix-themes-alpha-ring-scale.md +0 -19
- package/data/harnesses/apollo/skills/design/data/elevation/shadcn-neutral-derived-radius.md +0 -19
- package/data/harnesses/apollo/skills/design/data/elevation/stripe-brand-tinted-dual-shadow.md +0 -18
- package/data/harnesses/apollo/skills/design/data/elevation/tailwind-default-utility-scale.md +0 -19
- package/data/harnesses/apollo/skills/design/data/elevation/vercel-geist-shadow-as-border.md +0 -20
- package/data/harnesses/apollo/skills/design/data/iconography.md +0 -199
- package/data/harnesses/apollo/skills/design/data/motion/accordion-expand.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/destructive-hold-confirm.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/drag-dismiss-momentum.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/empty-state-arrive.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/hover-lift.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/modal-center-arrive.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/number-ticker-roll.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/onboarding-bounce.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/panel-slide.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/press-feedback.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/progress-linear-loop.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/route-crossfade.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/rubber-band-overscroll.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/skeleton-shimmer.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/stagger-arrive.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/toast-enter-stack.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/tooltip-chain-instant.md +0 -18
- package/data/harnesses/apollo/skills/design/data/motion/trigger-anchored-reveal.md +0 -18
- package/data/harnesses/apollo/skills/design/data/tells.csv +0 -34
- package/data/harnesses/apollo/skills/design/data/voices/bright-collaborator.md +0 -29
- package/data/harnesses/apollo/skills/design/data/voices/direct-operator.md +0 -29
- package/data/harnesses/apollo/skills/design/data/voices/earnest-helper.md +0 -29
- package/data/harnesses/apollo/skills/design/data/voices/editorial-composed.md +0 -29
- package/data/harnesses/apollo/skills/design/data/voices/quiet-concierge.md +0 -29
- package/data/harnesses/apollo/skills/design/data/voices/warm-guide.md +0 -29
- package/data/harnesses/apollo/skills/design/references/compose.md +0 -23
- package/data/harnesses/apollo/skills/design/references/content.md +0 -28
- package/data/harnesses/apollo/skills/design/references/emission.md +0 -21
- package/data/harnesses/apollo/skills/design/references/existing-apps.md +0 -27
- package/data/harnesses/apollo/skills/design/references/foundations.md +0 -57
- package/data/harnesses/apollo/skills/design/references/intake.md +0 -40
- package/data/harnesses/apollo/skills/design/references/object-pass.md +0 -24
- package/data/harnesses/apollo/skills/design/references/ship-gate.md +0 -29
- package/data/harnesses/apollo/skills/design/references/ux-patterns/Readme.md +0 -195
- package/data/harnesses/apollo/skills/design/references/ux-patterns/ai-layer.md +0 -49
- package/data/harnesses/apollo/skills/design/references/ux-patterns/attributes-lifecycle.md +0 -43
- package/data/harnesses/apollo/skills/design/references/ux-patterns/container-choice.md +0 -44
- package/data/harnesses/apollo/skills/design/references/ux-patterns/creation-paths.md +0 -46
- package/data/harnesses/apollo/skills/design/references/ux-patterns/dashboards.md +0 -42
- package/data/harnesses/apollo/skills/design/references/ux-patterns/modes.md +0 -42
- package/data/harnesses/apollo/skills/design/references/ux-patterns/object-model.md +0 -46
- package/data/harnesses/apollo/skills/design/references/ux-patterns/page-archetype.md +0 -48
- package/data/harnesses/apollo/skills/design/references/ux-patterns/promotion-demotion.md +0 -37
- package/data/harnesses/apollo/skills/design/references/ux-patterns/settings.md +0 -42
- package/data/harnesses/apollo/skills/design/references/ux-patterns/standard-pages.md +0 -46
- package/data/harnesses/apollo/skills/design/references/ux-patterns/state-matrix.md +0 -41
- package/data/harnesses/apollo/skills/design/references/ux-patterns/table-mechanics.md +0 -48
- package/data/harnesses/apollo/skills/design/references/ux-patterns/verbs.md +0 -45
- package/data/harnesses/apollo/skills/design/references/ux-patterns/viewer-context.md +0 -40
- package/data/harnesses/apollo/skills/design/scripts/README-validator.md +0 -27
- package/data/harnesses/apollo/skills/design/scripts/select-chart.mjs +0 -622
- package/data/harnesses/apollo/skills/design/scripts/validate-design-md.mjs +0 -1198
- package/data/harnesses/apollo/skills/documentation/SKILL.md +0 -135
- package/data/harnesses/apollo/skills/documentation/references/examples.md +0 -279
- package/data/harnesses/apollo/skills/documentation/references/frontmatter.md +0 -44
- package/data/harnesses/apollo/skills/documentation/references/instruction-style.md +0 -50
- package/data/harnesses/apollo/skills/documentation/references/style-guide.md +0 -384
- package/data/harnesses/apollo/skills/documentation/references/untemplated.md +0 -38
- package/data/harnesses/apollo/skills/documentation/scripts/doc-lint.mjs +0 -140
- package/data/harnesses/apollo/skills/documentation/templates/Readme.md +0 -35
- package/data/harnesses/apollo/skills/documentation/templates/api-reference.md +0 -20
- package/data/harnesses/apollo/skills/documentation/templates/architecture-decision.md +0 -121
- package/data/harnesses/apollo/skills/documentation/templates/backlog.md +0 -33
- package/data/harnesses/apollo/skills/documentation/templates/behavioral-checklist.md +0 -81
- package/data/harnesses/apollo/skills/documentation/templates/bookkeeping.md +0 -18
- package/data/harnesses/apollo/skills/documentation/templates/changelog-entry.md +0 -57
- package/data/harnesses/apollo/skills/documentation/templates/changelog-index.md +0 -41
- package/data/harnesses/apollo/skills/documentation/templates/docs-readme.md +0 -72
- package/data/harnesses/apollo/skills/documentation/templates/domain-model.md +0 -30
- package/data/harnesses/apollo/skills/documentation/templates/environment-registry.md +0 -15
- package/data/harnesses/apollo/skills/documentation/templates/feature.md +0 -150
- package/data/harnesses/apollo/skills/documentation/templates/glossary.md +0 -11
- package/data/harnesses/apollo/skills/documentation/templates/implementation-map.md +0 -42
- package/data/harnesses/apollo/skills/documentation/templates/journal.md +0 -13
- package/data/harnesses/apollo/skills/documentation/templates/memory-main.md +0 -25
- package/data/harnesses/apollo/skills/documentation/templates/migration-runbook.md +0 -34
- package/data/harnesses/apollo/skills/documentation/templates/non-goals.md +0 -11
- package/data/harnesses/apollo/skills/documentation/templates/operations-procedure.md +0 -36
- package/data/harnesses/apollo/skills/documentation/templates/plan.md +0 -88
- package/data/harnesses/apollo/skills/documentation/templates/protected-files.md +0 -37
- package/data/harnesses/apollo/skills/documentation/templates/root-readme.md +0 -104
- package/data/harnesses/apollo/skills/documentation/templates/security-overview.md +0 -27
- package/data/harnesses/apollo/skills/documentation/templates/structure.md +0 -65
- package/data/harnesses/apollo/skills/documentation/templates/system-overview.md +0 -32
- package/data/harnesses/apollo/skills/documentation/templates/testing-strategy.md +0 -29
- package/data/harnesses/apollo/skills/schema-design/SKILL.md +0 -141
- package/data/harnesses/apollo/skills/test/SKILL.md +0 -104
- package/data/harnesses/apollo/skills/testing/SKILL.md +0 -166
- package/data/harnesses/apollo/skills/workflow-mapper/SKILL.md +0 -451
- package/data/harnesses/apollo/skills/workflow-mapper/references/patterns.md +0 -214
- /package/data/harnesses/{apollo → code}/docs/README.md +0 -0
- /package/data/harnesses/{apollo → code}/docs/history/CHANGELOG.md +0 -0
- /package/data/harnesses/{apollo → code}/docs/history/backlog.md +0 -0
- /package/data/harnesses/{apollo → code}/docs/memory/main.md +0 -0
- /package/data/harnesses/{apollo → code}/docs/structure.md +0 -0
- /package/data/harnesses/{apollo → code}/rules/cloudflare.md +0 -0
|
@@ -1,141 +0,0 @@
|
|
|
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.
|
|
@@ -1,104 +0,0 @@
|
|
|
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,166 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: testing
|
|
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
|
-
---
|
|
5
|
-
|
|
6
|
-
# Testing — Ledger, Layers, Gate
|
|
7
|
-
|
|
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
|
-
|
|
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
|
|
49
|
-
|
|
50
|
-
| # | Behavior (LLD promise) | Layer | Target file | Oracle | Fixtures / faults | Outcome |
|
|
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. **`Outcome` is written empty here — it is gate mode's cell**, and the completion walls read it: a plan cannot reach `Status: Complete` while any row's Outcome is empty.
|
|
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
|
|
67
|
-
|
|
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.
|
|
69
|
-
|
|
70
|
-
| # | Layer | Owns (defects only it can originate) | Oracle | Technology class |
|
|
71
|
-
| --- | --- | --- | --- | --- |
|
|
72
|
-
| 1 | **Persistence schema** | Invariants the storage engine enforces against any code: uniqueness, checks, cascades, append-only triggers | The engine's refusal of a forbidden write | The real engine, in-memory — never a mock of storage you own |
|
|
73
|
-
| 2 | **Domain** | Business rules and state transitions, via direct calls | Resulting state in real storage | Real datastore + modeled fakes for external ports |
|
|
74
|
-
| 3 | **Service contract** | The published wire: authn/authz at the door, envelope shape, status mapping, field projection, stream/event grammar. For a CLI this is argv/stdout/exit codes; for a library, its exported API | Requests against the real app instance, in-process | `supertest` / `SELF.fetch` class — never a spawned server, never naked handler functions |
|
|
75
|
-
| 4 | **Contract artifact** | Nothing behavioral. Typed fixtures / interception handlers **compiled from the provider's own types**, exported for every consumer-side test | The typechecker — drift from the provider is a compile error | No runner; a module the provider owns |
|
|
76
|
-
| 5 | **Client data** | The consumer's half of the wire: error taxonomy, parsing, byte fidelity, stream reassembly, retry/abort | Intercepted network serving layer-4 artifacts; no DOM | `msw`-class interception |
|
|
77
|
-
| 6 | **View logic** | Pure presentation derivations: grouping, ordering, status vocabularies, formatting | Table-driven equality | Plain runner |
|
|
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 |
|
|
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 |
|
|
80
|
-
|
|
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.
|
|
82
|
-
|
|
83
|
-
### How many layers — derived, never chosen
|
|
84
|
-
|
|
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.
|
|
86
|
-
|
|
87
|
-
| Application shape | Strata present |
|
|
88
|
-
| --- | --- |
|
|
89
|
-
| Full-stack web app | all eight |
|
|
90
|
-
| Backend-only service | 1, 2, 3, 8 |
|
|
91
|
-
| CLI | 2, 3 (the CLI's argv/stdout contract), 8 |
|
|
92
|
-
| Frontend on a third-party API | 4 (derived from the API's published spec), 5, 6, 7, 8 |
|
|
93
|
-
| Library | 2 only — its exported API is the contract, its tests are its consumers |
|
|
94
|
-
|
|
95
|
-
Derive the stack first, then place tests: a placement argument that starts "this feels like an integration test" has skipped the derivation.
|
|
96
|
-
|
|
97
|
-
### Placement discipline
|
|
98
|
-
|
|
99
|
-
- **An assertion provable at two layers is misplaced — it belongs at the lower one.** The duplication check is mechanical, not judgment.
|
|
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.
|
|
104
|
-
|
|
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.
|
|
106
|
-
|
|
107
|
-
### The tree
|
|
108
|
-
|
|
109
|
-
```
|
|
110
|
-
tests/
|
|
111
|
-
schema/ L1 — infrastructure-bound runner (real engine)
|
|
112
|
-
domain/ L2 — infrastructure-bound runner (real storage, modeled ports)
|
|
113
|
-
routes/ L3 — infrastructure-bound runner (in-process app)
|
|
114
|
-
contract/ L4 — no runner; typed fixtures + interception handlers
|
|
115
|
-
client/ L5 — plain unit runner
|
|
116
|
-
view/ L6 — plain unit runner
|
|
117
|
-
component/ L7 — unit runner + DOM environment (per-file opt-in)
|
|
118
|
-
helpers/ shared seeds, fakes, and builders — owned by no stratum
|
|
119
|
-
e2e/ L8 — its own config, never mixed into the unit runner's glob
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
- **A stratum the app lacks gets no directory** — an empty `component/` in a backend service is a lie about the app's shape.
|
|
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.
|
|
126
|
-
|
|
127
|
-
### Bridge to the obligation ledger
|
|
128
|
-
|
|
129
|
-
The ledger's altitudes are coarse names for where a test *runs*; the strata say what it may *assert*:
|
|
130
|
-
|
|
131
|
-
| Ledger altitude | Strata it covers |
|
|
132
|
-
| --- | --- |
|
|
133
|
-
| unit | 2 (pure domain), 5, 6 |
|
|
134
|
-
| integration | 1, 2 (stateful), 3 |
|
|
135
|
-
| component | 7 |
|
|
136
|
-
| e2e | 8 |
|
|
137
|
-
|
|
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.
|
|
152
|
-
|
|
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:
|
|
156
|
-
|
|
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 |
|
|
162
|
-
|
|
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). **Open by re-reading the ledger from the plan file on disk, never from conversation memory** — a context-held copy is how rows get missed. **Write each row's outcome into its `Outcome` cell in the plan file** — exactly `satisfied`, `owes nothing — {reason}`, or `blocked → backlog` — because the Stop wall and CI read that cell, and a row left unexamined blocks completion.
|
|
166
|
-
6. **Report results honestly, including failures**; record which test now guards each checklist section (`Pinned:` traces) and each section's `AC:` line where the repo carries a keyed acceptance-criteria source, closing with the coverage count — `AC coverage: {n}/{m}; uncovered: {keys} → backlog`. Completion steps — freezing the plan, the changelog entry — are the root policy file's, not yours.
|