mishkan-harness 0.1.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.
Files changed (186) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +205 -0
  3. package/bin/mishkan.js +221 -0
  4. package/docs/design/MISHKAN_agent_aliases.md +140 -0
  5. package/docs/design/MISHKAN_decisions.md +172 -0
  6. package/docs/design/MISHKAN_harness_design.md +820 -0
  7. package/docs/design/MISHKAN_ontology.md +87 -0
  8. package/docs/design/MISHKAN_token_optimisation.md +181 -0
  9. package/docs/engineer/README.md +37 -0
  10. package/docs/engineer/profile.example.md +79 -0
  11. package/docs/usage/01-installation.md +178 -0
  12. package/docs/usage/02-project-init.md +151 -0
  13. package/docs/usage/03-orchestration.md +218 -0
  14. package/docs/usage/04-memory-layer.md +201 -0
  15. package/docs/usage/05-selective-ingest.md +177 -0
  16. package/docs/usage/06-llm-providers.md +195 -0
  17. package/docs/usage/07-troubleshooting.md +316 -0
  18. package/docs/usage/08-glossary.md +154 -0
  19. package/docs/usage/09-workflows.md +123 -0
  20. package/docs/usage/README.md +77 -0
  21. package/package.json +43 -0
  22. package/payload/install/settings.hooks.json +47 -0
  23. package/payload/mishkan/AGENT_SPEC.md +154 -0
  24. package/payload/mishkan/agents/ahikam.md +58 -0
  25. package/payload/mishkan/agents/aholiab.md +68 -0
  26. package/payload/mishkan/agents/asaph.md +73 -0
  27. package/payload/mishkan/agents/baruch.md +88 -0
  28. package/payload/mishkan/agents/benaiah.md +76 -0
  29. package/payload/mishkan/agents/bezalel.md +83 -0
  30. package/payload/mishkan/agents/caleb.md +74 -0
  31. package/payload/mishkan/agents/deborah.md +63 -0
  32. package/payload/mishkan/agents/elasah.md +58 -0
  33. package/payload/mishkan/agents/eliashib.md +68 -0
  34. package/payload/mishkan/agents/ezra.md +69 -0
  35. package/payload/mishkan/agents/hanun.md +64 -0
  36. package/payload/mishkan/agents/hiram.md +68 -0
  37. package/payload/mishkan/agents/hizkiah.md +76 -0
  38. package/payload/mishkan/agents/huldah.md +59 -0
  39. package/payload/mishkan/agents/huram.md +66 -0
  40. package/payload/mishkan/agents/hushai.md +59 -0
  41. package/payload/mishkan/agents/igal.md +58 -0
  42. package/payload/mishkan/agents/ira.md +86 -0
  43. package/payload/mishkan/agents/jahaziel.md +71 -0
  44. package/payload/mishkan/agents/jakin.md +66 -0
  45. package/payload/mishkan/agents/jehonathan.md +62 -0
  46. package/payload/mishkan/agents/jehoshaphat.md +68 -0
  47. package/payload/mishkan/agents/joab.md +71 -0
  48. package/payload/mishkan/agents/joah.md +62 -0
  49. package/payload/mishkan/agents/maaseiah.md +61 -0
  50. package/payload/mishkan/agents/meremoth.md +65 -0
  51. package/payload/mishkan/agents/meshullam.md +67 -0
  52. package/payload/mishkan/agents/nathan.md +70 -0
  53. package/payload/mishkan/agents/nehemiah.md +93 -0
  54. package/payload/mishkan/agents/obed.md +60 -0
  55. package/payload/mishkan/agents/oholiab.md +67 -0
  56. package/payload/mishkan/agents/palal.md +63 -0
  57. package/payload/mishkan/agents/phinehas.md +73 -0
  58. package/payload/mishkan/agents/rehum.md +60 -0
  59. package/payload/mishkan/agents/salma.md +69 -0
  60. package/payload/mishkan/agents/seraiah.md +73 -0
  61. package/payload/mishkan/agents/shallum.md +66 -0
  62. package/payload/mishkan/agents/shaphan.md +64 -0
  63. package/payload/mishkan/agents/shemaiah.md +67 -0
  64. package/payload/mishkan/agents/shevna.md +58 -0
  65. package/payload/mishkan/agents/uriah.md +70 -0
  66. package/payload/mishkan/agents/zaccur.md +58 -0
  67. package/payload/mishkan/agents/zadok.md +67 -0
  68. package/payload/mishkan/agents/zerubbabel.md +69 -0
  69. package/payload/mishkan/cognee/.env.curated.example +61 -0
  70. package/payload/mishkan/cognee/.env.example +165 -0
  71. package/payload/mishkan/cognee/Dockerfile +50 -0
  72. package/payload/mishkan/cognee/README.md +129 -0
  73. package/payload/mishkan/cognee/docker-compose.curated-ui.yml +61 -0
  74. package/payload/mishkan/cognee/docker-compose.curated.yml +85 -0
  75. package/payload/mishkan/cognee/docker-compose.hardening.yml +16 -0
  76. package/payload/mishkan/cognee/docker-compose.selfhosted.yml +114 -0
  77. package/payload/mishkan/cognee/docker-compose.ui.yml +70 -0
  78. package/payload/mishkan/cognee/docker-compose.yml +71 -0
  79. package/payload/mishkan/cognee/ingest-curated.py +92 -0
  80. package/payload/mishkan/commands/dep-audit.md +24 -0
  81. package/payload/mishkan/commands/mishkan-init.md +25 -0
  82. package/payload/mishkan/commands/mishkan-resume.md +21 -0
  83. package/payload/mishkan/commands/promote.md +19 -0
  84. package/payload/mishkan/commands/sefer-pull.md +19 -0
  85. package/payload/mishkan/commands/sprint-close.md +21 -0
  86. package/payload/mishkan/config/curated-library.yaml +113 -0
  87. package/payload/mishkan/config/improvement-queries.md +29 -0
  88. package/payload/mishkan/config/model-routing.yaml +87 -0
  89. package/payload/mishkan/config/projects.yaml +38 -0
  90. package/payload/mishkan/evals/baruch/README.md +93 -0
  91. package/payload/mishkan/evals/baruch/fixtures/invalid/bad-outcome-enum.json +15 -0
  92. package/payload/mishkan/evals/baruch/fixtures/invalid/bad-sprint-pattern.json +15 -0
  93. package/payload/mishkan/evals/baruch/fixtures/invalid/bad-trigger-enum.json +15 -0
  94. package/payload/mishkan/evals/baruch/fixtures/invalid/malformed-json.json +7 -0
  95. package/payload/mishkan/evals/baruch/fixtures/invalid/missing-required-field.json +14 -0
  96. package/payload/mishkan/evals/baruch/fixtures/valid/blocked-vendor.json +15 -0
  97. package/payload/mishkan/evals/baruch/fixtures/valid/curated-shortcircuit.json +15 -0
  98. package/payload/mishkan/evals/baruch/fixtures/valid/partial-no-write.json +14 -0
  99. package/payload/mishkan/evals/baruch/fixtures/valid/resolved-cross-harness.json +15 -0
  100. package/payload/mishkan/evals/baruch/golden_case/expected.yaml +35 -0
  101. package/payload/mishkan/evals/baruch/golden_case/input.yaml +47 -0
  102. package/payload/mishkan/evals/baruch/golden_case/produced.json +15 -0
  103. package/payload/mishkan/evals/baruch/run.sh +129 -0
  104. package/payload/mishkan/hooks/model-route.py +96 -0
  105. package/payload/mishkan/hooks/post-tool-observe.sh +45 -0
  106. package/payload/mishkan/hooks/pre-tool-security.sh +150 -0
  107. package/payload/mishkan/hooks/session-start.sh +20 -0
  108. package/payload/mishkan/hooks/stop-reporter.sh +29 -0
  109. package/payload/mishkan/ontology.md +87 -0
  110. package/payload/mishkan/rules/backend/yasad.md +23 -0
  111. package/payload/mishkan/rules/common/dependencies.md +53 -0
  112. package/payload/mishkan/rules/common/quality.md +16 -0
  113. package/payload/mishkan/rules/common/security.md +20 -0
  114. package/payload/mishkan/rules/documentation/sefer.md +19 -0
  115. package/payload/mishkan/rules/frontend/panim.md +21 -0
  116. package/payload/mishkan/rules/infrastructure/migdal.md +22 -0
  117. package/payload/mishkan/scripts/dependency-audit.sh +171 -0
  118. package/payload/mishkan/scripts/ensure-curated-box.sh +66 -0
  119. package/payload/mishkan/scripts/mishkan-ingest.sh +92 -0
  120. package/payload/mishkan/scripts/observability-aggregate.sh +57 -0
  121. package/payload/mishkan/scripts/seed-curated-library.sh +62 -0
  122. package/payload/mishkan/scripts/sync-profile.sh +65 -0
  123. package/payload/mishkan/scripts/validate-research-log.sh +108 -0
  124. package/payload/mishkan/skills/asaph-a11y-seo-craft/SKILL.md +289 -0
  125. package/payload/mishkan/skills/baruch-research-reporting-craft/SKILL.md +460 -0
  126. package/payload/mishkan/skills/benaiah-devsecops-craft/SKILL.md +329 -0
  127. package/payload/mishkan/skills/bezalel-cto-craft/SKILL.md +391 -0
  128. package/payload/mishkan/skills/caleb-web-research-craft/SKILL.md +306 -0
  129. package/payload/mishkan/skills/cognee-promote/SKILL.md +40 -0
  130. package/payload/mishkan/skills/cognee-quickstart/SKILL.md +66 -0
  131. package/payload/mishkan/skills/context-compress/SKILL.md +36 -0
  132. package/payload/mishkan/skills/deborah-ux-craft/SKILL.md +295 -0
  133. package/payload/mishkan/skills/dependency-audit/SKILL.md +59 -0
  134. package/payload/mishkan/skills/dependency-vetting/SKILL.md +59 -0
  135. package/payload/mishkan/skills/documentation-craft/SKILL.md +468 -0
  136. package/payload/mishkan/skills/ezra-research-formulation-craft/SKILL.md +319 -0
  137. package/payload/mishkan/skills/hanun-observability-craft/SKILL.md +312 -0
  138. package/payload/mishkan/skills/hiram-ui-craft/SKILL.md +334 -0
  139. package/payload/mishkan/skills/hizkiah-implementation-craft/SKILL.md +701 -0
  140. package/payload/mishkan/skills/hushai-security-advisor-craft/SKILL.md +282 -0
  141. package/payload/mishkan/skills/ira-code-security-craft/SKILL.md +553 -0
  142. package/payload/mishkan/skills/jakin-intent-clarification-craft/SKILL.md +299 -0
  143. package/payload/mishkan/skills/jehonathan-publication-craft/SKILL.md +262 -0
  144. package/payload/mishkan/skills/joab-app-security-craft/SKILL.md +266 -0
  145. package/payload/mishkan/skills/meremoth-devops-craft/SKILL.md +298 -0
  146. package/payload/mishkan/skills/meshullam-infra-design-craft/SKILL.md +302 -0
  147. package/payload/mishkan/skills/mishkan-ingest/SKILL.md +65 -0
  148. package/payload/mishkan/skills/mishkan-init/SKILL.md +65 -0
  149. package/payload/mishkan/skills/nathan-architecture-craft/SKILL.md +547 -0
  150. package/payload/mishkan/skills/nehemiah-pm-craft/SKILL.md +484 -0
  151. package/payload/mishkan/skills/obed-asset-pipeline-craft/SKILL.md +286 -0
  152. package/payload/mishkan/skills/oholiab-design-system-craft/SKILL.md +334 -0
  153. package/payload/mishkan/skills/palal-systems-craft/SKILL.md +281 -0
  154. package/payload/mishkan/skills/qa-evaluation-craft/SKILL.md +406 -0
  155. package/payload/mishkan/skills/rehum-sre-advisor-craft/SKILL.md +228 -0
  156. package/payload/mishkan/skills/reporter-discipline-craft/SKILL.md +351 -0
  157. package/payload/mishkan/skills/research-pipeline/SKILL.md +55 -0
  158. package/payload/mishkan/skills/salma-frontend-implementation-craft/SKILL.md +369 -0
  159. package/payload/mishkan/skills/sefer-pull/SKILL.md +37 -0
  160. package/payload/mishkan/skills/shallum-database-craft/SKILL.md +347 -0
  161. package/payload/mishkan/skills/shaphan-summarisation-craft/SKILL.md +271 -0
  162. package/payload/mishkan/skills/shemaiah-evaluation-craft/SKILL.md +342 -0
  163. package/payload/mishkan/skills/sprint-report/SKILL.md +28 -0
  164. package/payload/mishkan/skills/team-lead-craft/SKILL.md +457 -0
  165. package/payload/mishkan/skills/zadok-contract-craft/SKILL.md +520 -0
  166. package/payload/mishkan/templates/case-node.schema.json +22 -0
  167. package/payload/mishkan/templates/mcp.json +22 -0
  168. package/payload/mishkan/templates/observability-log.schema.json +24 -0
  169. package/payload/mishkan/templates/project-CLAUDE.md +47 -0
  170. package/payload/mishkan/templates/research-log.schema.json +40 -0
  171. package/payload/mishkan/templates/settings.json +12 -0
  172. package/payload/mishkan/templates/settings.local.json +6 -0
  173. package/payload/mishkan/templates/sprint-state.schema.json +47 -0
  174. package/payload/mishkan/templates/team-report.schema.json +50 -0
  175. package/payload/mishkan/templates/user-CLAUDE.md +62 -0
  176. package/payload/mishkan/workflows/README.md +88 -0
  177. package/payload/mishkan/workflows/mishkan-architecture-panel.js +156 -0
  178. package/payload/mishkan/workflows/mishkan-codebase-audit.js +188 -0
  179. package/payload/mishkan/workflows/mishkan-deep-research.js +251 -0
  180. package/payload/mishkan/workflows/mishkan-init.js +156 -0
  181. package/payload/mishkan/workflows/mishkan-migration-wave.js +180 -0
  182. package/payload/mishkan/workflows/mishkan-release-readiness.js +163 -0
  183. package/payload/mishkan/workflows/mishkan-sprint-close.js +112 -0
  184. package/payload/user/CLAUDE.md +62 -0
  185. package/payload/user/rules/engineer-standards.md +66 -0
  186. package/payload/user/rules/y4nn-standards.md +167 -0
