@nurix/etna 0.20.35 → 0.20.37

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 (233) hide show
  1. package/data/catalog/rules/changelog.md +1 -1
  2. package/data/catalog/skills/nustack/SKILL.md +2 -2
  3. package/data/catalog/skills/nustack/references/cli-design.md +35 -35
  4. package/data/catalog/skills/nustack/references/monorepo-master-list.md +6 -6
  5. package/data/catalog/skills/nustack/references/monorepo-setup.md +13 -13
  6. package/data/catalog/skills/nustack/references/publishing.md +40 -39
  7. package/data/harness-index.json +1 -1
  8. package/data/harnesses/code/CLAUDE.md +4 -4
  9. package/data/harnesses/code/agents/brainstorm.md +2 -2
  10. package/data/harnesses/code/agents/lld-mode.md +2 -2
  11. package/data/harnesses/code/agents/maintenance.md +17 -17
  12. package/data/harnesses/code/codex/agents/brainstorm.toml +6 -2
  13. package/data/harnesses/code/codex/agents/lld-mode.toml +2 -2
  14. package/data/harnesses/code/codex/agents/maintenance.toml +18 -17
  15. package/data/harnesses/code/docs/README.md +9 -17
  16. package/data/harnesses/code/docs/history/CHANGELOG.md +19 -18
  17. package/data/harnesses/code/docs/structure.md +60 -45
  18. package/data/harnesses/code/harness.json +2 -2
  19. package/data/harnesses/code/hooks/compliance.mjs +8 -2
  20. package/data/harnesses/code/hooks/lib/sweep.mjs +3 -2
  21. package/data/harnesses/code/rules/changelog.md +1 -1
  22. package/data/harnesses/code/skills/boundary/SKILL.md +33 -22
  23. package/data/harnesses/code/skills/boundary/references/index.md +2 -4
  24. package/data/harnesses/code/skills/boundary/references/mechanisms.md +3 -10
  25. package/data/harnesses/code/skills/boundary/references/packs/frontend.md +17 -17
  26. package/data/harnesses/code/skills/boundary/references/packs/generic-tail.md +1 -1
  27. package/data/harnesses/code/skills/brainstorm/README.md +11 -13
  28. package/data/harnesses/code/skills/brainstorm/SKILL.md +149 -130
  29. package/data/harnesses/code/skills/brainstorm/references/doctrine.md +48 -51
  30. package/data/harnesses/code/skills/brainstorm/references/harvest.md +51 -57
  31. package/data/harnesses/code/skills/brainstorm/references/session-schema.md +48 -42
  32. package/data/harnesses/code/skills/design/SKILL.md +31 -35
  33. package/data/harnesses/code/skills/design/data/iconography.md +2 -2
  34. package/data/harnesses/code/skills/design/references/compose.md +14 -16
  35. package/data/harnesses/code/skills/design/references/foundations.md +5 -5
  36. package/data/harnesses/code/skills/design/references/intake.md +6 -6
  37. package/data/harnesses/code/skills/design/references/object-pass.md +1 -1
  38. package/data/harnesses/code/skills/design/references/ship-gate.md +5 -5
  39. package/data/harnesses/code/skills/design/references/ux-patterns/Readme.md +4 -4
  40. package/data/harnesses/code/skills/design/references/ux-patterns/ai-layer.md +1 -1
  41. package/data/harnesses/code/skills/design/references/ux-patterns/container-choice.md +1 -1
  42. package/data/harnesses/code/skills/design/references/ux-patterns/dashboards.md +1 -1
  43. package/data/harnesses/code/skills/design/references/ux-patterns/modes.md +1 -1
  44. package/data/harnesses/code/skills/design/references/ux-patterns/object-model.md +2 -2
  45. package/data/harnesses/code/skills/design/references/ux-patterns/promotion-demotion.md +1 -1
  46. package/data/harnesses/code/skills/design/references/ux-patterns/settings.md +1 -1
  47. package/data/harnesses/code/skills/design/references/ux-patterns/standard-pages.md +1 -1
  48. package/data/harnesses/code/skills/documentation/SKILL.md +92 -143
  49. package/data/harnesses/code/skills/documentation/references/examples.md +45 -72
  50. package/data/harnesses/code/skills/documentation/references/frontmatter.md +8 -8
  51. package/data/harnesses/code/skills/documentation/references/instruction-style.md +3 -3
  52. package/data/harnesses/code/skills/documentation/references/source-comments.md +19 -0
  53. package/data/harnesses/code/skills/documentation/references/style-guide.md +25 -11
  54. package/data/harnesses/code/skills/documentation/references/untemplated.md +11 -10
  55. package/data/harnesses/code/skills/documentation/scripts/doc-lint.mjs +181 -40
  56. package/data/harnesses/code/skills/documentation/templates/Readme.md +68 -41
  57. package/data/harnesses/code/skills/documentation/templates/acceptance-criteria.md +11 -19
  58. package/data/harnesses/code/skills/documentation/templates/api-reference.md +8 -3
  59. package/data/harnesses/code/skills/documentation/templates/architecture-decision.md +8 -12
  60. package/data/harnesses/code/skills/documentation/templates/backlog.md +2 -2
  61. package/data/harnesses/code/skills/documentation/templates/behavioral-checklist.md +10 -17
  62. package/data/harnesses/code/skills/documentation/templates/bookkeeping.md +2 -2
  63. package/data/harnesses/code/skills/documentation/templates/changelog-entry.md +2 -2
  64. package/data/harnesses/code/skills/documentation/templates/changelog-index.md +21 -29
  65. package/data/harnesses/code/skills/documentation/templates/docs-readme.md +4 -4
  66. package/data/harnesses/code/skills/documentation/templates/doctrine.md +3 -3
  67. package/data/harnesses/code/skills/documentation/templates/domain-model.md +10 -5
  68. package/data/harnesses/code/skills/documentation/templates/environment-registry.md +7 -2
  69. package/data/harnesses/code/skills/documentation/templates/feature.md +6 -6
  70. package/data/harnesses/code/skills/documentation/templates/glossary.md +6 -1
  71. package/data/harnesses/code/skills/documentation/templates/handover.md +17 -17
  72. package/data/harnesses/code/skills/documentation/templates/implementation-map.md +10 -5
  73. package/data/harnesses/code/skills/documentation/templates/journal.md +2 -2
  74. package/data/harnesses/code/skills/documentation/templates/memory-main.md +2 -2
  75. package/data/harnesses/code/skills/documentation/templates/migration-runbook.md +1 -1
  76. package/data/harnesses/code/skills/documentation/templates/non-goals.md +8 -3
  77. package/data/harnesses/code/skills/documentation/templates/operations-procedure.md +8 -3
  78. package/data/harnesses/code/skills/documentation/templates/package.md +5 -9
  79. package/data/harnesses/code/skills/documentation/templates/plan.md +5 -11
  80. package/data/harnesses/code/skills/documentation/templates/protected-files.md +7 -2
  81. package/data/harnesses/code/skills/documentation/templates/root-readme.md +5 -5
  82. package/data/harnesses/code/skills/documentation/templates/security-overview.md +9 -4
  83. package/data/harnesses/code/skills/documentation/templates/setup.md +6 -6
  84. package/data/harnesses/code/skills/documentation/templates/system-overview.md +12 -7
  85. package/data/harnesses/code/skills/documentation/templates/system.md +2 -2
  86. package/data/harnesses/code/skills/documentation/templates/testing-strategy.md +10 -5
  87. package/data/harnesses/code/skills/documentation/templates/ux-behavior.md +14 -5
  88. package/data/harnesses/code/skills/issue-bridge/SKILL.md +51 -16
  89. package/data/harnesses/code/skills/issue-bridge/references/trackers/github.md +5 -5
  90. package/data/harnesses/code/skills/issue-bridge/references/trackers/jira.md +11 -8
  91. package/data/harnesses/code/skills/issue-bridge/references/trackers/linear.md +16 -11
  92. package/data/harnesses/code/skills/nustack-graph/SKILL.md +38 -29
  93. package/data/harnesses/code/skills/polygraph/references/dispatch.md +31 -18
  94. package/data/harnesses/code/skills/polygraph/references/rows.md +11 -6
  95. package/data/harnesses/code/skills/schema-design/SKILL.md +39 -41
  96. package/data/harnesses/code/skills/test/SKILL.md +38 -42
  97. package/data/harnesses/code/skills/testing/SKILL.md +83 -73
  98. package/data/harnesses/code/skills/workflow-mapper/SKILL.md +156 -193
  99. package/data/harnesses/code/skills/workflow-mapper/references/patterns.md +15 -15
  100. package/data/harnesses/delivery/CLAUDE.md +1 -1
  101. package/data/harnesses/delivery/agents/brainstorm.md +2 -2
  102. package/data/harnesses/delivery/agents/lld-mode.md +2 -2
  103. package/data/harnesses/delivery/agents/maintenance.md +17 -17
  104. package/data/harnesses/delivery/codex/agents/brainstorm.toml +6 -2
  105. package/data/harnesses/delivery/codex/agents/lld-mode.toml +2 -2
  106. package/data/harnesses/delivery/codex/agents/maintenance.toml +18 -17
  107. package/data/harnesses/delivery/harness.json +2 -2
  108. package/data/harnesses/delivery/rules/changelog.md +1 -1
  109. package/data/harnesses/delivery/skills/brainstorm/README.md +11 -13
  110. package/data/harnesses/delivery/skills/brainstorm/SKILL.md +149 -130
  111. package/data/harnesses/delivery/skills/brainstorm/references/doctrine.md +48 -51
  112. package/data/harnesses/delivery/skills/brainstorm/references/harvest.md +51 -57
  113. package/data/harnesses/delivery/skills/brainstorm/references/session-schema.md +48 -42
  114. package/data/harnesses/delivery/skills/cost-report/SKILL.md +10 -10
  115. package/data/harnesses/delivery/skills/documentation/SKILL.md +92 -143
  116. package/data/harnesses/delivery/skills/documentation/references/examples.md +45 -72
  117. package/data/harnesses/delivery/skills/documentation/references/frontmatter.md +8 -8
  118. package/data/harnesses/delivery/skills/documentation/references/instruction-style.md +3 -3
  119. package/data/harnesses/delivery/skills/documentation/references/source-comments.md +19 -0
  120. package/data/harnesses/delivery/skills/documentation/references/style-guide.md +25 -11
  121. package/data/harnesses/delivery/skills/documentation/references/untemplated.md +11 -10
  122. package/data/harnesses/delivery/skills/documentation/scripts/doc-lint.mjs +181 -40
  123. package/data/harnesses/delivery/skills/documentation/templates/Readme.md +68 -41
  124. package/data/harnesses/delivery/skills/documentation/templates/acceptance-criteria.md +11 -19
  125. package/data/harnesses/delivery/skills/documentation/templates/api-reference.md +8 -3
  126. package/data/harnesses/delivery/skills/documentation/templates/architecture-decision.md +8 -12
  127. package/data/harnesses/delivery/skills/documentation/templates/backlog.md +2 -2
  128. package/data/harnesses/delivery/skills/documentation/templates/behavioral-checklist.md +10 -17
  129. package/data/harnesses/delivery/skills/documentation/templates/bookkeeping.md +2 -2
  130. package/data/harnesses/delivery/skills/documentation/templates/changelog-entry.md +2 -2
  131. package/data/harnesses/delivery/skills/documentation/templates/changelog-index.md +21 -29
  132. package/data/harnesses/delivery/skills/documentation/templates/docs-readme.md +4 -4
  133. package/data/harnesses/delivery/skills/documentation/templates/doctrine.md +3 -3
  134. package/data/harnesses/delivery/skills/documentation/templates/domain-model.md +10 -5
  135. package/data/harnesses/delivery/skills/documentation/templates/environment-registry.md +7 -2
  136. package/data/harnesses/delivery/skills/documentation/templates/feature.md +6 -6
  137. package/data/harnesses/delivery/skills/documentation/templates/glossary.md +6 -1
  138. package/data/harnesses/delivery/skills/documentation/templates/handover.md +17 -17
  139. package/data/harnesses/delivery/skills/documentation/templates/implementation-map.md +10 -5
  140. package/data/harnesses/delivery/skills/documentation/templates/journal.md +2 -2
  141. package/data/harnesses/delivery/skills/documentation/templates/memory-main.md +2 -2
  142. package/data/harnesses/delivery/skills/documentation/templates/migration-runbook.md +1 -1
  143. package/data/harnesses/delivery/skills/documentation/templates/non-goals.md +8 -3
  144. package/data/harnesses/delivery/skills/documentation/templates/operations-procedure.md +8 -3
  145. package/data/harnesses/delivery/skills/documentation/templates/package.md +5 -9
  146. package/data/harnesses/delivery/skills/documentation/templates/plan.md +5 -11
  147. package/data/harnesses/delivery/skills/documentation/templates/protected-files.md +7 -2
  148. package/data/harnesses/delivery/skills/documentation/templates/root-readme.md +5 -5
  149. package/data/harnesses/delivery/skills/documentation/templates/security-overview.md +9 -4
  150. package/data/harnesses/delivery/skills/documentation/templates/setup.md +6 -6
  151. package/data/harnesses/delivery/skills/documentation/templates/system-overview.md +12 -7
  152. package/data/harnesses/delivery/skills/documentation/templates/system.md +2 -2
  153. package/data/harnesses/delivery/skills/documentation/templates/testing-strategy.md +10 -5
  154. package/data/harnesses/delivery/skills/documentation/templates/ux-behavior.md +14 -5
  155. package/data/harnesses/delivery/skills/escalate/SKILL.md +11 -11
  156. package/data/harnesses/delivery/skills/prompt-eval/SKILL.md +8 -8
  157. package/data/harnesses/delivery/skills/prompt-library/SKILL.md +30 -9
  158. package/data/harnesses/delivery/skills/route/SKILL.md +8 -8
  159. package/data/harnesses/etna/CLAUDE.md +2 -2
  160. package/data/harnesses/etna/agents/brainstorm.md +2 -2
  161. package/data/harnesses/etna/agents/lld-mode.md +2 -2
  162. package/data/harnesses/etna/agents/maintenance.md +17 -17
  163. package/data/harnesses/etna/codex/agents/brainstorm.toml +6 -2
  164. package/data/harnesses/etna/codex/agents/lld-mode.toml +2 -2
  165. package/data/harnesses/etna/codex/agents/maintenance.toml +18 -17
  166. package/data/harnesses/etna/harness.json +2 -2
  167. package/data/harnesses/etna/rules/changelog.md +1 -1
  168. package/data/harnesses/etna/skills/boundary/SKILL.md +33 -22
  169. package/data/harnesses/etna/skills/boundary/references/index.md +2 -4
  170. package/data/harnesses/etna/skills/boundary/references/mechanisms.md +3 -10
  171. package/data/harnesses/etna/skills/boundary/references/packs/frontend.md +17 -17
  172. package/data/harnesses/etna/skills/boundary/references/packs/generic-tail.md +1 -1
  173. package/data/harnesses/etna/skills/brainstorm/README.md +11 -13
  174. package/data/harnesses/etna/skills/brainstorm/SKILL.md +149 -130
  175. package/data/harnesses/etna/skills/brainstorm/references/doctrine.md +48 -51
  176. package/data/harnesses/etna/skills/brainstorm/references/harvest.md +51 -57
  177. package/data/harnesses/etna/skills/brainstorm/references/session-schema.md +48 -42
  178. package/data/harnesses/etna/skills/documentation/SKILL.md +92 -143
  179. package/data/harnesses/etna/skills/documentation/references/examples.md +45 -72
  180. package/data/harnesses/etna/skills/documentation/references/frontmatter.md +8 -8
  181. package/data/harnesses/etna/skills/documentation/references/instruction-style.md +3 -3
  182. package/data/harnesses/etna/skills/documentation/references/source-comments.md +19 -0
  183. package/data/harnesses/etna/skills/documentation/references/style-guide.md +25 -11
  184. package/data/harnesses/etna/skills/documentation/references/untemplated.md +11 -10
  185. package/data/harnesses/etna/skills/documentation/scripts/doc-lint.mjs +181 -40
  186. package/data/harnesses/etna/skills/documentation/templates/Readme.md +68 -41
  187. package/data/harnesses/etna/skills/documentation/templates/acceptance-criteria.md +11 -19
  188. package/data/harnesses/etna/skills/documentation/templates/api-reference.md +8 -3
  189. package/data/harnesses/etna/skills/documentation/templates/architecture-decision.md +8 -12
  190. package/data/harnesses/etna/skills/documentation/templates/backlog.md +2 -2
  191. package/data/harnesses/etna/skills/documentation/templates/behavioral-checklist.md +10 -17
  192. package/data/harnesses/etna/skills/documentation/templates/bookkeeping.md +2 -2
  193. package/data/harnesses/etna/skills/documentation/templates/changelog-entry.md +2 -2
  194. package/data/harnesses/etna/skills/documentation/templates/changelog-index.md +21 -29
  195. package/data/harnesses/etna/skills/documentation/templates/docs-readme.md +4 -4
  196. package/data/harnesses/etna/skills/documentation/templates/doctrine.md +3 -3
  197. package/data/harnesses/etna/skills/documentation/templates/domain-model.md +10 -5
  198. package/data/harnesses/etna/skills/documentation/templates/environment-registry.md +7 -2
  199. package/data/harnesses/etna/skills/documentation/templates/feature.md +6 -6
  200. package/data/harnesses/etna/skills/documentation/templates/glossary.md +6 -1
  201. package/data/harnesses/etna/skills/documentation/templates/handover.md +17 -17
  202. package/data/harnesses/etna/skills/documentation/templates/implementation-map.md +10 -5
  203. package/data/harnesses/etna/skills/documentation/templates/journal.md +2 -2
  204. package/data/harnesses/etna/skills/documentation/templates/memory-main.md +2 -2
  205. package/data/harnesses/etna/skills/documentation/templates/migration-runbook.md +1 -1
  206. package/data/harnesses/etna/skills/documentation/templates/non-goals.md +8 -3
  207. package/data/harnesses/etna/skills/documentation/templates/operations-procedure.md +8 -3
  208. package/data/harnesses/etna/skills/documentation/templates/package.md +5 -9
  209. package/data/harnesses/etna/skills/documentation/templates/plan.md +5 -11
  210. package/data/harnesses/etna/skills/documentation/templates/protected-files.md +7 -2
  211. package/data/harnesses/etna/skills/documentation/templates/root-readme.md +5 -5
  212. package/data/harnesses/etna/skills/documentation/templates/security-overview.md +9 -4
  213. package/data/harnesses/etna/skills/documentation/templates/setup.md +6 -6
  214. package/data/harnesses/etna/skills/documentation/templates/system-overview.md +12 -7
  215. package/data/harnesses/etna/skills/documentation/templates/system.md +2 -2
  216. package/data/harnesses/etna/skills/documentation/templates/testing-strategy.md +10 -5
  217. package/data/harnesses/etna/skills/documentation/templates/ux-behavior.md +14 -5
  218. package/data/harnesses/etna/skills/workflow-mapper/SKILL.md +156 -193
  219. package/data/harnesses/etna/skills/workflow-mapper/references/patterns.md +15 -15
  220. package/data/harnesses/hr/harness.json +1 -1
  221. package/data/harnesses/hr/skills/evaluator/SKILL.md +71 -73
  222. package/data/harnesses/hr/skills/evaluator/criteria/code-quality.md +28 -47
  223. package/data/harnesses/hr/skills/evaluator/criteria/deployment-readiness.md +33 -43
  224. package/data/harnesses/hr/skills/evaluator/criteria/maintainability.md +34 -46
  225. package/data/harnesses/hr/skills/evaluator/criteria/mechanical-ambition.md +13 -13
  226. package/data/harnesses/hr/skills/evaluator/criteria/problem-substantiveness.md +7 -7
  227. package/data/harnesses/hr/skills/evaluator/criteria/testing.md +36 -41
  228. package/data/harnesses/hr/skills/evaluator/criteria/ux-mechanics.md +25 -11
  229. package/data/harnesses/hr/skills/evaluator/criteria/value-articulation.md +7 -7
  230. package/data/harnesses/hr/skills/evaluator/preflight.md +27 -46
  231. package/data/harnesses/hr/skills/interview-eval/SKILL.md +37 -42
  232. package/data/harnesses/hr/skills/interview-eval/rubric.md +21 -23
  233. package/package.json +2 -2
