hstack 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +76 -163
  3. package/VERSION +1 -1
  4. package/dist/commands/doctor.js +18 -8
  5. package/dist/commands/doctor.js.map +1 -1
  6. package/dist/commands/init.js +7 -3
  7. package/dist/commands/init.js.map +1 -1
  8. package/dist/commands/update.js +9 -2
  9. package/dist/commands/update.js.map +1 -1
  10. package/dist/lib/descriptions.js +10 -17
  11. package/dist/lib/descriptions.js.map +1 -1
  12. package/dist/lib/wire.js +166 -138
  13. package/dist/lib/wire.js.map +1 -1
  14. package/dist/manifest.js +41 -29
  15. package/dist/manifest.js.map +1 -1
  16. package/package.json +3 -4
  17. package/template/.claude/agents/adversarial-reviewer.md +44 -50
  18. package/template/.claude/agents/app-architect.md +44 -96
  19. package/template/.claude/agents/data-architect.md +46 -89
  20. package/template/.claude/agents/test-strategist.md +39 -68
  21. package/template/.claude/skills/hstack-adr-new/SKILL.md +23 -73
  22. package/template/.claude/skills/hstack-adversarial-review/SKILL.md +46 -152
  23. package/template/.claude/skills/hstack-adversarial-review/references/finding-categories.md +89 -103
  24. package/template/.claude/skills/hstack-app-architecture/SKILL.md +43 -68
  25. package/template/.claude/skills/hstack-commit/SKILL.md +27 -68
  26. package/template/.claude/skills/hstack-data-architecture/SKILL.md +34 -56
  27. package/template/.claude/skills/hstack-promote/SKILL.md +55 -0
  28. package/template/.claude/skills/hstack-story/SKILL.md +58 -0
  29. package/template/.claude/skills/hstack-test-audit/SKILL.md +64 -0
  30. package/template/.claude/skills/hstack-wrap/SKILL.md +50 -0
  31. package/template/.claude/skills/hstack-wrap/references/pr-description.md +45 -0
  32. package/template/KERNEL.md +75 -318
  33. package/template/templates/adr.md +2 -18
  34. package/template/templates/app-architecture.md +38 -50
  35. package/template/templates/code-standards.md +107 -0
  36. package/template/templates/data-architecture.md +38 -89
  37. package/template/templates/eslint-clean-code.mjs +322 -0
  38. package/template/templates/infrastructure.md +54 -148
  39. package/template/templates/roadmap.md +6 -12
  40. package/template/templates/story.md +7 -13
  41. package/template/templates/tech-debt.md +2 -37
  42. package/template/templates/tech-stack.md +2 -12
  43. package/template/.claude/agents/data-specialist.md +0 -81
  44. package/template/.claude/agents/implementer.md +0 -97
  45. package/template/.claude/agents/kernel-fit-analyst.md +0 -129
  46. package/template/.claude/agents/planner.md +0 -75
  47. package/template/.claude/agents/product-discovery.md +0 -129
  48. package/template/.claude/agents/product-manager.md +0 -76
  49. package/template/.claude/agents/researcher.md +0 -87
  50. package/template/.claude/agents/security-reviewer.md +0 -88
  51. package/template/.claude/agents/spec-author.md +0 -89
  52. package/template/.claude/agents/stack-architect.md +0 -108
  53. package/template/.claude/agents/ui-ux-briefer.md +0 -75
  54. package/template/.claude/agents/verifier.md +0 -81
  55. package/template/.claude/skills/hstack-branch/SKILL.md +0 -76
  56. package/template/.claude/skills/hstack-brownfield-init/SKILL.md +0 -131
  57. package/template/.claude/skills/hstack-change-new/SKILL.md +0 -84
  58. package/template/.claude/skills/hstack-change-plan/SKILL.md +0 -108
  59. package/template/.claude/skills/hstack-configure/SKILL.md +0 -99
  60. package/template/.claude/skills/hstack-coord/SKILL.md +0 -95
  61. package/template/.claude/skills/hstack-data-review/SKILL.md +0 -93
  62. package/template/.claude/skills/hstack-finalize/SKILL.md +0 -140
  63. package/template/.claude/skills/hstack-flag/SKILL.md +0 -99
  64. package/template/.claude/skills/hstack-greenfield-init/SKILL.md +0 -148
  65. package/template/.claude/skills/hstack-help/SKILL.md +0 -153
  66. package/template/.claude/skills/hstack-implement/SKILL.md +0 -155
  67. package/template/.claude/skills/hstack-kernel-fit-promote/SKILL.md +0 -125
  68. package/template/.claude/skills/hstack-kernel-fit-scan/SKILL.md +0 -142
  69. package/template/.claude/skills/hstack-kernel-fit-scan/references/slack-setup.md +0 -42
  70. package/template/.claude/skills/hstack-kernel-fit-triage/SKILL.md +0 -121
  71. package/template/.claude/skills/hstack-module-spec/SKILL.md +0 -78
  72. package/template/.claude/skills/hstack-product-discovery/SKILL.md +0 -75
  73. package/template/.claude/skills/hstack-research/SKILL.md +0 -115
  74. package/template/.claude/skills/hstack-scaffold/SKILL.md +0 -113
  75. package/template/.claude/skills/hstack-security-review/SKILL.md +0 -95
  76. package/template/.claude/skills/hstack-ship/SKILL.md +0 -119
  77. package/template/.claude/skills/hstack-stack-decide/SKILL.md +0 -81
  78. package/template/.claude/skills/hstack-story-draft/SKILL.md +0 -92
  79. package/template/.claude/skills/hstack-tech-debt-new/SKILL.md +0 -95
  80. package/template/.claude/skills/hstack-tech-debt-resolve/SKILL.md +0 -123
  81. package/template/.claude/skills/hstack-tech-debt-stale/SKILL.md +0 -86
  82. package/template/.claude/skills/hstack-tech-debt-wontfix/SKILL.md +0 -77
  83. package/template/.claude/skills/hstack-telemetry/SKILL.md +0 -73
  84. package/template/.claude/skills/hstack-test-plan/SKILL.md +0 -159
  85. package/template/.claude/skills/hstack-ui-brief/SKILL.md +0 -81
  86. package/template/.claude/skills/hstack-verify/SKILL.md +0 -128
  87. package/template/scripts/compute-merge-readiness.mjs +0 -780
  88. package/template/scripts/coord/coord_scan.py +0 -681
  89. package/template/scripts/run-gates.sh +0 -388
  90. package/template/scripts/telemetry/__init__.py +0 -6
  91. package/template/scripts/telemetry/insights/__init__.py +0 -0
  92. package/template/scripts/telemetry/insights/contract_drift.py +0 -137
  93. package/template/scripts/telemetry/insights/kernel_fit.py +0 -438
  94. package/template/scripts/telemetry/insights/overengineering.py +0 -115
  95. package/template/scripts/telemetry/insights/quality_outcomes.py +0 -131
  96. package/template/scripts/telemetry/insights/token_economics.py +0 -302
  97. package/template/scripts/telemetry/insights/workflow_shape.py +0 -198
  98. package/template/scripts/telemetry/parsers/__init__.py +0 -0
  99. package/template/scripts/telemetry/parsers/bodies.py +0 -87
  100. package/template/scripts/telemetry/parsers/commits.py +0 -219
  101. package/template/scripts/telemetry/parsers/frontmatter.py +0 -322
  102. package/template/scripts/telemetry/parsers/sidecars.py +0 -61
  103. package/template/scripts/telemetry/parsers/transcripts.py +0 -294
  104. package/template/scripts/telemetry/render.py +0 -460
  105. package/template/scripts/telemetry/report.py +0 -150
  106. package/template/scripts/telemetry/run_kernel_fit.py +0 -95
  107. package/template/scripts/telemetry/session_id.py +0 -139
  108. package/template/scripts/validate-spec.mjs +0 -3303
  109. package/template/templates/adversarial-review.md +0 -54
  110. package/template/templates/bootstrap.md +0 -105
  111. package/template/templates/change-spec.md +0 -82
  112. package/template/templates/ci-cd.md +0 -41
  113. package/template/templates/coord-message.md +0 -56
  114. package/template/templates/data-review.md +0 -54
  115. package/template/templates/discovery/brainstorm.md +0 -69
  116. package/template/templates/discovery/forcing-questions.md +0 -76
  117. package/template/templates/discovery/project-brief.md +0 -53
  118. package/template/templates/figma-handoff.md +0 -38
  119. package/template/templates/glossary.md +0 -20
  120. package/template/templates/hardening-checklist.md +0 -73
  121. package/template/templates/incident-runbook.md +0 -57
  122. package/template/templates/kernel-fit-finding.md +0 -63
  123. package/template/templates/kernel-fit-flag.md +0 -52
  124. package/template/templates/module-spec.md +0 -49
  125. package/template/templates/persona.md +0 -38
  126. package/template/templates/plan.md +0 -53
  127. package/template/templates/product-brief.md +0 -56
  128. package/template/templates/security-review.md +0 -63
  129. package/template/templates/telemetry-sidecar.md +0 -227
  130. package/template/templates/test-plan.md +0 -119
  131. package/template/templates/threat-model.md +0 -54
  132. package/template/templates/ui-brief.md +0 -49
  133. package/template/templates/verification.md +0 -63
  134. package/template/templates/vision.md +0 -34