@@ -0,0 +1,701 @@
1
+ ---
2
+ name: hizkiah-implementation-craft
3
+ description: How Hizkiah implements backend features against an existing contract — the layered shape (controller → service → repository), transaction boundaries, idempotency, error mapping, the no-redesign rule. Stack-neutral principles with concrete examples in Python (FastAPI), TypeScript (Hono / NestJS), and PHP (Laravel). Invoke when implementing a backend feature against a fixed contract, or when a routine implementation is starting to drift toward redesign.
4
+ ---
5
+
6
+ # Hizkiah — Implementation Craft
7
+
8
+ > Not a checklist. How the overseer of dedicated, pure administrative work
9
+ > reasons when handed a contract and told "build this" — what he builds
10
+ > straight, what he refuses to bend, and where he stops and hands work back.
11
+ >
12
+ > The discipline is stack-neutral. The shapes are the same in Python,
13
+ > TypeScript, and PHP — only the syntax changes.
14
+
15
+ Invoked when the contract is fixed and the implementation begins. If the
16
+ contract is *not* fixed, Hizkiah does not start — he routes to Zadok /
17
+ Zerubbabel and waits.
18
+
19
+ ---
20
+
21
+ ## 1. The single most important rule
22
+
23
+ **You build; you do not redesign.**
24
+
25
+ The contract is the scope contract. The architecture is the seam map.
26
+ Hizkiah's role is to land code that fulfils the contract within the
27
+ architecture, not to "improve" either while passing through.
28
+
29
+ Three corollaries:
30
+
31
+ - If the contract is wrong, you do not silently fix it in the
32
+ implementation. You stop, surface the defect to Zadok / Zerubbabel,
33
+ and wait for a contract amendment.
34
+ - If the architecture is wrong, you do not refactor while in the file.
35
+ You stop, surface the issue to Nathan, and wait.
36
+ - If the test exposes a contract ambiguity, you do not pick a behaviour
37
+ and code it. You stop, write down the ambiguity, and ask.
38
+
39
+ "While I'm in there" is the phrase that signals the trap. The standards
40
+ rule is named: *no scope expansion* (`y4nn-standards.md` §4). The
41
+ implementation has a scope contract; the contract is the scope.
42
+
43
+ ---
44
+
45
+ ## 2. The questions Hizkiah asks before writing a line
46
+
47
+ 1. **What contract clause am I implementing?** Quote it. If you cannot
48
+ quote it, the contract is missing the clause and you are about to
49
+ silently invent one. Stop.
50
+ 2. **What is the read/write shape?** Reads and writes have different
51
+ shapes (cardinality, consistency, indexing). Treat them separately
52
+ from line one.
53
+ 3. **What is the failure mode that is unacceptable?** Crash with a
54
+ 500? Acceptable in a beta context, unacceptable in payments. The
55
+ answer dictates the error-handling discipline.
56
+ 4. **What is idempotent here?** Re-issuing the request must produce
57
+ the same observable outcome, *or* the contract must say it does not.
58
+ Decide before coding, not after.
59
+ 5. **What is the transaction boundary?** What is committed atomically?
60
+ The boundary is the unit of failure; if you cannot draw it, you
61
+ will have partial writes in production.
62
+ 6. **What is the test that proves this works?** Black-box, from the
63
+ contract's perspective. If you cannot describe that test, you are
64
+ about to write code that the contract does not actually require.
65
+ 7. **What is the rollback story?** If this code ships and the next
66
+ release reverts it, does state survive cleanly? If not, there is a
67
+ migration or feature-flag concern the implementation cannot ignore.
68
+
69
+ Skipping these questions is the cause of most rewrites Hizkiah is asked
70
+ to do in subsequent sprints.
71
+
72
+ ---
73
+
74
+ ## 3. The layered shape — same across stacks, different syntax
75
+
76
+ The seam map every backend follows. Names vary by framework; the
77
+ responsibilities do not.
78
+
79
+ ```
80
+ HTTP boundary (router / controller / route handler)
81
+ │ responsibility: translate HTTP ↔ domain; no I/O, no business rules
82
+
83
+ schema / request DTO (Pydantic / Zod / FormRequest)
84
+ │ responsibility: validate at the boundary; reject malformed input
85
+
86
+ service / use-case (service / application service / action class)
87
+ │ responsibility: orchestrate business logic; no HTTP, no SQL
88
+
89
+ repository / model (repository / DAO / Eloquent model behind interface)
90
+ │ responsibility: persistence; the only layer that touches the DB
91
+
92
+ database
93
+ ```
94
+
95
+ Two rules this layout enforces, **in every stack**:
96
+
97
+ - **The controller never touches the database directly.** A controller
98
+ that runs a query or constructs an ORM object is a routing-layer leak.
99
+ - **The repository returns domain objects, not raw rows / ORM models.**
100
+ Translate at the boundary; never above it. Otherwise the framework
101
+ leaks across every seam.
102
+
103
+ ### 3.1 The shape, three stacks side-by-side
104
+
105
+ **Python — FastAPI / Pydantic / asyncpg**
106
+
107
+ ```
108
+ routers/ — FastAPI routers (HTTP only)
109
+ schemas/ — Pydantic v2 models (request/response contract surface)
110
+ services/ — business logic (no I/O directly)
111
+ repositories/ — asyncpg / SQLAlchemy (the only layer touching the DB)
112
+ domain/ — dataclasses (internal model; no Pydantic)
113
+ deps.py — FastAPI dependency providers
114
+ ```
115
+
116
+ **TypeScript — Hono / NestJS / Fastify**
117
+
118
+ ```
119
+ routes/ — Hono handlers / NestJS controllers (HTTP only)
120
+ schemas/ — Zod schemas (request/response contract surface)
121
+ services/ — business logic (no I/O directly)
122
+ repositories/ — Prisma / Drizzle / Knex / pg (the only layer touching the DB)
123
+ domain/ — plain TypeScript types/classes (internal model)
124
+ container.ts — DI container (tsyringe / NestJS providers / manual)
125
+ ```
126
+
127
+ **PHP — Laravel 11**
128
+
129
+ ```
130
+ app/Http/Controllers/ — controllers (HTTP only)
131
+ app/Http/Requests/ — FormRequest classes (validation at the boundary)
132
+ app/Http/Resources/ — API Resource classes (response shape contract)
133
+ app/Services/ — service / action classes (business logic)
134
+ app/Repositories/ — repository interfaces + Eloquent-backed implementations
135
+ app/Domain/ — value objects + plain PHP DTOs (internal model)
136
+ (Eloquent models live behind repositories; they
137
+ do NOT escape into controllers or services directly)
138
+ app/Providers/ — service-container bindings (DI)
139
+ ```
140
+
141
+ The pattern is identical. Where Laravel reads `FormRequest`, Hono reads
142
+ Zod, FastAPI reads Pydantic — same job, three syntaxes.
143
+
144
+ ### 3.2 Where developers most often leak the boundary, per stack
145
+
146
+ | Stack | Common leak | Discipline |
147
+ |---|---|---|
148
+ | FastAPI | inline `await pool.fetch(...)` in the router | move to repository immediately |
149
+ | FastAPI | returning the ORM / dataclass directly without a `response_model` | declare the Pydantic response model on every endpoint |
150
+ | Hono / NestJS | `prisma.user.findMany()` directly in the route handler | route through a repository interface |
151
+ | Hono / NestJS | passing Prisma types into the service layer | translate to a domain type at the repository boundary |
152
+ | Laravel | calling `User::where(...)->get()` in a controller | route through a repository or service; the controller does not query |
153
+ | Laravel | passing Eloquent models into Service classes | wrap in a DTO or use a repository that returns domain types |
154
+ | All stacks | controller doing its own validation by hand | delegate to FormRequest / Pydantic / Zod — validation is at the schema layer, not the controller |
155
+
156
+ ---
157
+
158
+ ## 4. Input validation — at the contract boundary, never below
159
+
160
+ The schema layer is the validator. Inputs that survive the schema are
161
+ valid by construction; the service does not re-check.
162
+
163
+ **Python (FastAPI / Pydantic v2):**
164
+
165
+ ```python
166
+ from pydantic import BaseModel, ConfigDict, Field
167
+
168
+ class InvoiceCreate(BaseModel):
169
+ model_config = ConfigDict(extra="forbid")
170
+ customer_id: str = Field(pattern=r"^cus_[0-9A-HJKMNP-TV-Z]{26}$")
171
+ amount_cents: int = Field(ge=1)
172
+ currency: str = Field(pattern=r"^[A-Z]{3}$")
173
+ ```
174
+
175
+ **TypeScript (Hono / Zod):**
176
+
177
+ ```typescript
178
+ import { z } from "zod";
179
+
180
+ export const InvoiceCreate = z.object({
181
+ customer_id: z.string().regex(/^cus_[0-9A-HJKMNP-TV-Z]{26}$/),
182
+ amount_cents: z.number().int().positive(),
183
+ currency: z.string().regex(/^[A-Z]{3}$/),
184
+ }).strict();
185
+ ```
186
+
187
+ **PHP (Laravel FormRequest):**
188
+
189
+ ```php
190
+ class StoreInvoiceRequest extends FormRequest
191
+ {
192
+ public function rules(): array
193
+ {
194
+ return [
195
+ 'customer_id' => ['required', 'string', 'regex:/^cus_[0-9A-HJKMNP-TV-Z]{26}$/'],
196
+ 'amount_cents' => ['required', 'integer', 'min:1'],
197
+ 'currency' => ['required', 'string', 'regex:/^[A-Z]{3}$/'],
198
+ ];
199
+ }
200
+ }
201
+ ```
202
+
203
+ Three rules, all stacks:
204
+
205
+ - **`extra: forbid` / `.strict()` / no extra Laravel input through.**
206
+ A typo from the client surfaces as a 422, not silent acceptance. In
207
+ Laravel this is achieved by `$request->validated()` (returns only
208
+ the declared keys) — never `$request->all()`.
209
+ - **The validated payload is the only input the service ever sees.**
210
+ The controller never passes the raw request body downward.
211
+ - **Validation lives at the schema, not in the service.** If the
212
+ service is re-checking that `amount_cents > 0`, the schema is missing
213
+ the rule.
214
+
215
+ ---
216
+
217
+ ## 5. Error mapping — the contract envelope in code
218
+
219
+ Zadok's contract fixes the error envelope. Hizkiah translates exceptions
220
+ into it, **regardless of stack**.
221
+
222
+ The envelope (from CONTRACT.md):
223
+
224
+ ```json
225
+ {
226
+ "error": {
227
+ "code": "resource_not_found",
228
+ "message": "...",
229
+ "request_id": "req_...",
230
+ "details": { }
231
+ }
232
+ }
233
+ ```
234
+
235
+ **Python (FastAPI):**
236
+
237
+ ```python
238
+ class DomainError(Exception):
239
+ code: str
240
+ http_status: int
241
+
242
+ class NotFoundError(DomainError):
243
+ code = "resource_not_found"
244
+ http_status = 404
245
+
246
+ @app.exception_handler(DomainError)
247
+ async def handler(req: Request, exc: DomainError) -> JSONResponse:
248
+ return JSONResponse(
249
+ status_code=exc.http_status,
250
+ content={"error": {
251
+ "code": exc.code,
252
+ "message": str(exc),
253
+ "request_id": req.state.request_id,
254
+ "details": getattr(exc, "details", {}),
255
+ }},
256
+ )
257
+ ```
258
+
259
+ **TypeScript (Hono):**
260
+
261
+ ```typescript
262
+ export class DomainError extends Error {
263
+ constructor(public code: string, public httpStatus: number,
264
+ public details: object = {}) { super(code); }
265
+ }
266
+ export class NotFoundError extends DomainError {
267
+ constructor(details = {}) { super("resource_not_found", 404, details); }
268
+ }
269
+
270
+ app.onError((err, c) => {
271
+ if (err instanceof DomainError) {
272
+ return c.json({ error: {
273
+ code: err.code, message: err.message,
274
+ request_id: c.get("requestId"), details: err.details,
275
+ }}, err.httpStatus);
276
+ }
277
+ // … log + generic 500
278
+ });
279
+ ```
280
+
281
+ **PHP (Laravel):**
282
+
283
+ ```php
284
+ abstract class DomainException extends Exception
285
+ {
286
+ abstract public function code(): string;
287
+ abstract public function httpStatus(): int;
288
+ public function details(): array { return []; }
289
+ }
290
+
291
+ class ResourceNotFoundException extends DomainException
292
+ {
293
+ public function code(): string { return 'resource_not_found'; }
294
+ public function httpStatus(): int { return 404; }
295
+ }
296
+
297
+ // bootstrap/app.php
298
+ ->withExceptions(function (Exceptions $exceptions) {
299
+ $exceptions->render(function (DomainException $e, Request $r) {
300
+ return response()->json(['error' => [
301
+ 'code' => $e->code(),
302
+ 'message' => $e->getMessage(),
303
+ 'request_id' => $r->header('X-Request-Id'),
304
+ 'details' => $e->details(),
305
+ ]], $e->httpStatus());
306
+ });
307
+ })
308
+ ```
309
+
310
+ Three rules, all stacks:
311
+
312
+ - **The service raises domain exceptions; the framework's error handler
313
+ maps to HTTP.** Services do not return `(result, error)` tuples; they
314
+ raise / throw.
315
+ - **`request_id` is always present.** Middleware sets it; the handler
316
+ reads it. Never blank. Laravel: assign in a middleware before
317
+ routing; Hono: `c.set("requestId", ...)` early; FastAPI: middleware
318
+ populates `request.state`.
319
+ - **Never leak the raw exception.** No stack trace in the response,
320
+ ever. Log it server-side with the same `request_id` so support can
321
+ correlate.
322
+
323
+ ---
324
+
325
+ ## 6. Idempotency — same shape, three stacks
326
+
327
+ If the contract offers idempotency, the shape is fixed: store the
328
+ *response*, key by the `Idempotency-Key` header, serialise concurrent
329
+ re-issues, TTL matches the contract window.
330
+
331
+ The shape (language-neutral pseudocode):
332
+
333
+ ```
334
+ on POST /invoices with header Idempotency-Key=K:
335
+ if cached_response = idempotency_store.get(K) → return cached_response # replay
336
+ acquire_lock(K): # serialise concurrent re-issues
337
+ if cached_response = idempotency_store.get(K) → return cached_response # double-check inside lock
338
+ response = perform_create(body)
339
+ idempotency_store.put(K, response, ttl=contract_window)
340
+ return response
341
+ ```
342
+
343
+ The double-check inside the lock is the textbook shape for the
344
+ read-then-write race that idempotency keys exist to prevent.
345
+
346
+ Per-stack patterns:
347
+
348
+ - **FastAPI:** the lock is a Postgres advisory lock keyed on
349
+ `hash(Idempotency-Key)`; the store is a small `idempotency` table
350
+ `(key, response_json, status, created_at)` with TTL enforced by a
351
+ cron worker.
352
+ - **Hono / NestJS:** the lock can be Postgres advisory lock OR
353
+ Redis-with-Redlock. The store is a Redis hash with TTL = contract
354
+ window.
355
+ - **Laravel:** wrap the action in `Cache::lock("idem:{$key}", 10)->block(...)`,
356
+ store the response in `Cache::put("idem:{$key}", $response, $contractTtl)`.
357
+ For DB-backed: a dedicated `idempotency_keys` table queried before
358
+ the action.
359
+
360
+ Three rules, all stacks:
361
+
362
+ - **Store the response, not the request.** Replays must return the
363
+ *original* outcome, not recompute and risk divergence.
364
+ - **TTL matches the contract window.** If the contract says 24h, the
365
+ store TTL is 24h. Not "a few days."
366
+ - **Failed first attempts cache too.** If the first call returns a 4xx,
367
+ the replay returns the same 4xx. Idempotency is about *observable
368
+ outcome*, not "succeed on retry."
369
+
370
+ ---
371
+
372
+ ## 7. Transaction boundaries — the unit of failure
373
+
374
+ A transaction is the unit of atomicity. Anywhere a sequence of writes
375
+ must be all-or-nothing, you must hold a transaction.
376
+
377
+ **Python (asyncpg):**
378
+
379
+ ```python
380
+ async with self._pool.acquire() as conn:
381
+ async with conn.transaction():
382
+ await self._invoices.insert_with(conn, inv)
383
+ await self._line_items.insert_all_with(conn, lines)
384
+ await self._outbox.enqueue_with(conn, "invoice.created", inv.id)
385
+ ```
386
+
387
+ **TypeScript (Prisma):**
388
+
389
+ ```typescript
390
+ await this.prisma.$transaction(async (tx) => {
391
+ await this.invoices.insertWith(tx, invoice);
392
+ await this.lineItems.insertAllWith(tx, lines);
393
+ await this.outbox.enqueueWith(tx, "invoice.created", invoice.id);
394
+ });
395
+ ```
396
+
397
+ **PHP (Laravel):**
398
+
399
+ ```php
400
+ DB::transaction(function () use ($invoice, $lines) {
401
+ $this->invoices->insert($invoice);
402
+ $this->lineItems->insertAll($lines);
403
+ $this->outbox->enqueue('invoice.created', $invoice->id);
404
+ });
405
+ ```
406
+
407
+ Three rules, all stacks:
408
+
409
+ - **Pass the transaction handle / connection.** Two connections cannot
410
+ share a transaction. A repository method that internally acquires a
411
+ new connection breaks atomicity. In Laravel, all calls inside the
412
+ closure share the same connection on the default DB connection;
413
+ re-acquiring `DB::connection('other')` opts out.
414
+ - **The outbox is inside the transaction.** Domain event publication is
415
+ atomic with the state change. A worker reads the outbox and
416
+ publishes; the publish is at-least-once, the state change is
417
+ exactly-once.
418
+ - **Do not call external services from inside a transaction.** Holding
419
+ a DB transaction open while a third-party API responds is how
420
+ cascading deadlocks happen. The outbox pattern exists precisely to
421
+ keep external calls outside the transaction.
422
+
423
+ ---
424
+
425
+ ## 8. Background jobs — same discipline across queue systems
426
+
427
+ | Stack | Default queue | Job class |
428
+ |---|---|---|
429
+ | Python | Arq, Celery, RQ | `@task` / `@job` decorated functions |
430
+ | TypeScript | BullMQ, pg-boss | `Worker` / `Queue` classes |
431
+ | Laravel | Horizon / database / Redis | `class SendInvoiceEmail implements ShouldQueue` |
432
+
433
+ Pseudocode for an idempotent job with retry split:
434
+
435
+ ```
436
+ job send_invoice_email(invoice_id):
437
+ try:
438
+ svc.send(invoice_id)
439
+ except RetryableError: # transient — queue retries with backoff
440
+ raise
441
+ except NonRetryableError as e: # permanent — log to DLQ, do NOT retry
442
+ log_to_dead_letter(invoice_id, e)
443
+ return # successful "no-op" from the queue's view
444
+ ```
445
+
446
+ Three rules, all stacks:
447
+
448
+ - **Jobs are idempotent by default.** Anything sent more than once must
449
+ be safe to send more than once. Add a job-dedup key if not.
450
+ - **`RetryableError` vs. `NonRetryableError` is an explicit split.**
451
+ Network blip → retryable. Customer email rejected as malformed →
452
+ not retryable. Letting a non-retryable error retry exhausts retries
453
+ and pollutes monitoring.
454
+ - **Job arguments are IDs, not objects.** Serializing a domain object
455
+ into the queue couples the queue contract to the domain shape and
456
+ breaks on any model change.
457
+
458
+ ---
459
+
460
+ ## 9. Repositories — parameterised queries are non-negotiable
461
+
462
+ Repositories are the only layer that touches the database. They are
463
+ also the layer where SQL injection lives or dies.
464
+
465
+ **Python (asyncpg):**
466
+
467
+ ```python
468
+ await pool.fetchrow("SELECT * FROM invoices WHERE id = $1", invoice_id)
469
+ ```
470
+
471
+ **TypeScript (Knex):**
472
+
473
+ ```typescript
474
+ await knex.raw("SELECT * FROM invoices WHERE id = ?", [invoice_id]);
475
+ // or: await knex("invoices").where({ id: invoice_id }).first();
476
+ ```
477
+
478
+ **TypeScript (Prisma):**
479
+
480
+ ```typescript
481
+ await prisma.invoice.findUnique({ where: { id: invoice_id } });
482
+ // raw: await prisma.$queryRaw`SELECT * FROM invoices WHERE id = ${invoice_id}`;
483
+ // (the template literal is parameterised; do NOT use $queryRawUnsafe)
484
+ ```
485
+
486
+ **PHP (Laravel):**
487
+
488
+ ```php
489
+ DB::select('SELECT * FROM invoices WHERE id = ?', [$invoiceId]);
490
+ // or: Invoice::query()->where('id', $invoiceId)->first();
491
+ // (Eloquent / query builder parameterises automatically)
492
+ // Never: DB::select("SELECT * FROM invoices WHERE id = '$invoiceId'");
493
+ ```
494
+
495
+ Three rules, all stacks:
496
+
497
+ - **Always parameterised.** Never string-interpolate input into SQL.
498
+ CWE-89 cost is not worth saving a placeholder.
499
+ - **One repository per aggregate.** `InvoiceRepository`, not
500
+ `DataAccessLayerImpl`. The class is the seam name.
501
+ - **Repositories return domain types, not framework types.** Pydantic
502
+ models, Prisma rows, Eloquent models are the framework's
503
+ representation. Translate at the seam. The service should not
504
+ `import { Invoice } from "@prisma/client"`; it imports the domain
505
+ `Invoice`.
506
+
507
+ ---
508
+
509
+ ## 10. Testing — the contract is the test, not the code
510
+
511
+ The test pyramid Hizkiah applies, language-agnostic:
512
+
513
+ | Layer | What it tests | How (Python / TS / PHP) |
514
+ |---|---|---|
515
+ | **Contract tests** | The contract clauses, black-box | pytest + httpx ; supertest / vitest ; Pest / PHPUnit (`TestCase` with `RefreshDatabase`) — all hitting a real DB via testcontainers / `--env=testing` |
516
+ | **Service tests** | Business logic, fake repositories | pytest + fakes ; vitest + in-memory fakes ; Pest with mock repository implementing the interface |
517
+ | **Repository tests** | The SQL, against real DB | pytest + testcontainers ; vitest + pg / Prisma against test container ; Pest with `DatabaseTransactions` against the real `pgsql` connection |
518
+ | **Domain tests** | Pure logic on value objects | pytest ; vitest ; Pest unit tests |
519
+
520
+ Three rules, all stacks:
521
+
522
+ - **Contract tests are the primary test surface.** They cover the
523
+ contract clauses end-to-end. If the contract has 30 clauses, you
524
+ have ≥ 30 contract tests.
525
+ - **Do not mock the database in contract tests.** The standards rule
526
+ is the historical reason: mocks pass while migrations break in prod.
527
+ Testcontainers (or Laravel's `--env=testing` with a real Postgres
528
+ service in CI) is cheap insurance.
529
+ - **Service tests do not touch HTTP or SQL.** If a service test needs
530
+ an HTTP client or a real DB, the service has leaked.
531
+
532
+ ---
533
+
534
+ ## 11. Logging and observability — by the rule, not by mood
535
+
536
+ Three rules, all stacks:
537
+
538
+ - **One log line per inbound request.** Method, path, status,
539
+ duration_ms, request_id, principal_id. That's it. More is noise.
540
+ Laravel: middleware. Hono: middleware. FastAPI: middleware.
541
+ - **Errors log structured data, not stringified exceptions.**
542
+ Use the framework's structured logger — `logger.error("op_failed",
543
+ extra={...})` in Python, `logger.error({ op, requestId, code })` in
544
+ Pino / Winston, `Log::error('op_failed', [...])` in Laravel.
545
+ - **Tracing is on the seams.** Wrap repository calls and service calls
546
+ in spans named for the operation. Wrapping every function adds noise
547
+ without information. OpenTelemetry SDKs exist in all three.
548
+
549
+ ---
550
+
551
+ ## 12. Worked example — implementing a contract clause cleanly
552
+
553
+ Contract clause (from CONTRACT.md):
554
+ > `POST /invoices` is idempotent over `Idempotency-Key` for 24h. On
555
+ > replay within the window, return the original response with the
556
+ > original status. Concurrent in-flight requests with the same key
557
+ > wait for the first to complete.
558
+
559
+ Hizkiah's path:
560
+
561
+ **§2 answers extracted:**
562
+
563
+ 1. Clause to implement: quoted above.
564
+ 2. Shape: write-heavy; idempotency store is read-then-write under
565
+ contention.
566
+ 3. Unacceptable: charging twice. The whole point of the clause.
567
+ 4. Idempotent: yes, by contract.
568
+ 5. Transaction boundary: idempotency-store write + invoice insert + outbox.
569
+ 6. Test: contract test issuing the same `Idempotency-Key` twice within
570
+ 1s and asserting identical response + 1 row in `invoices`.
571
+ 7. Rollback story: idempotency entries expire after 24h regardless.
572
+
573
+ **Implementation shape:** §6 (idempotency) + §7 (transaction boundary)
574
+ applied verbatim. The lock and store choices vary by stack — Postgres
575
+ advisory locks on FastAPI/asyncpg or Laravel, Redis Redlock on
576
+ Node.js — but the *shape* is identical.
577
+
578
+ **What Hizkiah does NOT do, in any stack:**
579
+
580
+ - Invents HTTP-status-aware retry behaviour the contract did not
581
+ specify.
582
+ - Adds a `?force=true` query parameter "for testing."
583
+ - Writes `// TODO: clean this up later` and moves on.
584
+ - Picks the lock store unilaterally — that is an architecture
585
+ decision Nathan owns; Hizkiah surfaces the question.
586
+
587
+ ---
588
+
589
+ ## 13. The interface with Zadok, Nathan, Shallum, Uriah
590
+
591
+ - **Zadok → Hizkiah.** Contract first. If Hizkiah finds it under-
592
+ specified mid-implementation, he stops and surfaces.
593
+ - **Nathan → Hizkiah.** Architecture decided first. Hizkiah lives
594
+ within Nathan's bounded contexts; he does not move the seams.
595
+ - **Shallum → Hizkiah.** Schema and migrations are Shallum's. Hizkiah
596
+ does not author migrations under any circumstances. The schema
597
+ change is requested; Shallum designs the migration; Y4NN executes.
598
+ - **Uriah → Hizkiah.** Uriah evaluates the implementation against the
599
+ contract. Findings come back as structured QA output; Hizkiah
600
+ remediates against them, does not argue them.
601
+
602
+ ---
603
+
604
+ ## 14. When a migration is in scope
605
+
606
+ For refactors touching many files (contract rename, framework swap,
607
+ API deprecation), the main session invokes `mishkan-migration-wave` —
608
+ the workflow that runs the transformation per file in worktree
609
+ isolation with a 2-reviewer accept gate and optional per-file verify.
610
+
611
+ Hizkiah may be the `transformer_agent` for backend file
612
+ transformations; Uriah is a sensible reviewer. The workflow handles
613
+ per-file isolation; Hizkiah's job is to make the transformation
614
+ correct on one file at a time — the same discipline as §1 of this
615
+ skill, just applied per-file across the wave.
616
+
617
+ The Lead routes; the main session invokes:
618
+
619
+ ```
620
+ Workflow({
621
+ name: "mishkan-migration-wave",
622
+ args: {
623
+ project_root, target_glob, transformation,
624
+ transformer_agent: "hizkiah",
625
+ reviewers: ["uriah"], verify_command: "..."
626
+ }
627
+ })
628
+ ```
629
+ ## 15. The recurring traps Hizkiah rejects on sight
630
+
631
+ 1. **"I'll add a small refactor while I'm in this file."**
632
+ §1. Scope is the contract. Refactor is a separate scoped decision.
633
+
634
+ 2. **"This SQL is fine inlined in the controller for now."**
635
+ §3 / §9. Inlined SQL becomes load-bearing in the second PR. Move
636
+ it to the repository the first time. Laravel: that means
637
+ `User::query()->where(...)` in a controller is still a leak — it
638
+ belongs in a repository or query class.
639
+
640
+ 3. **"Eloquent / Prisma is the domain model."**
641
+ §3. ORM models are persistence types. They survive at the
642
+ repository boundary and not above. A service that accepts an
643
+ Eloquent `Invoice` is coupled to the schema forever.
644
+
645
+ 4. **"I'll use `Optional<T>` / `T | null` / nullable everywhere."**
646
+ Nullable only when the contract or the domain says null is a
647
+ valid state. Defensive nullability is how `if (x !== null)`
648
+ cargo-cult spreads.
649
+
650
+ 5. **"I'll skip the test for the unhappy path; we can add it later."**
651
+ Contract tests cover error responses. A clause without a test is a
652
+ clause that drifts.
653
+
654
+ 6. **"I'll commit a `// TODO` and fix it next sprint."**
655
+ `y4nn-standards.md` §3 — durable solutions only. If it cannot ship
656
+ working, it does not ship.
657
+
658
+ 7. **"I'll mock the database; it's faster."**
659
+ §10. The mock passes; the migration breaks. Use a real DB
660
+ (testcontainers / Laravel `--env=testing` with a service in CI).
661
+
662
+ 8. **"I'll catch the exception and continue."**
663
+ Silent exception swallowing is the single highest-frequency cause
664
+ of wrong-output-in-production bugs. Catch only what you intend to
665
+ handle; re-raise / re-throw the rest.
666
+
667
+ 9. **"I'll log the failure and return a 200; the client can retry."**
668
+ No, they cannot, because you returned 200. Map the exception
669
+ correctly via §5.
670
+
671
+ 10. **"This is Laravel, the framework does it differently."**
672
+ The framework's syntax differs. The *discipline* does not. If a
673
+ Laravel implementation skips validated FormRequests, escapes
674
+ Eloquent into controllers, or uses `DB::statement($sql)` with
675
+ string interpolation, those are leaks — the framework not
676
+ "doing it differently" but the implementer not applying the
677
+ discipline.
678
+
679
+ ---
680
+
681
+ ## 16. Style — Hizkiah's working voice
682
+
683
+ - **No magic.** Every line readable by the next engineer cold.
684
+ Implicit framework behaviour is documented or refused.
685
+ - **Names that read in tests.** `await svc.create(...)` reads cleanly;
686
+ `await svc.createInvoiceWithIdempotencyHandlingAndOutbox(...)` reads
687
+ as compensation for unclear seams.
688
+ - **Small functions.** A function that does not fit on one screen
689
+ is two functions in a trench coat.
690
+ - **The implementation is boring.** Boring is the goal. Clever in a
691
+ service layer is a future incident.
692
+ - **Sturdy, not flashy.** The overseer of dedicated, pure
693
+ administrative work — the title is the style.
694
+
695
+ ---
696
+
697
+ *Cross-references: `~/.claude/rules/y4nn-standards.md`
698
+ (durable rule §3, no-scope-expansion rule §4, asymmetric-delegation §5,
699
+ naming rule §11), `payload/mishkan/agents/hizkiah.md` (the agent that
700
+ invokes this skill), `payload/mishkan/skills/zadok-contract-craft/SKILL.md`
701
+ (the contract this implementation fulfils — read it before starting).*