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.
- package/CHANGELOG.md +53 -0
- package/README.md +76 -163
- package/VERSION +1 -1
- package/dist/commands/doctor.js +18 -8
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.js +7 -3
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/update.js +9 -2
- package/dist/commands/update.js.map +1 -1
- package/dist/lib/descriptions.js +10 -17
- package/dist/lib/descriptions.js.map +1 -1
- package/dist/lib/wire.js +166 -138
- package/dist/lib/wire.js.map +1 -1
- package/dist/manifest.js +41 -29
- package/dist/manifest.js.map +1 -1
- package/package.json +3 -4
- package/template/.claude/agents/adversarial-reviewer.md +44 -50
- package/template/.claude/agents/app-architect.md +44 -96
- package/template/.claude/agents/data-architect.md +46 -89
- package/template/.claude/agents/test-strategist.md +39 -68
- package/template/.claude/skills/hstack-adr-new/SKILL.md +23 -73
- package/template/.claude/skills/hstack-adversarial-review/SKILL.md +46 -152
- package/template/.claude/skills/hstack-adversarial-review/references/finding-categories.md +89 -103
- package/template/.claude/skills/hstack-app-architecture/SKILL.md +43 -68
- package/template/.claude/skills/hstack-commit/SKILL.md +27 -68
- package/template/.claude/skills/hstack-data-architecture/SKILL.md +34 -56
- package/template/.claude/skills/hstack-promote/SKILL.md +55 -0
- package/template/.claude/skills/hstack-story/SKILL.md +58 -0
- package/template/.claude/skills/hstack-test-audit/SKILL.md +64 -0
- package/template/.claude/skills/hstack-wrap/SKILL.md +50 -0
- package/template/.claude/skills/hstack-wrap/references/pr-description.md +45 -0
- package/template/KERNEL.md +75 -318
- package/template/templates/adr.md +2 -18
- package/template/templates/app-architecture.md +38 -50
- package/template/templates/code-standards.md +107 -0
- package/template/templates/data-architecture.md +38 -89
- package/template/templates/eslint-clean-code.mjs +322 -0
- package/template/templates/infrastructure.md +54 -148
- package/template/templates/roadmap.md +6 -12
- package/template/templates/story.md +7 -13
- package/template/templates/tech-debt.md +2 -37
- package/template/templates/tech-stack.md +2 -12
- package/template/.claude/agents/data-specialist.md +0 -81
- package/template/.claude/agents/implementer.md +0 -97
- package/template/.claude/agents/kernel-fit-analyst.md +0 -129
- package/template/.claude/agents/planner.md +0 -75
- package/template/.claude/agents/product-discovery.md +0 -129
- package/template/.claude/agents/product-manager.md +0 -76
- package/template/.claude/agents/researcher.md +0 -87
- package/template/.claude/agents/security-reviewer.md +0 -88
- package/template/.claude/agents/spec-author.md +0 -89
- package/template/.claude/agents/stack-architect.md +0 -108
- package/template/.claude/agents/ui-ux-briefer.md +0 -75
- package/template/.claude/agents/verifier.md +0 -81
- package/template/.claude/skills/hstack-branch/SKILL.md +0 -76
- package/template/.claude/skills/hstack-brownfield-init/SKILL.md +0 -131
- package/template/.claude/skills/hstack-change-new/SKILL.md +0 -84
- package/template/.claude/skills/hstack-change-plan/SKILL.md +0 -108
- package/template/.claude/skills/hstack-configure/SKILL.md +0 -99
- package/template/.claude/skills/hstack-coord/SKILL.md +0 -95
- package/template/.claude/skills/hstack-data-review/SKILL.md +0 -93
- package/template/.claude/skills/hstack-finalize/SKILL.md +0 -140
- package/template/.claude/skills/hstack-flag/SKILL.md +0 -99
- package/template/.claude/skills/hstack-greenfield-init/SKILL.md +0 -148
- package/template/.claude/skills/hstack-help/SKILL.md +0 -153
- package/template/.claude/skills/hstack-implement/SKILL.md +0 -155
- package/template/.claude/skills/hstack-kernel-fit-promote/SKILL.md +0 -125
- package/template/.claude/skills/hstack-kernel-fit-scan/SKILL.md +0 -142
- package/template/.claude/skills/hstack-kernel-fit-scan/references/slack-setup.md +0 -42
- package/template/.claude/skills/hstack-kernel-fit-triage/SKILL.md +0 -121
- package/template/.claude/skills/hstack-module-spec/SKILL.md +0 -78
- package/template/.claude/skills/hstack-product-discovery/SKILL.md +0 -75
- package/template/.claude/skills/hstack-research/SKILL.md +0 -115
- package/template/.claude/skills/hstack-scaffold/SKILL.md +0 -113
- package/template/.claude/skills/hstack-security-review/SKILL.md +0 -95
- package/template/.claude/skills/hstack-ship/SKILL.md +0 -119
- package/template/.claude/skills/hstack-stack-decide/SKILL.md +0 -81
- package/template/.claude/skills/hstack-story-draft/SKILL.md +0 -92
- package/template/.claude/skills/hstack-tech-debt-new/SKILL.md +0 -95
- package/template/.claude/skills/hstack-tech-debt-resolve/SKILL.md +0 -123
- package/template/.claude/skills/hstack-tech-debt-stale/SKILL.md +0 -86
- package/template/.claude/skills/hstack-tech-debt-wontfix/SKILL.md +0 -77
- package/template/.claude/skills/hstack-telemetry/SKILL.md +0 -73
- package/template/.claude/skills/hstack-test-plan/SKILL.md +0 -159
- package/template/.claude/skills/hstack-ui-brief/SKILL.md +0 -81
- package/template/.claude/skills/hstack-verify/SKILL.md +0 -128
- package/template/scripts/compute-merge-readiness.mjs +0 -780
- package/template/scripts/coord/coord_scan.py +0 -681
- package/template/scripts/run-gates.sh +0 -388
- package/template/scripts/telemetry/__init__.py +0 -6
- package/template/scripts/telemetry/insights/__init__.py +0 -0
- package/template/scripts/telemetry/insights/contract_drift.py +0 -137
- package/template/scripts/telemetry/insights/kernel_fit.py +0 -438
- package/template/scripts/telemetry/insights/overengineering.py +0 -115
- package/template/scripts/telemetry/insights/quality_outcomes.py +0 -131
- package/template/scripts/telemetry/insights/token_economics.py +0 -302
- package/template/scripts/telemetry/insights/workflow_shape.py +0 -198
- package/template/scripts/telemetry/parsers/__init__.py +0 -0
- package/template/scripts/telemetry/parsers/bodies.py +0 -87
- package/template/scripts/telemetry/parsers/commits.py +0 -219
- package/template/scripts/telemetry/parsers/frontmatter.py +0 -322
- package/template/scripts/telemetry/parsers/sidecars.py +0 -61
- package/template/scripts/telemetry/parsers/transcripts.py +0 -294
- package/template/scripts/telemetry/render.py +0 -460
- package/template/scripts/telemetry/report.py +0 -150
- package/template/scripts/telemetry/run_kernel_fit.py +0 -95
- package/template/scripts/telemetry/session_id.py +0 -139
- package/template/scripts/validate-spec.mjs +0 -3303
- package/template/templates/adversarial-review.md +0 -54
- package/template/templates/bootstrap.md +0 -105
- package/template/templates/change-spec.md +0 -82
- package/template/templates/ci-cd.md +0 -41
- package/template/templates/coord-message.md +0 -56
- package/template/templates/data-review.md +0 -54
- package/template/templates/discovery/brainstorm.md +0 -69
- package/template/templates/discovery/forcing-questions.md +0 -76
- package/template/templates/discovery/project-brief.md +0 -53
- package/template/templates/figma-handoff.md +0 -38
- package/template/templates/glossary.md +0 -20
- package/template/templates/hardening-checklist.md +0 -73
- package/template/templates/incident-runbook.md +0 -57
- package/template/templates/kernel-fit-finding.md +0 -63
- package/template/templates/kernel-fit-flag.md +0 -52
- package/template/templates/module-spec.md +0 -49
- package/template/templates/persona.md +0 -38
- package/template/templates/plan.md +0 -53
- package/template/templates/product-brief.md +0 -56
- package/template/templates/security-review.md +0 -63
- package/template/templates/telemetry-sidecar.md +0 -227
- package/template/templates/test-plan.md +0 -119
- package/template/templates/threat-model.md +0 -54
- package/template/templates/ui-brief.md +0 -49
- package/template/templates/verification.md +0 -63
- 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
|
-
|
|
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
|
|
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
|
|
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
|
-
**
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
| Step | Mechanism | Rationale (ties to measurable property) |
|
|
58
|
+
| Step | Mechanism | Rationale (names a property) |
|
|
58
59
|
| --- | --- | --- |
|
|
59
|
-
| 1. <step
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 |
|
|
87
|
-
| --- | --- | --- |
|
|
88
|
-
|
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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> | <
|
|
38
|
+
| <entity-name> | <one-line statement> | <user action OR tenancy-derived> | <FK refs> |
|
|
41
39
|
|
|
42
|
-
**Conventions**
|
|
40
|
+
**Conventions**
|
|
43
41
|
|
|
44
|
-
_Naming (snake_case, plural tables), timestamps (`created_at`, `updated_at
|
|
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
|
|
47
|
+
_"Does any entity here trace to nothing a user does? Name it."_
|
|
49
48
|
|
|
50
49
|
## 3. RLS Posture
|
|
51
50
|
|
|
52
|
-
|
|
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
|
|
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
|
|
63
|
+
| <table> | <lookup data \| public reference> | <one sentence> |
|
|
65
64
|
|
|
66
65
|
**Drift challenge answered**
|
|
67
66
|
|
|
68
|
-
_"Does any tenant-scoped entity have
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
--
|
|
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
|
|
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
|
-
|
|
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
|
-
**
|
|
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
|
|
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
|
|
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."_
|