@@ -1,26 +1,10 @@
1
- ---
2
- id: ADR-<NNNN>-<slug>
3
- type: adr
4
- status: proposed # proposed | accepted | deprecated | superseded
5
- owner: <git-handle>
6
- decision-date: <YYYY-MM-DD>
7
- supersedes: null # ADR id when this ADR replaces another
8
- superseded-by: null # ADR id when this ADR has been replaced; reciprocal with supersedes
9
- related-change-specs: []
10
- related-modules: []
11
- promoted-from-kernel-fit: [] # KF-NNNN ids that motivated this ADR; reciprocal with kernel-fit-finding.promoted-to (KF-04)
12
- created: <YYYY-MM-DD>
13
- updated: <YYYY-MM-DD>
14
- schema-version: 2
15
- ---
16
-
17
1
  ## Title
18
2
 
19
3
  _Short noun phrase._
20
4
 
21
5
  ## Status
22
6
 
23
- _Pointer to frontmatter `status`. Note any supersession relationship._
7
+ _Accepted on `<YYYY-MM-DD>`. When this ADR replaces another, one line of prose here: `Supersedes ADR-NNNN`. The superseded ADR is not edited — the grep is the link._
24
8
 
25
9
  ## Context
26
10
 
@@ -32,7 +16,7 @@ _The choice, stated as an active sentence. One paragraph._
32
16
 
