@@ -20,13 +20,23 @@ Pipeline: **compilers** (spec → IR) → **IR passes** (IR → IR) → **emitte
2020
2121## The documents are the spec — read them first
2222
23+ ` ls docs/*.md ` is the set; what follows is why each one matters, not how many there are.
24+
2325- ** ` docs/ir-design.md ` is normative.** Field names and struct shapes in it are the contract;
2426 receiver methods and helpers are not. When implementing the IR, match its shapes exactly.
27+ - ** ` docs/emitter-design.md ` is normative for the emitter half** , the way ` ir-design.md ` is for the
28+ IR. Nothing under ` emitters/ ` exists yet, so read it before writing the first one.
2529- ` docs/architecture.md ` — pipeline stages, package layout, layering rules, milestones.
2630- ` docs/ir-spec-matrix.md ` — the union of source-format capabilities the IR is designed against.
2731- ` docs/prior-art.md ` — the evidence base (oagen, Kiota, TypeSpec/TCGC) and the specific mistakes
2832 each Morphic decision is designed to avoid. Read this before proposing IR changes; most
2933 "simplifications" that come to mind are failure modes already rejected here.
34+ - ` docs/reference-learnings.md ` — the same evidence base widened to every shipped generator it
35+ surveys (its header names them), each finding carrying a verdict on a Morphic decision and
36+ citing the repo it came from.
37+ - ` docs/micro-compiler-design.md ` and ` docs/micro-compiler-plan.md ` — the restructuring that
38+ produced today's ` compilers/openapi ` . Both record work that has landed; read them for why the
39+ package boundaries fall where they do, not as a proposal or a backlog.
3040
3141## Invariants that must not be violated
3242
@@ -35,7 +45,7 @@ claim (lossless, spec-agnostic, many-target). Before changing any of them, re-re
3545in the docs.
3646
37471 . ** The IR is the ABI.** Compilers and emitters never see each other. A compiler's only output is
38- an IR document + diagnostics; a emitter's only input is an IR document + its own options.
48+ an IR document + diagnostics; an emitter's only input is an IR document + its own options.
39492 . ** Lossless by default, lowered late.** Compilers never flatten (no ` allOf ` merging, no
4050 union-to-optional-fields collapse, no primary-response selection). Composition, unions,
4151 visibility, discriminators, encodings, streaming stay in source-semantic form. Lowering to what
@@ -73,8 +83,13 @@ in the docs.
7383## Go representation conventions the design mandates
7484
7585- ** Closed sums = sealed interfaces** : unexported marker method (` typeDef() ` ), one concrete struct
76- per kind, a ` Kind() ` accessor for switch-dispatch, and a generated switch-completeness test over
77- the kind enum (the ` assertNever ` lesson). JSON encodes sums with an adjacent ` kind ` tag.
86+ per kind, a ` Kind() ` accessor for switch-dispatch, and a switch-completeness test over the kind
87+ enum (the ` assertNever ` lesson). None of it is code-generated — the module has no ` go:generate `
88+ at all (` grep -rn go:generate --include='*.go' . ` is empty). ` TestTypeDef_KindDispatchIsComplete `
89+ iterates a hand-written ` allKinds ` , and ` TestTypeDef_HandWrittenKindListsAreComplete ` holds that
90+ list and every other hand-written kind list to the kinds the ` ir ` sources declare, so adding a
91+ kind without updating a list reddens there instead of quietly narrowing what a test covers. JSON
92+ encodes sums with an adjacent ` kind ` tag.
7893- ** No ` float64 ` anywhere in the IR.** Numeric values, defaults, and constraints use arbitrary-
7994 precision decimal strings (` BigVal ` ). This is a hard rule (the TypeSpec ` Numeric ` lesson).
8095- ** Values are a separate channel from types** (` Value ` /` ValueKind ` ), per the TypeSpec Type-vs-Value
@@ -96,17 +111,20 @@ compilers/ Layer 1 — the Compiler contract and the format-keyed registry. Im
96111 compilers, compilers/compile, its own internal/* (+ own format libs); never each
97112 other, never emitters/engine.
98113 internal/ Layer 1 — that compiler's own packages, each with its own allowlist rather than
99- the compiler's. openapi has thirteen; the ordering among them is real and enforced,
100- from diag (reaches only ir) up to operation. None may reach the compiler above it.
114+ the compiler's. The ordering among them is real and enforced, from diag (reaches
115+ only ir) up to operation. None may reach the compiler above it.
101116pass/ Layer 1 — IR → IR passes. Imports ir only.
102117emitters/* Layer 2 — imports ir + emitter contract; never compiler. (Not built yet.)
103118engine/ Layer 3 — orchestration; imports everything below.
104- cmd/morphic/ Layer 4 — CLI; imports engine.
119+ cmd/morphic/ Layer 4 — CLI; imports ir + engine.
120+ cmd/morphic-harness/
121+ Layer 4 — sweeps a spec or directory through the oracles; imports internal/harness.
105122internal/ Test/tooling infrastructure, outside the pipeline (harness, archtest, testspec).
106123```
107124
108125The layering is enforced by ` internal/archtest ` , and ** its ` rules ` map is the source of truth** —
109- this diagram is prose, and deliberately does not name the thirteen. Read them off the tree:
126+ this diagram is prose, and deliberately neither names nor counts a compiler's internal packages.
127+ Read them off the tree:
110128
111129``` bash
112130git ls-files ' */*.go' | xargs -n1 dirname | sort -u
@@ -132,20 +150,30 @@ These all exist already — extend them rather than building a parallel mechanis
132150 Corpus under ` testdata/golden/ ` .
133151- ** Capability conformance corpus** (` testdata/conformance/ ` ): one minimal spec per
134152 ` ir-spec-matrix.md ` row per format that can express it, asserting lossless capture. This is what
135- keeps "lossless by default" honest.
153+ keeps "lossless by default" honest. The row↔spec mapping is machine-read, not prose: matrix rows
154+ carry stable keys, each case names the keys it witnesses, and
155+ ` compilers/openapi/conformance_matrix_test.go ` requires every expressible row to be witnessed or
156+ listed with a reason. What it cannot check is whether a spec that * names* a row exercises that
157+ capability — that claim is read by a reviewer, so weigh it like any other.
136158- ** Oracles** : ` internal/harness ` drives a spec through no-panic → no error diagnostic →
137- ` irverify ` invariants → JSON round-trip → determinism. ` irverify ` is the structural-invariant
138- checker (stable IDs, no dangling refs, neutral naming, routable ` Unmodeled ` , in-range
139- provenance); its findings are ` Violation ` values — * our* bugs — deliberately a channel separate
140- from ` ir.Diagnostic ` , which reports problems in the source spec.
159+ ` irverify ` invariants → JSON round-trip → determinism → order-invariance, stopping at the first
160+ one that fires. ` harness.Check ` is the list — read it there rather than trusting this sentence;
161+ ` go run ./cmd/morphic-harness <file|dir> ` runs them over one spec or a whole tree. ` irverify ` is
162+ the structural-invariant checker (stable IDs, no dangling refs, neutral naming, routable
163+ ` Unmodeled ` , in-range provenance); its findings are ` Violation ` values — * our* bugs —
164+ deliberately a channel separate from ` ir.Diagnostic ` , which reports problems in the source spec.
141165- ** Architecture test** : ` internal/archtest ` , per the layering section above.
142166
143167Beyond those, "verify by executing" below has consequences specific enough to write down as
144168assertion shapes:
145169
146170- ** Order-dependence needs a two-order diff.** Compile the same source twice with the declaration
147171 order swapped and ` cmp.Diff ` the two documents. A single-order test passes on * both* orders of a
148- colliding lowering, which is why the pointer collisions survived the suite.
172+ colliding lowering, which is why the pointer collisions survived the suite. The order-invariance
173+ oracle above is the general form of this and already runs it across the corpus under ` testdata/ ` ,
174+ so the usual way to cover a new construct is to add a spec the sweep reaches, not to hand-roll
175+ the diff. A targeted case still earns its place when it pins one lowering, because the oracle
176+ proves order-independence only for the constructs its inputs happen to contain.
149177 - A fixture's own declaration order is part of the test. A golden that happens to declare things
150178 in the order the * correct* lowering already produced cannot see the fix at all: reverting it
151179 leaves the golden green, and only the two-order oracle reddens. If a single-order case is meant
@@ -200,9 +228,12 @@ below are the ones most likely to bite in this codebase — the full guide gover
200228- ** Docs:** GoDoc on every exported symbol starting with its name, complete sentences; package
201229 comment on every package; comments explain * why* , not what.
202230- ** Serialization:** explicit JSON struct tags on every field; ` omitempty ` only on optional
203- fields; custom ` MarshalJSON ` /` UnmarshalJSON ` for special forms (the IR's sum types and
204- ` BigVal ` do this); never ` float64 ` for money — and in this repo, never in the IR at all, per the
205- representation conventions above.
231+ fields; custom codecs for special forms, but not symmetrically — each member of the IR's
232+ ` TypeDef ` sum has a ` MarshalJSON ` that writes its adjacent ` kind ` tag, and * decoding* is
233+ centralized in ` (*TypeRegistry).UnmarshalJSON ` , which reads that tag; no member unmarshals
234+ itself, and ` BigVal ` has no codec at all because it * is* a string type and already marshals as
235+ a JSON string. ` grep -rnE 'func .*(Unm|M)arshalJSON' ir/ ` is the current list; never ` float64 `
236+ for money — and in this repo, never in the IR at all, per the representation conventions above.
206237- ** Logging:** ` log/slog ` only, injected — but note the stronger repo invariant: pipeline
207238 stages don't log at all; they return diagnostics.
208239
0 commit comments