guardian-framework 0.1.104 → 0.1.109
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/README.md +11 -2
- package/dist/cli.js +232 -16
- package/dist/exports.js +232 -16
- package/package.json +1 -1
- package/templates/languages/rust-patterns.md +5 -2
- package/templates/pi/context/checklists.md +15 -1
- package/templates/pi/domain/exploration.md +9 -0
- package/templates/pi/domain/ubiquitous-language.md +12 -0
- package/templates/pi/extensions/architect-lib/generators.ts +163 -12
- package/templates/pi/extensions/architect-lib/helpers.ts +49 -0
- package/templates/pi/extensions/architect-lib/tdd-generator.ts +1 -1
- package/templates/pi/extensions/architect-lib/types.ts +44 -2
- package/templates/pi/extensions/architect.ts +105 -41
- package/templates/pi/extensions/ask-user-question.ts +2 -2
- package/templates/pi/extensions/bash-guard.ts +2 -2
- package/templates/pi/extensions/config-reload.ts +12 -5
- package/templates/pi/extensions/coordinator.ts +2 -2
- package/templates/pi/extensions/curator.ts +1 -1
- package/templates/pi/extensions/domain-explorer.ts +50 -3
- package/templates/pi/extensions/filechanges.ts +2 -2
- package/templates/pi/extensions/goal-loop.ts +3 -3
- package/templates/pi/extensions/hooks.ts +1 -1
- package/templates/pi/extensions/pi-env.d.ts +96 -0
- package/templates/pi/extensions/pipeline.ts +1 -1
- package/templates/pi/extensions/project-scaffolder.ts +1 -1
- package/templates/pi/extensions/read-only-mode.ts +5 -1
- package/templates/pi/extensions/tsconfig.json +9 -2
- package/templates/pi/preflight_report.json +1 -1
- package/templates/pi/prompts/ci-blueprint.md +2 -2
- package/templates/pi/scripts/check_model_judgment.sh +28 -0
- package/templates/pi/scripts/ci/check_judgment_contracts.sh +93 -0
- package/templates/pi/scripts/ci/run_preflight.sh +3 -3
- package/templates/pi/scripts/ci/run_stage.sh +1 -1
- package/templates/pi/scripts/ci/stage_lint.sh +1 -1
- package/templates/pi/scripts/coverage.sh +58 -0
- package/templates/pi/scripts/install_coverage_tools.sh +43 -0
- package/templates/pi/scripts/languages/go/check_model_judgment.sh +70 -0
- package/templates/pi/scripts/languages/go/check_runtime_reachability.sh +34 -0
- package/templates/pi/scripts/languages/go/coverage.sh +78 -0
- package/templates/pi/scripts/languages/go/install_coverage_tools.sh +17 -0
- package/templates/pi/scripts/languages/java/check_model_judgment.sh +142 -0
- package/templates/pi/scripts/languages/java/check_runtime_reachability.sh +31 -0
- package/templates/pi/scripts/languages/java/coverage.sh +103 -0
- package/templates/pi/scripts/languages/java/install_coverage_tools.sh +40 -0
- package/templates/pi/scripts/languages/java/validate-architecture.sh +20 -0
- package/templates/pi/scripts/languages/python/check_model_judgment.sh +67 -0
- package/templates/pi/scripts/languages/python/check_runtime_reachability.sh +34 -0
- package/templates/pi/scripts/languages/python/coverage.sh +88 -0
- package/templates/pi/scripts/languages/python/install_coverage_tools.sh +22 -0
- package/templates/pi/scripts/languages/rust/check_model_judgment.sh +61 -0
- package/templates/pi/scripts/languages/rust/check_runtime_reachability.sh +40 -0
- package/templates/pi/scripts/languages/rust/coverage.sh +79 -0
- package/templates/pi/scripts/languages/rust/install_coverage_tools.sh +20 -0
- package/templates/pi/scripts/languages/rust/validate-tests.sh +9 -6
- package/templates/pi/scripts/languages/typescript/check_model_judgment.sh +81 -0
- package/templates/pi/scripts/languages/typescript/check_runtime_reachability.sh +41 -0
- package/templates/pi/scripts/languages/typescript/coverage.sh +154 -0
- package/templates/pi/scripts/languages/typescript/install_coverage_tools.sh +41 -0
- package/templates/pi/scripts/languages/typescript/validate-canonical.sh +6 -2
- package/templates/pi/scripts/languages/typescript/validate-ci.sh +2 -2
- package/templates/pi/scripts/validate-architecture-readiness.sh +29 -0
- package/templates/pi/scripts/validate-exploration-depth.sh +112 -0
- package/templates/pi/scripts/validate-runtime-wiring.sh +35 -0
- package/templates/pi/skills/agents/architecture-validator.md +28 -0
- package/templates/pi/skills/agents/test-validator.md +1 -1
- package/templates/pi/skills/go-enterprise-codegen.md +46 -0
- package/templates/pi/skills/java-spring-enterprise-codegen.md +50 -0
- package/templates/pi/skills/python-enterprise-codegen.md +46 -0
- package/templates/pi/skills/rust-enterprise-codegen.md +46 -0
- package/templates/pi/skills/tdd-practice.md +17 -9
- package/templates/pi/skills/typescript-enterprise-codegen.md +46 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# validate-exploration-depth.sh — ADR-008 modeling-depth rules over a
|
|
4
|
+
# domain-exploration session's structured result (JSON).
|
|
5
|
+
#
|
|
6
|
+
# Deterministic, machine-parseable checks (no LLM):
|
|
7
|
+
# depth-subdomain every bounded context has subdomainType + rationale
|
|
8
|
+
# depth-invariants invariant entries carry rule + illegalStatePrevented
|
|
9
|
+
# event-triage every domain event passes >=1 triage question
|
|
10
|
+
# roots-justified aggregate-root entities claim identity (identityPersists)
|
|
11
|
+
# ul-cross-context no term reused across contexts without a context-map edge
|
|
12
|
+
#
|
|
13
|
+
# Usage:
|
|
14
|
+
# bash .pi/scripts/validate-exploration-depth.sh [<session-id>]
|
|
15
|
+
# without id: validates the most recent *.raw.json session
|
|
16
|
+
# exit 0 = all pass ; exit 1 = violations (one '[rule-id] error:' per line)
|
|
17
|
+
# ============================================================================
|
|
18
|
+
set -uo pipefail
|
|
19
|
+
|
|
20
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
21
|
+
EXPLORATION_DIR="${1:-}"
|
|
22
|
+
RAW=""
|
|
23
|
+
|
|
24
|
+
if [ -z "$EXPLORATION_DIR" ]; then
|
|
25
|
+
# No session id given: locate the most recent session raw JSON.
|
|
26
|
+
RAW="$(ls -1t "${SCRIPT_DIR}/../domain/exploration/"*.raw.json 2>/dev/null | head -1)"
|
|
27
|
+
elif [[ "$EXPLORATION_DIR" == */* || "$EXPLORATION_DIR" == *.json ]]; then
|
|
28
|
+
RAW="$EXPLORATION_DIR"
|
|
29
|
+
else
|
|
30
|
+
RAW="${SCRIPT_DIR}/../domain/exploration/${EXPLORATION_DIR}.raw.json"
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
if [ -z "$RAW" ] || [ ! -f "$RAW" ]; then
|
|
34
|
+
echo "no exploration session raw.json found (expected at .pi/domain/exploration/<session>.raw.json)" >&2
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
python3 - "$RAW" <<'PY'
|
|
39
|
+
import json, sys
|
|
40
|
+
|
|
41
|
+
path = sys.argv[1]
|
|
42
|
+
try:
|
|
43
|
+
with open(path, encoding="utf-8") as fh:
|
|
44
|
+
data = json.load(fh)
|
|
45
|
+
except Exception as exc: # noqa: BLE001
|
|
46
|
+
print(f"[json-invalid] error: {path} is not valid JSON — {exc}")
|
|
47
|
+
sys.exit(1)
|
|
48
|
+
|
|
49
|
+
errors = []
|
|
50
|
+
|
|
51
|
+
def err(rule, msg):
|
|
52
|
+
errors.append(f"[{rule}] error: {msg}")
|
|
53
|
+
|
|
54
|
+
# Rule 1: subdomain classification present
|
|
55
|
+
for bc in data.get("boundedContexts", []):
|
|
56
|
+
name = bc.get("name", "?")
|
|
57
|
+
st = bc.get("subdomainType")
|
|
58
|
+
if st not in ("core", "supporting", "generic"):
|
|
59
|
+
err("depth-subdomain", f"bounded context '{name}' missing subdomainType (core|supporting|generic)")
|
|
60
|
+
if not bc.get("subdomainRationale"):
|
|
61
|
+
err("depth-subdomain", f"bounded context '{name}' missing subdomainRationale")
|
|
62
|
+
|
|
63
|
+
# Rule 2: invariants well-formed
|
|
64
|
+
for bc in data.get("boundedContexts", []):
|
|
65
|
+
name = bc.get("name", "?")
|
|
66
|
+
for inv in bc.get("invariants", []) or []:
|
|
67
|
+
if not inv.get("rule"):
|
|
68
|
+
err("depth-invariants", f"context '{name}' has an invariant with an empty rule")
|
|
69
|
+
if not inv.get("illegalStatePrevented"):
|
|
70
|
+
err("depth-invariants", f"context '{name}' invariant '{inv.get('rule','')}' missing illegalStatePrevented")
|
|
71
|
+
|
|
72
|
+
# Rule 3: event triage (>=1 question answered yes)
|
|
73
|
+
for ev in data.get("domainEvents", []):
|
|
74
|
+
triage = ev.get("triage") or {}
|
|
75
|
+
hits = sum(bool(triage.get(k)) for k in ("domainExpertCares", "crossesAggregateBoundary", "needsAudit"))
|
|
76
|
+
if hits == 0:
|
|
77
|
+
err("event-triage", f"event '{ev.get('name','?')}' fails all triage questions (implementation detail, not a domain event)")
|
|
78
|
+
|
|
79
|
+
# Rule 4: aggregate roots claim identity
|
|
80
|
+
for ent in data.get("entities", []):
|
|
81
|
+
if ent.get("type") == "aggregate-root":
|
|
82
|
+
just = ent.get("justification") or {}
|
|
83
|
+
if not just.get("identityPersists"):
|
|
84
|
+
err("roots-justified", f"aggregate-root '{ent.get('name','?')}' must justify identityPersists=true")
|
|
85
|
+
|
|
86
|
+
# Rule 5: cross-context term reuse needs a context-map edge
|
|
87
|
+
term_contexts = {}
|
|
88
|
+
for t in data.get("ubiquitousLanguage", []):
|
|
89
|
+
term = t.get("term", "")
|
|
90
|
+
ctx = t.get("boundedContext", "")
|
|
91
|
+
if not term or not ctx:
|
|
92
|
+
continue
|
|
93
|
+
term_contexts.setdefault(term, set()).add(ctx)
|
|
94
|
+
edges = {
|
|
95
|
+
(e.get("from"), e.get("to"))
|
|
96
|
+
for e in data.get("contextMap", [])
|
|
97
|
+
if e.get("from") and e.get("to")
|
|
98
|
+
}
|
|
99
|
+
for term, contexts in term_contexts.items():
|
|
100
|
+
ctxs = sorted(contexts)
|
|
101
|
+
if len(ctxs) > 1:
|
|
102
|
+
connected = any((ctxs[0], c) in edges or (c, ctxs[0]) in edges for c in ctxs[1:])
|
|
103
|
+
if not connected:
|
|
104
|
+
err("ul-cross-context", f"term '{term}' used in contexts {', '.join(ctxs)} without a context-map edge or UL conflicts entry")
|
|
105
|
+
|
|
106
|
+
if errors:
|
|
107
|
+
print("\n".join(errors))
|
|
108
|
+
sys.exit(1)
|
|
109
|
+
print("depth validation passed (subdomain classification, invariants, event triage, aggregate roots, UL contexts)")
|
|
110
|
+
sys.exit(0)
|
|
111
|
+
PY
|
|
112
|
+
exit $?
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# validate-runtime-wiring.sh — ADR-009 production-reachability dispatcher
|
|
4
|
+
#
|
|
5
|
+
# Deterministic check: every application-layer service must be referenced from
|
|
6
|
+
# production code OUTSIDE its own module and OUTSIDE tests. A service whose
|
|
7
|
+
# only callers are its own tests is implemented-but-unwired (dead code with a
|
|
8
|
+
# test suite) — the recurring rigorix pattern ADR-009 fixes.
|
|
9
|
+
#
|
|
10
|
+
# Delegates to languages/<lang>/check_runtime_reachability.sh.
|
|
11
|
+
# Usage: bash .pi/scripts/validate-runtime-wiring.sh [--root <dir>]
|
|
12
|
+
# Exit: 0 all services reachable (or language not yet covered)
|
|
13
|
+
# 1 unreachable service(s) — one 'file: [runtime-reachability] ...' line each
|
|
14
|
+
# ============================================================================
|
|
15
|
+
set -uo pipefail
|
|
16
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
17
|
+
ROOT="$(pwd)"
|
|
18
|
+
if [ "${1:-}" = "--root" ] && [ -n "${2:-}" ]; then ROOT="$2"; fi
|
|
19
|
+
cd "$ROOT" || exit 1
|
|
20
|
+
|
|
21
|
+
detect_language() {
|
|
22
|
+
if [ -f "Cargo.toml" ]; then echo "rust"
|
|
23
|
+
elif [ -f "package.json" ]; then echo "typescript"
|
|
24
|
+
elif [ -f "go.mod" ]; then echo "go"
|
|
25
|
+
elif [ -f "pyproject.toml" ] || [ -f "requirements.txt" ]; then echo "python"
|
|
26
|
+
elif [ -f "pom.xml" ] || [ -f "build.gradle" ]; then echo "java"
|
|
27
|
+
else echo "unknown"; fi
|
|
28
|
+
}
|
|
29
|
+
LANG="$(detect_language)"
|
|
30
|
+
SCRIPT="${SCRIPT_DIR}/languages/${LANG}/check_runtime_reachability.sh"
|
|
31
|
+
if [ -f "$SCRIPT" ]; then
|
|
32
|
+
exec bash "$SCRIPT" --root "$ROOT"
|
|
33
|
+
fi
|
|
34
|
+
echo "runtime-wiring: no reachability check for '${LANG}' yet (ADR-009 planned: go/python/java)"
|
|
35
|
+
exit 0
|
|
@@ -65,6 +65,34 @@ grep -r "registry.register" [src paths]
|
|
|
65
65
|
grep -r "#\[from\]" [error file]
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
+
|
|
69
|
+
## Model Judgment Review (ADR-008) — all languages
|
|
70
|
+
|
|
71
|
+
**MANDATORY** when the reviewed scope touches domain/application code. Record
|
|
72
|
+
each finding as WARN with `file:line: <finding>` — never a merge block on its
|
|
73
|
+
own; promotion to a gate requires a written decision after a trial. Evidence
|
|
74
|
+
commands are per-language; hard gates (deterministic) live in
|
|
75
|
+
`.pi/scripts/languages/<lang>/check_model_judgment.sh`.
|
|
76
|
+
|
|
77
|
+
- **R1 anemic-model** — app/application layer reads object state to decide
|
|
78
|
+
(`if (x.getStatus()…)`, `x.status =`, `x.State ==`) then mutates via
|
|
79
|
+
setters; business rules outside the domain object. Evidence per language:
|
|
80
|
+
Java/TS getter/setter chains; Go struct field pokes in services; Python
|
|
81
|
+
attribute mutation on domain objects; Rust `.field` writes / `set_*` from
|
|
82
|
+
services.
|
|
83
|
+
- **R2 LSP / fragile-base** — domain entities inherit from a non-abstract
|
|
84
|
+
base (Java/TS `extends`, Go embedding, Python subclassing, Rust — prefer
|
|
85
|
+
composition over inheritance, flag base-class aggregates).
|
|
86
|
+
- **R3 cohesion** — `Helper | Utils | Manager | Orchestrator`-style catch-all
|
|
87
|
+
classes/structs/types in the domain layer.
|
|
88
|
+
- **R4 primitive obsession** — >2 raw primitives on a domain service method
|
|
89
|
+
signature where a value object/struct/record should exist (IDs, money,
|
|
90
|
+
ranges, statuses).
|
|
91
|
+
- **R5 production reachability (ADR-009)** — an `implemented` application
|
|
92
|
+
service with no production caller outside its own module and outside tests
|
|
93
|
+
is dead code: report it and require the Runtime-Integration issue. Evidence:
|
|
94
|
+
`grep -rn "<ServiceName>" <src> | grep -v tests` per language.
|
|
95
|
+
|
|
68
96
|
## Output
|
|
69
97
|
Use format from `.pi/context/output-formats.md` → "Validation Report"
|
|
70
98
|
|
|
@@ -17,7 +17,7 @@ You validate test coverage and quality.
|
|
|
17
17
|
## Checks
|
|
18
18
|
|
|
19
19
|
1. **All tests pass** — Run full test suite
|
|
20
|
-
2. **Coverage
|
|
20
|
+
2. **Coverage meets the project's real threshold** — Run the project's real coverage tool/gate (no per-module heuristic *_coverage.sh)
|
|
21
21
|
3. **New code tested** — Every new function has tests
|
|
22
22
|
4. **AAA pattern** — Arrange-Act-Assert structure
|
|
23
23
|
5. **No flaky tests** — Deterministic, no timing dependencies
|
|
@@ -471,3 +471,49 @@ go.mod
|
|
|
471
471
|
*Version: 1.0.0*
|
|
472
472
|
*Last updated: 2026-07-03*
|
|
473
473
|
*Source: Guardian DDD patterns + Clean Architecture + context7 (/wesionaryteam/go_clean_architecture, /golang/go)*
|
|
474
|
+
|
|
475
|
+
## Judgment Tables (ADR-008)
|
|
476
|
+
|
|
477
|
+
Compact decision tables distilled from professional OOP/DDD practice (token-lean;
|
|
478
|
+
the full rationale lives in the curriculum, not here).
|
|
479
|
+
|
|
480
|
+
### Entity vs Value Object
|
|
481
|
+
| Question | Entity | Value Object |
|
|
482
|
+
|----------|--------|--------------|
|
|
483
|
+
| Identity persists across state changes? | yes | no |
|
|
484
|
+
| Change = mutate in place? | yes | replace entirely |
|
|
485
|
+
| Equality by identity or by attributes? | identity | all attributes |
|
|
486
|
+
| Immutable by nature? | no | yes |
|
|
487
|
+
| Track change history? | yes | no |
|
|
488
|
+
|
|
489
|
+
### Aggregate Boundaries
|
|
490
|
+
- An aggregate is a **consistency boundary**, not a data grouping.
|
|
491
|
+
- One aggregate modified per transaction; everything else via events.
|
|
492
|
+
- Reference other aggregates **by identity**, never by object graph.
|
|
493
|
+
- Design **small** aggregates; the root is the only entry point — behavior methods, no setters.
|
|
494
|
+
|
|
495
|
+
### Service Classifier
|
|
496
|
+
| Service | Layer | Contains | Named by |
|
|
497
|
+
|---------|-------|----------|----------|
|
|
498
|
+
| Domain service | domain/ | business process spanning entities | ubiquitous language |
|
|
499
|
+
| Application service | application/ | use-case orchestration (thin) | technical intent |
|
|
500
|
+
| Infrastructure service | infrastructure/ | technical impl (persistence, http, events) | implementation |
|
|
501
|
+
| Persistence port (interface) | domain/ | aggregate persistence contract | aggregate language |
|
|
502
|
+
| Persistence adapter | infrastructure/ | framework impl (ORM/DB) | — |
|
|
503
|
+
|
|
504
|
+
### Event Triage (3 questions)
|
|
505
|
+
Keep a candidate event only if ≥1 is true: (1) would a domain expert care?
|
|
506
|
+
(2) does it cross an aggregate boundary? (3) do we need an audit trail?
|
|
507
|
+
Otherwise it is an implementation detail. Publish from the aggregate;
|
|
508
|
+
dispatch **after** save from the application service.
|
|
509
|
+
|
|
510
|
+
### Transaction & Persistence Placement
|
|
511
|
+
| Concern | Location | Why |
|
|
512
|
+
|---------|----------|-----|
|
|
513
|
+
| `sql.Tx` / transaction scope | application service only | one aggregate per transaction |
|
|
514
|
+
| Repository interface | domain/ package | dependency inversion |
|
|
515
|
+
| Repository impl (sqlx/gorm) | infrastructure/ | framework out of domain |
|
|
516
|
+
| Value object | domain/ (struct, immutable by convention) | replace, don't mutate |
|
|
517
|
+
| Wiring | explicit constructors in `cmd/` | no framework magic |
|
|
518
|
+
|
|
519
|
+
*Distilled 2026-09-03 from professional OOP/DDD curriculum (Modules 0–6) — ADR-008/009.*
|
|
@@ -540,3 +540,53 @@ pom.xml (or build.gradle)
|
|
|
540
540
|
*Version: 1.0.0*
|
|
541
541
|
*Last updated: 2026-07-03*
|
|
542
542
|
*Source: Guardian DDD patterns + Spring Boot best practices + context7 (/jkazama/ddd-java, /websites/spring_io_spring-boot)*
|
|
543
|
+
|
|
544
|
+
## 10. Judgment Tables (ADR-008)
|
|
545
|
+
|
|
546
|
+
Compact decision tables distilled from professional OOP/DDD/Spring practice
|
|
547
|
+
(keep token-lean — the full rationale lives in the curriculum, not here).
|
|
548
|
+
|
|
549
|
+
### Entity vs Value Object
|
|
550
|
+
| Question | Entity | Value Object |
|
|
551
|
+
|----------|--------|--------------|
|
|
552
|
+
| Identity persists across state changes? | yes | no |
|
|
553
|
+
| Change = mutate in place? | yes | replace entirely |
|
|
554
|
+
| Equality by identity or by attributes? | identity | all attributes |
|
|
555
|
+
| Immutable by nature? | no | yes |
|
|
556
|
+
| Track change history? | yes | no |
|
|
557
|
+
|
|
558
|
+
### Aggregate Boundaries
|
|
559
|
+
- An aggregate is a **consistency boundary**, not a data grouping.
|
|
560
|
+
- One aggregate modified per transaction; everything else via events.
|
|
561
|
+
- Reference other aggregates **by identity**, never by object graph.
|
|
562
|
+
- Design **small** aggregates (size by consistency, not relationships).
|
|
563
|
+
- Root is the only entry point — state changes via behavior methods (no setters).
|
|
564
|
+
|
|
565
|
+
### Service Classifier
|
|
566
|
+
| Service | Layer | Contains | Named by |
|
|
567
|
+
|---------|-------|----------|----------|
|
|
568
|
+
| Domain service | domain/ | business process spanning entities | ubiquitous language |
|
|
569
|
+
| Application service | application/ | use-case orchestration (thin) | technical intent |
|
|
570
|
+
| Infrastructure service | infrastructure/ | technical impl (email, storage, publishing) | implementation |
|
|
571
|
+
| Repository (interface) | domain/ | aggregate persistence contract | aggregate language |
|
|
572
|
+
| Repository (impl) | infrastructure/ | JPA/ORM adapter | — |
|
|
573
|
+
|
|
574
|
+
### Event Triage (3 questions)
|
|
575
|
+
Ask of every candidate event; keep it only if ≥1 is true:
|
|
576
|
+
1. Would a domain expert care?
|
|
577
|
+
2. Does it cross an aggregate boundary?
|
|
578
|
+
3. Do we need an audit trail?
|
|
579
|
+
Otherwise it is an implementation detail, not a domain event.
|
|
580
|
+
Publish events from the aggregate (`releaseEvents()`), dispatch **after** save
|
|
581
|
+
from the application service.
|
|
582
|
+
|
|
583
|
+
### Transaction & Persistence Placement
|
|
584
|
+
| Concern | Location | Why |
|
|
585
|
+
|---------|----------|-----|
|
|
586
|
+
| `@Transactional` | application service methods only | one aggregate per transaction |
|
|
587
|
+
| repository `save`/`find` | no `@Transactional` | don't open transactions per call |
|
|
588
|
+
| VO → `@Embeddable` | domain value objects | immutable + replaceable |
|
|
589
|
+
| Aggregate id | VO (`@EmbeddedId`) | type-safe identity |
|
|
590
|
+
| Optimistic lock | version field on aggregate | concurrency safety |
|
|
591
|
+
|
|
592
|
+
*Distilled 2026-09-03 from professional OOP/DDD/Spring curriculum (Modules 0–6) — ADR-008.*
|
|
@@ -401,3 +401,49 @@ pyproject.toml
|
|
|
401
401
|
*Version: 1.0.0*
|
|
402
402
|
*Last updated: 2026-07-03*
|
|
403
403
|
*Source: Guardian DDD patterns + Python best practices + context7 (/fastapi/fastapi)*
|
|
404
|
+
|
|
405
|
+
## Judgment Tables (ADR-008)
|
|
406
|
+
|
|
407
|
+
Compact decision tables distilled from professional OOP/DDD practice (token-lean;
|
|
408
|
+
the full rationale lives in the curriculum, not here).
|
|
409
|
+
|
|
410
|
+
### Entity vs Value Object
|
|
411
|
+
| Question | Entity | Value Object |
|
|
412
|
+
|----------|--------|--------------|
|
|
413
|
+
| Identity persists across state changes? | yes | no |
|
|
414
|
+
| Change = mutate in place? | yes | replace entirely |
|
|
415
|
+
| Equality by identity or by attributes? | identity | all attributes |
|
|
416
|
+
| Immutable by nature? | no | yes |
|
|
417
|
+
| Track change history? | yes | no |
|
|
418
|
+
|
|
419
|
+
### Aggregate Boundaries
|
|
420
|
+
- An aggregate is a **consistency boundary**, not a data grouping.
|
|
421
|
+
- One aggregate modified per transaction; everything else via events.
|
|
422
|
+
- Reference other aggregates **by identity**, never by object graph.
|
|
423
|
+
- Design **small** aggregates; the root is the only entry point — behavior methods, no setters.
|
|
424
|
+
|
|
425
|
+
### Service Classifier
|
|
426
|
+
| Service | Layer | Contains | Named by |
|
|
427
|
+
|---------|-------|----------|----------|
|
|
428
|
+
| Domain service | domain/ | business process spanning entities | ubiquitous language |
|
|
429
|
+
| Application service | application/ | use-case orchestration (thin) | technical intent |
|
|
430
|
+
| Infrastructure service | infrastructure/ | technical impl (persistence, http, events) | implementation |
|
|
431
|
+
| Persistence port (interface) | domain/ | aggregate persistence contract | aggregate language |
|
|
432
|
+
| Persistence adapter | infrastructure/ | framework impl (ORM/DB) | — |
|
|
433
|
+
|
|
434
|
+
### Event Triage (3 questions)
|
|
435
|
+
Keep a candidate event only if ≥1 is true: (1) would a domain expert care?
|
|
436
|
+
(2) does it cross an aggregate boundary? (3) do we need an audit trail?
|
|
437
|
+
Otherwise it is an implementation detail. Publish from the aggregate;
|
|
438
|
+
dispatch **after** save from the application service.
|
|
439
|
+
|
|
440
|
+
### Transaction & Persistence Placement
|
|
441
|
+
| Concern | Location | Why |
|
|
442
|
+
|---------|----------|-----|
|
|
443
|
+
| Session / commit scope | application service only | one aggregate per transaction |
|
|
444
|
+
| Repository Protocol/ABC | domain/ | dependency inversion |
|
|
445
|
+
| Repository impl (SQLAlchemy/Django) | infrastructure/ | ORM out of domain |
|
|
446
|
+
| Value object | domain/ (dataclass frozen / attrs) | replace, don't mutate |
|
|
447
|
+
| Framework (FastAPI/Django) | interfaces/ | web out of domain |
|
|
448
|
+
|
|
449
|
+
*Distilled 2026-09-03 from professional OOP/DDD curriculum (Modules 0–6) — ADR-008/009.*
|
|
@@ -1021,3 +1021,49 @@ live-tests = [] # Flag for tests that hit real APIs
|
|
|
1021
1021
|
*Version: 1.0.0*
|
|
1022
1022
|
*Last updated: 2026-07-03*
|
|
1023
1023
|
*Source: Guardian DDD patterns + context7 DDD reference (/jkazama/ddd-java, /ardalis/cleanarchitecture)*
|
|
1024
|
+
|
|
1025
|
+
## Judgment Tables (ADR-008)
|
|
1026
|
+
|
|
1027
|
+
Compact decision tables distilled from professional OOP/DDD practice (token-lean;
|
|
1028
|
+
the full rationale lives in the curriculum, not here).
|
|
1029
|
+
|
|
1030
|
+
### Entity vs Value Object
|
|
1031
|
+
| Question | Entity | Value Object |
|
|
1032
|
+
|----------|--------|--------------|
|
|
1033
|
+
| Identity persists across state changes? | yes | no |
|
|
1034
|
+
| Change = mutate in place? | yes | replace entirely |
|
|
1035
|
+
| Equality by identity or by attributes? | identity | all attributes |
|
|
1036
|
+
| Immutable by nature? | no | yes |
|
|
1037
|
+
| Track change history? | yes | no |
|
|
1038
|
+
|
|
1039
|
+
### Aggregate Boundaries
|
|
1040
|
+
- An aggregate is a **consistency boundary**, not a data grouping.
|
|
1041
|
+
- One aggregate modified per transaction; everything else via events.
|
|
1042
|
+
- Reference other aggregates **by identity**, never by object graph.
|
|
1043
|
+
- Design **small** aggregates; the root is the only entry point — behavior methods, no setters.
|
|
1044
|
+
|
|
1045
|
+
### Service Classifier
|
|
1046
|
+
| Service | Layer | Contains | Named by |
|
|
1047
|
+
|---------|-------|----------|----------|
|
|
1048
|
+
| Domain service | domain/ | business process spanning entities | ubiquitous language |
|
|
1049
|
+
| Application service | application/ | use-case orchestration (thin) | technical intent |
|
|
1050
|
+
| Infrastructure service | infrastructure/ | technical impl (persistence, http, events) | implementation |
|
|
1051
|
+
| Persistence port (interface) | domain/ | aggregate persistence contract | aggregate language |
|
|
1052
|
+
| Persistence adapter | infrastructure/ | framework impl (ORM/DB) | — |
|
|
1053
|
+
|
|
1054
|
+
### Event Triage (3 questions)
|
|
1055
|
+
Keep a candidate event only if ≥1 is true: (1) would a domain expert care?
|
|
1056
|
+
(2) does it cross an aggregate boundary? (3) do we need an audit trail?
|
|
1057
|
+
Otherwise it is an implementation detail. Publish from the aggregate;
|
|
1058
|
+
dispatch **after** save from the application service.
|
|
1059
|
+
|
|
1060
|
+
### Transaction & Persistence Placement
|
|
1061
|
+
| Concern | Location | Why |
|
|
1062
|
+
|---------|----------|-----|
|
|
1063
|
+
| Transaction handle scope | application service only | one aggregate per transaction |
|
|
1064
|
+
| Repository trait | domain/ (crate) | dependency inversion |
|
|
1065
|
+
| Repository impl (sqlx/diesel) | infrastructure/ | framework out of domain |
|
|
1066
|
+
| Value object | domain/ (struct/type, immutable) | replace, don't mutate |
|
|
1067
|
+
| HTTP (axum/actix) | interfaces/ | web out of domain |
|
|
1068
|
+
|
|
1069
|
+
*Distilled 2026-09-03 from professional OOP/DDD curriculum (Modules 0–6) — ADR-008/009.*
|
|
@@ -6,7 +6,9 @@ description: TDD practice guide for Guardian implementation agents. Use when TDD
|
|
|
6
6
|
# TDD Practice — Red → Green → Refactor
|
|
7
7
|
|
|
8
8
|
> This skill applies when TDD mode is enabled on the current epic (`--tdd`).
|
|
9
|
-
> Pre-generated failing test files exist in
|
|
9
|
+
> Pre-generated failing test files exist in the project's test directory
|
|
10
|
+
> (language-specific: `tests/unit/` for TypeScript, `src/test/java/` for Java,
|
|
11
|
+
> `tests/` for Python/Go/Rust).
|
|
10
12
|
> These tests were generated deterministically from architecture contracts — no LLM involvement.
|
|
11
13
|
|
|
12
14
|
## Core Principle
|
|
@@ -24,8 +26,14 @@ Do NOT treat the generated tests as rigid specs you must match exactly. Treat th
|
|
|
24
26
|
|
|
25
27
|
### 1. Red — See the test fail
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
Run the project's test runner on the generated test file:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# TypeScript: bun test tests/unit/<module>/<component>/<component>.test.ts
|
|
33
|
+
# Java: mvn test -Dtest=<Component>Test
|
|
34
|
+
# Python: pytest tests/<module>/<component>/test_<component>.py
|
|
35
|
+
# Go: go test ./tests/<module>/<component>/
|
|
36
|
+
# Rust: cargo test --test <component>
|
|
29
37
|
```
|
|
30
38
|
|
|
31
39
|
Confirm the test fails before writing any implementation code. This validates that:
|
|
@@ -41,7 +49,7 @@ Write the minimal implementation to make the failing test pass.
|
|
|
41
49
|
- Write only enough code to pass the test
|
|
42
50
|
- Do not over-engineer — YAGNI
|
|
43
51
|
- Use the test's TODO comments as a guide for what the API should look like
|
|
44
|
-
- The test file is in `tests/unit/`, implementation files go in `src/`
|
|
52
|
+
- The test file is in the project's test directory (`tests/unit/`, `src/test/java/`, or `tests/`), implementation files go in `src/`
|
|
45
53
|
- Import the implementation module into the test file
|
|
46
54
|
|
|
47
55
|
If the generated test assumes an API shape that doesn't fit, **update the test first**, then implement.
|
|
@@ -107,11 +115,11 @@ The generated tests are **suggestions, not constraints**. You should modify them
|
|
|
107
115
|
|
|
108
116
|
## File Locations
|
|
109
117
|
|
|
110
|
-
| Artifact |
|
|
111
|
-
|
|
112
|
-
| Tests | `tests/unit
|
|
113
|
-
| Implementation | `src/<module
|
|
114
|
-
| TDD guide | `.pi/skills/tdd-practice.md` |
|
|
118
|
+
| Artifact | TypeScript | Java | Python | Go | Rust |
|
|
119
|
+
|----------|------------|------|--------|-----|------|
|
|
120
|
+
| Tests | `tests/unit/.../component.test.ts` | `src/test/java/.../ComponentTest.java` | `tests/.../test_component.py` | `tests/.../component_test.go` | `tests/.../mod.rs` |
|
|
121
|
+
| Implementation | `src/<module>/` | `src/main/java/.../` | `src/<module>/` | `src/<module>/` | `src/<module>/` |
|
|
122
|
+
| TDD guide | `.pi/skills/tdd-practice.md` | same | same | same | same |
|
|
115
123
|
|
|
116
124
|
## Anti-Patterns
|
|
117
125
|
|
|
@@ -431,3 +431,49 @@ tsconfig.json
|
|
|
431
431
|
*Version: 1.0.0*
|
|
432
432
|
*Last updated: 2026-07-03*
|
|
433
433
|
*Source: Guardian DDD patterns + TypeScript best practices + context7 (/microsoft/typescript)*
|
|
434
|
+
|
|
435
|
+
## Judgment Tables (ADR-008)
|
|
436
|
+
|
|
437
|
+
Compact decision tables distilled from professional OOP/DDD practice (token-lean;
|
|
438
|
+
the full rationale lives in the curriculum, not here).
|
|
439
|
+
|
|
440
|
+
### Entity vs Value Object
|
|
441
|
+
| Question | Entity | Value Object |
|
|
442
|
+
|----------|--------|--------------|
|
|
443
|
+
| Identity persists across state changes? | yes | no |
|
|
444
|
+
| Change = mutate in place? | yes | replace entirely |
|
|
445
|
+
| Equality by identity or by attributes? | identity | all attributes |
|
|
446
|
+
| Immutable by nature? | no | yes |
|
|
447
|
+
| Track change history? | yes | no |
|
|
448
|
+
|
|
449
|
+
### Aggregate Boundaries
|
|
450
|
+
- An aggregate is a **consistency boundary**, not a data grouping.
|
|
451
|
+
- One aggregate modified per transaction; everything else via events.
|
|
452
|
+
- Reference other aggregates **by identity**, never by object graph.
|
|
453
|
+
- Design **small** aggregates; the root is the only entry point — behavior methods, no setters.
|
|
454
|
+
|
|
455
|
+
### Service Classifier
|
|
456
|
+
| Service | Layer | Contains | Named by |
|
|
457
|
+
|---------|-------|----------|----------|
|
|
458
|
+
| Domain service | domain/ | business process spanning entities | ubiquitous language |
|
|
459
|
+
| Application service | application/ | use-case orchestration (thin) | technical intent |
|
|
460
|
+
| Infrastructure service | infrastructure/ | technical impl (persistence, http, events) | implementation |
|
|
461
|
+
| Persistence port (interface) | domain/ | aggregate persistence contract | aggregate language |
|
|
462
|
+
| Persistence adapter | infrastructure/ | framework impl (ORM/DB) | — |
|
|
463
|
+
|
|
464
|
+
### Event Triage (3 questions)
|
|
465
|
+
Keep a candidate event only if ≥1 is true: (1) would a domain expert care?
|
|
466
|
+
(2) does it cross an aggregate boundary? (3) do we need an audit trail?
|
|
467
|
+
Otherwise it is an implementation detail. Publish from the aggregate;
|
|
468
|
+
dispatch **after** save from the application service.
|
|
469
|
+
|
|
470
|
+
### Transaction & Persistence Placement
|
|
471
|
+
| Concern | Location | Why |
|
|
472
|
+
|---------|----------|-----|
|
|
473
|
+
| Unit of Work / transaction scope | application service only | one aggregate per transaction |
|
|
474
|
+
| Repository interface | domain/ (interfaces/port) | dependency inversion |
|
|
475
|
+
| Repository impl (TypeORM/Prisma) | infrastructure/ | framework out of domain |
|
|
476
|
+
| Value object | domain/ (class/record, immutable) | replace, don't mutate |
|
|
477
|
+
| DI wiring | composition root (infrastructure) | explicit assembly |
|
|
478
|
+
|
|
479
|
+
*Distilled 2026-09-03 from professional OOP/DDD curriculum (Modules 0–6) — ADR-008/009.*
|