@behaghel/pi-domain-tree 0.4.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/LICENSE +373 -0
- package/README.md +173 -0
- package/package.json +33 -0
- package/pi/domain-core.ts +1065 -0
- package/pi/extension.ts +1111 -0
- package/skills/domain-navigator/SKILL.md +103 -0
- package/skills/domain-navigator/references/conventions.md +228 -0
- package/skills/domain-navigator/references/manifest-schema.md +277 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: domain-navigator
|
|
3
|
+
description: |
|
|
4
|
+
Navigate and enforce domain-driven codebase structure. Use when the user references a domain, asks where code or specs live, wants to understand project structure, asks about domain boundaries, or when you need to locate the right spec or code for a given feature area. Also use when creating new files to ensure they land in the correct domain namespace.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Domain Navigator
|
|
8
|
+
|
|
9
|
+
Provides domain-aware navigation and enforcement for codebases structured around `domains.yaml`.
|
|
10
|
+
|
|
11
|
+
Load `references/manifest-schema.md` when working with `domains.yaml`.
|
|
12
|
+
Load `references/conventions.md` when making decisions about file placement or domain boundaries.
|
|
13
|
+
|
|
14
|
+
## When to activate
|
|
15
|
+
|
|
16
|
+
- User references a domain or feature area ("the issuance flow", "wallet onboarding")
|
|
17
|
+
- User creates a new file and you need to decide where it belongs
|
|
18
|
+
- User asks "where does X live?" or "what domain owns Y?"
|
|
19
|
+
- Before starting spec collection (to scope which domain the spec belongs to)
|
|
20
|
+
- Before planning TDD iterations (to scope which domain boundaries apply)
|
|
21
|
+
|
|
22
|
+
## Core behavior
|
|
23
|
+
|
|
24
|
+
### 1. Load the domain tree
|
|
25
|
+
|
|
26
|
+
1. Read `domains.yaml` at the project root.
|
|
27
|
+
2. If it doesn't exist, tell the user: "No domain tree found. Run `/domain-tree:init` to create one."
|
|
28
|
+
3. Parse explicit `kind: group` entries as structural namespaces, then build a model of bounded-context domains, subdomains, code paths, and spec paths. Groups are not domains and own no code or specifications.
|
|
29
|
+
|
|
30
|
+
### 2. Resolve domain from context
|
|
31
|
+
|
|
32
|
+
When the user describes work or you're about to modify code:
|
|
33
|
+
|
|
34
|
+
1. Match the target file(s) against `code` paths in the manifest; the most-specific child path wins.
|
|
35
|
+
2. Identify the governing domain, subdomain, and **domain type** (core/supporting/generic/shared-kernel).
|
|
36
|
+
3. Locate the corresponding colocated spec path (by default, the first `code` path; `spec` may override it).
|
|
37
|
+
4. Report: "This is in the **[domain] > [subdomain]** namespace (type: **[type]**). Spec at `[code path]/README.md`. Code at `[code paths]`."
|
|
38
|
+
|
|
39
|
+
If a file doesn't match any domain:
|
|
40
|
+
- Flag it: "This file isn't covered by any domain in `domains.yaml`. Should we add it to an existing domain or create a new one?"
|
|
41
|
+
|
|
42
|
+
### 3. Enforce placement on new files
|
|
43
|
+
|
|
44
|
+
When creating new files:
|
|
45
|
+
|
|
46
|
+
1. Determine which domain the file belongs to based on its purpose.
|
|
47
|
+
2. Check that the target path is within that domain's `code` paths.
|
|
48
|
+
3. If it isn't, suggest the correct location: "This looks like it belongs in **[domain]**. The convention is `[correct path]`."
|
|
49
|
+
4. If a new subdomain is needed, propose updating `domains.yaml` first.
|
|
50
|
+
|
|
51
|
+
### 4. Spec-on-touch (classification-aware)
|
|
52
|
+
|
|
53
|
+
Default to a domain specification containing durable, present-tense domain truth. Define each concept, invariant, or contract once at its narrowest owner and link to it elsewhere. Use standard relative Markdown links and resolve canonical terms or aliases with `domain_tree_resolve_term`; link meaningful first occurrences to the one repository-wide owner. Use a declared system spec only for a stable RFC 2119 requirement that cannot belong to one domain. Keep temporary scope, acceptance criteria, sequencing, rollout, migration, progress, and verification plans in non-normative iteration artifacts outside both corpora.
|
|
54
|
+
|
|
55
|
+
When editing production code:
|
|
56
|
+
|
|
57
|
+
1. Resolve the governing domain and its `type`.
|
|
58
|
+
2. Check for the domain's normative `README.md`; sibling Markdown is normative only when it declares `domain` and `status` frontmatter.
|
|
59
|
+
3. Apply rigor based on domain type:
|
|
60
|
+
- **core** — Hard block. "No spec exists for **[domain]** (core). This domain requires a spec before any code change."
|
|
61
|
+
- **supporting** — Warning. "No spec exists for **[domain]** yet. Consider writing one before this change."
|
|
62
|
+
- **generic** — Soft note. "No spec for **[domain]**. Only needed if you're changing the integration boundary."
|
|
63
|
+
- **shared-kernel** — Hard block. "This is shared kernel — changes affect all consumers. Spec required, and consumers must be notified."
|
|
64
|
+
4. If a spec exists, check its `status` field:
|
|
65
|
+
- `draft` — "This spec is a draft. Verify it's accurate before implementing against it."
|
|
66
|
+
- `approved` — proceed normally.
|
|
67
|
+
- `stale` — "This spec may be outdated. Review it before implementation."
|
|
68
|
+
|
|
69
|
+
### 5. Cross-domain awareness (context-map-driven)
|
|
70
|
+
|
|
71
|
+
When work spans multiple domains:
|
|
72
|
+
|
|
73
|
+
1. Identify all affected domains.
|
|
74
|
+
2. Consult semantic `context-map` entries (`provider`, `consumers`, `pattern`, and canonical `contract`) for the declared relationship.
|
|
75
|
+
3. If a relationship exists:
|
|
76
|
+
- Report the pattern: "This crosses **[domain A]** → **[domain B]** (pattern: **[pattern]**)."
|
|
77
|
+
- Guide based on pattern:
|
|
78
|
+
- **shared-kernel**: "Both domains consume this. Update the kernel spec and notify all consumers."
|
|
79
|
+
- **customer-supplier**: "**[upstream]** owns the contract. Check if the change is compatible."
|
|
80
|
+
- **anti-corruption-layer**: "Changes should go through the ACL at `[via path]`, not bypass it."
|
|
81
|
+
- **conformist**: "**[downstream]** conforms to **[upstream]**'s model. Update the downstream's mapping."
|
|
82
|
+
4. If no relationship is declared:
|
|
83
|
+
- Flag it: "This crosses **[domain A]** and **[domain B]** but no relationship is declared in the context map. Add one before proceeding."
|
|
84
|
+
5. Note which specs need updating in each affected domain.
|
|
85
|
+
|
|
86
|
+
### 6. Shared kernel awareness
|
|
87
|
+
|
|
88
|
+
When editing code in a `shared-kernel` domain:
|
|
89
|
+
|
|
90
|
+
1. Identify all consumers from the `context-map` (domains with `pattern: shared-kernel` pointing to this domain).
|
|
91
|
+
2. Warn: "This is shared kernel. Changes affect: **[list of consuming domains]**."
|
|
92
|
+
3. Check for contract tests in each consuming domain.
|
|
93
|
+
4. Recommend: "Run contract tests for [consumers] after this change."
|
|
94
|
+
|
|
95
|
+
## Guardrails
|
|
96
|
+
|
|
97
|
+
- Do NOT create files outside the domain tree without flagging it.
|
|
98
|
+
- Do NOT let domain boundaries drift silently — every new code path should map to a domain.
|
|
99
|
+
- Allow parent/child overlap under subsidiarity; reject unrelated domains claiming the same path.
|
|
100
|
+
- Do NOT treat `domains.yaml` as documentation — it is a structural contract.
|
|
101
|
+
- Do NOT enforce domains on non-production files (tests, scripts, CI) unless they have their own technical domain.
|
|
102
|
+
- Do NOT allow cross-domain changes without consulting the context map.
|
|
103
|
+
- Do NOT allow shared-kernel changes without notifying all consumers.
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# Domain Tree Conventions
|
|
2
|
+
|
|
3
|
+
Rules for maintaining the 1:1 mirror between colocated domain specs and code namespaces, informed by Domain-Driven Design.
|
|
4
|
+
|
|
5
|
+
## The colocation principle
|
|
6
|
+
|
|
7
|
+
`domains.yaml` lives at the project root. Domain specs live beside the code they govern: the first `code` path is the default spec directory, `README.md` is the main domain spec, and additional behavior specs are sibling `*.md` files. If specs cannot live in the first `code` path, an explicit `spec:` override may point to the colocated spec directory.
|
|
8
|
+
|
|
9
|
+
For a project that uses `src/`, the common shape is:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
.
|
|
13
|
+
├── domains.yaml (the manifest)
|
|
14
|
+
└── src/
|
|
15
|
+
├── verification/
|
|
16
|
+
│ ├── README.md (main domain spec)
|
|
17
|
+
│ ├── presentation.md (specific behavior spec)
|
|
18
|
+
│ └── ...code...
|
|
19
|
+
├── issuance/
|
|
20
|
+
│ ├── README.md
|
|
21
|
+
│ └── ...code...
|
|
22
|
+
└── shared-kernel/
|
|
23
|
+
├── README.md
|
|
24
|
+
└── ...code...
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The domain tree SHOULD mirror the code's module/package structure. When they diverge, make the drift visible in `/domain-tree:check` and decide whether to refactor code or update `domains.yaml`.
|
|
28
|
+
|
|
29
|
+
## Ubiquitous language
|
|
30
|
+
|
|
31
|
+
Each domain has its own vocabulary. The same word can mean different things in different domains — that's expected and healthy. What matters is consistency WITHIN a domain.
|
|
32
|
+
|
|
33
|
+
- Define key terms in the domain's `language` field in `domains.yaml` or in `<domain code path>/README.md`
|
|
34
|
+
- Code names (types, functions, variables) MUST use the domain's vocabulary
|
|
35
|
+
- Spec text MUST use the domain's vocabulary
|
|
36
|
+
- When two domains need to communicate, the context map defines how terms translate
|
|
37
|
+
|
|
38
|
+
Example: "Credential" means an SD-JWT VC in the issuance domain, but a displayable card in the wallet domain. The ACL between them handles the translation.
|
|
39
|
+
|
|
40
|
+
## Domain classification and spec rigor
|
|
41
|
+
|
|
42
|
+
Not all domains deserve equal investment. Classification drives how thoroughly you spec:
|
|
43
|
+
|
|
44
|
+
### Core domains (`type: core`)
|
|
45
|
+
|
|
46
|
+
The competitive advantage. Invest the most here.
|
|
47
|
+
|
|
48
|
+
- **Spec rigor:** Full spec-driven collection (all 6 phases). Every behavioral change specced before code.
|
|
49
|
+
- **TDD rigor:** Thorough iteration plans, small slices, careful red-green-refactor.
|
|
50
|
+
- **Review:** Specs need human approval before implementation.
|
|
51
|
+
- **Spec-on-touch:** Hard requirement — no code changes without a spec.
|
|
52
|
+
- **Modeling:** Rich domain models with explicit aggregates, value objects, domain events.
|
|
53
|
+
|
|
54
|
+
### Supporting domains (`type: supporting`)
|
|
55
|
+
|
|
56
|
+
Necessary, custom-built, not differentiating.
|
|
57
|
+
|
|
58
|
+
- **Spec rigor:** Good specs but lighter process. Can skip some collection phases.
|
|
59
|
+
- **TDD rigor:** Standard TDD. Larger slices acceptable.
|
|
60
|
+
- **Review:** Self-approval OK for straightforward specs.
|
|
61
|
+
- **Spec-on-touch:** Warning when missing, not blocking.
|
|
62
|
+
- **Modeling:** Simpler models. CRUD-like patterns are fine.
|
|
63
|
+
|
|
64
|
+
### Generic domains (`type: generic`)
|
|
65
|
+
|
|
66
|
+
Solved problems. Use existing libraries, standards, infrastructure.
|
|
67
|
+
|
|
68
|
+
- **Spec rigor:** Spec the integration boundary only. Don't spec the internals of something you didn't build.
|
|
69
|
+
- **TDD rigor:** Contract tests at the boundary. Don't unit-test the library.
|
|
70
|
+
- **Review:** Minimal — the standard/library IS the spec.
|
|
71
|
+
- **Spec-on-touch:** Only when the integration surface changes.
|
|
72
|
+
- **Modeling:** Thin wrappers. Anti-corruption layers where the external model leaks.
|
|
73
|
+
|
|
74
|
+
### Shared kernel (`type: shared-kernel`)
|
|
75
|
+
|
|
76
|
+
Shared types and contracts across domains. Treated with the rigor of core, because changes ripple everywhere.
|
|
77
|
+
|
|
78
|
+
- **Spec rigor:** High. Every shared type must be explicitly specced.
|
|
79
|
+
- **TDD rigor:** High. Contract tests from every consumer.
|
|
80
|
+
- **Review:** Joint approval from all consuming domains.
|
|
81
|
+
- **Spec-on-touch:** Always — any change to shared kernel needs spec update.
|
|
82
|
+
- **Modeling:** Value objects and immutable types preferred. Minimize the kernel surface.
|
|
83
|
+
|
|
84
|
+
## Context map conventions
|
|
85
|
+
|
|
86
|
+
The context map in `domains.yaml` declares cross-domain relationships. These relationships are architectural decisions, not just documentation.
|
|
87
|
+
|
|
88
|
+
### Integration patterns and what to spec
|
|
89
|
+
|
|
90
|
+
| Pattern | Spec location | What the spec covers |
|
|
91
|
+
|---------|--------------|---------------------|
|
|
92
|
+
| shared-kernel | The kernel domain's colocated spec directory | The shared types, their invariants, and which domains consume them |
|
|
93
|
+
| customer-supplier | Upstream domain's spec | The contract — what the upstream promises and what can change |
|
|
94
|
+
| conformist | Downstream domain's spec | How the downstream maps upstream concepts to its own model |
|
|
95
|
+
| anti-corruption-layer | Downstream domain's spec | The ACL: what comes in, what comes out, where it lives in code |
|
|
96
|
+
| open-host-service | Upstream domain's spec | The protocol or API definition (often a published standard) |
|
|
97
|
+
| published-language | `<domain code path>/` of the publisher | The schema or interchange format |
|
|
98
|
+
|
|
99
|
+
### Cross-domain changes with context map awareness
|
|
100
|
+
|
|
101
|
+
When a change spans domains, the context map tells you how to handle it:
|
|
102
|
+
|
|
103
|
+
1. **shared-kernel change** → Update the kernel spec. Notify ALL consuming domains. Run contract tests from each consumer.
|
|
104
|
+
2. **customer-supplier change** → Upstream changes its contract → downstream must update its conformance. Check the downstream's ACL or conformist layer.
|
|
105
|
+
3. **anti-corruption-layer change** → Only the downstream's ACL needs updating. The upstream is unaware.
|
|
106
|
+
4. **partnership change** → Both domains update together. Coordinate specs.
|
|
107
|
+
|
|
108
|
+
If a cross-domain change doesn't fit any declared relationship, the context map is incomplete — add the relationship before proceeding.
|
|
109
|
+
|
|
110
|
+
## Spec file conventions
|
|
111
|
+
|
|
112
|
+
### Naming
|
|
113
|
+
|
|
114
|
+
- One spec per bounded concern: `src/issuance/credential-flow.md`
|
|
115
|
+
- Use the behavior name, not the implementation name: `verification-request.md` not `verify-handler.md`
|
|
116
|
+
- Keep iteration plans outside domain and system corpora; delete them when no longer operationally useful.
|
|
117
|
+
|
|
118
|
+
### Frontmatter
|
|
119
|
+
|
|
120
|
+
Every spec file should have YAML frontmatter:
|
|
121
|
+
|
|
122
|
+
```yaml
|
|
123
|
+
---
|
|
124
|
+
domain: issuance
|
|
125
|
+
status: draft | approved | stale
|
|
126
|
+
---
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
- `domain` — must match a domain in `domains.yaml`
|
|
130
|
+
- `status` — lifecycle state (`draft` → `approved` → `stale` when code outpaces spec)
|
|
131
|
+
- canonical term pages additionally declare `term` and may declare `aliases`
|
|
132
|
+
|
|
133
|
+
Keep normative metadata minimal. Do NOT use `last-reviewed`, delivery state, or `governs:`. Code ownership is already declared in `domains.yaml`; Git preserves review and delivery history.
|
|
134
|
+
|
|
135
|
+
### Domain-level spec
|
|
136
|
+
|
|
137
|
+
A domain MAY have a `README.md` at its code root:
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
src/issuance/
|
|
141
|
+
├── README.md ← main domain spec: ubiquitous language, invariants, domain events
|
|
142
|
+
├── credential-flow.md ← specific behavior spec
|
|
143
|
+
├── webhook-handling.md
|
|
144
|
+
└── ...code...
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`README.md` is the required domain overview. Keep it concise and substantive: ubiquitous language, responsibilities, invariants, boundaries, or domain events. Do not create an overview that only repeats manifest metadata.
|
|
148
|
+
|
|
149
|
+
`README.md` does NOT duplicate information already in `domains.yaml`:
|
|
150
|
+
- Description
|
|
151
|
+
- Domain type/classification
|
|
152
|
+
- Code paths
|
|
153
|
+
- Context map relationships
|
|
154
|
+
- Consumer lists
|
|
155
|
+
|
|
156
|
+
### Specification DRY
|
|
157
|
+
|
|
158
|
+
Define each concept, invariant, or contract once at its narrowest semantic owner. Other specifications link to that canonical definition and describe only their local use, constraints, or consequences. Domain specifications are the default normative artifact.
|
|
159
|
+
|
|
160
|
+
Use a declared system spec only for a durable RFC 2119 requirement that cannot be assigned to one domain by subsidiarity. Keep temporary acceptance criteria, implementation scope, sequencing, rollout, migration, progress, and verification plans in non-normative iteration artifacts outside both corpora. Normative specs describe current durable behavior in the present tense; they do not preserve legacy or transitional commentary.
|
|
161
|
+
|
|
162
|
+
### Specification wiki links
|
|
163
|
+
|
|
164
|
+
Use standard relative Markdown links, never a separate wikilink syntax. Canonical term pages declare `term` and optional `aliases`; canonical terms and aliases form one case-insensitive repository-wide namespace with one semantic owner per label. A term-labelled link must target that owner. Link meaningful first occurrences, not every repetition. Relative targets must exist, and fragments on Markdown targets must resolve to real heading anchors. External URLs are allowed but are outside repository-integrity checks.
|
|
165
|
+
|
|
166
|
+
## Code-paths rules
|
|
167
|
+
|
|
168
|
+
`code-paths` in `domains.yaml` should be **directories, not individual files**. A code-path means "everything under this directory belongs to this domain."
|
|
169
|
+
|
|
170
|
+
### Signals that code-paths need attention
|
|
171
|
+
|
|
172
|
+
1. **Individual files listed**: If a domain lists `ui/FooScreen.kt`, `ui/BarScreen.kt` instead of `ui/foo/`, the code likely needs refactoring into subdirectories that match the domain boundary.
|
|
173
|
+
2. **Many subdirectories of the same parent**: If a domain lists `ui/mapper/`, `ui/model/`, `ui/components/` — the parent `ui/` probably belongs to the domain. List the parent, not each child.
|
|
174
|
+
3. **Overlapping paths**: Parent/child overlap is valid and the most-specific matching child owns the file. If unrelated domains claim the same path, establish one semantic owner or refactor into distinct directories.
|
|
175
|
+
|
|
176
|
+
When `/domain-tree:check` detects these patterns, it should recommend the refactoring rather than silently accepting the file-level mappings.
|
|
177
|
+
|
|
178
|
+
### Spec files live next to code, not in docs/
|
|
179
|
+
|
|
180
|
+
All behavioral specifications must live under the domain's colocated spec directory (normally the first `code` path). `README.md` and sibling Markdown with `domain`/`status` frontmatter form the normative corpus. Prompts, agent instructions, guides, plans, and historical documents without that frontmatter are non-normative. If a spec-like document exists in `docs/`, move it next to the owning domain's code; keep only non-normative material in `docs/`.
|
|
181
|
+
|
|
182
|
+
## When to create a new domain
|
|
183
|
+
|
|
184
|
+
Create a new domain when:
|
|
185
|
+
- New code doesn't fit any existing domain's description
|
|
186
|
+
- An existing domain has grown to cover two distinct responsibilities
|
|
187
|
+
- A technical concern (monitoring, deployment) becomes complex enough to spec
|
|
188
|
+
|
|
189
|
+
Do NOT create a new domain when:
|
|
190
|
+
- The code is a utility used by exactly one domain (put it in that domain)
|
|
191
|
+
- The code is shared across domains (add it to the shared kernel)
|
|
192
|
+
- The work is a one-off script (not everything needs a domain)
|
|
193
|
+
|
|
194
|
+
## When to split a domain into subdomains
|
|
195
|
+
|
|
196
|
+
Split when:
|
|
197
|
+
- The domain has more than 5 spec files
|
|
198
|
+
- Two parts of the domain can change independently
|
|
199
|
+
- Different people/teams own different parts
|
|
200
|
+
- The code already has natural package boundaries
|
|
201
|
+
- The ubiquitous language has started to diverge within the domain (same word, different meanings)
|
|
202
|
+
|
|
203
|
+
## Shared kernel rules
|
|
204
|
+
|
|
205
|
+
The shared kernel is the most constrained domain:
|
|
206
|
+
|
|
207
|
+
1. **Minimize the surface.** The less you share, the less coupling.
|
|
208
|
+
2. **Prefer value objects.** Immutable, identity-less types are safe to share. Entities with lifecycles are not.
|
|
209
|
+
3. **No business logic.** The kernel is types and contracts, not behavior.
|
|
210
|
+
4. **Explicit consumers.** The context map must list every domain that depends on the kernel.
|
|
211
|
+
5. **Joint approval.** No unilateral changes — all consumers must agree.
|
|
212
|
+
6. **Contract tests.** Every consumer maintains tests that verify the kernel's contracts.
|
|
213
|
+
|
|
214
|
+
## Technical domains vs business domains
|
|
215
|
+
|
|
216
|
+
Technical domains follow the same rules but spec different things:
|
|
217
|
+
|
|
218
|
+
| Domain type | Spec contains |
|
|
219
|
+
|------------|---------------|
|
|
220
|
+
| Business | Acceptance criteria, user behavior, data flow |
|
|
221
|
+
| Security | Threat model, cryptographic requirements, invariants |
|
|
222
|
+
| UX | Design tokens, component behavior, accessibility rules |
|
|
223
|
+
| Infra | Deployment topology, scaling rules, network policies |
|
|
224
|
+
| CI/CD | Pipeline stages, quality gates, release process |
|
|
225
|
+
|
|
226
|
+
The common thread: if it has behavior or constraints worth verifying, it gets a spec.
|
|
227
|
+
|
|
228
|
+
Technical domains CAN be `type: core` — security in a trust provider is absolutely core, even though it's not "business logic" in the traditional DDD sense.
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# Domain Manifest Schema
|
|
2
|
+
|
|
3
|
+
The domain manifest lives at `domains.yaml` and is the structural contract between specs and code. It encodes the domain tree, subdomain classifications, and the context map.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
# domains.yaml
|
|
9
|
+
|
|
10
|
+
# Optional project-level metadata
|
|
11
|
+
project:
|
|
12
|
+
name: my-project
|
|
13
|
+
description: One-line project description
|
|
14
|
+
|
|
15
|
+
# Optional project-level normative corpus for requirements that cannot
|
|
16
|
+
# be assigned to one domain by subsidiarity.
|
|
17
|
+
system-specs: [doc/system/]
|
|
18
|
+
|
|
19
|
+
# Structural groups and domains
|
|
20
|
+
domains:
|
|
21
|
+
<group-name>:
|
|
22
|
+
kind: group
|
|
23
|
+
description: Optional navigation context
|
|
24
|
+
domains:
|
|
25
|
+
<domain-name>:
|
|
26
|
+
description: What this domain is responsible for
|
|
27
|
+
type: core | supporting | generic | shared-kernel # default: supporting
|
|
28
|
+
status: active | deprecated | planned # default: active
|
|
29
|
+
owners: [team-or-person] # optional
|
|
30
|
+
language: # optional: ubiquitous language
|
|
31
|
+
- term: Cachet
|
|
32
|
+
meaning: A privacy-preserving trust badge issued after credential verification
|
|
33
|
+
|
|
34
|
+
# Specs live in the first code path by default.
|
|
35
|
+
code: [path/to/code/, another/path/]
|
|
36
|
+
# Optional override when specs cannot live in the first code path:
|
|
37
|
+
spec: path/to/code/
|
|
38
|
+
|
|
39
|
+
# Bounded contexts may contain narrower bounded contexts.
|
|
40
|
+
subdomains:
|
|
41
|
+
<subdomain-name>:
|
|
42
|
+
description: ...
|
|
43
|
+
type: core | supporting | generic # inherits from parent if omitted
|
|
44
|
+
code: [path/to/code/]
|
|
45
|
+
# Optional override; otherwise specs live in path/to/code/.
|
|
46
|
+
# subdomains can nest further
|
|
47
|
+
|
|
48
|
+
# Cross-context relationships
|
|
49
|
+
context-map:
|
|
50
|
+
- provider: <fully-qualified/domain-name>
|
|
51
|
+
consumers: [<fully-qualified/consumer-name>]
|
|
52
|
+
pattern: <relationship-pattern>
|
|
53
|
+
contract: <canonical spec path>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Structural groups
|
|
57
|
+
|
|
58
|
+
A `kind: group` entry provides namespace and presentation structure only. Its `domains` may contain domains or nested groups. Groups are not bounded contexts: they have no classification, lifecycle, owners, language, code ownership, specification, subdomains, or context-map relationships. The only allowed group fields are `kind`, `description`, and `domains`. Fully qualified domain names retain their group path, such as `business/time-management`.
|
|
59
|
+
|
|
60
|
+
Entries without `kind: group` remain domains. Domain nesting uses `subdomains`, not `domains`. A domain must declare `code` or an explicit `spec`; a node with only children is invalid and should normally become a group.
|
|
61
|
+
|
|
62
|
+
## Normative corpora
|
|
63
|
+
|
|
64
|
+
Domain specifications are the default. They contain durable, present-tense behavior, ubiquitous language, boundaries, invariants, and semantic contracts owned by one domain.
|
|
65
|
+
|
|
66
|
+
`system-specs` may declare Markdown files or directories. Directories are scanned recursively. Every included file is normative and must use only this frontmatter:
|
|
67
|
+
|
|
68
|
+
```yaml
|
|
69
|
+
---
|
|
70
|
+
system: my-project
|
|
71
|
+
status: draft | approved | stale
|
|
72
|
+
---
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The `system` value must equal `project.name`, and every system spec must contain at least one uppercase RFC 2119 keyword: `MUST`, `MUST NOT`, `SHOULD`, `SHOULD NOT`, or `MAY`. System specs cannot own ubiquitous terms; they link to domain-owned canonical pages. Use system specs only for stable requirements that subsidiarity cannot assign to one domain.
|
|
76
|
+
|
|
77
|
+
Iteration specs are temporary delivery artifacts. They remain outside domain spec directories and declared `system-specs`, carry no normative frontmatter, and are deleted when no longer useful.
|
|
78
|
+
|
|
79
|
+
Together, normative files form a standard relative-Markdown wiki. Canonical term pages declare `term` and optional `aliases`. Every case-insensitive canonical term or alias has one repository-wide owner. Relative targets and Markdown heading anchors must resolve; term-labelled links must target the canonical owner. Meaningful first-occurrence linking remains a semantic authoring rule rather than a noisy lexical hard check.
|
|
80
|
+
|
|
81
|
+
Normative domain and system specs reject project-management sections such as roadmaps, rollout or migration plans, progress, delivery status, milestones, and verification plans. They state durable behavior rather than legacy comparisons or temporary implementation concerns.
|
|
82
|
+
|
|
83
|
+
## Domain classification
|
|
84
|
+
|
|
85
|
+
Every domain has a `type` that determines how much modeling rigor it deserves. This comes from DDD's subdomain classification:
|
|
86
|
+
|
|
87
|
+
| Type | What it means | Spec rigor | Example |
|
|
88
|
+
|------|--------------|------------|---------|
|
|
89
|
+
| **core** | Competitive advantage. The reason the project exists. | High — thorough specs, careful TDD, deep modeling | verification, issuance |
|
|
90
|
+
| **supporting** | Necessary, custom-built, but not differentiating. | Moderate — good specs, standard TDD | registry, receipts |
|
|
91
|
+
| **generic** | Solved problem. Use existing solutions where possible. | Light — spec the integration, not the internals | auth, email, payment |
|
|
92
|
+
| **shared-kernel** | Shared types and contracts across domains. Joint ownership. | High — changes affect all consumers, must be explicit | common types, shared events |
|
|
93
|
+
|
|
94
|
+
### How classification drives behavior
|
|
95
|
+
|
|
96
|
+
- **spec-on-touch**: Core domains require a spec before any code change. Supporting domains get a warning. Generic domains only need an integration spec.
|
|
97
|
+
- **spec-driven collection**: Core domains get the full 6-phase treatment. Supporting domains can use a lighter format. Generic domains spec only the boundary.
|
|
98
|
+
- **spec-tdd planning**: Core domains get thorough iteration plans. Supporting domains can take larger slices. Generic domains focus on contract tests.
|
|
99
|
+
- **code review**: Core domain specs need human approval before implementation. Supporting specs can be self-approved.
|
|
100
|
+
|
|
101
|
+
## Context map
|
|
102
|
+
|
|
103
|
+
The `context-map` section declares semantic contracts between domains. Each entry names one provider, one or more consumers, an integration pattern, and the canonical contract. Ordinary imports do not require context-map entries.
|
|
104
|
+
|
|
105
|
+
### Relationship patterns
|
|
106
|
+
|
|
107
|
+
| Pattern | Meaning | What to spec |
|
|
108
|
+
|---------|---------|-------------|
|
|
109
|
+
| **shared-kernel** | Both domains share a small, explicit set of types. Changes require agreement from both. | The shared types themselves — what's in the kernel and what's not |
|
|
110
|
+
| **customer-supplier** | Upstream domain provides what downstream needs. Downstream can negotiate. | The API contract between them — what's promised, what can change |
|
|
111
|
+
| **conformist** | Downstream accepts upstream's model as-is. No negotiation. | Only the downstream's usage — how it maps upstream concepts to its own |
|
|
112
|
+
| **anti-corruption-layer** | Downstream translates upstream's model to protect its own. | The translation layer — what comes in, what comes out, where the ACL lives |
|
|
113
|
+
| **open-host-service** | Upstream exposes a general-purpose protocol for many consumers. | The protocol spec (often an existing standard like OpenID4VCI) |
|
|
114
|
+
| **published-language** | A shared, documented interchange format. Often paired with open-host-service. | The schema or format definition |
|
|
115
|
+
| **separate-ways** | No integration. Domains are independent. | Nothing — but document WHY they're separate |
|
|
116
|
+
| **partnership** | Two domains evolve together with coordinated planning. | Joint specs that cover the shared evolution |
|
|
117
|
+
|
|
118
|
+
### How the context map drives behavior
|
|
119
|
+
|
|
120
|
+
- **boundary-enforcer**: When a cross-domain change is detected, consult the context map. The provider owns the canonical `contract`; consumers follow the declared pattern.
|
|
121
|
+
- **domain-tree:check**: Validate that providers and consumers exist, patterns are supported, and canonical contract paths resolve.
|
|
122
|
+
- **spec-driven collection**: When speccing a domain that consumes another, the context map tells you which integration pattern to follow — and therefore what to spec.
|
|
123
|
+
|
|
124
|
+
## Manifest integrity
|
|
125
|
+
|
|
126
|
+
The manifest is validated strictly. Unknown fields, malformed value types, unsupported classifications or lifecycle states, incomplete context relationships, and group references in the context map are errors. When any error exists, ownership resolution, maps, and spec coverage fail closed rather than using a partial tree. `/domain-tree:check` reports all diagnostics; malformed YAML includes line and column when available.
|
|
127
|
+
|
|
128
|
+
Legacy structural-only nodes are never rewritten automatically. Convert them explicitly to `kind: group` with `domains`, or give a real bounded context a `code` or `spec` anchor.
|
|
129
|
+
|
|
130
|
+
## Path rules
|
|
131
|
+
|
|
132
|
+
- `code` paths are relative to project root, always end with `/`.
|
|
133
|
+
- The most-specific matching child path owns a file. Parent/child overlap is valid; unrelated domains cannot claim the same path.
|
|
134
|
+
- Specs live next to code. The first `code` path is the default spec directory; `README.md` is the required main domain spec.
|
|
135
|
+
- `spec` is optional and only overrides the inferred spec directory when specs cannot live in the first `code` path.
|
|
136
|
+
- Groups use `domains`; bounded contexts use `subdomains`.
|
|
137
|
+
- Groups never own code or specifications and are excluded from domain coverage counts.
|
|
138
|
+
- A domain is either a **leaf** (has `code`) or a **branch** (has `subdomains`).
|
|
139
|
+
- Branch domains may also have `code` + `spec` for domain-level concerns (shared types, domain events).
|
|
140
|
+
|
|
141
|
+
## Domain types
|
|
142
|
+
|
|
143
|
+
Domains are not limited to business logic. The tree covers everything with behavior worth specifying:
|
|
144
|
+
|
|
145
|
+
| Category | Examples | What gets specced |
|
|
146
|
+
|----------|----------|------------------|
|
|
147
|
+
| **Business** | verification, issuance, registry | User-facing behavior, acceptance criteria |
|
|
148
|
+
| **Technical** | security, crypto, infra | Architectural constraints, invariants |
|
|
149
|
+
| **Platform** | ux, cicd, observability | Conventions, patterns, tooling behavior |
|
|
150
|
+
| **Integration** | webhooks, external-apis | Contract specs, error handling |
|
|
151
|
+
|
|
152
|
+
Domain category is about WHAT it covers. Domain type (core/supporting/generic) is about HOW MUCH rigor it deserves. They're orthogonal — a technical domain like security can be `core`.
|
|
153
|
+
|
|
154
|
+
## Naming conventions
|
|
155
|
+
|
|
156
|
+
- Domain names: lowercase, hyphenated (`issuance-gateway`, `receipts-log`)
|
|
157
|
+
- Match existing module/package names where possible
|
|
158
|
+
- Prefer domain language over implementation language ("verification" not "verifier-service")
|
|
159
|
+
|
|
160
|
+
## Coverage
|
|
161
|
+
|
|
162
|
+
Not every file needs to be in a domain. Exclude:
|
|
163
|
+
- Generated files (build output, lock files)
|
|
164
|
+
- Configuration that doesn't encode behavior (.editorconfig, .gitignore)
|
|
165
|
+
- Third-party code (vendor/, node_modules/)
|
|
166
|
+
|
|
167
|
+
Everything else should map to a domain. Unmapped production code is a smell.
|
|
168
|
+
|
|
169
|
+
## Example
|
|
170
|
+
|
|
171
|
+
```yaml
|
|
172
|
+
project:
|
|
173
|
+
name: cachet
|
|
174
|
+
description: Privacy-preserving trust provider
|
|
175
|
+
|
|
176
|
+
domains:
|
|
177
|
+
verification:
|
|
178
|
+
description: Credential presentation and cach'pack verification
|
|
179
|
+
type: core
|
|
180
|
+
language:
|
|
181
|
+
- term: Cach'Pack
|
|
182
|
+
meaning: Reusable, privacy-preserving credential template
|
|
183
|
+
- term: Presentation
|
|
184
|
+
meaning: Verifiable credential bundle verified against a policy
|
|
185
|
+
subdomains:
|
|
186
|
+
presentation:
|
|
187
|
+
description: Verify credential presentations against policies
|
|
188
|
+
code: [services/verifier/presentation/]
|
|
189
|
+
packs:
|
|
190
|
+
description: Cach'pack list management and definitions
|
|
191
|
+
code: [services/verifier/packs/]
|
|
192
|
+
|
|
193
|
+
issuance:
|
|
194
|
+
description: OpenID4VCI credential issuance via Veriff
|
|
195
|
+
type: core
|
|
196
|
+
code: [services/issuance-gateway/]
|
|
197
|
+
|
|
198
|
+
registry:
|
|
199
|
+
description: Policy and pack registry with DID-signed manifests
|
|
200
|
+
type: supporting
|
|
201
|
+
code: [services/registry/]
|
|
202
|
+
|
|
203
|
+
receipts:
|
|
204
|
+
description: Consent receipts and transparency logging
|
|
205
|
+
type: supporting
|
|
206
|
+
code: [services/receipts-log/]
|
|
207
|
+
|
|
208
|
+
wallet:
|
|
209
|
+
description: Mobile wallet application
|
|
210
|
+
type: core
|
|
211
|
+
subdomains:
|
|
212
|
+
onboarding:
|
|
213
|
+
description: First-run experience and identity verification
|
|
214
|
+
code: [mobile/shared/.../onboarding/, mobile/androidApp/.../onboarding/]
|
|
215
|
+
credentials:
|
|
216
|
+
description: Credential storage, display, and management
|
|
217
|
+
code: [mobile/shared/.../credentials/, mobile/androidApp/.../credentials/]
|
|
218
|
+
verification-flow:
|
|
219
|
+
description: QR scan, consent, presentation flow
|
|
220
|
+
code: [mobile/shared/.../verification/, mobile/androidApp/.../verification/]
|
|
221
|
+
|
|
222
|
+
common:
|
|
223
|
+
description: Shared types, value objects, and contracts across services
|
|
224
|
+
type: shared-kernel
|
|
225
|
+
code: [services/common/]
|
|
226
|
+
|
|
227
|
+
security:
|
|
228
|
+
description: Cryptographic operations, key management, threat model
|
|
229
|
+
type: core
|
|
230
|
+
code: [services/common/crypto/]
|
|
231
|
+
|
|
232
|
+
ux:
|
|
233
|
+
description: Design system, theming, shared UI components
|
|
234
|
+
type: supporting
|
|
235
|
+
code: [mobile/androidApp/.../ui/theme/, mobile/androidApp/.../ui/components/]
|
|
236
|
+
|
|
237
|
+
infra:
|
|
238
|
+
description: Deployment, Cloud Run, networking, service mesh
|
|
239
|
+
type: generic
|
|
240
|
+
code: [deploy/, terraform/]
|
|
241
|
+
|
|
242
|
+
cicd:
|
|
243
|
+
description: CI/CD pipelines, pre-commit hooks, release automation
|
|
244
|
+
type: generic
|
|
245
|
+
code: [.github/workflows/, scripts/]
|
|
246
|
+
|
|
247
|
+
context-map:
|
|
248
|
+
- provider: issuance
|
|
249
|
+
consumers: [wallet]
|
|
250
|
+
pattern: open-host-service
|
|
251
|
+
contract: services/issuance-gateway/README.md
|
|
252
|
+
|
|
253
|
+
- provider: verification
|
|
254
|
+
consumers: [wallet]
|
|
255
|
+
pattern: anti-corruption-layer
|
|
256
|
+
contract: services/verifier/presentation/README.md
|
|
257
|
+
|
|
258
|
+
- provider: registry
|
|
259
|
+
consumers: [verification]
|
|
260
|
+
pattern: customer-supplier
|
|
261
|
+
contract: services/registry/README.md
|
|
262
|
+
|
|
263
|
+
- provider: common
|
|
264
|
+
consumers: [issuance, verification]
|
|
265
|
+
pattern: shared-kernel
|
|
266
|
+
contract: services/common/README.md
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Lifecycle
|
|
270
|
+
|
|
271
|
+
1. **Init** — `/domain-tree:init` proposes the initial tree from codebase analysis
|
|
272
|
+
2. **Classify** — Assign core/supporting/generic to each domain based on business value
|
|
273
|
+
3. **Map** — Declare cross-context relationships in `context-map`
|
|
274
|
+
4. **Grow** — Add domains/subdomains as the project evolves
|
|
275
|
+
5. **Split** — When a domain gets too large, split into subdomains
|
|
276
|
+
6. **Deprecate** — Mark domains `status: deprecated` before removing
|
|
277
|
+
7. **Validate** — `/domain-tree:check` verifies the tree matches reality
|