33
17
  ## Consequences
34
18
 
35
- _Positive, negative, and neutral outcomes. Particularly the trade-offs accepted. Challenge prompt: name two consequences that look bad. If you can't, what alternative would have made them visible? 2–4 paragraphs._
19
+ _Positive, negative and neutral outcomes, and particularly the trade-off actually accepted. Challenge prompt, mandatory: name two consequences that look bad. If you can't, what alternative would have made them visible?_
36
20
 
37
21
  ## Alternatives Considered
38
22
 
@@ -1,77 +1,76 @@
1
1
  ---
2
2
  id: app-architecture
3
3
  type: app-architecture
4
- status: draft # draft | current | needs-refresh | archived
5
- owner: <git-handle>
6
- derived-from: [product-brief, data-architecture]
7
- downstream: [threat-model, hardening-checklist, tech-stack, module-spec/*]
8
- created: <YYYY-MM-DD>
9
4
  updated: <YYYY-MM-DD>
10
- schema-version: 1
11
5
  ---
12
6
 
13
7
  ## 1. Module Map
14
8
 
15
- _The set of modules and what each owns. Each module must trace to either a persona-named action in `product-brief.md` or to a logical clustering of entities from `data-architecture.md`. Orphan modules (no trace) halt the section with the drift challenge._
9
+ _The modules, what each owns, and what reaches them. `Critical: yes` means the kernel's
10
+ mandatory-test rule applies to this module's paths._
16
11
 
17
- **Modules**
12
+ _The **Exposure** column is the exposure map. One line per entry point: `path` · kind (`page`,
13
+ `api`, `action`, `job`, `webhook`) · status (`live`, `routable`, `off` — kernel § Context docs).
14
+ A module owning no entry point of its own names the ones that reach it; a module no live entry
15
+ point reaches is `dormant`, which is derived, not a fourth status. Updated in the PR that changes
16
+ exposure, verified at `/promote`, and checkable by diffing these paths against the real routes._
18
17
 
19
- | Module | Owns | Traces to |
20
- | --- | --- | --- |
21
- | <module-name> | <one-line statement of what this module owns> | <persona-action OR entity-cluster> |
18
+ | Module | Owns | Critical | Traces to | Exposure — entry points |
19
+ | --- | --- | --- | --- | --- |
20
+ | <module> | <what it owns, one line> | yes \| no | <user action OR entity cluster> | `app/(app)/<x>/page.tsx` · page · **live**<br>`app/api/<x>/route.ts` · api · **live**<br>`jobs/<x>.ts` · job · **off** |
21
+ | <module> | ... | no | ... | _no entry point of its own — reached by `<module>` (live)_ |
22
+ | <module> | ... | no | ... | _dormant — no live entry point reaches it_ |
23
+
24
+ _This column grades the **product** severity of a finding, never its security severity. Every
25
+ routable entry point is covered by the kernel's security checklist whatever the status says._
22
26
 
23
27
  **Drift challenge answered**
24
28
 
25
- _"Does any module here own state a persona never interacts with, OR does any persona's journey traverse modules in a way the boundaries don't support?"_
29
+ _"Does any module own something no user ever reaches, or does any user journey cross boundaries
30
+ this map does not support?"_
26
31
 
27
32
  ## 2. Agent Orchestration Model
28
33
 
29
- _How LLM calls compose. Which modules call the LLM, what each call returns, how downstream modules consume the output. Tool boundaries. Where prompts live. For an AI-native SaaS, this is the meaty section — implicit "the LLM handles it" answers are rejected; named call sites and downstream consumers are required._
30
-
31
- **LLM call sites**
34
+ _How the LLM calls compose: which module calls, what comes back, who consumes it, where the prompts
35
+ live. Named call sites, not "the LLM handles it"._
32
36
 
33
37
  | Call site (module.fn) | Trigger | Model | Returns | Downstream consumer |
34
38
  | --- | --- | --- | --- | --- |
35
- | <module>.<function> | <what triggers this call> | <model identifier> | <structured-output schema or one-line description> | <module that consumes> |
39
+ | <module>.<function> | <what triggers it> | <model id> | <schema or one line> | <module> |
36
40
 
37
41
  **Tool boundaries**
38
42
 
39
- _Which tools each call site can invoke, and which are explicitly out-of-bounds. Names the kill switches for LLM-driven actions._
43
+ _Which tools each call site may invoke, which are out of bounds, and where the kill switch is._
40
44
 
41
45
  **Drift challenge answered**
42
46
 
43
- _"Does any LLM call site bypass the tool boundaries declared, or have an unnamed retry / fallback path?"_
47
+ _"Does any LLM call site bypass its declared tool boundaries, or have an unnamed retry or fallback
48
+ path?"_
44
49
 
45
50
  ## 3. Deterministic-vs-LLM Split
46
51
 
47
- _Per user-facing flow, the per-step decision: code or prompt, with a rationale that ties to a measurable property (determinism, cost, latency, capability). Implicit "AI handles it end-to-end" answers are rejected; per-step declaration is mandatory._
48
-
49
- **Flow tables**
50
-
51
- For each flow named in the brief's persona vignettes:
52
+ _Per flow, per step: code or prompt, and why. A flow that genuinely is one model call is one row
53
+ with its schema and its rationale — the rule is against the undeclared boundary, not against short
54
+ tables._
52
55
 
53
56
  ### Flow: <flow-name>
54
57
 
55
- _From <persona>'s Tuesday-morning workflow._
56
-
57
- | Step | Mechanism | Rationale (ties to measurable property) |
58
+ | Step | Mechanism | Rationale (names a property) |
58
59
  | --- | --- | --- |
59
- | 1. <step description> | deterministic \| llm | <one sentence — property: determinism \| cost \| latency \| capability> |
60
- | 2. ... | ... | ... |
60
+ | 1. <step> | deterministic \| llm | <one sentence — determinism \| cost \| latency \| capability> |
61
61
 
62
62
  **Drift challenge answered**
63
63
 
64
- _"Does any flow have a step where the mechanism is undeclared, or a rationale that doesn't tie to a measurable property?"_
64
+ _"Does any flow have a step whose mechanism is undeclared, or a rationale tied to no property?"_
65
65
 
66
66
  ## 4. State-Ownership Map
67
67
 
68
- _Where conversation state lives, workspace state lives, ephemeral / browser-session state lives. Each state class names its owning module from Section 1 and its persistence layer from `data-architecture.md`. State without an owning module triggers the drift challenge._
69
-
70
- **State classes**
68
+ _Where conversation state, workspace state and ephemeral state live. Each names its owning module
69
+ from Section 1 and its persistence layer from `data-architecture.md`._
71
70
 
72
71
  | State class | Owning module | Persistence layer | Lifetime |
73
72
  | --- | --- | --- | --- |
74
- | <e.g., conversation history> | <module from §1> | <entity from data-architecture OR "ephemeral"> | <durable \| session \| request> |
73
+ | <e.g. conversation history> | <module> | <entity OR "ephemeral"> | durable \| session \| request |
75
74
 
76
75
  **Drift challenge answered**
77
76
 
@@ -79,24 +78,13 @@ _"Does any state class lack an owning module from Section 1?"_
79
78
 
80
79
  ## 5. Surface Boundaries
81
80
 
82
- _What the project's `surfaces` enum contains. The canonical floor is `[ui, api, agent, db, auth, infra]`; projects may add (e.g., `payments`) or omit (e.g., no `auth` if relying on a host system). Each declared surface is mapped to at least one module from Section 1._
83
-
84
- **Surface declaration**
81
+ _Which surfaces this application has, and which module carries each. The usual set is `ui`, `api`,
82
+ `agent`, `db`, `auth`, `infra`; add or omit as the product requires._
85
83
 
86
- | Surface | In v1? | Modules carrying this surface | Deferred to |
87
- | --- | --- | --- | --- |
88
- | ui | yes/no | <module names> | <v2 \| later \| N/A> |
89
- | api | ... | ... | ... |
90
- | agent | ... | ... | ... |
91
- | db | ... | ... | ... |
92
- | auth | ... | ... | ... |
93
- | infra | ... | ... | ... |
94
- | <custom> | ... | ... | ... |
84
+ | Surface | Present? | Modules carrying it |
85
+ | --- | --- | --- |
86
+ | <surface> | yes \| no | <modules> |
95
87
 
96
88
  **Drift challenge answered**
97
89
 
98
- _"Does any surface in the enum have no module from Section 1 mapped to it?"_
99
-
100
- ## Coherence check (end-of-atom)
101
-
102
- _When the atom reaches terminal state, the agent re-runs all five drift challenges to ensure section-targeted edits did not silently break other sections. The coherence check is fatal — any unanswered challenge halts the commit._
90
+ _"Does any surface have no module mapped to it?"_
@@ -0,0 +1,107 @@
1
+ ---
2
+ id: code-standards
3
+ type: code-standards
4
+ updated: <YYYY-MM-DD>
5
+ ---
6
+
7
+ _The rules that need judgment. Size, typing, unread errors, console and `process.env` are the
8
+ linter's job (`hstack/templates/eslint-clean-code.mjs`, at `error`, with a suppressions ratchet);
9
+ nothing here repeats what it already fails. Read this file before writing application code;
10
+ `/hstack-wrap` reads the diff against it before the PR. Each rule carries one example from this
11
+ repo — the observed failure it exists for — so a reader knows the rule is about here, not about a
12
+ book. Fill the `Seen here` lines from the codebase; leave a rule's line empty when it has not
13
+ happened yet, and add the example in the PR where it does._
14
+
15
+ ## 1. Where things live
16
+
17
+ _The one sanctioned path for each cross-cutting concern. A second one is a finding._
18
+
19
+ | Concern | Module | What it gives you |
20
+ | --- | --- | --- |
21
+ | Logging | `<lib/observability/logger>` | structured, redacted, tenant-tagged |
22
+ | Configuration | `<lib/env>` | every variable validated once at boot |
23
+ | Database client | `<lib/supabase/{server,client,admin}>` | the RLS posture chosen for you |
24
+ | Errors | `<lib/errors>` | `AppError` with `code` and `cause`; the boundary handler |
25
+ | Retry / sleep / chunk | `<lib/retry>` | abort-aware, already tested |
26
+ | Shared UI primitives | `<components/ui>` | — |
27
+
28
+ ## 2. Before writing, search
29
+
30
+ Before adding a helper, a client, a wrapper, a parser or a formatter, grep for one that exists.
31
+ If it exists, use it. If it is almost right, fix it in place and move the callers. Never write a
32
+ second one beside the first — the copies drift, and the drift is a bug nobody planned.
33
+
34
+ Seen here: _<e.g. seven `sleep` definitions; three copies of the same vendor client, one with
35
+ retry and two without>_
36
+
37
+ ## 3. One function, one responsibility, one level of abstraction
38
+
39
+ A function either orchestrates — calls named steps in order — or is one step. When a reader needs
40
+ a comment to find where the next phase starts, the function is two functions and the comment is
41
+ the name of the second. Extract until each name says what its body does.
42
+
43
+ Seen here: _<e.g. a stream handler that gates, fetches, builds the prompt, persists, retries and
44
+ reports cost, in one body>_
45
+
46
+ ## 4. A React component does not talk to the network
47
+
48
+ Fetching, streaming, protocol decoding and retry live in a hook or a service module. The
49
+ component receives data and callbacks, and renders. A component that parses a response body is
50
+ a service wearing a template.
51
+
52
+ Seen here: _<e.g. a chat component parsing server-sent events by hand inside a mutation>_
53
+
54
+ ## 5. A file's name says what it contains, and it contains one thing
55
+
56
+ If the folder or the file cannot be named after its single responsibility, it has more than one.
57
+ One vendor client per file. No `helpers.ts` past two hundred lines. No file that is a barrel
58
+ (`index.ts`) and an implementation at the same time.
59
+
60
+ Seen here: _<e.g. a file named after one OAuth broker holding six vendor APIs>_
61
+
62
+ ## 6. Names describe behaviour, not category
63
+
64
+ `get*` reads and has no side effect. `is*` / `has*` return a boolean. A name that needs its
65
+ docblock to be understood is the wrong name. One word per concept across the codebase — pick one
66
+ of the synonyms and use it everywhere. No `v2`, `new`, `old`, `legacy` in a name: the name says
67
+ what it is, git says how old.
68
+
69
+ Seen here: _<e.g. `getX` that creates and connects; card / job / task for one entity>_
70
+
71
+ ## 7. A returned error is a handled error
72
+
73
+ Errors travel one way: thrown (with `cause`) from where they happen, caught once at the boundary
74
+ — the route, the job, the server action — where a single handler logs them and shapes the
75
+ response. In between, code neither logs-and-rethrows nor catches-and-continues. A `catch` either
76
+ handles, rethrows with `cause`, or carries a one-line justification for swallowing. A client
77
+ that returns `{ data, error }` instead of throwing has `error` read on every call. A write is
78
+ never followed by `ok: true` without its result being checked.
79
+
80
+ Seen here: _<e.g. an update whose result was never read, answered with `{ ok: true }`>_
81
+
82
+ ## 8. Replace, do not add beside
83
+
84
+ A new implementation of an existing thing ships in the PR that deletes the old one, with every
85
+ import moved. No parallel folder, no second system left wired "for now", no legacy path still
86
+ reachable from a live route.
87
+
88
+ Seen here: _<e.g. two component trees for one screen; three agent loops each with its own cost
89
+ tracker>_
90
+
91
+ ## 9. Delete what nothing imports
92
+
93
+ An unused file, export or dependency is removed in the PR that orphaned it, not recorded as
94
+ debt. Dead code is read by the next agent as live.
95
+
96
+ Seen here: _<e.g. whole component folders no route reaches>_
97
+
98
+ ## 10. Constants live in one place
99
+
100
+ Table names, status strings, limits, timeouts and model ids are imported from a constants module,
101
+ never retyped as literals at the call site. A rename is one edit.
102
+
103
+ Seen here: _<e.g. one table name typed by hand in a hundred places>_
104
+
105
+ **Drift challenge answered**
106
+
107
+ _"Which rule above has the repo violated since this file was last updated, and where?"_
@@ -1,127 +1,103 @@
1
1
  ---
2
2
  id: data-architecture
3
3
  type: data-architecture
4
- status: draft # draft | current | needs-refresh | archived
5
- owner: <git-handle>
6
- assumes-database: postgres # explicit so Phase 4 stack-decide can flag drift
4
+ assumes-database: postgres # explicit, so a database change surfaces as a contradiction
7
5
  schema-snapshot-date: <YYYY-MM-DD>
8
- rag-architecture-version: 1
9
- derived-from: [product-brief]
10
- downstream: [app-architecture, threat-model, hardening-checklist, module-spec/*]
11
- created: <YYYY-MM-DD>
12
6
  updated: <YYYY-MM-DD>
13
- schema-version: 1
14
7
  ---
15
8
 
16
9
  ## 1. Tenancy Model
17
10
 
18
- _The load-bearing question. The data-architect refuses to advance past this section until the tenant definition is concrete and ties to a persona in the brief._
11
+ _The load-bearing question. Nothing downstream stabilizes until this one is concrete._
19
12
 
20
13
  **A tenant is a ...**
21
14
 
22
- _One concrete noun from this product's own vocabulary, plus the rule for who is inside one and who is not. Common shapes, offered as examples rather than as a menu: the customer organization (one workspace per paying customer, users inside share visibility); a sub-team within that organization (multi-workspace per customer); the individual user (workspace-per-user). Products whose tenant is a project, a device, a contract, a site, a case or a season are ordinary and none of those three fit them — name yours._
15
+ _One concrete noun from this product's own vocabulary, plus the rule for who is inside one and who
16
+ is not. Common shapes, offered as examples rather than as a menu: the customer organization (users
17
+ inside share visibility); a sub-team within it; the individual user. Products whose tenant is a
18
+ project, a device, a contract, a site or a case are ordinary and none of those three fit them —
19
+ name yours._
23
20
 
24
21
  **Rationale**
25
22
 
26
- _One sentence tying the tenant to a persona in `product-brief.md`. Example: "The tenant is the customer organization, because Maya (Director of CS) shares visibility with her 6 reps and no v1 persona asks for sub-team isolation."_
23
+ _One sentence tying the tenant to a real user. Example: "The tenant is the customer organization,
24
+ because Maya shares visibility with her 6 reps and nobody has asked for sub-team isolation."_
27
25
 
28
26
  **Tenant identifier**
29
27
 
30
- _The column name and type used to scope every tenant-scoped table (`workspace_id uuid`, `org_id text`, etc.). All downstream RLS predicates reference this column._
28
+ _The column name and type scoping every tenant-scoped table (`workspace_id uuid`, `org_id text`).
29
+ Every RLS predicate below references this column._
31
30
 
32
31
  ## 2. Entity Graph
33
32
 
34
- _The set of entities and their relationships. Each entity must trace to either a persona-named action in `product-brief.md` or to the tenancy model from Section 1. Orphan entities halt with the drift challenge._
35
-
36
- **Entities**
33
+ _The entities and their relationships. Each entity traces to something a user does, or to the
34
+ tenancy model above._
37
35
 
38
36
  | Entity | Owns | Traces to | Relationships |
39
37
  | --- | --- | --- | --- |
40
- | <entity-name> | <one-line statement> | <persona-action OR tenancy-derived> | <FK refs> |
38
+ | <entity-name> | <one-line statement> | <user action OR tenancy-derived> | <FK refs> |
41
39
 
42
- **Conventions** (subsumed from prior template)
40
+ **Conventions**
43
41
 
44
- _Naming (snake_case, plural tables), timestamps (`created_at`, `updated_at` on every table, ISO 8601), soft-deletes vs hard-deletes per entity, UUID vs serial choice with rationale, audit fields if any._
42
+ _Naming (snake_case, plural tables), timestamps (`created_at`, `updated_at`, ISO 8601), soft- vs
43
+ hard-deletes per entity, UUID vs serial with rationale, audit fields if any._
45
44
 
46
45
  **Drift challenge answered**
47
46
 
48
- _"Does any entity here have no trace to a persona or feature in the brief? Name it."_
47
+ _"Does any entity here trace to nothing a user does? Name it."_
49
48
 
50
49
  ## 3. RLS Posture
51
50
 
52
- _Per-table policy sketch. Every entity from Section 2 falls into one of two categories — no third._
51
+ _Every entity from Section 2 falls into one of two categories — there is no third._
53
52
 
54
53
  **Tenant-scoped tables**
55
54
 
56
55
  | Table | Policy predicate sketch | Notes |
57
56
  | --- | --- | --- |
58
- | <table> | `<tenant-column> = current_setting('app.<tenant-column>')::uuid` | <e.g., RPC-only writes, no direct INSERT> |
57
+ | <table> | `<tenant-column> = current_setting('app.<tenant-column>')::uuid` | <e.g. RPC-only writes> |
59
58
 
60
59
  **Intentionally global tables**
61
60
 
62
61
  | Table | Why no RLS | Rationale |
63
62
  | --- | --- | --- |
64
- | <table> | <lookup data \| public reference \| ...> | <one sentence> |
63
+ | <table> | <lookup data \| public reference> | <one sentence> |
65
64
 
66
65
  **Drift challenge answered**
67
66
 
68
- _"Does any tenant-scoped entity have an RLS policy that the chosen tenancy model wouldn't enforce? Name it."_
67
+ _"Does any tenant-scoped entity have a policy the tenancy model would not enforce? Name it."_
69
68
 
70
69
  ## 4. RAG / pgvector
71
70
 
72
- _Whether v1 uses embeddings, what entity carries them, which embedding model, the tenant-scoped retrieval RPC signature. If v1 does not use RAG, mark this section `not-in-v1` with rationale and skip ahead._
73
-
74
- **Use in v1**
75
-
76
- _`yes` or `not-in-v1`. If not-in-v1, justify in one sentence (e.g., "v1 ships change-awareness without semantic retrieval; pgvector is a v2 layer")._
77
-
78
- **Embedding-bearing entities**
71
+ _Whether embeddings are in use, what carries them, which model, and the tenant-scoped retrieval
72
+ RPC. If they are not in use, say so in one sentence and skip ahead._
79
73
 
80
74
  | Entity | Embedding column | Model | Dimensions | Tenant-scoped retrieval RPC |
81
75
  | --- | --- | --- | --- | --- |
82
- | <entity> | <column name> | <e.g., openai:text-embedding-3-small> | <e.g., 1536> | <RPC signature with tenant predicate> |
83
-
84
- **Tenant-scoping requirement**
76
+ | <entity> | <column> | <e.g. openai:text-embedding-3-small> | <e.g. 1536> | <RPC signature with the tenant predicate> |
85
77
 
86
- _Every retrieval RPC MUST filter by the Section 1 tenant identifier. The drift challenge verifies this._
78
+ _Every retrieval RPC filters by the Section 1 tenant identifier, inside the similarity search and
79
+ not after it._
87
80
 
88
81
  **Drift challenge answered**
89
82
 
90
- _"Does any embedding-bearing entity have a retrieval RPC that bypasses tenant scoping? Name it."_
83
+ _"Does any embedding-bearing entity have a retrieval path that bypasses tenant scoping?"_
91
84
 
92
85
  ## 5. Migration Sketches
93
86
 
94
- _Postgres-dialect DDL sketches for the initial migrations, with `-- TODO: confirm` markers on uncertain parts. The implementer reads these during bootstrap and writes the actual `.sql` files; the sketches are intent, not executable. Typical ordering: schema → RLS → pgvector. Data must never land before RLS._
95
-
96
- **Migration sequence**
97
-
98
- ```
99
- m_0001_initial_schema.sql -- Section 2 entities, tables + FKs, no policies yet
100
- m_0002_rls_policies.sql -- Section 3 policies, applied per tenant-scoped table
101
- m_0003_pgvector_setup.sql -- Section 4 RPCs and indexes (skip if not-in-v1)
102
- m_0004_seed_global_data.sql -- Section 3 intentionally-global table seeds (optional)
103
- ```
104
-
105
- **Sketch — m_0001_initial_schema.sql**
87
+ _Postgres-dialect sketches with `-- TODO: confirm` markers. The migration that ships is written by
88
+ the change that needs it; these are intent, not executable. Ordering: schema → RLS → pgvector. Data
89
+ never lands before its policy._
106
90
 
107
91
  ```sql
108
- -- TODO: confirm UUID extension is enabled
109
- create extension if not exists "uuid-ossp";
110
-
111
- -- TODO: confirm column types and FK ON DELETE behaviors per entity
92
+ -- schema
112
93
  create table <entity> (
113
- id uuid primary key default uuid_generate_v4(),
94
+ id uuid primary key default gen_random_uuid(),
114
95
  <tenant-column> uuid not null references <tenant-table>(id) on delete cascade,
115
- -- entity-specific columns
116
96
  created_at timestamptz not null default now(),
117
97
  updated_at timestamptz not null default now()
118
98
  );
119
- ```
120
-
121
- **Sketch — m_0002_rls_policies.sql**
122
99
 
123
- ```sql
124
- -- TODO: confirm session-variable name matches app convention
100
+ -- policy, in the same migration as the table (kernel § Security checklist)
125
101
  alter table <entity> enable row level security;
126
102
 
127
103
  create policy <entity>_tenant_isolation on <entity>
@@ -129,38 +105,11 @@ create policy <entity>_tenant_isolation on <entity>
129
105
  using (<tenant-column> = current_setting('app.<tenant-column>')::uuid);
130
106
  ```
131
107
 
132
- **Sketch — m_0003_pgvector_setup.sql** (skip if Section 4 is `not-in-v1`)
133
-
134
- ```sql
135
- -- TODO: confirm pgvector version compatible with embedding model dimensions
136
- create extension if not exists vector;
137
-
138
- alter table <embedding-bearing-entity>
139
- add column embedding vector(<dimensions>);
140
-
141
- create index on <embedding-bearing-entity> using ivfflat (embedding vector_cosine_ops);
142
-
143
- -- TODO: confirm RPC signature and tenant predicate
144
- create or replace function nearest_<entity>(query_embedding vector, k int, p_<tenant-column> uuid)
145
- returns table (id uuid, score float4)
146
- language sql stable
147
- as $$
148
- select id, embedding <=> query_embedding as score
149
- from <embedding-bearing-entity>
150
- where <tenant-column> = p_<tenant-column>
151
- order by score
152
- limit k;
153
- $$;
154
- ```
155
-
156
- **Data lifecycle** (subsumed from prior template)
108
+ **Data lifecycle**
157
109
 
158
- _Retention windows per entity (e.g., "interactions: 18 months hot, archived after"), archival path, deletion guarantees (hard-delete vs soft-delete vs anonymize-and-retain). Lifecycle differences per tenant tier (free vs paid) noted here._
110
+ _Retention per entity, archival path, deletion guarantees (hard-delete, soft-delete, or
111
+ anonymize-and-retain)._
159
112
 
160
113
  **Drift challenge answered**
161
114
 
162
- _"Does any migration in the sketch sequence land data before its RLS policy? Name it."_
163
-
164
- ## Coherence check (end-of-atom)
165
-
166
- _When the atom reaches terminal state, the agent re-runs all five drift challenges. Any unanswered challenge halts the commit._
115
+ _"Does any migration in this sequence land data before its policy? Name it."_