Skip to content

Commit 4c52ebc

Browse files
committed
docs(adr): ADR-0178 — derive the placement index instead of maintaining it
Smell-audited: depth 3: new ADR + ADR-0033 amendment. The smell that opened this cycle was mine, not the code's. I drafted "retire the Pattern A/B plan; Zig is the finished form for hot paths" on the strength of two claims, and the mandatory Devil's-advocate fork refuted both against the files: 1. The six `ready_for_migration` rows I read as "scheduled to move count/seq/ first into .clj, which would cost perf" are `pattern: B1` with NO `target_loc`. ADR-0033 D3 defines B1 as Zig-with-no-.clj — B1's finished form IS Zig. I attacked a straw man, and my proposed policy was already ADR-0033 D5 ("fast-path = Tag switch") restated. 2. The plan was not unexecuted. D-062 was discharged 2026-05-27; set.clj / string.clj / walk.clj carry 12 / 21 / 11 real (def …) bodies. What rotted was the LEDGER, not the plan — 42 rows still read `transient_zig` for vars whose .clj had shipped. I re-verified every one of those against the tree before acting on it, plus the fork's two extra findings (two `leaf_loc` paths point at nonexistent files; the `--list-vars` consumer the file cites has never been built). All confirmed. The fork also blocked the F-003 seizure: retiring an unbuilt-residual placement policy is not this cycle's to make, so no forward policy is stamped. So the fix is not deletion and not repair-by-hand — it is derivation: - `(cljw.internal/__dump-placement)` derives each Var's implementation home from its root tag (`builtin_fn` → zig, `fn_val`/`multi_fn` → clj, else value/ unbound) plus its flags. Decided by the runtime, so it cannot be wrong. - `scripts/gen_placement.sh` requires every bundled namespace first — namespaces are lazy (ADR-0163), so a bare dump under-reports — and renders data/placement.yaml. 49 hand-written rows became **1,326 generated ones across 31 namespaces**. - `scripts/check_placement_status.sh` is now the `placement_drift` gate: regenerate, diff, fail. Its old body audited the completed migration. I narrowed the fork's proposal in one place, deliberately. It asked for the full A/B1/B2/C-thin split via an AST classifier over the .clj bodies; that classifier must handle `(def x (fn* …))`, multi-arity fn*, macros and computed defs, and a misclassified generated row is a NEW lie class. The root-tag axis is reported, not inferred. An index that guesses is worse than one that reports; the finer split rides on top when a consumer needs it (`zig-leaf` already separates B1 from a B2 leaf). Two things the index made visible that no document could have: - 394 vars are Zig-implemented and **725 are Clojure-implemented**. The audit that opened this cycle argued from the 124.7K:8.8K LOC ratio that the Clojure surface was structurally thin. At the var level it is the majority. LOC was the wrong denominator. - `clojure.spec.alpha` appears with 123 clj vars + 17 values. It is bundled and working — and the same audit recorded it as unimplemented, because docs/works/ladder.md and COVERAGE.md carry no row for it. A working feature missing from the ledgers misleads exactly like a broken one. Smoke green (7 passed / 0 failed, corpus 4253/4253).
1 parent c601aa7 commit 4c52ebc

11 files changed

Lines changed: 2036 additions & 616 deletions

