@widnyana/design-thinking 0.0.1
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 +30 -0
- package/README.md +82 -0
- package/extensions/design-thinking.ts +166 -0
- package/package.json +51 -0
- package/prompts/cg-map.md +41 -0
- package/prompts/cg-plan.md +65 -0
- package/prompts/cg-review.md +69 -0
- package/prompts/cg.md +50 -0
- package/references/design-graph.md +88 -0
- package/references/effect-ts.md +156 -0
- package/references/method.md +229 -0
- package/references/protocol.md +228 -0
- package/skills/design-graph/SKILL.md +98 -0
- package/skills/design-method/SKILL.md +239 -0
- package/skills/graph-protocol/SKILL.md +238 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
> **Source**: Design Thinking by [r17x](https://github.com/r17x) —
|
|
2
|
+
> https://gist.github.com/r17x/90eb2f7be93932b5693753aedb09c01a
|
|
3
|
+
> Bundled verbatim as the original Effect-TS instantiation of the method.
|
|
4
|
+
> The generalized, stack-agnostic version lives in [method.md](method.md);
|
|
5
|
+
> the notation spec lives in [protocol.md](protocol.md).
|
|
6
|
+
|
|
7
|
+
# Design Thinking
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
X → Graph → Effect<A, E, R>
|
|
11
|
+
│ │ │ │ │
|
|
12
|
+
│ │ │ │ └─ what each node needs (§5)
|
|
13
|
+
│ │ │ └──── where the graph breaks (§4)
|
|
14
|
+
│ │ └─────── what flows through nodes (§2)
|
|
15
|
+
│ │
|
|
16
|
+
│ └─ nodes = functions, edges = data flow
|
|
17
|
+
│
|
|
18
|
+
└─ the problem: what you’re trying to build
|
|
19
|
+
|
|
20
|
+
§1 Shapes the nouns: records, IDs, variants, errors
|
|
21
|
+
§2 A happy path as call graph
|
|
22
|
+
§3 Cardinality one-shot (Effect) or many (Stream)
|
|
23
|
+
§4 E break points: retry, escape, die
|
|
24
|
+
§5 R dependencies: compile-time proof
|
|
25
|
+
§6 Boundary Schema: unknown → trusted at edges
|
|
26
|
+
§7 Behavior .pipe() wraps without changing the graph
|
|
27
|
+
§8 Scope acquire/release tied to lifecycle
|
|
28
|
+
§9 Test swap R, same graph shape
|
|
29
|
+
§10 Code gen body = A, .pipe() = E
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Read the problem. Draw the data flow as a call graph. Write code that IS the graph. If the code doesn't match the graph, something is wrong.
|
|
33
|
+
|
|
34
|
+
## 1. Name the shapes
|
|
35
|
+
|
|
36
|
+
What are the things? Before drawing a graph, define the domain language.
|
|
37
|
+
|
|
38
|
+
- **Records** -- entities that flow through nodes. A User, a Product, an Order.
|
|
39
|
+
- **IDs** -- identity of things. Branded, constrained, never a bare string.
|
|
40
|
+
- **Variants** -- internal state transitions. A step is Continue or Finished. A status is Pending, Active, or Cancelled.
|
|
41
|
+
- **Errors** -- named failure modes. Not strings. Tagged, structured, carrying context.
|
|
42
|
+
|
|
43
|
+
These are the nouns. The graph is the verbs. You cannot draw the graph until you know what flows through it.
|
|
44
|
+
|
|
45
|
+
## 2. Think A first
|
|
46
|
+
|
|
47
|
+
Map the happy path as a call graph before writing any code.
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
F1(A) -> F2(A) -> F3(A)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
What is the success data flow? What goes in, what comes out, what transforms happen in between? This graph IS your program structure. Draw it first. The code follows.
|
|
54
|
+
|
|
55
|
+
## 3. One or many?
|
|
56
|
+
|
|
57
|
+
Is each node one-shot or a flow?
|
|
58
|
+
|
|
59
|
+
- **One value** -- the node runs, produces A, done. This is Effect.
|
|
60
|
+
- **Many values over time** -- the node emits A repeatedly. Events, subscriptions, paginated pulls. This is Stream.
|
|
61
|
+
- **Time-bounded** -- the result is valid for a window. Cache it. Deduplicate concurrent lookups.
|
|
62
|
+
|
|
63
|
+
Same three channels (A, E, R) in all cases. Different cardinality. Mark it on the graph so the code matches.
|
|
64
|
+
|
|
65
|
+
## 4. Think E second
|
|
66
|
+
|
|
67
|
+
Mark where the graph can break. Each break point is one of three things:
|
|
68
|
+
|
|
69
|
+
- **Retry** -- transient failure, try again. Network timeout, rate limit, connection reset.
|
|
70
|
+
- **Escape Hatch** -- recoverable, return an alternative. Fallback value, cached result, default.
|
|
71
|
+
- **Die** -- defect, invariant violation, programmer bug. NOT a domain error. Something is fundamentally wrong.
|
|
72
|
+
|
|
73
|
+
Errors are VALUES in the E channel until you truly cannot handle them. They flow through the graph like data. You decide at each node: retry, escape, or let it propagate. Only `die` when the program's assumptions are violated.
|
|
74
|
+
|
|
75
|
+
## 5. Think R third
|
|
76
|
+
|
|
77
|
+
Mark what each node in the graph needs to exist. "We cannot do X if we don't have Y."
|
|
78
|
+
|
|
79
|
+
R is compile-time proof that dependencies are satisfied. Every node declares what it requires -- a database connection, a config value, an HTTP client. R shrinks as you provide layers. When R is empty (`never`), the program can run. If R is not empty, the compiler tells you exactly what's missing.
|
|
80
|
+
|
|
81
|
+
## 6. Trust at boundary
|
|
82
|
+
|
|
83
|
+
Where does untrusted data enter the graph? HTTP requests, file reads, environment variables, user input, third-party API responses.
|
|
84
|
+
|
|
85
|
+
Schema converts `unknown -> trusted` at the edges. One definition = type + validator + transformer. Define it once, use it everywhere.
|
|
86
|
+
|
|
87
|
+
Trust nothing at the boundary. Trust everything inside. The boundary is the only place you parse. After that, the types guarantee shape.
|
|
88
|
+
|
|
89
|
+
## 7. Layer behavior
|
|
90
|
+
|
|
91
|
+
What cross-cutting concerns wrap nodes? Retry policies, timeouts, logging, tracing, caching.
|
|
92
|
+
|
|
93
|
+
These wrap via `.pipe()` WITHOUT changing the core graph. The happy path stays clean -- you can read it without wading through retry logic and timeout configuration. Behavior is composed orthogonally. The graph says WHAT happens. The layers say HOW it behaves under pressure.
|
|
94
|
+
|
|
95
|
+
## 8. Scope resources
|
|
96
|
+
|
|
97
|
+
What nodes acquire resources? Database connections, file handles, WebSocket connections, child processes.
|
|
98
|
+
|
|
99
|
+
Acquire/release is a type guarantee. If a node opens a connection, scope ensures it closes -- even on error, even on interrupt. Cleanup is structural, not a TODO comment you hope someone remembers.
|
|
100
|
+
|
|
101
|
+
## 9. Swap R to prove it
|
|
102
|
+
|
|
103
|
+
The call graph doesn't change between production and tests. Only R changes.
|
|
104
|
+
|
|
105
|
+
Same graph shape. Same A flowing through. Same E possible. Different layer behind R. If the graph can't run with a test R, the design has hidden dependencies. If you have to mock the world to test one node, the node is doing too much.
|
|
106
|
+
|
|
107
|
+
This is the payoff of separating A, E, and R -- you prove the graph correct by swapping what's behind R.
|
|
108
|
+
|
|
109
|
+
## 10. gen is A, pipe is E
|
|
110
|
+
|
|
111
|
+
The separation of A (step 2) and E (step 4) maps directly to Effect code structure:
|
|
112
|
+
|
|
113
|
+
- **`Effect.gen` body** -- the happy path. Every `yield*` is an A flowing through the graph. No error handling inside.
|
|
114
|
+
- **`.pipe()` after gen** -- the complete E enumeration. Before writing the pipe, read the actual E type of every yielded effect. The pipe catches, retries, or transforms every E that the gen body can produce.
|
|
115
|
+
|
|
116
|
+
This is not a stylistic preference. If error handling lives inside the gen body, the A path and E path are tangled -- you cannot read the happy path without wading through catches and retries. The gen body IS the call graph from step 2. The pipe IS the error annotation from step 4.
|
|
117
|
+
|
|
118
|
+
### E scoping at graph layers
|
|
119
|
+
|
|
120
|
+
Each layer in the call graph scopes its own E before passing to the next layer:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
Services → Auth → Handlers
|
|
124
|
+
E=SqlError E=DatabaseError E=AuthError E=RPC errors
|
|
125
|
+
↓ scope ↓ scope ↓ scope
|
|
126
|
+
DatabaseError AuthError RPC errors
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
A service catches SqlError and produces DatabaseError. Auth catches DatabaseError and produces AuthError. The handler catches AuthError and produces the RPC-declared error. Each layer's pipe is a complete enumeration of what IT received -- not what originated three layers down. Consumers never see implementation errors from deeper layers.
|
|
130
|
+
|
|
131
|
+
### Divergent strategies
|
|
132
|
+
|
|
133
|
+
When two effects in the same gen body need different E handling -- one should fail hard, the other should fall back gracefully -- that is a **divergent strategy**. Handle each effect's E inline within the gen body, because the outer pipe cannot distinguish which yield produced which error.
|
|
134
|
+
|
|
135
|
+
This is the ONE exception to "no error handling in gen." It exists because the graph has a fork: two branches with different failure semantics sharing a node. Mark it clearly -- it should be rare.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## The Pipeline
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
PROBLEM
|
|
143
|
+
-> "What are the shapes?" -> define the domain language
|
|
144
|
+
-> "What is the happy path?" -> draw the call graph (A)
|
|
145
|
+
-> "Is each node one-shot or a flow?" -> mark cardinality
|
|
146
|
+
-> "Where can it break?" -> annotate errors on the graph (E)
|
|
147
|
+
-> "What does each node need?" -> annotate requirements on the graph (R)
|
|
148
|
+
-> "Where does untrusted data enter?" -> Schema at graph boundaries
|
|
149
|
+
-> "What wraps nodes without changing them?" -> pipe behavior orthogonally
|
|
150
|
+
-> "What resources need cleanup?" -> scope lifecycle
|
|
151
|
+
-> "Can I swap R and the graph still works?" -> verify with test layers
|
|
152
|
+
-> "Does my code separate A from E structurally?" -> gen body = A, pipe = E
|
|
153
|
+
-> CODE -> the code IS the graph
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**If the code doesn't match the call graph, the implementation is wrong.**
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# Design Thinking — The Method
|
|
2
|
+
|
|
3
|
+
A stack-agnostic design methodology. Read the problem. Draw the data flow as a
|
|
4
|
+
call graph (the **Design Graph**). Write code that IS the graph. If the code
|
|
5
|
+
doesn't match the graph, something is wrong.
|
|
6
|
+
|
|
7
|
+
The method is language-neutral. Where it says "Effect", "gen", or "pipe" in the
|
|
8
|
+
Effect-TS appendix (`effect-ts.md`), substitute your stack's equivalent: Result
|
|
9
|
+
types, exceptions at layer joins, middleware, RAII, constructor injection —
|
|
10
|
+
whatever your language provides. The discipline never changes, only the vocabulary.
|
|
11
|
+
|
|
12
|
+
Render every result in [Graph Protocol](protocol.md) format.
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
X → DesignGraph<A, E, R>
|
|
16
|
+
│ │ │ │ │
|
|
17
|
+
│ │ │ │ └─ what each node needs (§5)
|
|
18
|
+
│ │ │ └──── where the graph breaks (§4)
|
|
19
|
+
│ │ └─────── what flows through nodes (§2)
|
|
20
|
+
│ │
|
|
21
|
+
│ └─ nodes = functions, edges = data flow
|
|
22
|
+
│
|
|
23
|
+
└─ the problem: what you're trying to build
|
|
24
|
+
|
|
25
|
+
§1 Shapes the nouns: records, IDs, variants, errors
|
|
26
|
+
§2 A happy path as call graph
|
|
27
|
+
§3 Cardinality one-shot (1), many (N), or time-bounded (T)
|
|
28
|
+
§4 E break points: retry, escape, die
|
|
29
|
+
§5 R dependencies: compile-time or wiring-time proof
|
|
30
|
+
§6 Boundary parse: unknown → trusted at edges
|
|
31
|
+
§7 Behavior orthogonal layers wrap without changing the graph
|
|
32
|
+
§8 Scope acquire/release tied to lifecycle
|
|
33
|
+
§9 Test swap R, same graph shape
|
|
34
|
+
§10 Structure happy path = A, failure handling = E, never tangled
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## 1. Name the shapes
|
|
38
|
+
|
|
39
|
+
What are the things? Before drawing a graph, define the domain language.
|
|
40
|
+
|
|
41
|
+
- **Records** — entities that flow through nodes. A User, a Product, an Order.
|
|
42
|
+
- **IDs** — identity of things. Branded, constrained, never a bare string.
|
|
43
|
+
- **Variants** — internal state transitions. A step is Continue or Finished.
|
|
44
|
+
A status is Pending, Active, or Cancelled.
|
|
45
|
+
- **Errors** — named failure modes. Not strings. Structured, carrying context.
|
|
46
|
+
|
|
47
|
+
These are the nouns. The graph is the verbs. You cannot draw the graph until
|
|
48
|
+
you know what flows through it.
|
|
49
|
+
|
|
50
|
+
→ *Protocol section: SHAPES.*
|
|
51
|
+
|
|
52
|
+
## 2. Think A first
|
|
53
|
+
|
|
54
|
+
Map the happy path as a call graph before writing any code.
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
F1(A) -> F2(A) -> F3(A)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
What is the success data flow? What goes in, what comes out, what transforms
|
|
61
|
+
happen in between? This graph IS your program structure. Draw it first. The
|
|
62
|
+
code follows.
|
|
63
|
+
|
|
64
|
+
→ *Protocol section: GRAPH (edges) + header tree (A).*
|
|
65
|
+
|
|
66
|
+
## 3. One or many?
|
|
67
|
+
|
|
68
|
+
Is each node one-shot or a flow?
|
|
69
|
+
|
|
70
|
+
- **One value** — the node runs, produces A, done. `(1)`
|
|
71
|
+
- **Many values over time** — the node emits A repeatedly: events,
|
|
72
|
+
subscriptions, paginated pulls, batches. `(N)`
|
|
73
|
+
- **Time-bounded** — the result is valid for a window. Cache it. Deduplicate
|
|
74
|
+
concurrent lookups. `(T)`
|
|
75
|
+
|
|
76
|
+
Same three channels (A, E, R) in all cases. Different cardinality. Mark it on
|
|
77
|
+
the graph so the code matches.
|
|
78
|
+
|
|
79
|
+
→ *Protocol section: CARDINALITY.*
|
|
80
|
+
|
|
81
|
+
## 4. Think E second
|
|
82
|
+
|
|
83
|
+
Mark where the graph can break. Each break point is one of three things:
|
|
84
|
+
|
|
85
|
+
- **Retry** `⟳` — transient failure, try again. Network timeout, rate limit,
|
|
86
|
+
connection reset.
|
|
87
|
+
- **Escape Hatch** `↯` — recoverable, return an alternative. Fallback value,
|
|
88
|
+
cached result, default, skip-and-continue.
|
|
89
|
+
- **Die** `☠` — defect, invariant violation, programmer bug. NOT a domain
|
|
90
|
+
error. Something is fundamentally wrong.
|
|
91
|
+
|
|
92
|
+
Failures are first-class values in the E channel until you truly cannot handle
|
|
93
|
+
them. They flow through the graph like data — whether your language expresses
|
|
94
|
+
that as Result types, checked exceptions, error returns, or a strict rule that
|
|
95
|
+
exceptions only cross at layer joins. You decide at each node: retry, escape,
|
|
96
|
+
or propagate. Only `die` when the program's assumptions are violated.
|
|
97
|
+
|
|
98
|
+
→ *Protocol section: GRAPH (E annotations) + VERDICT.*
|
|
99
|
+
|
|
100
|
+
## 5. Think R third
|
|
101
|
+
|
|
102
|
+
Mark what each node in the graph needs to exist. "We cannot do X if we don't
|
|
103
|
+
have Y."
|
|
104
|
+
|
|
105
|
+
R is proof that dependencies are satisfied — at compile time (injected via
|
|
106
|
+
constructors, generics, typeclasses, traits) or at wiring time (composition
|
|
107
|
+
root, DI container, context object). Every node declares what it requires: a
|
|
108
|
+
database connection, a config value, an HTTP client. R shrinks as you provide
|
|
109
|
+
layers. When R is empty, the program can run. If R is not empty, the compiler
|
|
110
|
+
or the wiring tells you exactly what's missing.
|
|
111
|
+
|
|
112
|
+
→ *Protocol section: GRAPH (R annotations) + TEST LAYERS.*
|
|
113
|
+
|
|
114
|
+
## 6. Trust at boundary
|
|
115
|
+
|
|
116
|
+
Where does untrusted data enter the graph? HTTP requests, file reads,
|
|
117
|
+
environment variables, user input, third-party API responses.
|
|
118
|
+
|
|
119
|
+
Convert `unknown → trusted` at the edges — a schema, a parsing constructor, a
|
|
120
|
+
validating factory, a DTO boundary. One definition = type + validator +
|
|
121
|
+
transformer. Define it once, use it everywhere.
|
|
122
|
+
|
|
123
|
+
Trust nothing at the boundary. Trust everything inside. The boundary is the
|
|
124
|
+
only place you parse. After that, the types guarantee shape.
|
|
125
|
+
|
|
126
|
+
→ *Protocol section: BOUNDARIES.*
|
|
127
|
+
|
|
128
|
+
## 7. Layer behavior
|
|
129
|
+
|
|
130
|
+
What cross-cutting concerns wrap nodes? Retry policies, timeouts, logging,
|
|
131
|
+
tracing, caching.
|
|
132
|
+
|
|
133
|
+
These wrap WITHOUT changing the core graph — middleware, decorators,
|
|
134
|
+
higher-order functions, aspects. The happy path stays clean: you can read it
|
|
135
|
+
without wading through retry logic and timeout configuration. Behavior is
|
|
136
|
+
composed orthogonally. The graph says WHAT happens. The layers say HOW it
|
|
137
|
+
behaves under pressure.
|
|
138
|
+
|
|
139
|
+
→ *Protocol section: BEHAVIOR.*
|
|
140
|
+
|
|
141
|
+
## 8. Scope resources
|
|
142
|
+
|
|
143
|
+
What nodes acquire resources? Database connections, file handles, WebSocket
|
|
144
|
+
connections, child processes, locks.
|
|
145
|
+
|
|
146
|
+
Acquire/release must be structurally bound: RAII, `defer`, context managers,
|
|
147
|
+
`try/finally` at the owning node — even on error, even on interrupt. Cleanup
|
|
148
|
+
is structural, not a TODO comment you hope someone remembers.
|
|
149
|
+
|
|
150
|
+
→ *Protocol section: SCOPE.*
|
|
151
|
+
|
|
152
|
+
## 9. Swap R to prove it
|
|
153
|
+
|
|
154
|
+
The call graph doesn't change between production and tests. Only R changes.
|
|
155
|
+
|
|
156
|
+
Same graph shape. Same A flowing through. Same E possible. Different layer
|
|
157
|
+
behind R. If the graph can't run with a test R, the design has hidden
|
|
158
|
+
dependencies. If you have to mock the world to test one node, the node is
|
|
159
|
+
doing too much.
|
|
160
|
+
|
|
161
|
+
This is the payoff of separating A, E, and R — you prove the graph correct by
|
|
162
|
+
swapping what's behind R.
|
|
163
|
+
|
|
164
|
+
→ *Protocol section: TEST LAYERS.*
|
|
165
|
+
|
|
166
|
+
## 10. Separate A from E structurally
|
|
167
|
+
|
|
168
|
+
The separation of A (§2) and E (§4) maps directly onto code structure, in any
|
|
169
|
+
language:
|
|
170
|
+
|
|
171
|
+
- **The happy path body** — one readable place. Every call is an A flowing
|
|
172
|
+
through the graph. No error handling inside. (In Effect-TS: the `Effect.gen`
|
|
173
|
+
body. Elsewhere: the main function, the do-notation block, the straight-line
|
|
174
|
+
pipeline of calls.)
|
|
175
|
+
- **The failure join points** — the complete E enumeration. Before writing
|
|
176
|
+
them, read the actual failure modes of every node on the happy path. The
|
|
177
|
+
join points catch, retry, or translate every E the happy path can produce.
|
|
178
|
+
(In Effect-TS: `.pipe()` after gen. Elsewhere: the middleware stack, the
|
|
179
|
+
Result chain, the try/catch at the layer boundary.)
|
|
180
|
+
|
|
181
|
+
This is not a stylistic preference. If error handling lives inside the happy
|
|
182
|
+
path body, the A path and E path are tangled — you cannot read the happy path
|
|
183
|
+
without wading through catches and retries. The happy path body IS the call
|
|
184
|
+
graph from §2. The join points ARE the error annotations from §4.
|
|
185
|
+
|
|
186
|
+
### E scoping at graph layers
|
|
187
|
+
|
|
188
|
+
Each layer scopes its own E before passing to the next layer. A service catches
|
|
189
|
+
storage errors and produces a domain error. Auth catches domain errors and
|
|
190
|
+
produces an access error. The handler catches access errors and produces the
|
|
191
|
+
declared interface error. Each layer's handling is a complete enumeration of
|
|
192
|
+
what IT received — not what originated three layers down. Consumers never see
|
|
193
|
+
implementation errors from deeper layers. *(See protocol.md for the diagram.)*
|
|
194
|
+
|
|
195
|
+
### Divergent strategies
|
|
196
|
+
|
|
197
|
+
When two nodes in the same happy path need different failure handling — one
|
|
198
|
+
should fail hard, the other should fall back gracefully — that is a
|
|
199
|
+
**divergent strategy**. Handle each node's E inline within the happy path at
|
|
200
|
+
the fork, because the outer join point cannot distinguish which node produced
|
|
201
|
+
which error. This is the ONE exception to "no error handling in the happy
|
|
202
|
+
path." It exists because the graph has a fork: two branches with different
|
|
203
|
+
failure semantics sharing a node. Mark it clearly — it should be rare.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## The Pipeline
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
PROBLEM
|
|
211
|
+
-> "What are the shapes?" -> define the domain language
|
|
212
|
+
-> "What is the happy path?" -> draw the call graph (A)
|
|
213
|
+
-> "Is each node one-shot or a flow?" -> mark cardinality
|
|
214
|
+
-> "Where can it break?" -> annotate errors on the graph (E)
|
|
215
|
+
-> "What does each node need?" -> annotate requirements on the graph (R)
|
|
216
|
+
-> "Where does untrusted data enter?" -> parse at graph boundaries
|
|
217
|
+
-> "What wraps nodes without changing them?" -> layer behavior orthogonally
|
|
218
|
+
-> "What resources need cleanup?" -> scope lifecycle
|
|
219
|
+
-> "Can I swap R and the graph still works?" -> verify with test layers
|
|
220
|
+
-> "Does my code separate A from E structurally?" -> happy path = A, joins = E
|
|
221
|
+
-> CODE -> the code IS the graph
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**If the code doesn't match the call graph, the implementation is wrong.**
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
*For the original Effect-TS instantiation of this method, see
|
|
229
|
+
[effect-ts.md](effect-ts.md).*
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# Graph Protocol
|
|
2
|
+
|
|
3
|
+
The notation spec for **Design Graphs**. A Design Graph is the annotated call graph
|
|
4
|
+
of a concrete problem: nodes are functions, edges are data flow, and every node
|
|
5
|
+
carries annotations for data (A), cardinality, failure (E), and requirements (R).
|
|
6
|
+
|
|
7
|
+
The Protocol is stack-agnostic. A graph written for Go, Rust, Python, TypeScript,
|
|
8
|
+
SQL, or a spreadsheet renders identically. Only the vocabulary inside the
|
|
9
|
+
annotations changes (e.g. what "retry" means in your language).
|
|
10
|
+
|
|
11
|
+
Every tool in this package (`/cg`, `/cg-plan`, `/cg-review`, `/cg-map`, and the
|
|
12
|
+
`/dt` active mode) MUST emit output in the format below.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Canonical output skeleton
|
|
17
|
+
|
|
18
|
+
Sections appear in this fixed order. Omit a section only when it is genuinely
|
|
19
|
+
empty for the problem at hand (say so: `SCOPE: none`), never to skip thinking.
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
PROBLEM: <one-line statement of what you're building or examining>
|
|
23
|
+
|
|
24
|
+
X → DesignGraph<A, E, R>
|
|
25
|
+
│ │ │ │ │
|
|
26
|
+
│ │ │ │ └─ R: what each node needs (§5)
|
|
27
|
+
│ │ │ └──── E: where the graph breaks (§4)
|
|
28
|
+
│ │ └─────── A: what flows through nodes (§2)
|
|
29
|
+
│ │
|
|
30
|
+
│ └─ nodes = functions, edges = data flow
|
|
31
|
+
│
|
|
32
|
+
└─ the problem
|
|
33
|
+
|
|
34
|
+
SHAPES: <the nouns: records, IDs, variants, errors>
|
|
35
|
+
|
|
36
|
+
GRAPH:
|
|
37
|
+
<ascii call graph, annotated inline>
|
|
38
|
+
|
|
39
|
+
CARDINALITY: <node (1|N|T) list>
|
|
40
|
+
BOUNDARIES: <every edge crossing untrusted → trusted>
|
|
41
|
+
BEHAVIOR: <cross-cutting layers that wrap nodes>
|
|
42
|
+
SCOPE: <resources: acquire@… → release@…>
|
|
43
|
+
TEST LAYERS: <the R that tests provide — same GRAPH, zero code change>
|
|
44
|
+
VERDICT: <code matches graph? what is missing/tangled?>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## The header tree
|
|
50
|
+
|
|
51
|
+
The header names the problem and anchors the three channels. `X` is your problem;
|
|
52
|
+
everything after it is the graph you build.
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
X → DesignGraph<A, E, R>
|
|
56
|
+
│ │ │ │ │
|
|
57
|
+
│ │ │ │ └─ R: what each node needs (§5)
|
|
58
|
+
│ │ │ └──── E: where the graph breaks (§4)
|
|
59
|
+
│ │ └─────── A: what flows through nodes (§2)
|
|
60
|
+
│ │
|
|
61
|
+
│ └─ nodes = functions, edges = data flow
|
|
62
|
+
│
|
|
63
|
+
└─ the problem: what you're trying to build
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Section rules
|
|
69
|
+
|
|
70
|
+
### PROBLEM
|
|
71
|
+
|
|
72
|
+
One line. The thing you're building or the question you're answering. If you
|
|
73
|
+
cannot write it in one line, the problem is not understood — stop and refine.
|
|
74
|
+
|
|
75
|
+
### SHAPES
|
|
76
|
+
|
|
77
|
+
The domain nouns. Name them before drawing anything:
|
|
78
|
+
|
|
79
|
+
- **Records** — entities that flow through nodes (User, Order, Config).
|
|
80
|
+
- **IDs** — identity, never a bare primitive (UserId, Email).
|
|
81
|
+
- **Variants** — internal state transitions (pending|active|cancelled).
|
|
82
|
+
- **Errors** — named failure modes, structured, carrying context (not strings).
|
|
83
|
+
|
|
84
|
+
Format: comma-separated list. These are the nouns; the graph is the verbs.
|
|
85
|
+
|
|
86
|
+
### GRAPH
|
|
87
|
+
|
|
88
|
+
ASCII call graph. Nodes are functions/steps; edges are data flow. Annotate
|
|
89
|
+
**inline**, using the branch conventions from the header tree:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
parse → Handler → Service → Repo
|
|
93
|
+
│ │ │
|
|
94
|
+
│ │ └─ R: DbConn · (N)
|
|
95
|
+
│ └─ E: timeout ⟳retry×3 → ↯escape(cached)
|
|
96
|
+
└─ 🔒 HTTP body → trusted
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Inline annotation grammar:
|
|
100
|
+
|
|
101
|
+
| Annotation | Meaning | Example |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `R: a, b` | what the node requires (§5) | `R: DbConn, Config` |
|
|
104
|
+
| `E: <err> ⟳retry[×n] → ↯escape(alt)` | failure handling (§4) | `E: NetErr ⟳retry×3 → ↯escape(fallback)` |
|
|
105
|
+
| `E: <err> ☠die` | defect, not domain error (§4) | `E: NullConfig ☠die` |
|
|
106
|
+
| `🔒 x → y` | untrusted → trusted boundary (§6) | `🔒 JSON → Settings` |
|
|
107
|
+
| `(1) (N) (T)` | cardinality (§3) | `(N)` = many over time |
|
|
108
|
+
| `⛈ name` | behavior layer wrapping the node (§7) | `⛈logging wraps Service` |
|
|
109
|
+
| `@scope` | resource bound to a scope (§8) | `DbConn @main` |
|
|
110
|
+
|
|
111
|
+
### CARDINALITY
|
|
112
|
+
|
|
113
|
+
One line listing each node's arity:
|
|
114
|
+
|
|
115
|
+
- `(1)` — one-shot: runs once, produces one value.
|
|
116
|
+
- `(N)` — many: emits values over time (stream, iterator, subscription, pages).
|
|
117
|
+
- `(T)` — time-bounded: result valid for a window; cache it, dedupe concurrent lookups.
|
|
118
|
+
|
|
119
|
+
Same three channels (A, E, R) in all cases. Different cardinality. The code must
|
|
120
|
+
match: don't implement a `(N)` node as a one-shot.
|
|
121
|
+
|
|
122
|
+
### BOUNDARIES
|
|
123
|
+
|
|
124
|
+
Every place untrusted data enters the graph: HTTP requests, file reads, env
|
|
125
|
+
vars, user input, third-party responses. The rule: **parse, don't validate** —
|
|
126
|
+
convert `unknown → trusted` once, at the edge, then trust the types everywhere
|
|
127
|
+
inside. One definition per shape = type + validator + transformer, reused
|
|
128
|
+
everywhere.
|
|
129
|
+
|
|
130
|
+
### BEHAVIOR
|
|
131
|
+
|
|
132
|
+
Cross-cutting concerns that wrap nodes WITHOUT changing the core graph:
|
|
133
|
+
retry policies, timeouts, logging, tracing, caching. Composed orthogonally
|
|
134
|
+
(middleware, decorators, higher-order wrappers, aspects). The graph says WHAT
|
|
135
|
+
happens; the layers say HOW it behaves under pressure. If removing logging
|
|
136
|
+
forces you to edit business logic, behavior is not orthogonal.
|
|
137
|
+
|
|
138
|
+
### SCOPE
|
|
139
|
+
|
|
140
|
+
Resources that must be released: connections, file handles, sockets, child
|
|
141
|
+
processes. Acquire/release must be structurally bound (RAII, `defer`, context
|
|
142
|
+
managers, `try/finally`) — never convention or a TODO. Notation:
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
SCOPE: DbConn acquire@main → release@main · WS acquire@Handler → release@Handler
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### TEST LAYERS
|
|
149
|
+
|
|
150
|
+
The R that tests provide. The call graph does not change between production and
|
|
151
|
+
tests — only R changes. Same graph, same A, same E, different layers behind R.
|
|
152
|
+
If the graph can't run with a test R, the design has hidden dependencies. If
|
|
153
|
+
you must mock the world to test one node, the node does too much.
|
|
154
|
+
|
|
155
|
+
### VERDICT
|
|
156
|
+
|
|
157
|
+
The structural check. Answer explicitly:
|
|
158
|
+
|
|
159
|
+
- Is the happy path (A) readable on its own, free of failure handling?
|
|
160
|
+
- Is every E the graph can produce handled at a defined join point?
|
|
161
|
+
- Do per-layer E scopes translate errors (inner errors never leak through)?
|
|
162
|
+
- Does the code match the graph? **If the code doesn't match the call graph,
|
|
163
|
+
the implementation is wrong.**
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## E scoping at graph layers
|
|
168
|
+
|
|
169
|
+
Each layer scopes its own E before passing to the next:
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
Services → Auth → Handlers
|
|
173
|
+
E=SqlErr E=DbErr E=AuthErr E=RPC errs
|
|
174
|
+
↓ scope ↓ scope ↓ scope
|
|
175
|
+
DbErr AuthErr RPC errs
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
A service catches `SqlErr` and produces `DbErr`. Auth catches `DbErr` and
|
|
179
|
+
produces `AuthErr`. The handler catches `AuthErr` and produces the declared
|
|
180
|
+
error. Each layer's handling enumerates what IT receives — never what originated
|
|
181
|
+
three layers down. Consumers never see implementation errors from deeper layers.
|
|
182
|
+
|
|
183
|
+
## Divergent strategies (the one exception)
|
|
184
|
+
|
|
185
|
+
When two nodes in the same happy path need different failure semantics — one
|
|
186
|
+
must fail hard, another must fall back gracefully — handle that node's E
|
|
187
|
+
inline at the fork, because the outer layer cannot distinguish which node
|
|
188
|
+
produced which error. Mark it clearly; it should be rare.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Worked example (non-TS: Python CLI)
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
PROBLEM: sync a list of URLs to local files, resumable, with a status line
|
|
196
|
+
|
|
197
|
+
X → DesignGraph<A, E, R>
|
|
198
|
+
│ │ │ │ │
|
|
199
|
+
│ │ │ │ └─ R: Http, Clock, Logger (§5)
|
|
200
|
+
│ │ │ └──── E: NetErr, DiskErr (§4)
|
|
201
|
+
│ │ └─────── A: (url, bytes), Status (§2)
|
|
202
|
+
│ │
|
|
203
|
+
│ └─ nodes = functions, edges = data flow
|
|
204
|
+
│
|
|
205
|
+
└─ the problem
|
|
206
|
+
|
|
207
|
+
SHAPES: Url, Target(path), Downloaded(url, bytes), Status(pending|done|failed), NetErr, DiskErr
|
|
208
|
+
|
|
209
|
+
GRAPH:
|
|
210
|
+
load_state → plan → fetch … download → write → report
|
|
211
|
+
│ │ │ │
|
|
212
|
+
│ │ ├─ E: NetErr ⟳retry×3(-backoff) → ↯escape(skip, mark failed)
|
|
213
|
+
│ │ └─ 🔒 raw bytes → Downloaded (size, content-type checked)
|
|
214
|
+
│ └─ R: StateFile · (1)
|
|
215
|
+
└─ 🔒 state file → trusted (schema-validated; corrupt → ↯escape(empty, warn))
|
|
216
|
+
|
|
217
|
+
CARDINALITY: load_state (1) · plan (1) · fetch (N) · download (1) · write (1) · report (T)
|
|
218
|
+
BOUNDARIES: load_state 🔒 file → State · fetch 🔒 wire → Downloaded
|
|
219
|
+
BEHAVIOR: ⛈retry wraps fetch · ⛈rate-limit wraps fetch · ⛈progress wraps write
|
|
220
|
+
SCOPE: Http acquire@main → release@main · StateFile @main (context manager)
|
|
221
|
+
TEST LAYERS: R = {Http: FakeHttp, Clock: FrozenClock, Logger: Silent} — same GRAPH, no stubs in nodes
|
|
222
|
+
VERDICT: happy path reads as the pipeline above; all NetErr handling lives at the
|
|
223
|
+
write/report join, not inside download; state corruption escapes at the edge.
|
|
224
|
+
Matches graph → implementation valid.
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Note the stack vocabulary: `context manager` for scope, exceptions caught at
|
|
228
|
+
layer joins for E, plain functions for nodes. Protocol unchanged.
|