@@ -1,20 +1,23 @@
1
1
  # Linear — connector pack
2
2
 
3
- Access options in preference order. Probe each in order; use the first that answers. Research of record: `discussions/code-analysis-tools/research-tracker-connectors/03-linear.md` (2026-08-19).
3
+ Access options in preference order. Probe each in order; use the first that answers.
4
4
 
5
5
  ## Option 1 — the official Linear MCP server
6
6
 
7
7
  Probe: a `linear`/`linear-server` entry in `.mcp.json` / Linear MCP tools visible in-session.
8
- Setup: `claude mcp add --transport http linear-server https://mcp.linear.app/mcp`, then `/mcp` for OAuth 2.1. A **read-only endpoint exists**: `https://mcp.linear.app/mcp/readonly` — prefer it for triage-only sessions. Direct bearer also works (`Authorization: Bearer <token>` — OAuth token or a Linear personal API key; a Read-only API key gives enforced read-only).
9
- **Tool names are unpublished by Linear** — introspect the live tool list before planning calls; expect find/create/update tools over issues, projects, comments.
8
+ Setup: `claude mcp add --transport http linear-server https://mcp.linear.app/mcp`, then `/mcp` for OAuth 2.1. A **read-only endpoint exists**: `https://mcp.linear.app/mcp/readonly` — prefer it for triage-only sessions. Direct bearer also works (`Authorization: Bearer <token>` — an OAuth token or a Linear personal API key; a Read-only API key enforces read-only).
9
+ **Linear publishes no tool names** — introspect the live tool list before planning calls; expect find/create/update tools over issues, projects, comments.
10
10
 