‎.claude/CLAUDE.md‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -588,13 +588,16 @@ zig fmt src/ # format
588588
C / D classification per var, special form, and host class. Read by
589589
test runner, REPL error message, and future `cljw --list-vars`. See
590590
ADR-0013 for the Tier D rationale.
591-
- [`placement.yaml`](../data/placement.yaml) — Clojure-ns var placement SSOT
592-
(Pattern A/B + transient_zig migration status + dependencies). Read
593-
by `scripts/check_placement_status.sh` (audit + status flip), future
594-
`cljw --list-vars` (alongside compat_tiers.yaml), and ADR-0033
595-
amendment history. Role split: compat_tiers.yaml = Java/cljw surface
596-
(Class-level), placement.yaml = Clojure-ns vars (var-level). Per
597-
`private/notes/clj_vs_zig_split_proposal_v5.md` §15.
591+
- [`placement.yaml`](../data/placement.yaml) — **GENERATED** index of where every
592+
interned Var's implementation lives (`zig` / `clj` / `value` / `unbound` +
593+
flags), covering every var in every bundled namespace. Produced by
594+
`scripts/gen_placement.sh` from `(cljw.internal/__dump-placement)`; the
595+
`placement_drift` gate regenerates and diffs it on every full gate, so it
596+
cannot disagree with the code (ADR-0178). **Do not hand-edit.** Role split:
597+
compat_tiers.yaml = Java/cljw surface (Class-level), placement.yaml =
598+
Clojure-ns vars (var-level). Whether a var's *current* placement is its
599+
*intended* one is a judgement, not a fact — that lives in `.dev/debt.yaml`
600+
with a barrier (today one row, D-094). ADR-0033 D2-D5 is the placement rule.
598601
- [`host_interfaces.yaml`](../data/host_interfaces.yaml) — closed-set SSOT for the
599602
deftype/reify/extend-type host-supertype markers (`Object`, `clojure.lang.*`).
600603
Single in-code read point: `src/runtime/host_interface.zig`. Gated by

‎.dev/README.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,6 @@ templates in **ROADMAP §15.2**:
3434
- `debt.yaml` — row-level debt ledger (ADR-0072; replaced the planned
3535
`known_issues.md`). The live SSOT for technical debt.
3636
- `data/compat_tiers.yaml` — per-namespace/class Clojure tier table.
37-
- `data/placement.yaml` — Clojure-ns var placement SSOT (ADR-0033; covers the planned
38-
`status/vars.yaml` var tracking, which was not built).
37+
- `data/placement.yaml` — GENERATED per-var placement index (ADR-0178; supersedes
38+
the planned `status/vars.yaml` tracking, which was never built). Regenerate with
39+
`scripts/gen_placement.sh`; the `placement_drift` gate keeps it honest.

‎.dev/ROADMAP.md‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -674,7 +674,7 @@ ClojureWasm/ (working dir on disk)
674674
│ ├── compat_tiers.yaml per-namespace tier (created at Phase 10)
675675
│ ├── handover.md session-state memo (created when needed mid-task)
676676
│ ├── debt.yaml row-level debt ledger (ADR-0072; replaced the planned known_issues.md)
677-
│ ├── placement.yaml Clojure-ns var placement SSOT (ADR-0033; covers the planned status/vars.yaml)
677+
│ ├── placement.yaml GENERATED per-var placement index (ADR-0178; supersedes status/vars.yaml)
678678
│ ├── principle.md Bad Smell catalogue + revision depths
679679
│ └── project_facts.md user-declared invariants (F-001…)
680680
│
@@ -2048,9 +2048,14 @@ landed); do not create `compat_tiers.md`.
20482048

20492049
#### `.dev/status/vars.yaml` — SUPERSEDED (not created)
20502050

2051-
Per-var tracking is covered by `placement.yaml` (Clojure-ns var placement SSOT,
2052-
ADR-0033) + `compat_tiers.yaml` (var/class tier). The originally-planned
2053-
`status/vars.yaml` + `generate_vars_yaml.clj` generator were not built.
2051+
Per-var tracking is covered by `placement.yaml` + `compat_tiers.yaml` (var/class
2052+
tier). The originally-planned `status/vars.yaml` + `generate_vars_yaml.clj`
2053+
generator were not built — and per **ADR-0178** they no longer need to be:
2054+
`placement.yaml` IS a generator's output now (`scripts/gen_placement.sh` reading
2055+
`(cljw.internal/__dump-placement)`), covering every var in every bundled
2056+
namespace, with the `placement_drift` gate failing on any disagreement with the
2057+
code. The hand-maintained form it replaced covered 49 of ~683 `clojure.core`
2058+
publics and had drifted to contradict its own source tree.
20542059

20552060
### 15.2 Local reference clones (already present)
20562061

