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.
- package/LICENSE +21 -0
- package/README.md +205 -0
- package/bin/mishkan.js +221 -0
- package/docs/design/MISHKAN_agent_aliases.md +140 -0
- package/docs/design/MISHKAN_decisions.md +172 -0
- package/docs/design/MISHKAN_harness_design.md +820 -0
- package/docs/design/MISHKAN_ontology.md +87 -0
- package/docs/design/MISHKAN_token_optimisation.md +181 -0
- package/docs/engineer/README.md +37 -0
- package/docs/engineer/profile.example.md +79 -0
- package/docs/usage/01-installation.md +178 -0
- package/docs/usage/02-project-init.md +151 -0
- package/docs/usage/03-orchestration.md +218 -0
- package/docs/usage/04-memory-layer.md +201 -0
- package/docs/usage/05-selective-ingest.md +177 -0
- package/docs/usage/06-llm-providers.md +195 -0
- package/docs/usage/07-troubleshooting.md +316 -0
- package/docs/usage/08-glossary.md +154 -0
- package/docs/usage/09-workflows.md +123 -0
- package/docs/usage/README.md +77 -0
- package/package.json +43 -0
- package/payload/install/settings.hooks.json +47 -0
- package/payload/mishkan/AGENT_SPEC.md +154 -0
- package/payload/mishkan/agents/ahikam.md +58 -0
- package/payload/mishkan/agents/aholiab.md +68 -0
- package/payload/mishkan/agents/asaph.md +73 -0
- package/payload/mishkan/agents/baruch.md +88 -0
- package/payload/mishkan/agents/benaiah.md +76 -0
- package/payload/mishkan/agents/bezalel.md +83 -0
- package/payload/mishkan/agents/caleb.md +74 -0
- package/payload/mishkan/agents/deborah.md +63 -0
- package/payload/mishkan/agents/elasah.md +58 -0
- package/payload/mishkan/agents/eliashib.md +68 -0
- package/payload/mishkan/agents/ezra.md +69 -0
- package/payload/mishkan/agents/hanun.md +64 -0
- package/payload/mishkan/agents/hiram.md +68 -0
- package/payload/mishkan/agents/hizkiah.md +76 -0
- package/payload/mishkan/agents/huldah.md +59 -0
- package/payload/mishkan/agents/huram.md +66 -0
- package/payload/mishkan/agents/hushai.md +59 -0
- package/payload/mishkan/agents/igal.md +58 -0
- package/payload/mishkan/agents/ira.md +86 -0
- package/payload/mishkan/agents/jahaziel.md +71 -0
- package/payload/mishkan/agents/jakin.md +66 -0
- package/payload/mishkan/agents/jehonathan.md +62 -0
- package/payload/mishkan/agents/jehoshaphat.md +68 -0
- package/payload/mishkan/agents/joab.md +71 -0
- package/payload/mishkan/agents/joah.md +62 -0
- package/payload/mishkan/agents/maaseiah.md +61 -0
- package/payload/mishkan/agents/meremoth.md +65 -0
- package/payload/mishkan/agents/meshullam.md +67 -0
- package/payload/mishkan/agents/nathan.md +70 -0
- package/payload/mishkan/agents/nehemiah.md +93 -0
- package/payload/mishkan/agents/obed.md +60 -0
- package/payload/mishkan/agents/oholiab.md +67 -0
- package/payload/mishkan/agents/palal.md +63 -0
- package/payload/mishkan/agents/phinehas.md +73 -0
- package/payload/mishkan/agents/rehum.md +60 -0
- package/payload/mishkan/agents/salma.md +69 -0
- package/payload/mishkan/agents/seraiah.md +73 -0
- package/payload/mishkan/agents/shallum.md +66 -0
- package/payload/mishkan/agents/shaphan.md +64 -0
- package/payload/mishkan/agents/shemaiah.md +67 -0
- package/payload/mishkan/agents/shevna.md +58 -0
- package/payload/mishkan/agents/uriah.md +70 -0
- package/payload/mishkan/agents/zaccur.md +58 -0
- package/payload/mishkan/agents/zadok.md +67 -0
- package/payload/mishkan/agents/zerubbabel.md +69 -0
- package/payload/mishkan/cognee/.env.curated.example +61 -0
- package/payload/mishkan/cognee/.env.example +165 -0
- package/payload/mishkan/cognee/Dockerfile +50 -0
- package/payload/mishkan/cognee/README.md +129 -0
- package/payload/mishkan/cognee/docker-compose.curated-ui.yml +61 -0
- package/payload/mishkan/cognee/docker-compose.curated.yml +85 -0
- package/payload/mishkan/cognee/docker-compose.hardening.yml +16 -0
- package/payload/mishkan/cognee/docker-compose.selfhosted.yml +114 -0
- package/payload/mishkan/cognee/docker-compose.ui.yml +70 -0
- package/payload/mishkan/cognee/docker-compose.yml +71 -0
- package/payload/mishkan/cognee/ingest-curated.py +92 -0
- package/payload/mishkan/commands/dep-audit.md +24 -0
- package/payload/mishkan/commands/mishkan-init.md +25 -0
- package/payload/mishkan/commands/mishkan-resume.md +21 -0
- package/payload/mishkan/commands/promote.md +19 -0
- package/payload/mishkan/commands/sefer-pull.md +19 -0
- package/payload/mishkan/commands/sprint-close.md +21 -0
- package/payload/mishkan/config/curated-library.yaml +113 -0
- package/payload/mishkan/config/improvement-queries.md +29 -0
- package/payload/mishkan/config/model-routing.yaml +87 -0
- package/payload/mishkan/config/projects.yaml +38 -0
- package/payload/mishkan/evals/baruch/README.md +93 -0
- package/payload/mishkan/evals/baruch/fixtures/invalid/bad-outcome-enum.json +15 -0
- package/payload/mishkan/evals/baruch/fixtures/invalid/bad-sprint-pattern.json +15 -0
- package/payload/mishkan/evals/baruch/fixtures/invalid/bad-trigger-enum.json +15 -0
- package/payload/mishkan/evals/baruch/fixtures/invalid/malformed-json.json +7 -0
- package/payload/mishkan/evals/baruch/fixtures/invalid/missing-required-field.json +14 -0
- package/payload/mishkan/evals/baruch/fixtures/valid/blocked-vendor.json +15 -0
- package/payload/mishkan/evals/baruch/fixtures/valid/curated-shortcircuit.json +15 -0
- package/payload/mishkan/evals/baruch/fixtures/valid/partial-no-write.json +14 -0
- package/payload/mishkan/evals/baruch/fixtures/valid/resolved-cross-harness.json +15 -0
- package/payload/mishkan/evals/baruch/golden_case/expected.yaml +35 -0
- package/payload/mishkan/evals/baruch/golden_case/input.yaml +47 -0
- package/payload/mishkan/evals/baruch/golden_case/produced.json +15 -0
- package/payload/mishkan/evals/baruch/run.sh +129 -0
- package/payload/mishkan/hooks/model-route.py +96 -0
- package/payload/mishkan/hooks/post-tool-observe.sh +45 -0
- package/payload/mishkan/hooks/pre-tool-security.sh +150 -0
- package/payload/mishkan/hooks/session-start.sh +20 -0
- package/payload/mishkan/hooks/stop-reporter.sh +29 -0
- package/payload/mishkan/ontology.md +87 -0
- package/payload/mishkan/rules/backend/yasad.md +23 -0
- package/payload/mishkan/rules/common/dependencies.md +53 -0
- package/payload/mishkan/rules/common/quality.md +16 -0
- package/payload/mishkan/rules/common/security.md +20 -0
- package/payload/mishkan/rules/documentation/sefer.md +19 -0
- package/payload/mishkan/rules/frontend/panim.md +21 -0
- package/payload/mishkan/rules/infrastructure/migdal.md +22 -0
- package/payload/mishkan/scripts/dependency-audit.sh +171 -0
- package/payload/mishkan/scripts/ensure-curated-box.sh +66 -0
- package/payload/mishkan/scripts/mishkan-ingest.sh +92 -0
- package/payload/mishkan/scripts/observability-aggregate.sh +57 -0
- package/payload/mishkan/scripts/seed-curated-library.sh +62 -0
- package/payload/mishkan/scripts/sync-profile.sh +65 -0
- package/payload/mishkan/scripts/validate-research-log.sh +108 -0
- package/payload/mishkan/skills/asaph-a11y-seo-craft/SKILL.md +289 -0
- package/payload/mishkan/skills/baruch-research-reporting-craft/SKILL.md +460 -0
- package/payload/mishkan/skills/benaiah-devsecops-craft/SKILL.md +329 -0
- package/payload/mishkan/skills/bezalel-cto-craft/SKILL.md +391 -0
- package/payload/mishkan/skills/caleb-web-research-craft/SKILL.md +306 -0
- package/payload/mishkan/skills/cognee-promote/SKILL.md +40 -0
- package/payload/mishkan/skills/cognee-quickstart/SKILL.md +66 -0
- package/payload/mishkan/skills/context-compress/SKILL.md +36 -0
- package/payload/mishkan/skills/deborah-ux-craft/SKILL.md +295 -0
- package/payload/mishkan/skills/dependency-audit/SKILL.md +59 -0
- package/payload/mishkan/skills/dependency-vetting/SKILL.md +59 -0
- package/payload/mishkan/skills/documentation-craft/SKILL.md +468 -0
- package/payload/mishkan/skills/ezra-research-formulation-craft/SKILL.md +319 -0
- package/payload/mishkan/skills/hanun-observability-craft/SKILL.md +312 -0
- package/payload/mishkan/skills/hiram-ui-craft/SKILL.md +334 -0
- package/payload/mishkan/skills/hizkiah-implementation-craft/SKILL.md +701 -0
- package/payload/mishkan/skills/hushai-security-advisor-craft/SKILL.md +282 -0
- package/payload/mishkan/skills/ira-code-security-craft/SKILL.md +553 -0
- package/payload/mishkan/skills/jakin-intent-clarification-craft/SKILL.md +299 -0
- package/payload/mishkan/skills/jehonathan-publication-craft/SKILL.md +262 -0
- package/payload/mishkan/skills/joab-app-security-craft/SKILL.md +266 -0
- package/payload/mishkan/skills/meremoth-devops-craft/SKILL.md +298 -0
- package/payload/mishkan/skills/meshullam-infra-design-craft/SKILL.md +302 -0
- package/payload/mishkan/skills/mishkan-ingest/SKILL.md +65 -0
- package/payload/mishkan/skills/mishkan-init/SKILL.md +65 -0
- package/payload/mishkan/skills/nathan-architecture-craft/SKILL.md +547 -0
- package/payload/mishkan/skills/nehemiah-pm-craft/SKILL.md +484 -0
- package/payload/mishkan/skills/obed-asset-pipeline-craft/SKILL.md +286 -0
- package/payload/mishkan/skills/oholiab-design-system-craft/SKILL.md +334 -0
- package/payload/mishkan/skills/palal-systems-craft/SKILL.md +281 -0
- package/payload/mishkan/skills/qa-evaluation-craft/SKILL.md +406 -0
- package/payload/mishkan/skills/rehum-sre-advisor-craft/SKILL.md +228 -0
- package/payload/mishkan/skills/reporter-discipline-craft/SKILL.md +351 -0
- package/payload/mishkan/skills/research-pipeline/SKILL.md +55 -0
- package/payload/mishkan/skills/salma-frontend-implementation-craft/SKILL.md +369 -0
- package/payload/mishkan/skills/sefer-pull/SKILL.md +37 -0
- package/payload/mishkan/skills/shallum-database-craft/SKILL.md +347 -0
- package/payload/mishkan/skills/shaphan-summarisation-craft/SKILL.md +271 -0
- package/payload/mishkan/skills/shemaiah-evaluation-craft/SKILL.md +342 -0
- package/payload/mishkan/skills/sprint-report/SKILL.md +28 -0
- package/payload/mishkan/skills/team-lead-craft/SKILL.md +457 -0
- package/payload/mishkan/skills/zadok-contract-craft/SKILL.md +520 -0
- package/payload/mishkan/templates/case-node.schema.json +22 -0
- package/payload/mishkan/templates/mcp.json +22 -0
- package/payload/mishkan/templates/observability-log.schema.json +24 -0
- package/payload/mishkan/templates/project-CLAUDE.md +47 -0
- package/payload/mishkan/templates/research-log.schema.json +40 -0
- package/payload/mishkan/templates/settings.json +12 -0
- package/payload/mishkan/templates/settings.local.json +6 -0
- package/payload/mishkan/templates/sprint-state.schema.json +47 -0
- package/payload/mishkan/templates/team-report.schema.json +50 -0
- package/payload/mishkan/templates/user-CLAUDE.md +62 -0
- package/payload/mishkan/workflows/README.md +88 -0
- package/payload/mishkan/workflows/mishkan-architecture-panel.js +156 -0
- package/payload/mishkan/workflows/mishkan-codebase-audit.js +188 -0
- package/payload/mishkan/workflows/mishkan-deep-research.js +251 -0
- package/payload/mishkan/workflows/mishkan-init.js +156 -0
- package/payload/mishkan/workflows/mishkan-migration-wave.js +180 -0
- package/payload/mishkan/workflows/mishkan-release-readiness.js +163 -0
- package/payload/mishkan/workflows/mishkan-sprint-close.js +112 -0
- package/payload/user/CLAUDE.md +62 -0
- package/payload/user/rules/engineer-standards.md +66 -0
- 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).*
|