11
11
  ## Option 2 — community MCP servers
12
12
 
13
- `tacticlaunch/mcp-linear` (146★, MIT — the exhaustive option, 150+ `linear_*` tools, TOOLS.md published) or `prime-radiant-inc/streamlinear` (89★, MIT — the token-efficient option: one dispatch tool `action: search|get|update|comment|create|graphql|help`, ~500 schema tokens; auth `LINEAR_API_TOKEN`).
13
+ | Server | Shape | Pick when |
14
+ | --- | --- | --- |
15
+ | `tacticlaunch/mcp-linear` (146★, MIT) | 150+ `linear_*` tools, TOOLS.md published | You need exhaustive coverage |
16
+ | `prime-radiant-inc/streamlinear` (89★, MIT) | One dispatch tool, `action: search\|get\|update\|comment\|create\|graphql\|help`, ~500 schema tokens; auth `LINEAR_API_TOKEN` | You want token efficiency |
14
17
 
15
18
  ## Option 3 — CLI
16
19
 
17
- `@schpet/linear-cli` (binary `linear`, 911★, actively maintained; Linear's own npm CLIs are abandoned — do not use `@linear/cli`). Probe: `linear --version`. Install: `brew install schpet/tap/linear`. Branch-aware issue detection, list/start/create, PR text generation.
20
+ `@schpet/linear-cli` (binary `linear`, 911★, actively maintained). **Never `@linear/cli`** — Linear's own npm CLIs are abandoned. Probe: `linear --version`. Install: `brew install schpet/tap/linear`. Branch-aware issue detection, list/start/create, PR text generation.
18
21
 
19
22
  ## Option 4 — GraphQL fallback
20
23
 
@@ -23,15 +26,17 @@ Endpoint `https://api.linear.app/graphql`. **Auth header for a personal API key
23
26
  - Search: `{ issues(first: 25, filter: {title: {contains: "…"}}) { edges { node { id identifier title } } pageInfo { hasNextPage endCursor } } }` (Relay cursors; also `issueSearch`/`searchIssues`)
24
27
  - Create (approval-gated): `mutation { issueCreate(input: {title: "…", teamId: "<uuid>", description: "…", labelIds: […]}) { success issue { id identifier url } } }`
25
28
  - Comment (approval-gated): `mutation { commentCreate(input: {issueId: "<uuid>", body: "…"}) { success comment { id } } }`
26
- Rate limits: API key 2,500 req/hr + complexity budget (single request cap 10,000 points — keep `first:` small on nested connections); watch `X-RateLimit-*`/`X-Complexity` headers.
29
+
30
+ Rate limits: API key 2,500 req/hr plus a complexity budget (single request cap 10,000 points — keep `first:` small on nested connections); watch the `X-RateLimit-*` / `X-Complexity` headers.
27
31
 
28
32
  ## Composing for Linear — including Linear's own agent rules
29
33
 
30
- Linear publishes **Agent Interaction Guidelines** (linear.app/developers/aig); this pack adopts the two that bind writers even outside formal Agent Sessions:
34
+ Linear's **Agent Interaction Guidelines** (linear.app/developers/aig) carry two rules that bind a writer even outside formal Agent Sessions:
31
35
  - **You are a `delegate`, never an `assignee`** — humans keep ownership of issues you touch.
32
- - **Never move work to a terminal state** — done/canceled transitions belong to humans (the same rule the linear-use community plugin hard-codes). Starting work maps to the team's lowest-position "started" state, and only when instructed.
33
- Body is native markdown. Pick team and project explicitly — never guess a team. Priority conservatively (default: none). Cycles and estimates stay human.
36
+ - **Never move work to a terminal state** — done/canceled transitions belong to humans. Starting work maps to the team's lowest-position "started" state, and only when instructed.
37
+
38
+ Body is native markdown. Pick team and project explicitly — **never** guess a team. Set priority conservatively (default: none). Cycles and estimates stay human.
34
39
 
35
- ## Ecosystem notes (2026-08-19)
40
+ ## Ecosystem notes
36
41
 
37
- The strongest community skill (`wrsmith108/linear-claude-skill`, 121★) itself recommends the official MCP server — aligned with this pack's ordering. No official Anthropic Linear skill exists.
42
+ No official Anthropic Linear skill exists. The strongest community skill (`wrsmith108/linear-claude-skill`, 121★) itself recommends the official MCP server, matching this pack's Option 1.
@@ -6,59 +6,68 @@ description: Query the code graph before reading source. An LLM-first codebase i
6
6
 
7
7
  # nustack graph — the index-first method
8
8
 
9
- **If the tool is absent** (`nustack --version` fails): do not stall. Read `.claude/state/decisions.tsv` for `tool.nustack`.
9
+ ## When the tool is absent
10
10
 
11
- - **`declined`** — fall back to the cold method (grep, targeted reads, `git log`) **silently**. Do not mention the tool, do not re-offer. The user has answered.
12
- - **undecided** (no file, no key, or anything unrecognised) — fall back to the cold method *and* make the offer **once**: this repository would answer "where is / who calls / what changed / who owns" from an index instead of a grep sweep. `npx @nurix/nustack graph overview` runs it without installing anything; `npm install -g @nurix/nustack` puts it on `PATH`. **Ask, never act** — a global npm install modifies the machine, and indexing writes to `~/.codegraph`. Record the answer with `recordDecision` either way, so the question is asked at most once per repo per machine.
13
- - **`accepted` but the binary is gone** — treat as undecided; a failed or removed install is not a standing yes.
14
-
15
- **A failed install is `absent`, not an error to retry.** Global installs fail on machines without permission; say so once, fall back, and move on. `npx @nurix/nustack` needs no install at all and is the shorter ladder when one command is all you want.
11
+ If `nustack --version` fails, do not stall: read `tool.nustack` in `.claude/state/decisions.tsv` and fall back to the cold method — grep, targeted reads, `git log`.
16
12
 
17
- **An explicit invocation overrides a stored decline.** If the user asks for this skill by name, honour it and re-offer — "stop asking" must never become "never usable".
13
+ - **`declined`** — fall back **silently**. Never mention the tool or re-offer it.
14
+ - **Undecided** (no file, no key, or an unrecognised value) — fall back *and* offer the tool **once**: this repository could answer "where is / who calls / what changed / who owns" from an index instead of a grep sweep. `npx @nurix/nustack graph overview` runs it with no install; `npm install -g @nurix/nustack` puts it on `PATH`. **Ask, never act** — a global install modifies the machine and indexing writes to `~/.codegraph`. Record the answer with `recordDecision` either way, so the question is asked at most once per repo per machine.
15
+ - **`accepted` but the binary is gone** — treat as undecided; a failed or removed install is not a standing yes.
18
16
 
19
- **A repository that has never been indexed** answers with the tool saying so rather than with a wrong answer. That is the tool degrading correctly, not a failure: `nustack graph index` builds it, under the same ask-first rule.
17
+ **A failed install means absent — never retry it.** Global installs fail on machines without permission: say so once, fall back, move on. `npx` is the shorter ladder when one command is all you want.
20
18
 
21
- **Freshness is not your problem.** `nustack graph` re-indexes a stale store before it answers, so a query after a commit reports the new HEAD without anyone running an index first. Pass `--no-refresh` when you deliberately want the index as it stands — answering from a known-old index is a choice you can make, never one you make by accident. This is the one thing that changed when the code graph moved behind `nustack`: there is no staleness ritual left to perform.
19
+ **An explicit invocation overrides a stored decline.** When the user asks for this skill by name, honour it and re-offer — "stop asking" must never become "never usable".
22
20
 
23
21
  Everything below assumes the tool is present.
24
22
 
25
- `nustack graph` defaults to the git root of the working directory — inside this repository, no flags are needed; `--repo <pathOrId>` asks about another one. Every answer states its own confidence per section (`CERTAIN` / `INFERRED` / `UNRESOLVED`), announces anything the token budget dropped, and says what an empty section means. Trust those labels.
23
+ ## What every answer carries
24
+
25
+ - **Scope** — the git root of the working directory; no flags needed inside this repository. `--repo <pathOrId>` asks about another one.
26
+ - **Confidence** — each section is labelled `CERTAIN`, `INFERRED` or `UNRESOLVED`; the answer announces anything the token budget dropped and says what an empty section means. Trust those labels.
27
+ - **Freshness** — `nustack graph` re-indexes a stale store before it answers, so never run `index` to make a query correct. `--no-refresh` answers from the index as it stands — a deliberate choice, never an accidental one.
28
+ - **A never-indexed repository** — the tool says so instead of answering wrongly. `nustack graph index` builds it, under the same ask-first rule as the install.
26
29
 
27
30
  ## The method order
28
31
 
29
32
  Localize through the index **before** opening any file, in this order; then read only the files it points at.
30
33
 
31
- 1. **`nustack graph find <fragment>`** — symbols and file paths by name fragment. The entry point when you only have issue vocabulary. Symbol names only — it will never match string literals, dict keys, or SQL fragments (grep for those, without shame).
32
- 2. **`nustack graph where-defined <symbol>`** / **`nustack graph outline <file>`** — pin the definition; map a big file. `outline` turns a 100+ declaration file into a navigable, line-numbered list — use it instead of reading god files blind. On a large file add `--budget 0` (see traps).
33
- 3. **`nustack graph who-calls <symbol>`** / **`nustack graph impact <target>`** — callers, and blast radius (static reach + historical co-change reach, reported separately on purpose: they disagree, and the disagreement is information).
34
- 4. **`nustack graph churn --days N --path <dir>`** · **`coupling --path <file>`** · **`owners <path>`** · **`tests-covering <file>`** — the history layer: what's hot, what changes together (including *hidden coupling* — co-change with no static reference, the one fact reading code cannot reveal), who knows this area and how recently, which test file historically guards this file.
34
+ 1. **`nustack graph find <fragment>`** — symbols and file paths by name fragment; the entry point when you have only the issue's vocabulary. It matches symbol names only — never string literals, dict keys, or SQL fragments; grep for those.
35
+ 2. **`nustack graph where-defined <symbol>`** / **`nustack graph outline <file>`** — pin the definition; map a big file. Use `outline` instead of reading a 100+ declaration file blind: it returns a navigable, line-numbered list. The default token budget truncates a large file's outline — pass `--limit N` (which lifts the budget) or `--budget 0`; an explicit `--budget` wins over `--limit`.
36
+ 3. **`nustack graph who-calls <symbol>`** / **`nustack graph impact <target>`** — callers, and blast radius. `impact` reports static reach and historical co-change reach separately; never merge them — where they disagree, the disagreement is the finding. `who-calls` also tabulates the keyword arguments each call site passes, so "which call sites omit this option" is one query.
37
+ 4. **`nustack graph churn --days N --path <dir>`** · **`coupling --path <file>`** · **`owners <path>`** · **`tests-covering <file>`** — the history layer: what is hot, what changes together, who knows this area and how recently, which test file historically guards this file. `coupling` surfaces *hidden coupling* — co-change with no static reference, which no amount of reading reveals.
35
38
  5. **`nustack graph god-objects --minSymbols 30`** / **`hotspots`** / **`overview`** — repo-scale orientation and risk.
36
39
 
37
- Then read, with `sed -n` ranges at the lines the index gave you. The index does discovery; reading does diagnosis. Add `--json` when a wrapper needs structure — the answer arrives inside the CLI's machine envelope, `{ nustackJsonVersion, command, ok, data }`.
40
+ Then read with `sed -n` ranges at the lines the index gave you — the index does discovery, reading does diagnosis. Add `--json` when a wrapper needs structure; the answer arrives in the CLI's envelope, `{ nustackJsonVersion, command, ok, data }`.
38
41
 
39
- There is also **`nustack graph blame <file> [--line N]`** — the commits behind a file or one specific line, answered by git at query time; **`nustack graph refs <name>`** — the data-reference layer: where a name is used as a dict/object key, attribute/field, import, or type annotation, with read/write direction, and a cross-language flag when the same name spans Python and TypeScript files (this answers "who reads `x_axis_sort`" / "who writes `chart.query_context`" in one call — the questions that used to need grep); and **`nustack graph drift`** — near-duplicate function bodies crossed with co-change history: similar code whose files never change together is an unfinished refactor waiting to disagree. `who-calls` additionally tabulates which keyword arguments each call site passes, so "which call sites omit this option" is one query.
42
+ | Other verb | Answers |
43
+ | --- | --- |
44
+ | `nustack graph blame <file> [--line N]` | The commits behind a file or one line, from git at query time |
45
+ | `nustack graph refs <name>` | The data-reference layer: where a name is used as a dict/object key, attribute/field, import, or type annotation, with read/write direction and a cross-language flag when the name spans Python and TypeScript — "who reads `x_axis_sort`" / "who writes `chart.query_context`" in one call |
46
+ | `nustack graph drift` | Near-duplicate function bodies crossed with co-change history — similar code whose files never change together is an unfinished refactor waiting to disagree |
47
+ | `nustack graph status` | What the index holds — indexed HEAD, file and symbol counts, staleness. Reach for it when an answer surprises you |
48
+ | `nustack graph index [--commits N]` | An explicit rebuild |
40
49
 
41
- **`nustack graph status`** reports what the index holds for this repository — indexed HEAD, file and symbol counts, whether it is stale — and **`nustack graph index [--commits N]`** rebuilds it explicitly. Reach for `status` when an answer surprises you and you want to know what it was computed from; you should never need `index` to make a query correct.
50
+ ## The boundary — what the index cannot answer
42
51
 
43
- ## The boundary — what the index cannot answer (fall back immediately, don't retry)
52
+ Fall back to reading immediately for these; never retry the index.
44
53
 
45
- - **Free-text string literals** (SQL templates, prose, format strings) — grep. Identifier-shaped keys and fields ARE indexed (`refs <name>`); anything longer or with spaces is not.
46
- - **Framework reflection** (Marshmallow `@post_load`, signal handlers, `appbuilder.add_api(Cls)` registration) — invisible to call edges. Bare decorator *application* (`@has_access_api`) IS indexed as a reference; what the decorator does and when it runs is not.
54
+ - **Free-text string literals** (SQL templates, prose, format strings) — grep. Identifier-shaped keys and fields **are** indexed (`refs <name>`); anything longer or containing spaces is not.
55
+ - **Framework reflection** (Marshmallow `@post_load`, signal handlers, `appbuilder.add_api(Cls)` registration) — invisible to call edges. A bare decorator *application* (`@has_access_api`) **is** indexed as a reference; what the decorator does and when it runs is not.
47
56
  - **Data flow and mechanism** ("does this value reach that function", "what order do these run in") — always reading work.
48
57
 
49
- Indexed symbol forms include statement declarations, `const X = () => {}`, named function expressions inside wrapper calls (`typedMemo(function DataTable() {})`), TS/JS class fields, and Python class-body fields (`is_dttm = Column(...)`); nested members render as `Parent › member` in `outline`.
58
+ Indexed symbol forms: statement declarations, `const X = () => {}`, named function expressions inside wrapper calls (`typedMemo(function DataTable() {})`), TS/JS class fields, and Python class-body fields (`is_dttm = Column(...)`). `outline` renders nested members as `Parent › member`.
50
59
 
51
- ## Traps (each cost a prior agent real time)
60
+ ## Traps
52
61
 
53
62
  - **Bare-name collision.** Call references match by name, not resolved binding. When a name has several definitions, `who-calls` lists every candidate and says so in its subject line — read that section before trusting the call sites, and pass `--scope <path-fragment>` (also on `find`, `where-defined`, `impact`) to narrow to a package.
54
- - **`nustack graph functions` lists the graph's own verbs**, not the repository's functions. For repo functions use `find`. An explicit `--limit` lifts the default token budget, so a full `outline` needs no `--budget 0` incantation (an explicit `--budget` still wins).
55
- - **Churn windows.** `churn --days 180` returning nothing does not mean quiet history — widen the window. Directory-scope churn ranks by raw commit count and buries small-but-critical files; for specific files use `owners`/`coupling` per file.
63
+ - **`nustack graph functions` lists the graph's own verbs**, not the repository's functions. For repository functions use `find`.
64
+ - **Churn windows.** An empty `churn --days 180` does not mean quiet history — widen the window. Directory-scope churn ranks by raw commit count and buries small-but-critical files; for specific files use `owners` / `coupling` per file.
56
65
  - **`tests-covering` is co-change evidence, not executed coverage** — it names the right test file to open; it cannot say the specific scenario is tested.
57
- - **Coupling on a busy file has a noise tail** — partners at <15% shared commits are usually PR-cohabitation, not real coupling. Verify a partner with a read before adding it to a touch-list.
58
- - **The history layer sees one window, and the refresh keeps resetting it to 5000 commits.** `churn`, `owners`, `coupling`, `blame` and `tests-covering` answer over the ingested commits, and an index — the automatic one included — takes the most recent 5000 unless `index --commits N` says otherwise. On a deeper repository those answers are true of the window, not of the repository, and the next refresh silently narrows a wider window back. Say "over the last N commits" when you report them, and re-ask for the depth you need.
59
- - **`owners` is authorship weight, not a file-touch tally** — an author is counted once per commit touching the path, however many of the path's files that commit changed. Do not transcribe its numbers as "files changed".
60
- - **The first query after a long gap pays for the re-index.** Absorption is not free: on a large repository the refresh takes tens of seconds before the answer appears. That is one wait, not a per-query cost — and `--no-refresh` skips it when you would rather have the old answer now.
66
+ - **Coupling on a busy file has a noise tail** — partners below 15% shared commits are usually PR cohabitation, not real coupling. Verify a partner with a read before adding it to a touch-list.
67
+ - **The history layer sees one window, and every refresh resets it to 5000 commits.** `churn`, `owners`, `coupling`, `blame` and `tests-covering` answer over the ingested commits; an index, the automatic one included, takes the most recent 5000 unless `index --commits N` says otherwise. On a deeper repository those answers are true of the window, not the repository, and the next refresh silently narrows a wider window back. Always report them as "over the last N commits", and re-ask for the depth you need.
68
+ - **`owners` is authorship weight, not a file-touch tally** — an author counts once per commit touching the path, however many of its files that commit changed. Never transcribe its numbers as "files changed".
69
+ - **The first query after a long gap pays for the re-index** — tens of seconds on a large repository before the answer appears. That is one wait, not a per-query cost; `--no-refresh` skips it when the old answer will do.
61
70
 
62
71
  ## Division of truth in this repository
63
72
 
64
- `docs/` is the source of truth for **intent** — what a feature is for, its invariants, decisions, vocabulary. The code graph is the source of truth for **facts** — where things are, who calls what, what changed together, who owns what. Never copy live facts (caller counts, churn numbers) into docs as if permanent; never ask the index for intent.
73
+ `docs/` is the source of truth for **intent** — what a feature is for, its invariants, decisions, vocabulary. The code graph is the source of truth for **facts** — where things are, who calls what, what changed together, who owns what. **Never** copy live facts (caller counts, churn numbers) into docs as if permanent; never ask the index for intent.
@@ -4,7 +4,7 @@ Load when: composing a context pack, dispatching any role, packeting rows, recov
4
4
 
5
5
  ## The run folder
6
6
 
7
- `.claude/state/polygraph/{YYYY-MM-DD-slug}/` — machine-local, never tracked, deleted when the run closes:
7
+ `.claude/state/polygraph/{YYYY-MM-DD-slug}/` is machine-local, never tracked, and deleted when the run closes:
8
8
 
9
9
  ```
10
10
  partials/{doc}.psv one per finder — its whole output, no header
@@ -16,22 +16,22 @@ apply-{doc}.psv id|outcome|note
16
16
  packs/{doc}.md the context pack each finder and applier reads
17
17
  ```
18
18
 
19
- Work items pass through files, never through a dispatch call's arguments — a resumed agent may not carry arguments, and one that dies on a missing argument dies before it reads anything.
19
+ **Work items pass through files, never through a dispatch call's arguments** — a resumed agent may not carry its arguments, and one that dies on a missing argument dies before it reads anything.
20
20
 
21
21
  ## The context pack — the agent's whole reading list
22
22
 
23
- One page per target document, written by you before the first dispatch. A brief that sends an agent to the charter, the backlog, the ownership index, or the findings file hands it a 500 KB read to extract 2 KB of fact — and hands the same read to every other agent in the wave. The pack carries the 2 KB:
23
+ Write one page per target document before the first dispatch. **Never point an agent at the charter, the backlog, the ownership index, or the findings file** — that hands it a 500 KB read to extract 2 KB of fact, and hands the same read to every other agent in the wave. The pack carries the 2 KB:
24
24
 
25
25
  | Section | Content |
26
26
  | --- | --- |
27
- | Target | the doc path; for a plan target, the plan path and the Proposed Changes paths that intersect this doc's `owns:` |
28
- | Code | the doc's `owns:` globs, resolved to the files that exist today |
27
+ | Target | the doc path; for a plan target, the plan path and the Proposed Changes paths that intersect the files this doc speaks for |
28
+ | Code | the files the doc speaks for — the source files whose headers name it, and its `owns:` globs resolved to the files that exist |
29
29
  | Precedent | the quotations from guardrails, accepted decisions, and doctrine that **name** a subject this doc covers — the naming words only, with `path:line` |
30
30
  | Already tracked | backlog and register rows that name a subject this doc covers, as `path:line` |
31
31
  | Rulings | owner answers that bind this subject, verbatim |
32
32
  | Read budget | the two clauses below, verbatim |
33
33
 
34
- The two clauses every pack carries:
34
+ Every pack carries these two clauses:
35
35
 
36
36
  > Tree state: re-check every citation against today's tree before acting; a claim that fails to reproduce dies as *did not reproduce* — never becomes an edit. READ-ONLY everywhere except your one output file.
37
37
 
@@ -51,30 +51,43 @@ Where the runtime has no such agent installed, dispatch an independent subagent
51
51
 
52
52
  ## Packets — one doc, one applier
53
53
 
54
- Routing is judgment, so you compose packets by hand; `audit.mjs` asserts the one mechanical invariant: **no document appears in two packets** — appliers run in parallel, and two agents in one file silently lose each other's edits.
54
+ Compose packets by hand; routing is judgment. `audit.mjs` asserts the one mechanical invariant: **no document appears in two packets** — appliers run in parallel, and two agents in one file silently lose each other's edits.
55
55
 
56
- **Packet by the doc the fix belongs in, not the doc a row's evidence cites.** A row's citations say where the *evidence* lives; the fix usually belongs elsewhere, and a bounced row's `needs-cross-doc` note says exactly where. A row with no derivable doc goes to the owner — never into a packet "to see what the agent makes of it".
56
+ - **Packet by the doc the fix belongs in, never the doc a row's evidence cites.** Citations say where the evidence lives; the fix usually belongs elsewhere, and a bounced row's `needs-cross-doc` note names where.
57
+ - **A row with no derivable doc goes to the owner** — never into a packet "to see what the agent makes of it".
57
58
 
58
- By verdict: QUESTION and `depends_on` rows → the owner, inline. CONTESTED → you, with both sets of evidence. RESIDUE, and any row whose subject is a source file — a stale comment included — → the register. Everything else → the owning doc's packet.
59
+ | Rows | Destination |
60
+ | --- | --- |
61
+ | QUESTION, and any row with `depends_on` set | the owner, in your report |
62
+ | CONTESTED | you, with both sets of evidence |
63
+ | RESIDUE, and any row whose subject is a source file — a stale comment included | the register |
64
+ | everything else | the owning doc's packet |
59
65
 
60
66
  ## Verification — budgeted by what it caught
61
67
 
62
- - **The applier re-check is the law.** In the proving run it killed more false positives than every other mechanism combined.
63
- - **Blind re-derivation is discretionary.** Send a row *you* doubt — claim and subject only. As a mandatory stage it refuted nothing: finders' self-doubt is conservative, and the errors live in the confident pile, which is where your own read of the evidence looks. Disagreement makes the row CONTESTED; a verification never overwrites a verdict.
68
+ - **The applier re-check is the law** — it kills more false positives than every other mechanism combined.
69
+ - **Blind re-derivation is discretionary.** Send only a row *you* doubt, with claim and subject only. As a mandatory stage it refutes nothing: finders' self-doubt is conservative, and the errors sit in the confident pile, which is where your own read of the evidence looks. Disagreement makes the row CONTESTED; a verification never overwrites a verdict.
64
70
 
65
71
  ## Fold-back
66
72
 
67
- After each applier returns, its outcomes become row state by your own edit, then `audit.mjs` checks the arithmetic:
73
+ After each applier returns, turn its outcomes into row state by your own edit, then run `audit.mjs` to check the arithmetic.
74
+
75
+ | Outcome | Row state |
76
+ | --- | --- |
77
+ | `applied` · `registered` · `exempt-frozen` | `resolved`, the resolution stamped with how |
78
+ | `refuted-on-apply` | `resolved`, resolution `did not reproduce` — a legitimate death; its count is the quality signal you report |
79
+ | `needs-cross-doc` · `blocked-on-question` · `skipped` | stays open — repacket, or hold for the answer |
68
80
 
69
- - `applied` · `registered` · `exempt-frozen` → `resolved`, the resolution stamped with how.
70
- - `refuted-on-apply` → `resolved`, resolution `did not reproduce`. A legitimate death; its count is the quality signal you report.
71
- - `needs-cross-doc` · `blocked-on-question` · `skipped` → stays open — repacket, or hold for the answer.
72
- - COHERENT rows close when recorded; they carry no work.
81
+ COHERENT rows close when recorded; they carry no work.
73
82
 
74
83
  ## Recovery
75
84
 
76
- **The file on disk is the recovery unit, never the wave.** A dead agent costs its remainder; read what landed and relaunch only that. A finder that returns **partial** at its turn cap is resumed once from its partial — a second partial is accepted as that doc's sweep and named as such in the report. Every dispatch names its output file and a time bound; an agent past its bound with no file is dead — relaunch it, never wait on it, and never let one runaway agent hold the run. Gates — lint, links, ownership — run in your own seat.
85
+ **The file on disk is the recovery unit, never the wave.** A dead agent costs only its remainder: read what landed and relaunch that.
86
+
87
+ - **Partial at the turn cap:** resume the finder once from its partial; a second partial is accepted as that doc's sweep and named as such in the report.
88
+ - **Time bound:** every dispatch names its output file and a time bound. An agent past its bound with no file is dead — relaunch it, never wait on it, and never let one runaway agent hold the run.
89
+ - **Gates** — lint, links, ownership — run in your own seat.
77
90
 
78
91
  ## Price
79
92
 
80
- State before dispatching, whenever a run exceeds ten documents or an owner directive names a tier: the per-doc cost at the pinned tier, the wave count under the concurrency ceiling, and the total; for a stronger tier, the multiplier and both totals. The directive and the estimate go in the changelog entry.
93
+ **Before dispatching, state the price whenever a run exceeds ten documents or an owner directive names a tier:** the per-doc cost at the pinned tier, the wave count under the concurrency ceiling, and the total; for a stronger tier, the multiplier and both totals. The directive and the estimate go in the changelog entry.
@@ -35,23 +35,28 @@ id|subject|claim|evidence|counter_evidence|verdict|confidence|depends_on|status|
35
35
  | `UNBUILT` | a documented design with no code behind it — decided-unbuilt (the repo's marker convention), abandoned, or a document that should die; only the owner distinguishes the last two, so suspecting either makes the row a QUESTION | the marker |
36
36
  | `RESIDUE` | code a standing decision condemns by name — running, even load-bearing, and still retiring | a register row, never a doc edit |
37
37
  | `CONTESTED` | the evidence conflicts — a verifier and a finder disagree, or a claim has no code to arbitrate and a sibling document says otherwise | the primary judges with both sets of evidence in hand |
38
- | `QUESTION` | only the owner can resolve it — intent, not evidence, is what is missing | asked inline, answered in the same pass |
38
+ | `QUESTION` | only the owner can resolve it — intent, not evidence, is what is missing | returned to the owner in full in the report; the answer arrives as a further dispatch |
39
39
 
40
40
  A disposition is the verdict's default; where the primary overrides it, the resolution says so. **A verification never overwrites a verdict** — disagreement makes the row CONTESTED.
41
41
 
42
42
  ## The residue guard
43
43
 
44
- Before an UNDOCUMENTED row becomes a doc edit, its subject is tested against the precedent pack — the one place the audit can silently undo the repo's own decisions. The test is **naming**: a precedent matches only when it names the subject — the file, table, route, command, or surface by the product's own name for it — and the row quotes the naming words into `evidence`. A precedent describing a *resembling* thing is not a match; shared adjectives ("legacy", "old export lane") are not a match; a misread pronoun is not a match. Unsure → QUESTION, never a silent RESIDUE.
44
+ Test an UNDOCUMENTED row's subject against the precedent pack before it becomes a doc edit — this is the one place the audit can silently undo the repo's own decisions.
45
45
 
46
- The asymmetry runs both ways. A missed match resurfaces: the register is owner-read, and an UNDOCUMENTED row that should have been RESIDUE is caught when the owner reads the doc it produced. A false match does not: it retires code the product still ships and withholds its documentation — worse than the disease. When in doubt, ask.
46
+ - **The test is naming.** A precedent matches only when it names the subject — the file, table, route, command, or surface, by the product's own name for it — and the row quotes the naming words into `evidence`.
47
+ - **No match:** a precedent describing a *resembling* thing; shared adjectives ("legacy", "old export lane"); a misread pronoun.
48
+ - **Unsure → QUESTION, never a silent RESIDUE.** The errors are not symmetric: a missed match resurfaces when the owner reads the doc it produced, but a false match retires code the product still ships and withholds its documentation.
47
49
 
48
50
  ## Frozen records and code
49
51
 
50
52
  - **Frozen records are exempt** — a completed plan, a changelog entry, an archived document resolves `exempt-frozen`; its claims were true when frozen and the tree does not correct history.
51
- - **The audit never edits code.** A code-side finding — a bug, a stale source comment, the unbuilt, residue — exits as a register row for the planning workflow, however small the edit and however directly it is asked for. **Markdown is not source**: every document read may be edited under the applier discipline, package and root READMEs included; the line is doc versus code, not proximity to `src/`.
53
+ - **The audit never edits code.** A code-side finding — a bug, a stale source comment, the unbuilt, residue — exits as a register row for the planning workflow, however small the edit and however directly it is asked for.
54
+ - **Markdown is not source.** Every document read may be edited under the applier discipline, package and root READMEs included; the line is doc versus code, not proximity to `src/`.
52
55
 
53
56
  ## A document's right to exist
54
57
 
55
- A wholly stale document passes a sentence-by-sentence audit one true sentence at a time, so a finder that reads a document whose subject is gone, whose authority another doc has absorbed, or whose content belongs inside a sibling emits **one QUESTION row proposing deletion or merge**, carrying the inbound-reference count (who links here, which index rows list it, which ownership globs it claims). Deletion is owner-approved, always. An approved deletion removes the file, repoints every inbound reference to the surviving authority, and leaves **no redirect stub** — an annotation, a banner, or a status flag left in the reading path is not a source of truth however carefully the surviving sentences are hedged. A document whose subject survives but whose content is wrong is MISDESCRIBED, not a deletion candidate.
58
+ A wholly stale document passes a sentence-by-sentence audit one true sentence at a time. So a finder reading a document whose subject is gone, whose authority another doc has absorbed, or whose content belongs inside a sibling emits **one QUESTION row proposing deletion or merge**, carrying the inbound-reference count: who links here, which index rows list it, which ownership globs it claims.
56
59
 
57
- **Abandonment has no marker.** A design decided against is not owed: its claims come **out** of every marker that carried them rather than being reworded inside one, and the document survives as the record of a decision not to build, its status saying so. The tell that this went wrong: a reader finishes the paragraph still expecting the feature.
60
+ - **Deletion is owner-approved, always.** An approved deletion removes the file, repoints every inbound reference to the surviving authority, and leaves **no redirect stub** — an annotation, banner, or status flag left in the reading path is not a source of truth, however carefully the surviving sentences are hedged.
61
+ - **A surviving subject with wrong content** is MISDESCRIBED, never a deletion candidate.
62
+ - **Abandonment has no marker.** A design decided against is not owed: its claims come **out** of every marker that carried them rather than being reworded inside one, and the document survives as the record of a decision not to build, its status saying so. The tell that this went wrong: a reader finishes the paragraph still expecting the feature.
@@ -5,13 +5,13 @@ description: "Use when designing or altering a relational schema — a new table
5
5
 
6
6
  # Schema Design — the doctrine
7
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.
8
+ You decide **what earns a table and where a fact lives** for every Nurix relational schema — 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; this skill never restates them.
9
9
 
10
- Apply the sections below to the change in hand; when reviewing, report violations with the section that names them.
10
+ Apply the sections below to the change in hand; when reviewing, report each violation with the section that names it.
11
11
 
12
12
  ## What earns a table
13
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.
14
+ A table costs a migration to reshape, 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
15
 
16
16
  | A table is earned by… | Lacking all of these, the data belongs in… |
17
17
  | -------------------------------------------------------------------------- | ------------------------------------------------------- |
@@ -27,15 +27,14 @@ A table is the most expensive home for data — a migration to change its shape,
27
27
 
28
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
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.
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 or 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
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.
33
+ - **Table count tracks requirements, not domain richness** — a rich domain is not entitled to a table per noun.
35
34
 
36
35
  ## State, rules, and history
37
36
 
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.
37
+ **One fact has one home, chosen by what kind of fact it is.**
39
38
 
40
39
  | The fact | Home | Example |
41
40
  | --- | --- | --- |
@@ -45,7 +44,7 @@ Table count tracks **requirements, not domain richness** — a rich domain is no
45
44
  | What a consumer must **replay in order** | the **sequence log** | an `app_events` row keyed `(app_id, sequence)` |
46
45
 
47
46
  - **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.
47
+ - **A pending-work table mirroring row state is the anti-pattern** — `*_actions`, `*_inbox`, `*_flags`; it is the most common redundant table in a schema. 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
48
  - **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
49
 
51
50
  **The two logs are not substitutes**, and no one table serves both:
@@ -58,36 +57,36 @@ Table count tracks **requirements, not domain richness** — a rich domain is no
58
57
  | Records | only what **happened** | **attempts too** — `denied`, `failure` |
59
58
  | Read by | cursors replaying forward | investigations, at any age |
60
59
 
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.
60
+ The Scope and Records 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
61
 
63
62
  ### The audit log's own shape
64
63
 
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:
64
+ **An audit log outlives every row it names, so it stores what it needs to stay readable without them.**
66
65
 
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.
66
+ - **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.
68
67
  - **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
68
  - **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
69
 
71
70
  ## Scope, not parallel tables
72
71
 
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.
72
+ 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
73
 
75
74
  - **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
75
  - **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
76
  - **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.
77
+ - **A nullable owner column lets a row exist before its owner does** — a pending enrolment or unclaimed record is creatable before the owner is known, which collapses a `pending_*` / `*_requests` table into the real one.
78
+ - **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
79
 
81
80
  ## Copying rows between scopes
82
81
 
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.
82
+ - **A copied junction row is already correct with no remapping** — its endpoints are `slug` references (the `database.md` rule § 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
83
  - **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
84
  - **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.
85
+ - **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
86
 
88
87
  ## A junction spells many-to-many — nothing else
89
88
 
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:
89
+ **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 is unenforced from the moment it is written. Cardinality decides between the only two shapes:
91
90
 
92
91
  | Relationship | Shape |
93
92
  | --- | --- |
@@ -95,7 +94,7 @@ One entity that exists at several scopes is **one table with a nullable scope co
95
94
  | **M:N** — both sides have many | a **`<a>_<b>_map` junction** with a composite unique |
96
95
 
97
96
  - **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.
97
+ - **A 1:M junction also** makes the association a row that can go missing independently of the child — the child exists correctly while its wiring does not — and forces a join onto the most common read in the system.
99
98
  - **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
99
  - **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
100
  - **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).
@@ -107,13 +106,13 @@ One entity that exists at several scopes is **one table with a nullable scope co
107
106
  | Action | Why it fails on an append-only table |
108
107
  | --- | --- |
109
108
  | `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. |
109
+ | `CASCADE` | Deletes history to delete a person. The log exists to outlive its subjects. |
110
+ | `NO ACTION` | Makes anyone who ever appeared in the log undeletable — the hub-table defect of the `database.md` rule § Keys & the shared vocabulary. |
112
111
 
113
112
  - **`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.
113
+ - **Record the actor as a plain column** — the same soft-reference treatment provenance gets (`source_id`, `thread_id`), so the log keeps naming who acted after the account is gone.
114
+ - **`CASCADE` from the append-only side's own parent is 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.
115
+ - **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 — one line saying so.
117
116
 
118
117
  ## Uniqueness with a nullable column
119
118
 
@@ -128,28 +127,27 @@ CREATE UNIQUE INDEX <t>_child_slug_unique ON <t> (child_id, slug) WHERE child_
128
127
 
129
128
  ## Vocabulary / lookup tables
130
129
 
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.
130
+ 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: 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
131
 
133
- - **Seeds are generated, never hand-written** — the source of truth is an authored tree (§ Seeding) 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.
132
+ - **Seeds are generated, never hand-written** — the source of truth is an authored tree or a shared package, compiled and loaded per § Seeding.
134
133
 
135
134
  ## Seeding — authored source, compiled artifact, loaded rows
136
135
 
137
136
  Curated data that ships with the product — vocabularies, catalogs, reference profiles — is **authored in a tree, compiled to SQL, and loaded**. The compiled artifact is build output, not source.
138
137
 
139
- ```
140
- seed/<data>/** authored source, COMMITTED — the only thing a human edits
141
- ↓ compile mechanical: parse, validate, emit. No judgment, no network
142
- seed/sql/*.seed.sql build output, GITIGNORED — rebuilt on every run
143
- ↓ load the engine's own client (wrangler d1 execute, psql -f)
144
- ```
138
+ | Stage | Where | Rule |
139
+ | --- | --- | --- |
140
+ | Author | `seed/<data>/**` | committed — the only thing a human edits |
141
+ | Compile | emits `seed/sql/*.seed.sql` | mechanical — parse, validate, emit; no judgment, no network. Output is gitignored and rebuilt on every run |
142
+ | Load | the engine's own client — `wrangler d1 execute`, `psql -f` | idempotent |
145
143
 
146
- - **Never commit the compiled artifact.** Rebuilt seconds before it is applied, it cannot go stale — so nothing checks it for drift and no freshness test is owed. A committed `.sql` needs that check; a gitignored one needs nothing. ❌ Hand-editing compiled SQL — regenerate.
144
+ - **Never commit the compiled artifact.** Rebuilt seconds before it is applied, it cannot go stale, so it owes no drift check and no freshness test; a committed `.sql` would owe both. **Never hand-edit seed SQL** — regenerate it.
147
145
  - **The load is idempotent**: UUIDv5 ids derived from the natural key plus `ON CONFLICT(slug) DO UPDATE`, preserving live counts and admin-tuned columns. Re-seeding is a no-op, never a duplicate.
148
- - **A build step that reads the artifact runs the compiler first.** Bundlers resolve imports at *build* time, so a gitignored artifact breaks a fresh clone with a resolution error rather than a test failure — wire `pretest` / `prebuild`.
146
+ - **A build step that reads the artifact runs the compiler first** — wire `pretest` / `prebuild`. Bundlers resolve imports at *build* time, so a gitignored artifact breaks a fresh clone with a resolution error rather than a test failure.
149
147
 
150
148
  ### Choosing the authoring format
151
149
 
152
- **Choose by what the payload is, not by where it lands.** That the destination is a relational table argues nothing about the source: rows routinely carry document-valued columns, and the compiler exists precisely to bridge the two shapes. The litmus is one question per field — **is this a scalar a human scans in a table, or prose a human reads in paragraphs?**
150
+ **Choose by what the payload is, not by where it lands.** A relational destination argues nothing about the source: rows routinely carry document-valued columns, and the compiler exists to bridge the two shapes. The litmus is one question per field — **is this a scalar a human scans in a table, or prose a human reads in paragraphs?**
153
151
 
154
152
  | Payload | Format | Why |
155
153
  | --- | --- | --- |
@@ -164,7 +162,7 @@ seed/sql/*.seed.sql build output, GITIGNORED — rebuilt on every run
164
162
 
165
163
  ### Rigidity comes from validators, never from containers
166
164
 
167
- **No file format enforces a shape.** CSV checks no types, no required fields, no nesting — not even column count. YAML and JSON check only that the syntax parses. Every format is a container; the shape is enforced by a schema or not at all, so a format migration undertaken to gain rigidity buys none.
165
+ **No file format enforces a shape.** CSV checks no types, no required fields, no nesting — not even column count; YAML and JSON check only that the syntax parses. The shape is enforced by a schema or not at all, so a format migration undertaken to gain rigidity buys none.
168
166
 
169
167
  - **Validate at the compile boundary and fail the build**, naming the file and the reason. A compiler that checks only *parseability* ships a typo'd key or a missing map straight to production, where it is served with full authority.
170
168
  - **Declare the shape as a schema and share it** between the compiler that writes the column and the code that reads it — one definition, so compile-time and read-time cannot disagree. A `values_json`-style column is opaque to the database; that schema is the only thing standing between it and `unknown` at the read site.
@@ -173,24 +171,24 @@ seed/sql/*.seed.sql build output, GITIGNORED — rebuilt on every run
173
171
 
174
172
  ### Canonicalize, never repair
175
173
 
176
- Two operations get called "cleaning the data" and only one is safe:
174
+ Two operations get called "cleaning the data"; only one is safe:
177
175
 
178
176
  | Operation | Verdict | Why |
179
177
  | --- | --- | --- |
180
178
  | **Canonicalization** — deterministic and information-preserving: slug folding, diacritic normalization, key and unit ordering | ✅ belongs in the compiler | The same input always yields the same output, and nothing is invented |
181
179
  | **Repair** — filling, clamping, defaulting, or dropping content that failed validation | ❌ reject loudly instead | Deciding what the author meant *is* judgment; a repaired row is then served indistinguishably from a curated one |
182
180
 
183
- **Correction cannot be both unintelligent and correct.** A pipeline that guesses at malformed input has swapped a loud rejection for a quiet wrong answer — and for curated data, whose whole value is that a human judged it, that is corruption wearing the name of automation. Reject-with-report is what scales: the producer retries, or a human triages.
181
+ **Never guess at malformed input** — a guessing pipeline swaps a loud rejection for a quiet wrong answer, and for curated data, whose whole value is that a human judged it, that is corruption. Reject with a report: the producer retries, or a human triages.
184
182
 
185
183
  ### Ingesting scraped or generated data
186
184
 
187
- Automated sources produce **candidate raw material, not rows.** Publish the compile-boundary schema as the ingestion contract and run scraped output through it unchanged — validate, reject loudly on mismatch, queue the survivors for curation. A curator promotes a candidate by adding the judgment the source could not supply: the rationale, the caveat, the editorial line. A pipeline that lets unreviewed extracts land directly as catalog rows has quietly swapped a curated corpus for a scraped one.
185
+ Automated sources produce **candidate raw material, not rows.** Publish the compile-boundary schema as the ingestion contract and run scraped output through it unchanged — validate, reject loudly on mismatch, queue the survivors for curation. A curator promotes a candidate by adding the judgment the source could not supply: the rationale, the caveat, the editorial line. **Never** let an unreviewed extract land directly as a catalog row — that swaps a curated corpus for a scraped one.
188
186
 
189
187
  ## Run
190
188
 
191
189
  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.
192
190
  2. **Classify every fact by § State, rules, and history** — now-state → entity column; rules → code; what-was-done → audit log; replayable → sequence log.
193
- 3. **Check cardinality before any junction** (§ A junction spells many-to-many) and every FK's `ON DELETE` (§ Keys, § Append-only).
194
- 4. **Prove uniqueness claims by inserting the forbidden duplicate** — a schema review cannot tell a firing index from a silent one by reading.
191
+ 3. **Check cardinality before any junction** (§ A junction spells many-to-many) and every FK's `ON DELETE` — hub edges per the `database.md` rule § Keys & the shared vocabulary, append-only edges per § Foreign keys into and out of append-only tables.
192
+ 4. **Prove every uniqueness claim by inserting the forbidden duplicate** (§ Uniqueness with a nullable column).
195
193
  5. **For a seed pipeline, walk § Seeding** — format chosen by payload rather than by destination, shape enforced by a schema at the compile boundary, artifact gitignored, correction limited to canonicalization.
196
194
  6. **Report as a findings list** — one line per violation: the section, the table/column, the fix.