‎.dev/cw_v0_parity_and_gap_plan.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ owning phase decides the concrete design (F-003).
140140
| G8 | Wasm Component output (`cljw component build`) | **Yes (differentiation core)** | F-001/zwasm v2 integration per F-008 spec; **F-010 orders it AFTER the quality loop** (Phase 16). Not v0 (v0 had none); the design is the zwasm-v2 consumer shape | **Phase 16** (post-M, zwasm-v2 ready) | §9.18 Phase 16 (already placeholder) | D-036/037/038 |
141141
| G9 | clojure.core remaining (lazy (b) + breadth) | **Yes (recurring)** | Cluster-by-cluster over existing primitives; lazy forms with the lazy-seq Layer-2 wiring | Recurring quality-loop | §9.16 row 14.13 follow-ups + quality-loop rows | D-134 |
142142
| G10 | multimethods breadth / `&form`/`&env` | **Yes** | `&form`/`&env` injection into the user-macro path (decide Value shapes for Form/Scope); multimethod breadth audit | Opportunistic / test-corpus-driven | Follow-up rows | D-111 |
143-
| G11 | `--list-vars` / tier introspection | **Yes** | `cljw --list-vars` reading `compat_tiers.yaml` + `placement.yaml` (SSOTs ready); shared completion source for G1 nREPL + G2 REPL | Quality loop | New §9 row | file new D-NNN |
143+
| G11 | `--list-vars` / tier introspection | **Yes** | `cljw --list-vars` over `compat_tiers.yaml` + `placement.yaml`; shared completion source for G1 nREPL + G2 REPL. NOTE 2026-08-04: `--list-vars` is still unbuilt, but its data source now exists for real — ADR-0178 made `placement.yaml` a generated, total, gated index derived from `(cljw.internal/__dump-placement)`, so the CLI is a formatting job over `src/runtime/introspect.zig` rather than a yaml-plumbing one | Quality loop | New §9 row | file new D-NNN |
144144
| G12 | generational GC | **Defer** | Both sides future (ROADMAP §89.2); cw v0 reached maturity without it. Not a near-term gap | Far future | §89.2 reserve | (none) |
145145

146146
### Cross-cutting ordering note

‎.dev/decisions/0033_clojure_ns_placement_naming_polymorphism.md‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,3 +381,21 @@ Phase 6.16.a-0 cycle 以降の Affected files:
381381
(public wrapper / user qualified denied / filter denied / same-ns
382382
ok)。 D-071 row 更新は次 commit (sub-cycle b/c/d で ADR-0035 完了
383383
時に併せて Part 3 完了に flip)。
384+
385+
- **2026-08-04 (ADR-0178)**: the **migration schedule** this ADR carried is
386+
recorded **complete**; the **placement rule (D2-D5) is unchanged and still
387+
governs new vars**. D-062, the cluster row tracking the Pattern A/B migration
388+
of the Phase 6.9-6.11 Zig-written vars, was discharged 2026-05-27 with
389+
`clojure.string/escape` as the sole residual (D-094) — but `data/placement.yaml`
390+
never followed: 42 of its 49 rows still read `status: transient_zig` for vars
391+
whose `.clj` bodies had already shipped, and the file had drifted further
392+
(two `leaf_loc` paths pointing at nonexistent files, two of three cited helper
393+
scripts never written, no gate reading it, 49 of ~683 publics covered).
394+
ADR-0178 replaces the hand-maintained file with a **generated** index derived
395+
from `(cljw.internal/__dump-placement)` over every bundled namespace, plus a
396+
`placement_drift` gate. What the generated index deliberately does NOT record
397+
is D7's aspirational half — "this var's current placement is not its intended
398+
one" is a judgement with a barrier and belongs in `.dev/debt.yaml` (today one
399+
row, D-094). No forward policy about where future `clojure.core` vars should
400+
live is stamped: that would be F-003 seizure over an unbuilt residual, and D5
401+
already answers it for hot paths (fast-path = Tag switch).

0 commit comments

Comments
 (0)