wdi-method 0.5.4 → 0.5.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/wdi-method.js +17 -0
- package/kit/.constitution/method/README.md +5 -3
- package/kit/.constitution/method/document/architecture-guide.md +209 -209
- package/kit/.constitution/method/document/corpus-guide.md +2 -2
- package/kit/.constitution/method/document/sdd-guide.md +3 -2
- package/kit/.constitution/method/document/templates/architecture-spine.md +100 -100
- package/kit/.constitution/method/document/templates/inventory.md +2 -2
- package/kit/.constitution/method/document/templates/sdd.md +1 -1
- package/kit/.constitution/method/document/templates/srs.md +149 -149
- package/kit/.constitution/method/document/templates/uc.md +69 -69
- package/kit/.constitution/method/scripts/inventory.py +105 -293
- package/kit/.constitution/method/scripts/validate.py +1676 -1648
- package/kit/.constitution/method/why/portability.md +7 -0
- package/kit/.constitution/project/README.md +8 -0
- package/kit/.constitution/project/inventory-readers.py +85 -0
- package/kit/skills/wdi-blueprint/SKILL.md +203 -203
- package/kit/skills/wdi-build/SKILL.md +4 -2
- package/kit/skills/wdi-init/SKILL.md +38 -4
- package/kit/skills/wdi-log/SKILL.md +107 -107
- package/kit/skills/wdi-report/SKILL.md +6 -6
- package/kit-overlay/README.md +5 -3
- package/kit-overlay/portability.md +7 -0
- package/package.json +1 -1
- package/scaffold/.control/registry/decisions.yaml +3 -3
package/bin/wdi-method.js
CHANGED
|
@@ -61,6 +61,15 @@ const AGENT_LABELS = {
|
|
|
61
61
|
const BMAD_INSTALL = `npx bmad-method install`;
|
|
62
62
|
const REPO_URL = "https://github.com/wiradigitalid/wdi-method";
|
|
63
63
|
const HELP_SKILL = "wdi-help";
|
|
64
|
+
const INIT_SKILL = "wdi-init";
|
|
65
|
+
// The room's readers file is seeded as a skeleton and is useless until a product writes it. The
|
|
66
|
+
// flag is the skeleton's own declaration, so this reads the same thing the engine does rather than
|
|
67
|
+
// guessing from the file's size or its age.
|
|
68
|
+
function readersAreSkeleton(target) {
|
|
69
|
+
const file = path.join(target, ".constitution", "project", "inventory-readers.py");
|
|
70
|
+
if (!fs.existsSync(file)) return false;
|
|
71
|
+
return /^SKELETON\s*=\s*True\b/m.test(fs.readFileSync(file, "utf8"));
|
|
72
|
+
}
|
|
64
73
|
const BMAD_REPO = "https://github.com/bmad-code-org/BMAD-METHOD";
|
|
65
74
|
const WDI_REPO = "https://github.com/wiradigitalid/wdi-method";
|
|
66
75
|
|
|
@@ -800,6 +809,14 @@ function printSummary(target, agents, { first, was, written, skipped, skills, to
|
|
|
800
809
|
}
|
|
801
810
|
summaryLine("agents", agents.join(", ") || "none");
|
|
802
811
|
console.log("");
|
|
812
|
+
// The readers are the one seeded file that does nothing until somebody writes it, and its
|
|
813
|
+
// silence is expensive: inventory.py refuses to run and the reason is a folder deep. One line
|
|
814
|
+
// here, only while it is still the skeleton, so it stops appearing once it is done.
|
|
815
|
+
if (readersAreSkeleton(target)) {
|
|
816
|
+
summaryLine("todo", `${DIM}.constitution/project/inventory-readers.py${RESET} is a skeleton — ` +
|
|
817
|
+
`run the ${INIT_SKILL} skill, intent ${DIM}readers${RESET}, ` +
|
|
818
|
+
`to write it for this repo's stack`);
|
|
819
|
+
}
|
|
803
820
|
summaryLine("next", `invoke the ${HELP_SKILL} skill and ask what to do`);
|
|
804
821
|
summaryLine("", REPO_URL);
|
|
805
822
|
console.log(`${DIM}${"─".repeat(62)}${RESET}`);
|
|
@@ -68,6 +68,8 @@ used to reject a change.
|
|
|
68
68
|
## `scripts/`
|
|
69
69
|
|
|
70
70
|
`validate.py` — registry gates and the `.control/generated/` generator. `timeline.py` — the time
|
|
71
|
-
dimension from git history. `inventory.py` —
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
dimension from git history. `inventory.py` — compares the three inventories against the code
|
|
72
|
+
and reports the difference; it MUST NOT patch either side into agreement. It reads no code itself:
|
|
73
|
+
the patterns live in `../project/inventory-readers.py`, because comparing is generic and reading a
|
|
74
|
+
stack is not. That file ships as a skeleton and `wdi-init` intent `readers` writes it for the repo
|
|
75
|
+
in front of it. All three run through `uv run`.
|
|
@@ -1,209 +1,209 @@
|
|
|
1
|
-
---
|
|
2
|
-
status: Accepted
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# Architecture Guide
|
|
6
|
-
|
|
7
|
-
**Loaded when:** writing or changing the architecture spine, an `AD-N`, or the C4 set
|
|
8
|
-
|
|
9
|
-
The spine holds the invariants that stop separately built components from diverging. It is not a design
|
|
10
|
-
document — one component's design is its `SDD-<pc>.md`. If a statement only affects one component, it does
|
|
11
|
-
not belong here.
|
|
12
|
-
|
|
13
|
-
The spine, the C4 set, `cross-cutting.md`, and the three inventories are all **G3 Blueprint** output, written
|
|
14
|
-
by `wdi-blueprint` intent `platform`. All of them exist at every `mode`, including `catalog`: they belong to
|
|
15
|
-
the blueprint, and the blueprint is not touched by the depth knob.
|
|
16
|
-
|
|
17
|
-
## Home
|
|
18
|
-
|
|
19
|
-
`.how/_platform/ARCHITECTURE-SPINE.md` — a file directly in `_platform/`. The `architecture/` sub-folder is
|
|
20
|
-
**repealed**; one file does not earn a folder, and the extra level made the spine harder to find than the
|
|
21
|
-
things it constrains.
|
|
22
|
-
|
|
23
|
-
## Invariants only
|
|
24
|
-
|
|
25
|
-
An entry earns a place when breaking it in one component would break another. Everything else is a **seed**:
|
|
26
|
-
useful as a starting point, not binding.
|
|
27
|
-
|
|
28
|
-
| Kind of statement | Spine? |
|
|
29
|
-
|---|---|
|
|
30
|
-
| "Every service authenticates through the same token format" | Yes — invariant |
|
|
31
|
-
| "Money is stored as integer minor units, never float" | Yes — invariant |
|
|
32
|
-
| "We use
|
|
33
|
-
| "The repo is laid out as
|
|
34
|
-
| "The member portal caches its dashboard for 60 seconds" | No — one component's design |
|
|
35
|
-
|
|
36
|
-
Stack, tree shape, and data shapes are seeds and MUST be marked as such. Writing them as contracts makes the
|
|
37
|
-
spine wrong the first time anything is upgraded, and a spine that is wrong in a visible place stops being
|
|
38
|
-
read in the places where it is right.
|
|
39
|
-
|
|
40
|
-
## Every `AD-N` carries three things
|
|
41
|
-
|
|
42
|
-
| Field | States |
|
|
43
|
-
|---|---|
|
|
44
|
-
| **Binds** | Which components or containers this holds for. "All" is a valid answer and MUST be written, not left blank |
|
|
45
|
-
| **Prevents** | The concrete failure this exists to stop. Written as the thing going wrong, not as a principle |
|
|
46
|
-
| **Rule** | One sentence, quotable, checkable. If a reviewer cannot tell whether code obeys it, it is not a rule yet |
|
|
47
|
-
|
|
48
|
-
An `AD-N` with no **Prevents** is a preference. Preferences belong in
|
|
49
|
-
`.constitution/project/codebase-conventions-guide.md`, where nothing has to justify itself.
|
|
50
|
-
|
|
51
|
-
## The spine stops being touched every story
|
|
52
|
-
|
|
53
|
-
This is the change that ends the tax that was being paid before the information existed.
|
|
54
|
-
|
|
55
|
-
- **The spine holds invariants and nothing else.** It changes only when an `AD-N` is born or reversed, and
|
|
56
|
-
both are decision events. For a mid-sized product: once at the start, around 6–10 `AD-N`, then almost
|
|
57
|
-
never.
|
|
58
|
-
- **A story MUST NOT touch the spine.** A story that contradicts an `AD-N` **stops** and opens a `DEC-` —
|
|
59
|
-
the one case where recording a decision is still mandatory.
|
|
60
|
-
- **Editing an `AD-N` MUST NOT quietly reverse it.** A reversal is a decision, and it goes through
|
|
61
|
-
`wdi-decision` first.
|
|
62
|
-
- **An inventory is not the spine.** The three inventories are living registers, derived from code once code
|
|
63
|
-
exists. A plan-versus-reality difference is a validator finding, not a spine amendment.
|
|
64
|
-
|
|
65
|
-
## `AD-N` versus `DEC-NNN`
|
|
66
|
-
|
|
67
|
-
`AD-N` is a **living rule**, edited in place as understanding improves. `DEC-NNN` is a **decision event**,
|
|
68
|
-
never edited once `applied`, only superseded.
|
|
69
|
-
|
|
70
|
-
An `AD-N` usually has a `DEC-` behind it: the invariant is what people obey, the decision record is why it
|
|
71
|
-
exists and what it cost. The full comparison is in `decision-guide.md` and MUST NOT be restated here.
|
|
72
|
-
|
|
73
|
-
The spine MUST NOT name a decision's alternatives or its cost. Those live in the `DEC-` behind it; repeating
|
|
74
|
-
them here creates a second version that will drift.
|
|
75
|
-
|
|
76
|
-
## The C4 set, and what else a run produces
|
|
77
|
-
|
|
78
|
-
`bmad-architecture` runs at **initiative** altitude for the spine. Anything else the run produces at feature
|
|
79
|
-
altitude — a deck, a fuller solution document — is a **rendering**, not the spine, and the two MUST NOT be
|
|
80
|
-
confused:
|
|
81
|
-
|
|
82
|
-
| Rendering | Has a slot | What follows |
|
|
83
|
-
|---|---|---|
|
|
84
|
-
| The **C4 set** — L1, L2, and one L3 per `built: true` container **holding more than one Product Component** | Yes: `.how/_platform/c4-l1-system-context.md` · `c4-l2-containers.md` · `c4-l3-<container>.md` | `wdi-blueprint` lands it, amending rather than overwriting. Once landed it is corpus, and `c4-l2-containers.md` **owns** the container list. The next section owns what a container *is* |
|
|
85
|
-
| Deck, solution document, anything else | No | Stays in the run folder, cited by path, never promoted |
|
|
86
|
-
|
|
87
|
-
**Placement is what makes a C4 binding, and only for what its level owns.** Before placement it is one run's
|
|
88
|
-
drawing; after, it is the corpus's description of the system. The spine still wins on any invariant, because
|
|
89
|
-
the C4 set describes and the spine forbids. A C4 file MUST NOT be used to justify a rule; a rule that matters
|
|
90
|
-
belongs in an `AD-N`.
|
|
91
|
-
|
|
92
|
-
The C4 set is **living**. It is amended when a container is added or changed — it MUST NOT be regenerated from
|
|
93
|
-
scratch, which would drop the annotations three waves of amendment put there.
|
|
94
|
-
|
|
95
|
-
## What counts as a container, and what does not
|
|
96
|
-
|
|
97
|
-
Two questions, and **both MUST be yes**:
|
|
98
|
-
|
|
99
|
-
1. **Does it run its own code or store its own data?** Its own process, its own browser page, its own
|
|
100
|
-
engine — not a folder inside another container's process.
|
|
101
|
-
2. **Can it be replaced without rebuilding another container?** It has its own build or publish path.
|
|
102
|
-
|
|
103
|
-
**Shipping two containers in one release does not merge them.** One atomic deploy swapping a binary and
|
|
104
|
-
two browser bundles together is a deployment choice, and a deployment choice MUST NOT be read as an
|
|
105
|
-
architectural one. The failure this guards against is the quiet one: a browser bundle with its own
|
|
106
|
-
JavaScript filed as "static assets" of the web server, carrying real behaviour with no row, no owner, and
|
|
107
|
-
no NFR. Handwritten HTML that runs **no** code of its own is content the web server delivers; the moment
|
|
108
|
-
it carries a script, it passes question 1.
|
|
109
|
-
|
|
110
|
-
### `built` — the one boolean, and its four consequences
|
|
111
|
-
|
|
112
|
-
A container is inside the boundary whether or not we wrote it. `built:` records which:
|
|
113
|
-
|
|
114
|
-
| | `built: true` | `built: false` |
|
|
115
|
-
|---|---|---|
|
|
116
|
-
| An L3 | Yes, where it holds more than one PC | **Never.** No box inside it is ours to draw |
|
|
117
|
-
| An `LC` naming it as `container` | Yes | **Never** |
|
|
118
|
-
| A heading in `structure-codebase.md` | **Required** | **Never** — no code of ours lives there |
|
|
119
|
-
| Listed in a PC's `containers:` | Yes | **Never** — see the matrix below |
|
|
120
|
-
|
|
121
|
-
V25 checks all four. This is what makes the class settled rather than re-argued: a database and a web
|
|
122
|
-
server are containers, they carry NFRs, and they still produce no design artifact of ours.
|
|
123
|
-
|
|
124
|
-
**What IS ours about a `built: false` container MUST have a home outside the C4 set** — its configuration
|
|
125
|
-
and any invariant it enforces belong in an `AD-N` or in `cross-cutting.md`. A rule surviving only as a
|
|
126
|
-
note beside a C4 box is a rule nobody can find.
|
|
127
|
-
|
|
128
|
-
### External system — outside the boundary
|
|
129
|
-
|
|
130
|
-
**The line is who deploys the runtime.** We deploy it, whoever wrote it → container. Someone else
|
|
131
|
-
runs it and we call it or configure it through their control plane → external system.
|
|
132
|
-
|
|
133
|
-
An external system appears at **C4 L1 and nowhere else**. It MUST NOT be registered in `containers`,
|
|
134
|
-
MUST NOT be an `LC`'s `container`, and MUST NOT get a heading in the codebase map. What the product
|
|
135
|
-
depends on it for lives in `cross-cutting.md` or an integration contract.
|
|
136
|
-
|
|
137
|
-
### The PC × container matrix
|
|
138
|
-
|
|
139
|
-
A PC and a container cross, so neither list implies the other — and the crossing is what a builder needs
|
|
140
|
-
first: *which container does this promise live in, and is it more than one?*
|
|
141
|
-
|
|
142
|
-
- The **SSOT is each PC's `containers:`** in `components.yaml`; C4 L2 renders it, and V25 fails when the
|
|
143
|
-
two disagree.
|
|
144
|
-
- Complete at **G3** for every PC. It is blueprint content, so `mode` does not touch it.
|
|
145
|
-
- A PC MUST list every `built: true` container it lives in. Listing only the main one is the error the
|
|
146
|
-
matrix exists to catch.
|
|
147
|
-
|
|
148
|
-
### Which C4 files exist, and when
|
|
149
|
-
|
|
150
|
-
`artifact-map.md` owns the schedule: all three at **G3**, at every `mode`. Two conditions belong here.
|
|
151
|
-
|
|
152
|
-
**L2 MUST be complete** — every container, plus the matrix. **L3 exists once per `built: true` container
|
|
153
|
-
holding more than one PC**; a one-PC container needs none because the matrix already places it.
|
|
154
|
-
|
|
155
|
-
**Not one of the three waits for a wave.** Which container a PC lives in cannot be discovered by a wave,
|
|
156
|
-
because a wave picks its stories from that answer — a wave forced to invent it answers a G3 question with
|
|
157
|
-
a fraction of G3's information.
|
|
158
|
-
|
|
159
|
-
## Cross-cutting
|
|
160
|
-
|
|
161
|
-
`.how/_platform/cross-cutting.md` holds what is defined once for the whole product, the error envelope first
|
|
162
|
-
among them. Every contract references it rather than restating it.
|
|
163
|
-
|
|
164
|
-
A rule that belongs there MUST NOT also be written as an `AD-N` unless breaking it in one component breaks
|
|
165
|
-
another. One fact, one home.
|
|
166
|
-
|
|
167
|
-
**The platform MAY own things, and owning one costs a row here.** `_platform` is a legitimate owner in every
|
|
168
|
-
position that asks which component owns something — a domain entity through `platform_owns`, an inventory row,
|
|
169
|
-
an `LC`. Whatever it owns MUST be described under `## Platform-owned` in this file: what it is, its kind, why
|
|
170
|
-
no component's promise explains it, who touches it, and the shape every toucher obeys. V21 checks that second
|
|
171
|
-
half, because a platform that owns something without documenting it has taken ownership without taking
|
|
172
|
-
responsibility.
|
|
173
|
-
|
|
174
|
-
Four kinds qualify today — data, endpoint, job, screen — and the list is open. What is **not** open is the
|
|
175
|
-
test, and `corpus-guide.md` owns it: no single component's promise explains it, **and** more than one
|
|
176
|
-
component depends on it. Failing either half, it belongs to a component.
|
|
177
|
-
|
|
178
|
-
`_platform` is **not** a Product Component — `corpus-guide.md` owns that distinction and the test for when an
|
|
179
|
-
entity legitimately belongs here. It has no `mode` and no `risk_accepted`: the documents in this folder exist
|
|
180
|
-
at every mode.
|
|
181
|
-
|
|
182
|
-
## Binding order
|
|
183
|
-
|
|
184
|
-
Spine first, then the SDD, then `bmad-spec`. An `SDD-<pc>.md` written before the spine will be rewritten,
|
|
185
|
-
because the constraints it was supposed to inherit did not exist yet. A `SPEC.md` written before the SDD has
|
|
186
|
-
nothing to project. `bmad-spec` MUST keep `AD` ids stable so downstream artifacts can cite them.
|
|
187
|
-
|
|
188
|
-
## Inheritance downward
|
|
189
|
-
|
|
190
|
-
From `mode: guarded` up, every `AD-N` that reaches a component MUST appear in that component's SDD under
|
|
191
|
-
**Inherited Constraints**, quoted rather than paraphrased. Below `guarded` the quoting is not written, and the
|
|
192
|
-
spine still binds — an invariant does not stop holding because a document is thin.
|
|
193
|
-
|
|
194
|
-
## Review is manual here
|
|
195
|
-
|
|
196
|
-
`bmad-architecture` excludes the spine from `doc_standards` deliberately — the spine is terse by design and
|
|
197
|
-
prose polish softens rules that need to stay rigid. Two consequences:
|
|
198
|
-
|
|
199
|
-
- This guide is installed as `persistent_facts`, not `doc_standards`.
|
|
200
|
-
- `bmad-review` over the spine MUST be invoked through `wdi-review` before G3 closes.
|
|
201
|
-
|
|
202
|
-
## Rules
|
|
203
|
-
|
|
204
|
-
- The spine MUST stay short enough to be read in one sitting. An `AD-N` nobody remembers is not an invariant,
|
|
205
|
-
it is a document.
|
|
206
|
-
- An `AD-N` MUST NOT be added because something feels important. The test is only ever: does breaking this in
|
|
207
|
-
one place break another?
|
|
208
|
-
- Structure statements MUST be marked as seeds, and MUST NOT be checked as if they were rules.
|
|
209
|
-
- Memlog goes to `.control/memlog/spine.md` via `--path`, never beside the spine.
|
|
1
|
+
---
|
|
2
|
+
status: Accepted
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Architecture Guide
|
|
6
|
+
|
|
7
|
+
**Loaded when:** writing or changing the architecture spine, an `AD-N`, or the C4 set
|
|
8
|
+
|
|
9
|
+
The spine holds the invariants that stop separately built components from diverging. It is not a design
|
|
10
|
+
document — one component's design is its `SDD-<pc>.md`. If a statement only affects one component, it does
|
|
11
|
+
not belong here.
|
|
12
|
+
|
|
13
|
+
The spine, the C4 set, `cross-cutting.md`, and the three inventories are all **G3 Blueprint** output, written
|
|
14
|
+
by `wdi-blueprint` intent `platform`. All of them exist at every `mode`, including `catalog`: they belong to
|
|
15
|
+
the blueprint, and the blueprint is not touched by the depth knob.
|
|
16
|
+
|
|
17
|
+
## Home
|
|
18
|
+
|
|
19
|
+
`.how/_platform/ARCHITECTURE-SPINE.md` — a file directly in `_platform/`. The `architecture/` sub-folder is
|
|
20
|
+
**repealed**; one file does not earn a folder, and the extra level made the spine harder to find than the
|
|
21
|
+
things it constrains.
|
|
22
|
+
|
|
23
|
+
## Invariants only
|
|
24
|
+
|
|
25
|
+
An entry earns a place when breaking it in one component would break another. Everything else is a **seed**:
|
|
26
|
+
useful as a starting point, not binding.
|
|
27
|
+
|
|
28
|
+
| Kind of statement | Spine? |
|
|
29
|
+
|---|---|
|
|
30
|
+
| "Every service authenticates through the same token format" | Yes — invariant |
|
|
31
|
+
| "Money is stored as integer minor units, never float" | Yes — invariant |
|
|
32
|
+
| "We use version 11 of our database and version 1.23 of our language" | No — seed. It informs, it does not forbid |
|
|
33
|
+
| "The repo is laid out as one folder per deployable" | No — seed, and `structure-codebase.md` describes it |
|
|
34
|
+
| "The member portal caches its dashboard for 60 seconds" | No — one component's design |
|
|
35
|
+
|
|
36
|
+
Stack, tree shape, and data shapes are seeds and MUST be marked as such. Writing them as contracts makes the
|
|
37
|
+
spine wrong the first time anything is upgraded, and a spine that is wrong in a visible place stops being
|
|
38
|
+
read in the places where it is right.
|
|
39
|
+
|
|
40
|
+
## Every `AD-N` carries three things
|
|
41
|
+
|
|
42
|
+
| Field | States |
|
|
43
|
+
|---|---|
|
|
44
|
+
| **Binds** | Which components or containers this holds for. "All" is a valid answer and MUST be written, not left blank |
|
|
45
|
+
| **Prevents** | The concrete failure this exists to stop. Written as the thing going wrong, not as a principle |
|
|
46
|
+
| **Rule** | One sentence, quotable, checkable. If a reviewer cannot tell whether code obeys it, it is not a rule yet |
|
|
47
|
+
|
|
48
|
+
An `AD-N` with no **Prevents** is a preference. Preferences belong in
|
|
49
|
+
`.constitution/project/codebase-conventions-guide.md`, where nothing has to justify itself.
|
|
50
|
+
|
|
51
|
+
## The spine stops being touched every story
|
|
52
|
+
|
|
53
|
+
This is the change that ends the tax that was being paid before the information existed.
|
|
54
|
+
|
|
55
|
+
- **The spine holds invariants and nothing else.** It changes only when an `AD-N` is born or reversed, and
|
|
56
|
+
both are decision events. For a mid-sized product: once at the start, around 6–10 `AD-N`, then almost
|
|
57
|
+
never.
|
|
58
|
+
- **A story MUST NOT touch the spine.** A story that contradicts an `AD-N` **stops** and opens a `DEC-` —
|
|
59
|
+
the one case where recording a decision is still mandatory.
|
|
60
|
+
- **Editing an `AD-N` MUST NOT quietly reverse it.** A reversal is a decision, and it goes through
|
|
61
|
+
`wdi-decision` first.
|
|
62
|
+
- **An inventory is not the spine.** The three inventories are living registers, derived from code once code
|
|
63
|
+
exists. A plan-versus-reality difference is a validator finding, not a spine amendment.
|
|
64
|
+
|
|
65
|
+
## `AD-N` versus `DEC-NNN`
|
|
66
|
+
|
|
67
|
+
`AD-N` is a **living rule**, edited in place as understanding improves. `DEC-NNN` is a **decision event**,
|
|
68
|
+
never edited once `applied`, only superseded.
|
|
69
|
+
|
|
70
|
+
An `AD-N` usually has a `DEC-` behind it: the invariant is what people obey, the decision record is why it
|
|
71
|
+
exists and what it cost. The full comparison is in `decision-guide.md` and MUST NOT be restated here.
|
|
72
|
+
|
|
73
|
+
The spine MUST NOT name a decision's alternatives or its cost. Those live in the `DEC-` behind it; repeating
|
|
74
|
+
them here creates a second version that will drift.
|
|
75
|
+
|
|
76
|
+
## The C4 set, and what else a run produces
|
|
77
|
+
|
|
78
|
+
`bmad-architecture` runs at **initiative** altitude for the spine. Anything else the run produces at feature
|
|
79
|
+
altitude — a deck, a fuller solution document — is a **rendering**, not the spine, and the two MUST NOT be
|
|
80
|
+
confused:
|
|
81
|
+
|
|
82
|
+
| Rendering | Has a slot | What follows |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| The **C4 set** — L1, L2, and one L3 per `built: true` container **holding more than one Product Component** | Yes: `.how/_platform/c4-l1-system-context.md` · `c4-l2-containers.md` · `c4-l3-<container>.md` | `wdi-blueprint` lands it, amending rather than overwriting. Once landed it is corpus, and `c4-l2-containers.md` **owns** the container list. The next section owns what a container *is* |
|
|
85
|
+
| Deck, solution document, anything else | No | Stays in the run folder, cited by path, never promoted |
|
|
86
|
+
|
|
87
|
+
**Placement is what makes a C4 binding, and only for what its level owns.** Before placement it is one run's
|
|
88
|
+
drawing; after, it is the corpus's description of the system. The spine still wins on any invariant, because
|
|
89
|
+
the C4 set describes and the spine forbids. A C4 file MUST NOT be used to justify a rule; a rule that matters
|
|
90
|
+
belongs in an `AD-N`.
|
|
91
|
+
|
|
92
|
+
The C4 set is **living**. It is amended when a container is added or changed — it MUST NOT be regenerated from
|
|
93
|
+
scratch, which would drop the annotations three waves of amendment put there.
|
|
94
|
+
|
|
95
|
+
## What counts as a container, and what does not
|
|
96
|
+
|
|
97
|
+
Two questions, and **both MUST be yes**:
|
|
98
|
+
|
|
99
|
+
1. **Does it run its own code or store its own data?** Its own process, its own browser page, its own
|
|
100
|
+
engine — not a folder inside another container's process.
|
|
101
|
+
2. **Can it be replaced without rebuilding another container?** It has its own build or publish path.
|
|
102
|
+
|
|
103
|
+
**Shipping two containers in one release does not merge them.** One atomic deploy swapping a binary and
|
|
104
|
+
two browser bundles together is a deployment choice, and a deployment choice MUST NOT be read as an
|
|
105
|
+
architectural one. The failure this guards against is the quiet one: a browser bundle with its own
|
|
106
|
+
JavaScript filed as "static assets" of the web server, carrying real behaviour with no row, no owner, and
|
|
107
|
+
no NFR. Handwritten HTML that runs **no** code of its own is content the web server delivers; the moment
|
|
108
|
+
it carries a script, it passes question 1.
|
|
109
|
+
|
|
110
|
+
### `built` — the one boolean, and its four consequences
|
|
111
|
+
|
|
112
|
+
A container is inside the boundary whether or not we wrote it. `built:` records which:
|
|
113
|
+
|
|
114
|
+
| | `built: true` | `built: false` |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| An L3 | Yes, where it holds more than one PC | **Never.** No box inside it is ours to draw |
|
|
117
|
+
| An `LC` naming it as `container` | Yes | **Never** |
|
|
118
|
+
| A heading in `structure-codebase.md` | **Required** | **Never** — no code of ours lives there |
|
|
119
|
+
| Listed in a PC's `containers:` | Yes | **Never** — see the matrix below |
|
|
120
|
+
|
|
121
|
+
V25 checks all four. This is what makes the class settled rather than re-argued: a database and a web
|
|
122
|
+
server are containers, they carry NFRs, and they still produce no design artifact of ours.
|
|
123
|
+
|
|
124
|
+
**What IS ours about a `built: false` container MUST have a home outside the C4 set** — its configuration
|
|
125
|
+
and any invariant it enforces belong in an `AD-N` or in `cross-cutting.md`. A rule surviving only as a
|
|
126
|
+
note beside a C4 box is a rule nobody can find.
|
|
127
|
+
|
|
128
|
+
### External system — outside the boundary
|
|
129
|
+
|
|
130
|
+
**The line is who deploys the runtime.** We deploy it, whoever wrote it → container. Someone else
|
|
131
|
+
runs it and we call it or configure it through their control plane → external system.
|
|
132
|
+
|
|
133
|
+
An external system appears at **C4 L1 and nowhere else**. It MUST NOT be registered in `containers`,
|
|
134
|
+
MUST NOT be an `LC`'s `container`, and MUST NOT get a heading in the codebase map. What the product
|
|
135
|
+
depends on it for lives in `cross-cutting.md` or an integration contract.
|
|
136
|
+
|
|
137
|
+
### The PC × container matrix
|
|
138
|
+
|
|
139
|
+
A PC and a container cross, so neither list implies the other — and the crossing is what a builder needs
|
|
140
|
+
first: *which container does this promise live in, and is it more than one?*
|
|
141
|
+
|
|
142
|
+
- The **SSOT is each PC's `containers:`** in `components.yaml`; C4 L2 renders it, and V25 fails when the
|
|
143
|
+
two disagree.
|
|
144
|
+
- Complete at **G3** for every PC. It is blueprint content, so `mode` does not touch it.
|
|
145
|
+
- A PC MUST list every `built: true` container it lives in. Listing only the main one is the error the
|
|
146
|
+
matrix exists to catch.
|
|
147
|
+
|
|
148
|
+
### Which C4 files exist, and when
|
|
149
|
+
|
|
150
|
+
`artifact-map.md` owns the schedule: all three at **G3**, at every `mode`. Two conditions belong here.
|
|
151
|
+
|
|
152
|
+
**L2 MUST be complete** — every container, plus the matrix. **L3 exists once per `built: true` container
|
|
153
|
+
holding more than one PC**; a one-PC container needs none because the matrix already places it.
|
|
154
|
+
|
|
155
|
+
**Not one of the three waits for a wave.** Which container a PC lives in cannot be discovered by a wave,
|
|
156
|
+
because a wave picks its stories from that answer — a wave forced to invent it answers a G3 question with
|
|
157
|
+
a fraction of G3's information.
|
|
158
|
+
|
|
159
|
+
## Cross-cutting
|
|
160
|
+
|
|
161
|
+
`.how/_platform/cross-cutting.md` holds what is defined once for the whole product, the error envelope first
|
|
162
|
+
among them. Every contract references it rather than restating it.
|
|
163
|
+
|
|
164
|
+
A rule that belongs there MUST NOT also be written as an `AD-N` unless breaking it in one component breaks
|
|
165
|
+
another. One fact, one home.
|
|
166
|
+
|
|
167
|
+
**The platform MAY own things, and owning one costs a row here.** `_platform` is a legitimate owner in every
|
|
168
|
+
position that asks which component owns something — a domain entity through `platform_owns`, an inventory row,
|
|
169
|
+
an `LC`. Whatever it owns MUST be described under `## Platform-owned` in this file: what it is, its kind, why
|
|
170
|
+
no component's promise explains it, who touches it, and the shape every toucher obeys. V21 checks that second
|
|
171
|
+
half, because a platform that owns something without documenting it has taken ownership without taking
|
|
172
|
+
responsibility.
|
|
173
|
+
|
|
174
|
+
Four kinds qualify today — data, endpoint, job, screen — and the list is open. What is **not** open is the
|
|
175
|
+
test, and `corpus-guide.md` owns it: no single component's promise explains it, **and** more than one
|
|
176
|
+
component depends on it. Failing either half, it belongs to a component.
|
|
177
|
+
|
|
178
|
+
`_platform` is **not** a Product Component — `corpus-guide.md` owns that distinction and the test for when an
|
|
179
|
+
entity legitimately belongs here. It has no `mode` and no `risk_accepted`: the documents in this folder exist
|
|
180
|
+
at every mode.
|
|
181
|
+
|
|
182
|
+
## Binding order
|
|
183
|
+
|
|
184
|
+
Spine first, then the SDD, then `bmad-spec`. An `SDD-<pc>.md` written before the spine will be rewritten,
|
|
185
|
+
because the constraints it was supposed to inherit did not exist yet. A `SPEC.md` written before the SDD has
|
|
186
|
+
nothing to project. `bmad-spec` MUST keep `AD` ids stable so downstream artifacts can cite them.
|
|
187
|
+
|
|
188
|
+
## Inheritance downward
|
|
189
|
+
|
|
190
|
+
From `mode: guarded` up, every `AD-N` that reaches a component MUST appear in that component's SDD under
|
|
191
|
+
**Inherited Constraints**, quoted rather than paraphrased. Below `guarded` the quoting is not written, and the
|
|
192
|
+
spine still binds — an invariant does not stop holding because a document is thin.
|
|
193
|
+
|
|
194
|
+
## Review is manual here
|
|
195
|
+
|
|
196
|
+
`bmad-architecture` excludes the spine from `doc_standards` deliberately — the spine is terse by design and
|
|
197
|
+
prose polish softens rules that need to stay rigid. Two consequences:
|
|
198
|
+
|
|
199
|
+
- This guide is installed as `persistent_facts`, not `doc_standards`.
|
|
200
|
+
- `bmad-review` over the spine MUST be invoked through `wdi-review` before G3 closes.
|
|
201
|
+
|
|
202
|
+
## Rules
|
|
203
|
+
|
|
204
|
+
- The spine MUST stay short enough to be read in one sitting. An `AD-N` nobody remembers is not an invariant,
|
|
205
|
+
it is a document.
|
|
206
|
+
- An `AD-N` MUST NOT be added because something feels important. The test is only ever: does breaking this in
|
|
207
|
+
one place break another?
|
|
208
|
+
- Structure statements MUST be marked as seeds, and MUST NOT be checked as if they were rules.
|
|
209
|
+
- Memlog goes to `.control/memlog/spine.md` via `--path`, never beside the spine.
|
|
@@ -159,11 +159,11 @@ outbound channel) · **endpoint** (`/health`, `robots.txt` — plumbing no `FR`
|
|
|
159
159
|
(none yet).
|
|
160
160
|
|
|
161
161
|
Failing either half, it belongs to a Product Component — and the component is found by asking which `FR`
|
|
162
|
-
would have to be withdrawn for the entity to stop being needed. Two examples of the trap
|
|
162
|
+
would have to be withdrawn for the entity to stop being needed. Two examples of the trap:
|
|
163
163
|
|
|
164
164
|
| Entity | Looks platform-shaped | Actually |
|
|
165
165
|
|---|---|---|
|
|
166
|
-
| `
|
|
166
|
+
| `activity_events` | product-wide telemetry, several components write it | **one component** — an `FR` promises somebody can SEE those counts, and withdrawing it is what would make the table unnecessary |
|
|
167
167
|
| `email_logs` | one component sends first | **`_platform`** — it is the trace of one outbound channel that order notifications and password recovery both use, and neither promise is why the channel exists |
|
|
168
168
|
|
|
169
169
|
**One guard, and it is what stops this becoming a drawer:** everything `_platform` owns — in any position —
|
|
@@ -159,8 +159,9 @@ An unlabelled claim is read as verified. That is why the labels are mandatory an
|
|
|
159
159
|
|
|
160
160
|
- A claim is raised to verified only after the evidence it names has actually been read. Reading the
|
|
161
161
|
controller does not verify the repository.
|
|
162
|
-
- The raise MUST record **what was read**. "Verified against
|
|
163
|
-
integration test" is a raise; "verified" is not.
|
|
162
|
+
- The raise MUST record **what was read**. "Verified against `<the file that was read>` and its
|
|
163
|
+
integration test" is a raise; "verified" is not. There is no example path here on purpose: a guide
|
|
164
|
+
is portable, and naming a tree — or even a file extension — teaches one product's stack, not the rule.
|
|
164
165
|
- `[PARTIAL]` MUST state what is **not** covered. A partial that only says "partial" is an unlabelled claim
|
|
165
166
|
wearing a label.
|
|
166
167
|
- A claim MUST NOT be raised because it has survived several readings. Familiarity is not evidence.
|