Files
agentic-dev/docs/architecture/feature-conformance-explainer.html
Danijel Martinek 63d54dcddc docs(architecture): feature-conformance interactive explainer + extensions
Adds the interactive HTML explainer for the feature-conformance enforcement
design, covering the four layers (TS brands, ESLint, boot assertion, CI gate),
manifest playground, mistake catalog, layer composition, build-order milestones,
anchor points in the repo, and an "extensions" section on sharper AST checks
and code-conventions enforcement.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:08:34 +02:00

2032 lines
106 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=1280">
<title>feature-conformance / template-vertical / explainer</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght,SOFT@0,9..144,300..900,0..100;1,9..144,300..900,0..100&family=JetBrains+Mono:ital,wght@0,400;0,500;0,700;1,400&display=swap" rel="stylesheet">
<style>
:root {
--paper: #f4ecd8;
--paper-2: #ebe2cb;
--paper-3: #f9f3e3;
--paper-edge: #ddd0b1;
--ink: #1a1310;
--ink-2: #5b4a3c;
--ink-3: #8a7563;
--rule: rgba(26, 19, 16, 0.12);
--rule-strong: rgba(26, 19, 16, 0.28);
--accent: #7d2828;
--accent-soft: #b86a47;
--accent-2: #1f3d4a;
--accent-3: #c5912f;
--ok: #4a6a32;
--warn: #a86a18;
--miss: #b89c70;
--code-bg: #16110d;
--code-bg-2: #1d1814;
--code-paper: #ebe2cb;
--code-mute: #8a7563;
--code-accent: #d8a04a;
--code-red: #d97a6a;
--code-green: #b3c8a6;
--code-blue: #9bb6cc;
}
* { box-sizing: border-box; }
html, body {
margin: 0;
padding: 0;
background: var(--paper);
color: var(--ink);
font-family: "Fraunces", Georgia, serif;
font-feature-settings: "ss01", "ss02";
-webkit-font-smoothing: antialiased;
font-variation-settings: "opsz" 14, "SOFT" 50;
}
body {
position: relative;
background-image:
repeating-linear-gradient(0deg, transparent 0 31px, rgba(26,19,16,0.04) 31px 32px);
}
body::before {
content: "";
position: fixed; inset: 0;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200' opacity='0.25'><filter id='n'><feTurbulence baseFrequency='0.95' numOctaves='3'/><feColorMatrix values='0 0 0 0 0.10 0 0 0 0 0.07 0 0 0 0 0.05 0 0 0 0 0.10 0'/></filter><rect width='200' height='200' filter='url(%23n)'/></svg>");
pointer-events: none;
opacity: 0.45;
mix-blend-mode: multiply;
z-index: 1;
}
main, header, footer { position: relative; z-index: 2; }
a { color: inherit; text-decoration: underline; text-decoration-thickness: 1px; text-decoration-color: var(--rule-strong); text-underline-offset: 3px; transition: text-decoration-color 0.2s ease; }
a:hover { text-decoration-color: var(--accent); }
.frame { max-width: 1280px; margin: 0 auto; padding: 0 56px; }
/* ─── Masthead ───────────────────────────────────────────────────── */
header.masthead { border-bottom: 1px solid var(--rule-strong); padding-top: 48px; padding-bottom: 12px; }
.folio-row {
display: flex; justify-content: space-between; align-items: baseline;
font-family: "JetBrains Mono", monospace; font-size: 11px;
letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink-2);
border-bottom: 1px solid var(--rule); padding-bottom: 12px; margin-bottom: 64px;
}
.folio-row .folio-label { display: inline-flex; gap: 14px; align-items: baseline; }
.folio-row .folio-mark { width: 9px; height: 9px; background: var(--accent-2); display: inline-block; border-radius: 50%; transform: translateY(1px); }
.title-block {
display: grid; grid-template-columns: minmax(0, 1fr) 320px;
gap: 80px; align-items: end; padding-bottom: 56px; border-bottom: 1px solid var(--rule);
}
h1.title {
font-family: "Fraunces", serif;
font-variation-settings: "opsz" 144, "SOFT" 0, "wght" 380;
font-size: clamp(60px, 7vw, 104px);
line-height: 0.92; letter-spacing: -0.025em;
margin: 0 0 0 -6px; color: var(--ink);
}
h1.title em {
font-style: italic;
font-variation-settings: "opsz" 144, "SOFT" 100, "wght" 380;
color: var(--accent-2);
}
.lede {
font-family: "Fraunces", serif;
font-variation-settings: "opsz" 18, "SOFT" 30;
font-size: 17px; line-height: 1.55;
color: var(--ink-2); margin: 0; max-width: 360px; text-wrap: pretty;
}
.contents-row {
margin-top: 36px; padding-top: 28px; border-top: 1px dashed var(--rule);
display: grid; grid-template-columns: 110px 1fr; gap: 32px; align-items: start;
}
.contents-label {
font-family: "JetBrains Mono", monospace; font-size: 11px;
letter-spacing: 0.22em; text-transform: uppercase;
color: var(--ink-3); padding-top: 4px;
}
ol.contents {
list-style: none; margin: 0; padding: 0;
display: grid; grid-template-columns: repeat(8, 1fr); gap: 16px;
font-family: "JetBrains Mono", monospace; font-size: 12px; letter-spacing: 0.04em;
}
ol.contents li { border-top: 1px solid var(--ink); padding-top: 10px; }
ol.contents a { display: block; text-decoration: none; color: var(--ink); }
ol.contents .num { color: var(--accent-2); display: block; font-size: 11px; margin-bottom: 6px; letter-spacing: 0.18em; }
ol.contents li:hover .num { color: var(--accent); }
/* ─── Sections ───────────────────────────────────────────────────── */
main { padding-top: 56px; padding-bottom: 80px; }
section { padding: 80px 0; border-bottom: 1px solid var(--rule); }
.section-head {
display: grid; grid-template-columns: 110px 1fr; gap: 32px;
align-items: baseline; margin-bottom: 48px;
}
.section-num {
font-family: "JetBrains Mono", monospace; font-size: 11px;
letter-spacing: 0.22em; text-transform: uppercase;
color: var(--accent-2); padding-top: 12px;
}
.section-title {
font-family: "Fraunces", serif;
font-variation-settings: "opsz" 96, "SOFT" 0, "wght" 360;
font-size: clamp(40px, 5vw, 60px); line-height: 0.96;
letter-spacing: -0.018em; margin: 0 0 14px;
}
.section-title em {
font-style: italic;
font-variation-settings: "opsz" 96, "SOFT" 100, "wght" 360;
color: var(--accent-2);
}
.section-blurb {
max-width: 72ch; font-family: "Fraunces", serif;
font-variation-settings: "opsz" 16, "SOFT" 40;
font-size: 16.5px; line-height: 1.6; color: var(--ink-2); margin: 0;
}
p { line-height: 1.6; text-wrap: pretty; }
code { font-family: "JetBrains Mono", monospace; font-size: 12.5px; background: var(--paper-2); padding: 1px 5px; border-radius: 2px; }
/* ─── Code blocks ────────────────────────────────────────────────── */
pre.code {
background: var(--code-bg); color: var(--code-paper);
font-family: "JetBrains Mono", monospace;
font-size: 12.5px; line-height: 1.62;
padding: 22px 24px; border-radius: 4px; overflow-x: auto;
margin: 0; border: 1px solid var(--code-bg-2);
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.04) inset, 0 14px 40px -20px rgba(20, 14, 8, 0.5);
}
pre.code .c { color: var(--code-mute); font-style: italic; }
pre.code .k { color: var(--code-accent); }
pre.code .s { color: var(--code-paper); opacity: 0.92; }
pre.code .t { color: var(--code-green); }
pre.code .n { color: #d2b896; }
pre.code .r { color: var(--code-red); }
pre.code .b { color: var(--code-blue); }
pre.code .h { background: rgba(216, 160, 74, 0.16); padding: 0 4px; border-radius: 2px; }
pre.code .err { background: rgba(217, 122, 106, 0.18); padding: 0 4px; border-radius: 2px; border-bottom: 1px dashed var(--code-red); }
pre.code::before {
content: attr(data-lang); display: block; font-size: 10px;
letter-spacing: 0.22em; text-transform: uppercase;
color: #6b574a; margin-bottom: 10px; padding-bottom: 8px;
border-bottom: 1px solid rgba(216, 160, 74, 0.18);
}
/* ─── Manifest playground ────────────────────────────────────────── */
.playground {
background: var(--paper-3);
border: 1px solid var(--rule-strong);
border-radius: 4px;
padding: 36px 40px;
margin-top: 16px;
}
.playground-head {
display: flex; justify-content: space-between; align-items: end;
margin-bottom: 28px; padding-bottom: 16px;
border-bottom: 1px solid var(--rule); gap: 20px;
}
.playground-head h3 {
font-family: "Fraunces", serif;
font-variation-settings: "opsz" 36, "SOFT" 30, "wght" 380;
font-size: 26px; margin: 0; letter-spacing: -0.005em;
}
.playground-head h3 em {
font-style: italic;
font-variation-settings: "opsz" 36, "SOFT" 100, "wght" 380;
color: var(--accent-2);
}
.playground-head .meta {
font-family: "JetBrains Mono", monospace;
font-size: 10.5px; color: var(--ink-3);
letter-spacing: 0.06em;
}
.playground-grid {
display: grid;
grid-template-columns: 360px 1fr;
gap: 32px; align-items: start;
}
/* — Toggle controls — */
.controls-panel { display: flex; flex-direction: column; gap: 22px; }
.control-group {
border-top: 1px solid var(--rule);
padding-top: 14px;
}
.control-group:first-child { border-top: 0; padding-top: 0; }
.control-label {
font-family: "JetBrains Mono", monospace;
font-size: 10.5px; letter-spacing: 0.22em;
text-transform: uppercase; color: var(--ink-3);
margin-bottom: 10px;
display: flex; justify-content: space-between; align-items: baseline;
}
.control-label .hint { color: var(--ink-3); text-transform: none; letter-spacing: 0.04em; font-size: 10.5px; font-style: italic; }
.toggle-row { display: flex; flex-wrap: wrap; gap: 6px; }
.toggle {
appearance: none; border: 1px solid var(--ink);
background: var(--paper);
font-family: "JetBrains Mono", monospace;
font-size: 11.5px; letter-spacing: 0.04em;
color: var(--ink-2);
padding: 6px 12px; border-radius: 999px;
cursor: pointer;
transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease;
}
.toggle:hover { color: var(--ink); border-color: var(--ink); }
.toggle[aria-pressed="true"] {
background: var(--ink); color: var(--paper);
border-color: var(--ink);
}
.toggle.accent[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); }
.toggle.events[aria-pressed="true"] { background: var(--accent-2); border-color: var(--accent-2); }
.toggle.audit[aria-pressed="true"] { background: var(--ok); border-color: var(--ok); }
/* — Output panel — */
.output-panel {
display: flex; flex-direction: column; gap: 18px;
}
.output-block {
border: 1px solid var(--rule-strong);
background: var(--paper);
border-radius: 4px;
padding: 0;
overflow: hidden;
}
.output-block-head {
display: flex; justify-content: space-between; align-items: baseline;
padding: 12px 18px;
background: var(--ink); color: var(--paper);
font-family: "JetBrains Mono", monospace;
font-size: 10.5px; letter-spacing: 0.22em; text-transform: uppercase;
}
.output-block-head .layer-id { color: var(--code-accent); }
.output-block-body { padding: 16px 20px; }
.output-block-body pre {
margin: 0;
font-family: "JetBrains Mono", monospace;
font-size: 12.5px;
line-height: 1.62;
color: var(--ink);
white-space: pre-wrap;
word-break: break-word;
}
.output-block-body pre .ok { color: var(--ok); }
.output-block-body pre .warn { color: var(--warn); }
.output-block-body pre .err { color: var(--accent); }
.output-block-body pre .dim { color: var(--ink-3); }
.output-block-body pre .label { color: var(--accent-2); }
.output-block-body pre .file { color: var(--ink-2); font-style: italic; }
.output-block-body .empty {
font-family: "Fraunces", serif;
font-style: italic; color: var(--ink-3);
font-size: 14px;
}
/* ─── Layer pillbar ──────────────────────────────────────────────── */
.layers-controls {
display: flex; align-items: center; gap: 12px;
margin-bottom: 28px; flex-wrap: wrap;
}
.layers-controls .label {
font-family: "JetBrains Mono", monospace;
font-size: 11px; letter-spacing: 0.22em;
text-transform: uppercase; color: var(--ink-3);
margin-right: 12px;
}
.pillbar {
display: inline-flex; gap: 0;
border: 1px solid var(--ink);
background: var(--paper-3);
padding: 4px; border-radius: 999px;
}
.pillbar button {
appearance: none; border: 0; background: transparent;
font-family: "JetBrains Mono", monospace;
font-size: 11.5px; letter-spacing: 0.08em;
color: var(--ink-2); padding: 7px 16px;
cursor: pointer; border-radius: 999px;
transition: background 0.18s ease, color 0.18s ease;
text-transform: lowercase;
}
.pillbar button:hover { color: var(--ink); }
.pillbar button[aria-pressed="true"] { background: var(--ink); color: var(--paper); }
.layer-board {
background: var(--paper-3);
border: 1px solid var(--rule-strong);
border-radius: 4px;
padding: 36px 40px;
}
.layer-board-head {
display: grid; grid-template-columns: 1fr auto; gap: 24px; align-items: end;
margin-bottom: 28px; padding-bottom: 16px;
border-bottom: 1px solid var(--rule);
}
.layer-board-head h3 {
font-family: "Fraunces", serif;
font-variation-settings: "opsz" 36, "SOFT" 30, "wght" 380;
font-size: 28px; margin: 0; letter-spacing: -0.005em;
}
.layer-board-head h3 em {
font-style: italic;
font-variation-settings: "opsz" 36, "SOFT" 100, "wght" 380;
color: var(--accent-2);
}
.layer-board-head .latency {
font-family: "JetBrains Mono", monospace;
font-size: 10.5px; letter-spacing: 0.22em;
text-transform: uppercase; color: var(--ink-3);
text-align: right;
}
.layer-board-head .latency strong { color: var(--accent); font-weight: 500; display: block; font-size: 22px; font-family: "Fraunces", serif; font-style: italic; font-variation-settings: "opsz" 24, "SOFT" 100, "wght" 380; letter-spacing: -0.005em; margin-bottom: 2px; }
.layer-board-grid {
display: grid;
grid-template-columns: 320px 1fr;
gap: 28px; align-items: start;
}
.layer-narrative {
font-family: "Fraunces", serif;
font-variation-settings: "opsz" 16, "SOFT" 40;
font-size: 15.5px; line-height: 1.6;
color: var(--ink); padding-right: 4px;
}
.layer-narrative .scenario-tag {
font-family: "JetBrains Mono", monospace;
font-size: 10.5px; letter-spacing: 0.22em;
text-transform: uppercase; color: var(--accent);
margin-bottom: 10px;
}
.layer-narrative h4 {
font-family: "Fraunces", serif;
font-variation-settings: "opsz" 24, "SOFT" 30, "wght" 460;
font-size: 22px; line-height: 1.1;
letter-spacing: -0.005em;
margin: 0 0 12px;
}
.layer-narrative p { margin: 0 0 12px; }
.layer-narrative ul { margin: 0 0 12px 0; padding-left: 18px; }
.layer-narrative li { margin-bottom: 6px; font-size: 14.5px; color: var(--ink-2); }
.layer-catches {
margin-top: 14px; padding-top: 14px;
border-top: 1px solid var(--rule);
font-family: "JetBrains Mono", monospace;
font-size: 11px; letter-spacing: 0.04em;
color: var(--ink-3);
}
.layer-catches strong { color: var(--accent-2); font-weight: 500; font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase; display: block; margin-bottom: 6px; }
/* ─── Mistake catalog ───────────────────────────────────────────── */
.mistakes-table {
border: 1px solid var(--rule-strong);
background: var(--paper-3);
border-radius: 4px;
overflow: hidden;
}
.mistakes-head {
display: grid;
grid-template-columns: 1fr 100px 100px 100px 100px;
background: var(--ink); color: var(--paper);
padding: 14px 22px;
font-family: "JetBrains Mono", monospace;
font-size: 10.5px; letter-spacing: 0.22em; text-transform: uppercase;
align-items: baseline;
}
.mistakes-head .col-catch { text-align: center; color: var(--code-accent); }
.mistake-row {
display: grid;
grid-template-columns: 1fr 100px 100px 100px 100px;
padding: 18px 22px;
border-top: 1px solid var(--rule);
align-items: center;
cursor: pointer;
transition: background 0.18s ease;
}
.mistake-row:hover { background: var(--paper-2); }
.mistake-row[aria-expanded="true"] { background: var(--paper-2); }
.mistake-row .mistake-label {
font-family: "Fraunces", serif;
font-variation-settings: "opsz" 16, "SOFT" 40;
font-size: 15px;
color: var(--ink);
}
.mistake-row .mistake-label .sub {
font-family: "JetBrains Mono", monospace;
font-size: 11px;
color: var(--ink-3);
display: block;
margin-top: 4px;
letter-spacing: 0.02em;
}
.catch-cell {
display: flex; justify-content: center; align-items: center;
font-family: "JetBrains Mono", monospace;
font-size: 11px; letter-spacing: 0.18em;
text-transform: uppercase;
}
.catch-cell.yes {
color: var(--ok); font-weight: 500;
}
.catch-cell.yes::before {
content: "✓"; display: inline-block;
width: 22px; height: 22px; line-height: 22px;
border-radius: 50%; text-align: center;
background: var(--ok); color: var(--paper);
font-size: 12px; font-weight: 500;
}
.catch-cell.no::before {
content: "·"; display: inline-block;
width: 22px; height: 22px; line-height: 18px;
border-radius: 50%; text-align: center;
border: 1px solid var(--rule-strong);
background: transparent; color: var(--ink-3);
font-size: 18px; font-weight: 500;
}
.mistake-detail {
display: none;
grid-column: 1 / -1;
padding: 20px 22px 24px;
background: var(--paper);
border-top: 1px dashed var(--rule-strong);
font-family: "Fraunces", serif;
font-variation-settings: "opsz" 15, "SOFT" 40;
font-size: 14.5px; line-height: 1.55;
color: var(--ink-2);
}
.mistake-row[aria-expanded="true"] + .mistake-detail { display: block; }
.mistake-detail .errors {
display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px;
margin-top: 16px;
}
.mistake-detail .err-card {
border: 1px solid var(--rule-strong);
background: var(--paper-3);
border-radius: 3px;
padding: 14px 16px;
}
.mistake-detail .err-card .layer-tag {
font-family: "JetBrains Mono", monospace;
font-size: 10px; letter-spacing: 0.22em;
text-transform: uppercase; color: var(--accent);
margin-bottom: 8px;
}
.mistake-detail .err-card pre {
margin: 0; font-family: "JetBrains Mono", monospace;
font-size: 11.5px; line-height: 1.5; white-space: pre-wrap;
color: var(--ink);
}
.mistake-detail .err-card pre .err-line { color: var(--accent); }
.mistake-detail .err-card pre .file { color: var(--accent-2); }
/* ─── Timeline / build order ────────────────────────────────────── */
.milestones-controls {
display: flex; align-items: center; gap: 12px;
margin-bottom: 24px; flex-wrap: wrap;
}
.milestone-board {
background: var(--paper-3);
border: 1px solid var(--rule-strong);
border-radius: 4px;
padding: 36px 40px;
}
.milestone-board-head {
display: grid; grid-template-columns: auto 1fr auto; gap: 24px; align-items: end;
margin-bottom: 24px; padding-bottom: 16px;
border-bottom: 1px solid var(--rule);
}
.milestone-num {
font-family: "Fraunces", serif;
font-style: italic; font-variation-settings: "opsz" 72, "SOFT" 100, "wght" 360;
font-size: 64px; line-height: 0.9;
color: var(--accent-2);
letter-spacing: -0.02em;
}
.milestone-title {
font-family: "Fraunces", serif;
font-variation-settings: "opsz" 36, "SOFT" 30, "wght" 380;
font-size: 28px; margin: 0; letter-spacing: -0.005em;
}
.milestone-effort {
text-align: right;
font-family: "JetBrains Mono", monospace;
font-size: 10.5px; letter-spacing: 0.18em;
text-transform: uppercase; color: var(--ink-3);
}
.milestone-effort strong {
font-family: "Fraunces", serif;
font-style: italic; font-variation-settings: "opsz" 24, "SOFT" 100, "wght" 380;
font-size: 22px; font-weight: 400;
color: var(--accent);
display: block; letter-spacing: -0.005em;
margin-bottom: 2px;
}
.milestone-body {
display: grid; grid-template-columns: 1fr 1fr; gap: 32px;
}
.milestone-body .col h5 {
font-family: "JetBrains Mono", monospace;
font-size: 10.5px; letter-spacing: 0.22em;
text-transform: uppercase; color: var(--ink-3);
margin: 0 0 12px;
}
.milestone-body .col p {
font-family: "Fraunces", serif;
font-variation-settings: "opsz" 16, "SOFT" 40;
font-size: 15px; line-height: 1.55;
color: var(--ink-2); margin: 0 0 12px;
}
.milestone-body .col ul {
list-style: none; margin: 0; padding: 0;
font-family: "JetBrains Mono", monospace; font-size: 12px;
}
.milestone-body .col ul li {
padding: 8px 0; border-top: 1px solid var(--rule);
color: var(--ink);
}
.milestone-body .col ul li:first-child { border-top: 0; padding-top: 0; }
.milestone-body .col ul li .sub {
color: var(--ink-3); font-size: 11px;
margin-top: 2px; display: block; font-family: "Fraunces", serif; font-style: italic;
}
/* ─── Anchor cards (existing infra) ──────────────────────────────── */
.anchors-grid {
display: grid; grid-template-columns: repeat(2, 1fr); gap: 24px;
}
.anchor-card {
border: 1px solid var(--rule-strong);
background: var(--paper-3);
padding: 28px 32px; border-radius: 4px;
position: relative;
}
.anchor-card.exists {
border-color: var(--ok);
border-left: 4px solid var(--ok);
}
.anchor-card.partial {
border-color: var(--warn);
border-left: 4px solid var(--warn);
}
.anchor-card.missing {
border-color: var(--accent);
border-left: 4px solid var(--accent);
border-style: dashed;
}
.anchor-card .anchor-status {
font-family: "JetBrains Mono", monospace;
font-size: 10.5px; letter-spacing: 0.22em; text-transform: uppercase;
margin-bottom: 10px;
}
.anchor-card.exists .anchor-status { color: var(--ok); }
.anchor-card.partial .anchor-status { color: var(--warn); }
.anchor-card.missing .anchor-status { color: var(--accent); }
.anchor-card h3 {
font-family: "JetBrains Mono", monospace;
font-weight: 500; font-size: 16px;
margin: 0 0 12px; color: var(--ink);
}
.anchor-card .role {
font-family: "Fraunces", serif;
font-variation-settings: "opsz" 14, "SOFT" 40;
font-style: italic; font-size: 14px;
color: var(--ink-3); margin: 0 0 14px;
}
.anchor-card p {
font-family: "Fraunces", serif;
font-variation-settings: "opsz" 15, "SOFT" 40;
font-size: 14.5px; line-height: 1.55;
color: var(--ink-2); margin: 0 0 12px;
}
.anchor-card .runs-when {
margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--rule);
font-family: "JetBrains Mono", monospace; font-size: 10.5px;
color: var(--ink-3); letter-spacing: 0.04em;
}
.anchor-card .runs-when strong { color: var(--accent-2); font-weight: 500; }
/* ─── Open questions ───────────────────────────────────────────── */
.questions-list {
display: flex; flex-direction: column; gap: 18px;
}
.q-item {
display: grid; grid-template-columns: 64px 1fr; gap: 24px;
padding: 20px 0; border-top: 1px solid var(--rule);
align-items: start;
}
.q-item:first-child { border-top: 0; }
.q-num {
font-family: "Fraunces", serif;
font-style: italic; font-variation-settings: "opsz" 36, "SOFT" 100, "wght" 360;
font-size: 32px; line-height: 1;
color: var(--accent);
letter-spacing: -0.01em;
}
.q-body h5 {
font-family: "Fraunces", serif;
font-variation-settings: "opsz" 20, "SOFT" 30, "wght" 460;
font-size: 19px; margin: 0 0 8px;
letter-spacing: -0.005em;
}
.q-body p {
font-family: "Fraunces", serif;
font-variation-settings: "opsz" 16, "SOFT" 40;
font-size: 15.5px; line-height: 1.55;
color: var(--ink-2); margin: 0;
max-width: 70ch;
}
/* ─── Boundary / hero card ──────────────────────────────────────── */
.boundary-card {
background: var(--ink);
color: var(--paper);
padding: 48px 56px;
border-radius: 4px;
position: relative;
overflow: hidden;
}
.boundary-card::before {
content: "";
position: absolute; inset: 0;
background-image:
radial-gradient(circle at 90% 10%, rgba(31, 61, 74, 0.6), transparent 50%),
radial-gradient(circle at 5% 90%, rgba(125, 40, 40, 0.4), transparent 50%);
pointer-events: none;
}
.boundary-card h3 {
font-family: "Fraunces", serif;
font-variation-settings: "opsz" 72, "SOFT" 0, "wght" 360;
font-size: 44px; line-height: 1.0;
letter-spacing: -0.018em;
margin: 0 0 18px; color: var(--paper);
position: relative;
}
.boundary-card h3 em {
font-style: italic;
font-variation-settings: "opsz" 72, "SOFT" 100, "wght" 360;
color: var(--code-accent);
}
.boundary-card p {
font-family: "Fraunces", serif;
font-variation-settings: "opsz" 17, "SOFT" 40;
font-size: 17px; line-height: 1.55;
margin: 0 0 12px; max-width: 76ch; color: var(--code-paper);
position: relative;
}
.boundary-card p:last-child { margin-bottom: 0; }
.boundary-card em.h { color: var(--code-accent); font-style: italic; font-variation-settings: "opsz" 17, "SOFT" 100; }
/* ─── Footer ────────────────────────────────────────────────────── */
footer.colophon {
padding: 64px 0 80px;
border-top: 1px solid var(--rule-strong);
}
footer.colophon .row {
display: grid;
grid-template-columns: 220px 1fr 1fr;
gap: 48px;
align-items: start;
}
footer.colophon h4 {
font-family: "JetBrains Mono", monospace;
font-size: 10.5px; letter-spacing: 0.22em; text-transform: uppercase;
color: var(--ink-3); margin: 0 0 12px;
}
footer.colophon p, footer.colophon li {
font-family: "Fraunces", serif;
font-variation-settings: "opsz" 14, "SOFT" 40;
font-size: 14px; line-height: 1.55; color: var(--ink-2);
margin: 0 0 8px;
}
footer.colophon ul { list-style: none; padding: 0; margin: 0; }
footer.colophon ul li { padding: 6px 0; border-top: 1px solid var(--rule); }
footer.colophon ul li:first-child { border-top: 0; }
footer.colophon ul li a { font-family: "JetBrains Mono", monospace; font-size: 12px; color: var(--ink); }
@media (max-width: 1100px) {
.frame { padding: 0 40px; }
.playground-grid, .layer-board-grid, .milestone-body, .anchors-grid, footer.colophon .row, .title-block { grid-template-columns: 1fr; gap: 32px; }
ol.contents { grid-template-columns: repeat(3, 1fr); }
.mistakes-head, .mistake-row { grid-template-columns: 1fr 60px 60px 60px 60px; }
.mistake-detail .errors { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<header class="masthead">
<div class="frame">
<div class="folio-row">
<span class="folio-label"><span class="folio-mark"></span> template-vertical</span>
<span>vol. 04 / explainer / conformance</span>
<span>folio 01</span>
</div>
<div class="title-block">
<h1 class="title">Feature<br><em>Conformance</em></h1>
<p class="lede">A four-layer feedback system for AI agents writing features. The manifest is the source of truth; the compiler, the editor, the dev server, and CI are the agent's correction signal — fast, structured, layered.</p>
</div>
<div class="contents-row">
<span class="contents-label">Contents</span>
<ol class="contents">
<li><a href="#drift"><span class="num">01</span>Drift</a></li>
<li><a href="#agent-loop"><span class="num">02</span>Agent Loop</a></li>
<li><a href="#manifest"><span class="num">03</span>Manifest</a></li>
<li><a href="#layers"><span class="num">04</span>Four Layers</a></li>
<li><a href="#mistakes"><span class="num">05</span>Mistakes</a></li>
<li><a href="#composition"><span class="num">06</span>Composition</a></li>
<li><a href="#milestones"><span class="num">07</span>Build Order</a></li>
<li><a href="#anchors"><span class="num">08</span>Anchors</a></li>
<li><a href="#extensions"><span class="num">09</span>Beyond</a></li>
<li><a href="#questions"><span class="num">10</span>Questions</a></li>
</ol>
</div>
</div>
</header>
<main>
<!-- ═════════════ §01 Drift ═════════════ -->
<section id="drift">
<div class="frame">
<div class="section-head">
<span class="section-num">§ 01</span>
<div>
<h2 class="section-title">The <em>drift</em> problem.</h2>
<p class="section-blurb">A feature-based monorepo lasts for years and is increasingly authored by AI agents. Conventions that lived in someone's head when the second feature was written are not legible to an agent writing the eighth. Agents can't be trained by code review, can't infer culture from PR history, and iterate dozens of times per hour. The job of conformance is to turn every convention into a machine-readable signal that fires fast enough to close the agent's correction loop.</p>
</div>
</div>
<div class="boundary-card">
<h3>Every convention is a <em>promise</em>. Every promise needs a <em>watchman</em>.</h3>
<p>This repo already enforces some promises: <em class="h">no-handler-reexport</em>, <em class="h">no-direct-socket-io</em>, the PII grep gate in CI. Those are watchmen. The question is how to scale the watchman model so that every new core capability — audit, events, realtime, observability — gets the same enforcement quality, and so that the resulting signals are sharp enough for an <em class="h">AI agent</em> to read, understand, and act on without human triage.</p>
<p>The answer below is a single declarative primitive (a feature manifest) read by four independent enforcement layers, each catching what the layer above missed. The earlier the layer fires, the tighter the agent's correction loop.</p>
</div>
</div>
</section>
<!-- ═════════════ §02 Agent feedback loop ═════════════ -->
<section id="agent-loop">
<div class="frame">
<div class="section-head">
<span class="section-num">§ 02</span>
<div>
<h2 class="section-title">Built for the <em>agent loop</em>.</h2>
<p class="section-blurb">An AI agent writing a new feature does not have intuition, taste, or memory of last quarter's incident. It has exactly what the toolchain emits — diagnostics, exit codes, stack traces, and the contents of error messages. Every design choice in the four layers below is shaped by that single fact: the system's output is the agent's correction signal.</p>
</div>
</div>
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 32px;">
<div style="border: 1px solid var(--rule-strong); background: var(--paper-3); padding: 24px 26px; border-radius: 4px; border-left: 4px solid var(--accent-2);">
<div style="font-family: 'JetBrains Mono', monospace; font-size: 10.5px; letter-spacing: 0.22em; text-transform: uppercase; color: var(--accent-2); margin-bottom: 10px;">Loop property</div>
<h3 style="font-family: 'Fraunces', serif; font-variation-settings: 'opsz' 22, 'SOFT' 30, 'wght' 460; font-size: 20px; line-height: 1.1; margin: 0 0 12px; letter-spacing: -0.005em;">Fast</h3>
<p style="font-family: 'Fraunces', serif; font-variation-settings: 'opsz' 14, 'SOFT' 40; font-size: 14px; line-height: 1.5; color: var(--ink-2); margin: 0;">Sub-second feedback at compile and lint time. Agents iterate dozens of times per hour; a 2-minute CI gate is the wrong place to discover most mistakes.</p>
</div>
<div style="border: 1px solid var(--rule-strong); background: var(--paper-3); padding: 24px 26px; border-radius: 4px; border-left: 4px solid var(--ok);">
<div style="font-family: 'JetBrains Mono', monospace; font-size: 10.5px; letter-spacing: 0.22em; text-transform: uppercase; color: var(--ok); margin-bottom: 10px;">Loop property</div>
<h3 style="font-family: 'Fraunces', serif; font-variation-settings: 'opsz' 22, 'SOFT' 30, 'wght' 460; font-size: 20px; line-height: 1.1; margin: 0 0 12px; letter-spacing: -0.005em;">Structured</h3>
<p style="font-family: 'Fraunces', serif; font-variation-settings: 'opsz' 14, 'SOFT' 40; font-size: 14px; line-height: 1.5; color: var(--ink-2); margin: 0;">Error output is parseable: rule id, file path, line, machine-readable code. An agent grepping output recognises a category, not a prose blurb.</p>
</div>
<div style="border: 1px solid var(--rule-strong); background: var(--paper-3); padding: 24px 26px; border-radius: 4px; border-left: 4px solid var(--warn);">
<div style="font-family: 'JetBrains Mono', monospace; font-size: 10.5px; letter-spacing: 0.22em; text-transform: uppercase; color: var(--warn); margin-bottom: 10px;">Loop property</div>
<h3 style="font-family: 'Fraunces', serif; font-variation-settings: 'opsz' 22, 'SOFT' 30, 'wght' 460; font-size: 20px; line-height: 1.1; margin: 0 0 12px; letter-spacing: -0.005em;">Actionable</h3>
<p style="font-family: 'Fraunces', serif; font-variation-settings: 'opsz' 14, 'SOFT' 40; font-size: 14px; line-height: 1.5; color: var(--ink-2); margin: 0;">Every diagnostic ends with a <code>Fix:</code> line — the concrete next edit. Diagnosis without prescription forces the agent to guess.</p>
</div>
<div style="border: 1px solid var(--rule-strong); background: var(--paper-3); padding: 24px 26px; border-radius: 4px; border-left: 4px solid var(--accent);">
<div style="font-family: 'JetBrains Mono', monospace; font-size: 10.5px; letter-spacing: 0.22em; text-transform: uppercase; color: var(--accent); margin-bottom: 10px;">Loop property</div>
<h3 style="font-family: 'Fraunces', serif; font-variation-settings: 'opsz' 22, 'SOFT' 30, 'wght' 460; font-size: 20px; line-height: 1.1; margin: 0 0 12px; letter-spacing: -0.005em;">Layered</h3>
<p style="font-family: 'Fraunces', serif; font-variation-settings: 'opsz' 14, 'SOFT' 40; font-size: 14px; line-height: 1.5; color: var(--ink-2); margin: 0;">Four checks at four moments. If one is silenced or skipped, another catches it. No silent passes — exactly one layer must complain.</p>
</div>
</div>
<div style="background: var(--paper-3); border: 1px solid var(--rule-strong); border-radius: 4px; padding: 32px 40px;">
<div style="display: grid; grid-template-columns: 220px 1fr; gap: 32px; align-items: start;">
<div>
<div style="font-family: 'JetBrains Mono', monospace; font-size: 10.5px; letter-spacing: 0.22em; text-transform: uppercase; color: var(--ink-3); margin-bottom: 10px;">Agent iteration model</div>
<h4 style="font-family: 'Fraunces', serif; font-variation-settings: 'opsz' 28, 'SOFT' 30, 'wght' 460; font-size: 26px; margin: 0 0 8px; line-height: 1.05; letter-spacing: -0.005em;">Write &middot; <em style="font-style: italic; font-variation-settings: 'opsz' 28, 'SOFT' 100, 'wght' 460; color: var(--accent-2);">read</em> &middot; correct.</h4>
<p style="font-family: 'Fraunces', serif; font-variation-settings: 'opsz' 15, 'SOFT' 40; font-size: 14.5px; line-height: 1.55; color: var(--ink-2); margin: 0;">The middle step is the bottleneck. The faster and clearer the signal, the more loops the agent can run inside a unit of work — and the smaller the chance of the agent's mental model diverging from what the codebase actually requires.</p>
</div>
<pre class="code" data-lang="agent feedback transcript — fictional but representative">$ pnpm dev
<span class="b">[bindAll]</span> resolving billing feature…
<span class="r">✗ ConformanceError</span>
billing.charge: binding missing brand '__audited'
manifest declares <span class="t">mutates: true</span> with <span class="t">audits: ["payment.captured"]</span>
but binding is not wrapped in withAudit().
<span class="k">Fix:</span> in <span class="n">packages/features/billing/src/di/bind-production.ts:36</span>
wrap the use case as: <span class="t">withSpan(t, opts, withAudit(a, withCapture(l, tags, chargeUseCase(...))))</span>
<span class="c"># Agent reads diagnostic.
# Agent edits bind-production.ts:36 per the Fix line.
# Agent re-runs pnpm dev. Loop closes in ~3s.</span></pre>
</div>
</div>
</div>
</section>
<!-- ═════════════ §03 Manifest playground ═════════════ -->
<section id="manifest">
<div class="frame">
<div class="section-head">
<span class="section-num">§ 03</span>
<div>
<h2 class="section-title">The <em>manifest</em>.</h2>
<p class="section-blurb">Every feature owns a typed manifest describing its contract with the core packages: which use cases mutate state, which audit events they emit, which cross-feature events they publish or consume, which realtime channels they own. The manifest is the only place these facts are declared — code that contradicts the manifest is what enforcement catches.</p>
</div>
</div>
<pre class="code" data-lang="packages/features/auth/src/feature.manifest.ts"><span class="k">import</span> { defineFeature } <span class="k">from</span> <span class="s">"@repo/core-shared/conformance"</span>;
<span class="k">export const</span> <span class="t">authManifest</span> = <span class="t">defineFeature</span>({
<span class="n">name</span>: <span class="s">"auth"</span>,
<span class="n">requiredCores</span>: [<span class="s">"audit"</span>, <span class="s">"events"</span>], <span class="c">// hard deps</span>
<span class="n">useCases</span>: {
<span class="n">signIn</span>: { <span class="n">mutates</span>: <span class="b">false</span>, <span class="n">audits</span>: [], <span class="n">publishes</span>: [], <span class="n">consumes</span>: [] },
<span class="n">signUp</span>: { <span class="n">mutates</span>: <span class="b">true</span>, <span class="n">audits</span>: [<span class="s">"user.created"</span>], <span class="n">publishes</span>: [<span class="s">"auth.signed-up"</span>], <span class="n">consumes</span>: [] },
<span class="n">signOut</span>: { <span class="n">mutates</span>: <span class="b">true</span>, <span class="n">audits</span>: [<span class="s">"session.ended"</span>], <span class="n">publishes</span>: [], <span class="n">consumes</span>: [] },
},
<span class="n">realtimeChannels</span>: [],
<span class="n">jobs</span>: [<span class="s">"auth.welcome-email"</span>],
} <span class="k">as const</span>);</pre>
<div class="playground">
<div class="playground-head">
<h3>Manifest <em>playground</em></h3>
<span class="meta">toggle properties · watch enforcement light up</span>
</div>
<div class="playground-grid">
<!-- Toggle panel -->
<div class="controls-panel">
<div class="control-group">
<div class="control-label">Use case <span class="hint">configure one use case below</span></div>
<div style="font-family: 'JetBrains Mono', monospace; font-size: 13.5px; color: var(--ink); padding: 8px 0 4px;">
<code id="pg-usecase-name">signUp</code>
</div>
</div>
<div class="control-group">
<div class="control-label">Behavior</div>
<div class="toggle-row">
<button class="toggle accent" data-pg="mutates" aria-pressed="true">mutates</button>
</div>
</div>
<div class="control-group">
<div class="control-label">Audit emissions</div>
<div class="toggle-row">
<button class="toggle audit" data-pg="audits" data-value="user.created" aria-pressed="true">user.created</button>
<button class="toggle audit" data-pg="audits" data-value="session.ended" aria-pressed="false">session.ended</button>
</div>
</div>
<div class="control-group">
<div class="control-label">Cross-feature publishes</div>
<div class="toggle-row">
<button class="toggle events" data-pg="publishes" data-value="auth.signed-up" aria-pressed="true">auth.signed-up</button>
<button class="toggle events" data-pg="publishes" data-value="auth.signed-out" aria-pressed="false">auth.signed-out</button>
</div>
</div>
<div class="control-group">
<div class="control-label">Required cores</div>
<div class="toggle-row">
<button class="toggle" data-pg="cores" data-value="audit" aria-pressed="true">core-audit</button>
<button class="toggle" data-pg="cores" data-value="events" aria-pressed="true">core-events</button>
<button class="toggle" data-pg="cores" data-value="realtime" aria-pressed="false">core-realtime</button>
</div>
</div>
<div class="control-group">
<div class="control-label">Implementation present?</div>
<div class="toggle-row">
<button class="toggle" data-pg="impl-audit" aria-pressed="true">auditLogger injected</button>
<button class="toggle" data-pg="impl-bus" aria-pressed="true">bus.publish called</button>
<button class="toggle" data-pg="impl-wrap" aria-pressed="true">withSpan + withCapture</button>
</div>
</div>
</div>
<!-- Output panel -->
<div class="output-panel">
<div class="output-block">
<div class="output-block-head">
<span>compiler</span><span class="layer-id">▮ tsc / IDE — 0s</span>
</div>
<div class="output-block-body">
<pre id="pg-ts">Type-checking signUp use-case binding…</pre>
</div>
</div>
<div class="output-block">
<div class="output-block-head">
<span>editor</span><span class="layer-id">▮ eslint — &lt;1s</span>
</div>
<div class="output-block-body">
<pre id="pg-eslint">Linting feature.manifest.ts and use-case files…</pre>
</div>
</div>
<div class="output-block">
<div class="output-block-head">
<span>dev server</span><span class="layer-id">▮ pnpm dev — ~3s</span>
</div>
<div class="output-block-body">
<pre id="pg-boot">Asserting container conformance…</pre>
</div>
</div>
<div class="output-block">
<div class="output-block-head">
<span>continuous integration</span><span class="layer-id">▮ pnpm conformance — ~120s</span>
</div>
<div class="output-block-body">
<pre id="pg-ci">Running cross-feature checks…</pre>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- ═════════════ §03 Four enforcement points ═════════════ -->
<section id="layers">
<div class="frame">
<div class="section-head">
<span class="section-num">§ 04</span>
<div>
<h2 class="section-title">Four <em>enforcement</em> points.</h2>
<p class="section-blurb">Each layer runs in a different process at a different moment, and so each layer sees something the others don't. The compiler sees types but not runtime calls. ESLint sees the AST but not bindings. The dev server sees the wired container but not what's missing. CI sees the whole repo. Composed, they catch nearly every drift class.</p>
</div>
</div>
<div class="layers-controls">
<span class="label">Layer</span>
<div class="pillbar" id="layers-pill" role="tablist">
<button data-layer="ts" aria-pressed="true">compiler</button>
<button data-layer="eslint" aria-pressed="false">editor</button>
<button data-layer="boot" aria-pressed="false">dev server</button>
<button data-layer="ci" aria-pressed="false">ci</button>
</div>
</div>
<div class="layer-board">
<div class="layer-board-head">
<h3 id="lb-title">TypeScript <em>brands</em></h3>
<div class="latency"><strong id="lb-latency">0s</strong>red squiggle, real-time</div>
</div>
<div class="layer-board-grid">
<div class="layer-narrative" id="lb-narrative">
<span class="scenario-tag">compile time</span>
<h4>If you forget the wrapper, you can't even <em>bind</em>.</h4>
<p>Wrap helpers — <code>withSpan</code>, <code>withCapture</code>, <code>withAudit</code> — return branded function types. The DI <code>bind</code> signature requires those brands as input. A use-case factory that hasn't been wrapped is not assignable to the binding slot.</p>
<p>The manifest is consumed at the type level: a use case declared <code>mutates: true</code> demands an <code>Audited</code> brand in its binding. The IDE lights up the moment you save.</p>
<ul>
<li>No runtime cost. No test required.</li>
<li>Refactor-safe: rename a wrapper and every call site fails at once.</li>
<li>Doesn't catch behavior — only structural omissions.</li>
</ul>
<div class="layer-catches">
<strong>Catches</strong>
forgotten <code>withSpan</code>; forgotten <code>withAudit</code> on mutation; missing dependency on the binder's <code>ctx</code> type.
</div>
</div>
<pre class="code" id="lb-code" data-lang="core-shared/instrumentation/with-span.ts"><span class="k">export type</span> <span class="t">Instrumented</span>&lt;<span class="t">F</span>&gt; = <span class="t">F</span> &amp; { <span class="k">readonly</span> __instrumented: <span class="b">true</span> };
<span class="k">export function</span> <span class="t">withSpan</span>&lt;<span class="t">I</span>, <span class="t">O</span>&gt;(
tracer: <span class="t">ITracer</span>,
opts: <span class="t">SpanOpts</span>,
fn: (input: <span class="t">I</span>) =&gt; <span class="t">Promise</span>&lt;<span class="t">O</span>&gt;,
): <span class="t">Instrumented</span>&lt;(input: <span class="t">I</span>) =&gt; <span class="t">Promise</span>&lt;<span class="t">O</span>&gt;&gt; { <span class="c">/* … */</span> }
<span class="c">// core-shared/di/bind.ts</span>
<span class="k">type</span> <span class="t">ProductionUseCase</span>&lt;<span class="t">I</span>, <span class="t">O</span>, <span class="t">M</span> <span class="k">extends</span> <span class="t">UseCaseManifest</span>&gt; =
&amp; <span class="t">Instrumented</span>&lt;(input: <span class="t">I</span>) =&gt; <span class="t">Promise</span>&lt;<span class="t">O</span>&gt;&gt;
&amp; (<span class="t">M</span>[<span class="s">"mutates"</span>] <span class="k">extends</span> <span class="b">true</span> ? <span class="t">Audited</span>&lt;<span class="k">any</span>&gt; : <span class="k">unknown</span>);
<span class="c">// In bind-production.ts:</span>
bind&lt;<span class="t">ISignUpUseCase</span>&gt;(SYMBOL).toDynamicValue&lt;
<span class="t">ProductionUseCase</span>&lt;<span class="t">SignUpInput</span>, <span class="t">SignUpOutput</span>, <span class="t">AuthManifest</span>[<span class="s">"useCases"</span>][<span class="s">"signUp"</span>]&gt;
&gt;(ctx =&gt; <span class="h">withSpan</span>(tracer, opts, <span class="h">withAudit</span>(auditLogger, <span class="h">withCapture</span>(logger, tags, signUpUseCase(...)))));
<span class="c">// Forget withAudit on a mutating use case:</span>
<span class="err">Type '...' is not assignable to type 'Audited&lt;...&gt;'.
Property '__audited' is missing.</span></pre>
</div>
</div>
</div>
</section>
<!-- ═════════════ §04 Mistake catalog ═════════════ -->
<section id="mistakes">
<div class="frame">
<div class="section-head">
<span class="section-num">§ 05</span>
<div>
<h2 class="section-title">A catalog of <em>mistakes</em>.</h2>
<p class="section-blurb">A working enforcement system is best judged by the mistakes it catches and where. Below is a matrix of common drift patterns mapped to the four layers — click any row to see the actual error message each layer surfaces. The earlier the catch, the cheaper the fix; the rightmost catch is the last line of defence.</p>
</div>
</div>
<div class="mistakes-table" id="mistakes-table">
<div class="mistakes-head">
<span>Mistake</span>
<span class="col-catch">tsc</span>
<span class="col-catch">eslint</span>
<span class="col-catch">boot</span>
<span class="col-catch">ci</span>
</div>
<!-- rows generated by JS to keep markup tight -->
</div>
</div>
</section>
<!-- ═════════════ §05 Composition / time-to-feedback ═════════════ -->
<section id="composition">
<div class="frame">
<div class="section-head">
<span class="section-num">§ 06</span>
<div>
<h2 class="section-title">Layer <em>composition</em>.</h2>
<p class="section-blurb">The layers don't replace each other — they compose. Each is independently shippable, and each is best at catching a different class of mistake. Read left-to-right: the further right, the longer the feedback loop and the more situational the rule.</p>
</div>
</div>
<div style="background: var(--paper-3); border: 1px solid var(--rule-strong); border-radius: 4px; padding: 40px 48px;">
<div style="display: grid; grid-template-columns: 100px 1fr 130px 110px; gap: 14px; padding-bottom: 16px; border-bottom: 1px solid var(--rule); font-family: 'JetBrains Mono', monospace; font-size: 10.5px; letter-spacing: 0.22em; text-transform: uppercase; color: var(--ink-3);">
<span>Layer</span><span>Surface</span><span style="text-align: right;">Latency</span><span style="text-align: right;">Best at</span>
</div>
<div style="display: grid; grid-template-columns: 100px 1fr 130px 110px; gap: 14px; padding: 22px 0; border-bottom: 1px solid var(--rule); align-items: center;">
<span style="font-family: 'Fraunces', serif; font-style: italic; font-variation-settings: 'opsz' 24, 'SOFT' 100, 'wght' 460; font-size: 22px; color: var(--accent-2);">tsc</span>
<div style="position: relative; height: 12px; background: var(--paper-2); border-radius: 6px; overflow: hidden;">
<div style="position: absolute; left: 0; top: 0; bottom: 0; width: 2%; background: var(--accent-2);"></div>
</div>
<span style="text-align: right; font-family: 'JetBrains Mono', monospace; font-size: 12px; color: var(--accent-2); font-weight: 500;">0s</span>
<span style="text-align: right; font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--ink-3); letter-spacing: 0.06em;">shape</span>
</div>
<div style="display: grid; grid-template-columns: 100px 1fr 130px 110px; gap: 14px; padding: 22px 0; border-bottom: 1px solid var(--rule); align-items: center;">
<span style="font-family: 'Fraunces', serif; font-style: italic; font-variation-settings: 'opsz' 24, 'SOFT' 100, 'wght' 460; font-size: 22px; color: var(--ok);">eslint</span>
<div style="position: relative; height: 12px; background: var(--paper-2); border-radius: 6px; overflow: hidden;">
<div style="position: absolute; left: 0; top: 0; bottom: 0; width: 8%; background: var(--ok);"></div>
</div>
<span style="text-align: right; font-family: 'JetBrains Mono', monospace; font-size: 12px; color: var(--ok); font-weight: 500;">&lt;1s</span>
<span style="text-align: right; font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--ink-3); letter-spacing: 0.06em;">policy</span>
</div>
<div style="display: grid; grid-template-columns: 100px 1fr 130px 110px; gap: 14px; padding: 22px 0; border-bottom: 1px solid var(--rule); align-items: center;">
<span style="font-family: 'Fraunces', serif; font-style: italic; font-variation-settings: 'opsz' 24, 'SOFT' 100, 'wght' 460; font-size: 22px; color: var(--warn);">boot</span>
<div style="position: relative; height: 12px; background: var(--paper-2); border-radius: 6px; overflow: hidden;">
<div style="position: absolute; left: 0; top: 0; bottom: 0; width: 32%; background: var(--warn);"></div>
</div>
<span style="text-align: right; font-family: 'JetBrains Mono', monospace; font-size: 12px; color: var(--warn); font-weight: 500;">~3s</span>
<span style="text-align: right; font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--ink-3); letter-spacing: 0.06em;">wiring</span>
</div>
<div style="display: grid; grid-template-columns: 100px 1fr 130px 110px; gap: 14px; padding: 22px 0; align-items: center;">
<span style="font-family: 'Fraunces', serif; font-style: italic; font-variation-settings: 'opsz' 24, 'SOFT' 100, 'wght' 460; font-size: 22px; color: var(--accent);">ci</span>
<div style="position: relative; height: 12px; background: var(--paper-2); border-radius: 6px; overflow: hidden;">
<div style="position: absolute; left: 0; top: 0; bottom: 0; width: 100%; background: var(--accent);"></div>
</div>
<span style="text-align: right; font-family: 'JetBrains Mono', monospace; font-size: 12px; color: var(--accent); font-weight: 500;">~120s</span>
<span style="text-align: right; font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--ink-3); letter-spacing: 0.06em;">closure</span>
</div>
<p style="margin: 28px 0 0; font-family: 'Fraunces', serif; font-variation-settings: 'opsz' 15, 'SOFT' 40; font-size: 14.5px; line-height: 1.55; color: var(--ink-2); max-width: 70ch; font-style: italic;">
Bar length is feedback latency. Layer order is also the order in which mistakes get progressively more expensive to discover — a CI failure on a merge-day branch costs more than a red squiggle on save.
</p>
</div>
</div>
</section>
<!-- ═════════════ §06 Build order / milestones ═════════════ -->
<section id="milestones">
<div class="frame">
<div class="section-head">
<span class="section-num">§ 07</span>
<div>
<h2 class="section-title">Build <em>order</em>.</h2>
<p class="section-blurb">Four independently shippable milestones. Build them in this order because each is small on its own, and each catches mistakes the next milestone otherwise has to handle. Stop after any milestone and the remainder still works as a manual checklist.</p>
</div>
</div>
<div class="milestones-controls">
<span style="font-family: 'JetBrains Mono', monospace; font-size: 11px; letter-spacing: 0.22em; text-transform: uppercase; color: var(--ink-3); margin-right: 12px;">Milestone</span>
<div class="pillbar" id="ms-pill" role="tablist">
<button data-ms="1" aria-pressed="true">i · manifest + brands</button>
<button data-ms="2" aria-pressed="false">ii · boot assertions</button>
<button data-ms="3" aria-pressed="false">iii · eslint rules</button>
<button data-ms="4" aria-pressed="false">iv · ci drift</button>
</div>
</div>
<div class="milestone-board">
<div class="milestone-board-head">
<span class="milestone-num" id="ms-num">i.</span>
<h3 class="milestone-title" id="ms-title">Manifest helper &amp; branded wrappers</h3>
<div class="milestone-effort"><strong id="ms-effort">23 days</strong>scope · effort</div>
</div>
<div class="milestone-body">
<div class="col">
<h5>What ships</h5>
<p id="ms-ships"></p>
<h5 style="margin-top: 18px;">Files created or touched</h5>
<ul id="ms-files"></ul>
</div>
<div class="col">
<h5>What it catches</h5>
<p id="ms-catches"></p>
<h5 style="margin-top: 18px;">Why this milestone first</h5>
<p id="ms-why"></p>
</div>
</div>
</div>
</div>
</section>
<!-- ═════════════ §07 Anchors in this repo ═════════════ -->
<section id="anchors">
<div class="frame">
<div class="section-head">
<span class="section-num">§ 08</span>
<div>
<h2 class="section-title">Anchor points <em>already here</em>.</h2>
<p class="section-blurb">Most of the foundation for this system already exists in <code>template-vertical</code>. The list below shows what's in place (green), what's partially there (amber), and what needs to be built fresh (red). The plan is to extend existing muscle, not introduce a parallel mechanism.</p>
</div>
</div>
<div class="anchors-grid">
<div class="anchor-card exists">
<div class="anchor-status">exists</div>
<h3>@repo/core-eslint custom rules</h3>
<p class="role"><em>The watchman registry.</em></p>
<p><code>no-handler-reexport</code>, <code>no-realtime-handler-reexport</code>, <code>no-direct-socket-io</code>. The plugin scaffolding is in place — new rules drop in next to the existing ones.</p>
<div class="runs-when"><strong>Extends to →</strong> manifest/usecase-signature-matches, manifest/no-undeclared-event-publish, manifest/no-undeclared-audit</div>
</div>
<div class="anchor-card exists">
<div class="anchor-status">exists</div>
<h3>BindContext + bindAll() dispatcher</h3>
<p class="role"><em>The boot-time assertion site.</em></p>
<p>Each feature exports <code>bindProductionX(ctx)</code>; the app aggregator composes them. This is the natural place to run a single <code>assertConformance(container, manifests)</code> at the tail of <code>bindAll()</code>.</p>
<div class="runs-when"><strong>Extends to →</strong> read every feature manifest, walk the container, fail boot on mismatch</div>
</div>
<div class="anchor-card exists">
<div class="anchor-status">exists</div>
<h3>withSpan / withCapture composition</h3>
<p class="role"><em>The wrap convention.</em></p>
<p>Use cases are already wrapped at bind time with <code>withSpan(tracer, opts, withCapture(logger, tags, factory(deps)))</code>. Brand types attach a phantom flag — zero runtime cost.</p>
<div class="runs-when"><strong>Extends to →</strong> add <code>Instrumented&lt;F&gt;</code>, <code>Captured&lt;F&gt;</code>, and a new <code>Audited&lt;F&gt;</code> when core-audit is on</div>
</div>
<div class="anchor-card exists">
<div class="anchor-status">exists</div>
<h3>turbo gen feature / event / job / realtime</h3>
<p class="role"><em>The scaffold-on-creation pipeline.</em></p>
<p>Generators already exist for features, events, jobs, realtime channels. They become core-aware: read <code>pnpm-workspace.yaml</code>, scaffold matching wiring and a manifest entry alongside the use-case file.</p>
<div class="runs-when"><strong>Extends to →</strong> write the manifest stub, wire <code>auditLogger</code> dep when core-audit is present, register the symbol</div>
</div>
<div class="anchor-card partial">
<div class="anchor-status">partial</div>
<h3>PII grep gate</h3>
<p class="role"><em>One example of a CI-only check.</em></p>
<p>The PII grep runs only in CI. The same shape applies to conformance: a <code>pnpm conformance</code> task that's cheap enough to also run locally as a watch, but authoritative in CI.</p>
<div class="runs-when"><strong>Extends to →</strong> add the conformance script, surface ESLint rules in the editor so CI is the backstop, not the front line</div>
</div>
<div class="anchor-card missing">
<div class="anchor-status">missing</div>
<h3>The manifest primitive itself</h3>
<p class="role"><em>The new declarative source of truth.</em></p>
<p>Nothing today plays this role. <code>defineFeature</code> needs to live in <code>core-shared/conformance/</code> with a strict <code>as const</code> contract, plus a typed registry the dispatcher can iterate.</p>
<div class="runs-when"><strong>Build →</strong> define-feature.ts, feature.manifest.ts in each feature, registry export in the app's bind-production.ts</div>
</div>
</div>
</div>
</section>
<!-- ═════════════ §09 Beyond the four ═════════════ -->
<section id="extensions">
<div class="frame">
<div class="section-head">
<span class="section-num">§ 09</span>
<div>
<h2 class="section-title">Beyond the <em>four</em>.</h2>
<p class="section-blurb">Two natural extensions came out of design conversations — sharper AST machinery, and a separate story for code conventions. Both lean on the same four-layer chassis; neither requires a new tool.</p>
</div>
</div>
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 56px;">
<div class="layer-narrative">
<span class="scenario-tag">ast · extensions</span>
<h4>Make ESLint <em>type-aware</em> first.</h4>
<p>The rules in §04 walk the syntax tree but don't ask the compiler questions. Three escalating options, ordered by leverage for an agent loop.</p>
<ul>
<li><strong>Type-aware ESLint</strong> via <code>parserServices.program</code> — cross-file checks like "factory signature matches manifest deps", "binding-slot type resolves to <code>Audited&lt;F&gt;</code>". Same <code>&lt;1s</code> layer; 10100× slower than syntax rules but still sub-second.</li>
<li><strong>Template-literal types</strong> on the manifest — e.g. <code>audits: Array&lt;`${FeatureName}.${string}`&gt;</code>. Pushes invariants down to the <code>0s</code> compiler tier. A feature can't declare an audit it doesn't own.</li>
<li><strong>TS Language-Service plugin</strong> — same diagnostics injected into <code>tsserver</code>, surfaced instantly without an ESLint round-trip. Marginal gain for agents (no IDE), big gain for humans.</li>
</ul>
<div class="layer-catches">
<strong>Recommendation</strong>
Start with type-aware ESLint — same layer, no new tool, biggest reach. Add template-literal types for invariants the type system can model directly. Skip the LS plugin unless humans complain about lint lag.
</div>
</div>
<div class="layer-narrative">
<span class="scenario-tag">conventions · two camps</span>
<h4>Structure vs. <em>shape</em>, two different layers.</h4>
<p>Code conventions split cleanly. Each camp wants a different primitive — don't pick one tool and try to bend it across both.</p>
<ul>
<li><strong>Structural</strong> — file layout, required exports, mock siblings, scaffold shape. Use <code>turbo gen feature</code> as the canonical source; CI regenerates into a tmp dir and diffs. <em>One tool catches dozens of conventions; cost scales sublinearly with each new convention.</em> (Milestone iv.)</li>
<li><strong>In-file shape</strong> — factory signature, <code>.strict()</code> on schemas, the presenter function, trailing <code>outputSchema.parse(result)</code>, repository methods calling <code>this.tracer.startSpan</code> inline. Each is one small ESLint rule in <code>@repo/core-eslint</code>.</li>
<li><strong>Pattern restrictions</strong> — "no <code>socket.io</code> outside <code>core-realtime</code>", "no <code>payload.jobs.queue()</code> outside <code>core-shared/jobs/</code>". <code>no-restricted-imports</code> plus the existing <code>no-handler-reexport</code> / <code>no-direct-socket-io</code> custom rules. Already in place; keep extending.</li>
<li><strong>Type-encodable</strong> — anything expressible in a type, push to the compiler. Binding-slot brands, <code>as const</code> manifests, branded IDs. Free at runtime, refactor-safe.</li>
</ul>
<div class="layer-catches">
<strong>Recommendation</strong>
Lean on the generator-drift gate for structure; reserve ESLint for in-file shape. Resist writing 30 layout rules — one scaffold-diff catches the same drift, and the cost of a new convention drops to "regenerate and commit", not "write a rule".
</div>
</div>
</div>
</div>
</section>
<!-- ═════════════ §10 Open questions ═════════════ -->
<section id="questions">
<div class="frame">
<div class="section-head">
<span class="section-num">§ 10</span>
<div>
<h2 class="section-title">Open <em>questions</em>.</h2>
<p class="section-blurb">Decisions to make before the spec is written. None block starting milestone i, but the answers shape how the manifest is shaped and which layer carries which check.</p>
</div>
</div>
<div class="questions-list">
<div class="q-item">
<span class="q-num">i.</span>
<div class="q-body">
<h5>Where does the manifest live?</h5>
<p>Inside the feature package (<code>src/feature.manifest.ts</code>, co-located with use cases) so it ships with the package — or in the app's <code>server/</code> directory so it's authoritative per-app? The first feels right for clean architecture; the second matches the existing aggregator pattern. <em>Recommendation: feature package, with a tiny app-side registry that imports each.</em></p>
</div>
</div>
<div class="q-item">
<span class="q-num">ii.</span>
<div class="q-body">
<h5>How much of the manifest is generated vs hand-written?</h5>
<p>The generator writes the initial scaffold. After that: do humans edit it directly, or does ESLint auto-fix from the use-case file's signature? The auto-fix is convenient but means the manifest is no longer the source of truth — the code is. <em>Recommendation: humans edit; ESLint flags mismatches both ways without fixing.</em></p>
</div>
</div>
<div class="q-item">
<span class="q-num">iii.</span>
<div class="q-body">
<h5>Does the manifest enumerate symbols, or does the registry?</h5>
<p>The bind-time assertion needs to know which container symbol each use case is bound to. Either the manifest declares it inline, or the registry holds a mapping. Inline is more self-contained; registry is less repetitive.</p>
</div>
</div>
<div class="q-item">
<span class="q-num">iv.</span>
<div class="q-body">
<h5>What happens when an optional core is absent?</h5>
<p>If <code>core-audit</code> isn't installed, a manifest entry like <code>audits: ["user.created"]</code> should ideally not even type-check. This requires the manifest's typed surface to depend on which cores are present in <code>pnpm-workspace.yaml</code> — non-trivial. Alternative: type the field as <code>readonly never[]</code> when audit is absent, gated by a build-time const.</p>
</div>
</div>
<div class="q-item">
<span class="q-num">v.</span>
<div class="q-body">
<h5>Escape hatch?</h5>
<p>Real systems need exceptions. A <code>// @conformance-skip: &lt;rule&gt;&lt;reason&gt;</code> comment that ESLint and the boot assertion both honour, recorded in a single allowlist file so exceptions are visible and reviewable. <em>Recommendation: yes, plus a CI step that fails when allowlist entries grow without an issue link.</em></p>
</div>
</div>
</div>
</div>
</section>
</main>
<footer class="colophon">
<div class="frame">
<div class="row">
<div>
<h4>Colophon</h4>
<p>Fraunces (display + body) &amp; JetBrains Mono (code) over Linseed Mill.</p>
<p style="margin-top: 12px;">An explainer of a proposed enforcement system. Not yet implemented. Read alongside the ADRs.</p>
</div>
<div>
<h4>Sibling explainers</h4>
<ul>
<li><a href="./di-explainer.html">di-explainer.html — dependency injection &amp; bindings</a></li>
<li><a href="./data-flow-explainer.html">data-flow-explainer.html — request &amp; event flows</a></li>
<li><a href="./audit-and-compliance-explainer.html">audit-and-compliance-explainer.html — DPA audit logging</a></li>
</ul>
</div>
<div>
<h4>Adjacent reading</h4>
<ul>
<li><a href="./vertical-feature-spec.md">vertical-feature-spec.md — feature shape &amp; decisions</a></li>
<li><a href="./template-tiers.md">template-tiers.md — must-have vs optional cores</a></li>
<li><a href="../guides/scaffolding-a-feature.md">scaffolding-a-feature.md — turbo gen feature</a></li>
</ul>
</div>
</div>
</div>
</footer>
<script>
// ═════════════ §02 Manifest playground ═════════════
(function() {
const state = {
mutates: true,
audits: new Set(["user.created"]),
publishes: new Set(["auth.signed-up"]),
cores: new Set(["audit", "events"]),
"impl-audit": true,
"impl-bus": true,
"impl-wrap": true,
};
function setPressed(btn, pressed) {
btn.setAttribute("aria-pressed", pressed ? "true" : "false");
}
function render() {
const out = {
ts: document.getElementById("pg-ts"),
eslint: document.getElementById("pg-eslint"),
boot: document.getElementById("pg-boot"),
ci: document.getElementById("pg-ci"),
};
const tsLines = [];
const eslintLines = [];
const bootLines = [];
const ciLines = [];
// === Compiler (TypeScript) ===
if (state.mutates && state.audits.size > 0 && !state["impl-wrap"]) {
tsLines.push('<span class="err">error TS2322:</span> <span class="file">bind-production.ts</span>');
tsLines.push(' Type \'(input: SignUpInput) =&gt; Promise&lt;SignUpOutput&gt;\'');
tsLines.push(' is not assignable to \'<span class="label">Instrumented</span>&lt;...&gt;\'.');
tsLines.push(' Property \'__instrumented\' is missing.');
}
if (state.mutates && state.audits.size > 0 && !state["impl-audit"] && state.cores.has("audit")) {
tsLines.push('<span class="err">error TS2322:</span> <span class="file">bind-production.ts</span>');
tsLines.push(' signUp declares <code>mutates: true</code> + audits[],');
tsLines.push(' but binding is not <span class="label">Audited&lt;F&gt;</span>.');
tsLines.push(' Property \'__audited\' is missing.');
}
if (state.publishes.size > 0 && !state["impl-bus"] && state.cores.has("events")) {
tsLines.push('<span class="err">error TS2741:</span> <span class="file">sign-up.usecase.ts</span>');
tsLines.push(' Property \'bus\' is missing in factory deps');
tsLines.push(' but required by manifest <span class="label">publishes</span>.');
}
if (state.cores.has("audit") && !state.cores.has("events") && state.publishes.size > 0) {
tsLines.push('<span class="err">error TS2344:</span> <span class="file">feature.manifest.ts</span>');
tsLines.push(' publishes[] requires \'events\' in requiredCores.');
}
if (tsLines.length === 0) {
tsLines.push('<span class="ok">✓</span> <span class="dim">Type-check passed.</span>');
tsLines.push('<span class="dim"> signUp: ProductionUseCase&lt;SignUpInput, SignUpOutput, M&gt;</span>');
if (state.mutates) tsLines.push('<span class="dim"> brand: __instrumented &amp; __audited &amp; __captured</span>');
}
out.ts.innerHTML = tsLines.join("\n");
// === ESLint ===
if (state.publishes.size > 0 && !state["impl-bus"]) {
eslintLines.push('<span class="err">manifest/no-undeclared-event-publish</span>');
eslintLines.push(' <span class="file">sign-up.usecase.ts:42</span>');
eslintLines.push(' Manifest declares <code>publishes: ["auth.signed-up"]</code>');
eslintLines.push(' but no <code>bus.publish()</code> call found in factory body.');
}
if (state.publishes.size === 0 && state["impl-bus"]) {
eslintLines.push('<span class="err">manifest/no-undeclared-event-publish</span>');
eslintLines.push(' <span class="file">sign-up.usecase.ts:42</span>');
eslintLines.push(' Found <code>bus.publish("auth.signed-up")</code> but no');
eslintLines.push(' matching entry in manifest.publishes.');
}
if (state.audits.size === 0 && state["impl-audit"] && state.mutates) {
eslintLines.push('<span class="warn">manifest/no-undeclared-audit</span>');
eslintLines.push(' <span class="file">sign-up.usecase.ts:38</span>');
eslintLines.push(' Found <code>auditLogger.log({...})</code> call but');
eslintLines.push(' manifest.audits is empty for this use case.');
}
if (state.audits.size > 0 && !state.cores.has("audit")) {
eslintLines.push('<span class="err">manifest/required-cores-installed</span>');
eslintLines.push(' <span class="file">feature.manifest.ts:7</span>');
eslintLines.push(' Manifest declares audits[] but <code>core-audit</code>');
eslintLines.push(' is not in requiredCores or pnpm-workspace.yaml.');
}
if (eslintLines.length === 0) {
eslintLines.push('<span class="ok">✓</span> <span class="dim">No conformance lint errors.</span>');
eslintLines.push('<span class="dim"> manifest ↔ use-case signatures match</span>');
eslintLines.push('<span class="dim"> declared publishes ↔ bus.publish calls match</span>');
}
out.eslint.innerHTML = eslintLines.join("\n");
// === Boot-time assertions ===
bootLines.push('<span class="dim">[bindAll] resolving auth feature…</span>');
let bootErrored = false;
if (state.mutates && state.audits.size > 0 && !state["impl-wrap"]) {
bootLines.push('<span class="err">✗ assertConformance failed</span>');
bootLines.push(' auth.signUp: binding missing brand \'__instrumented\'');
bootLines.push(' did you forget withSpan() at bind time?');
bootErrored = true;
}
if (state.cores.has("audit") && state.mutates && state.audits.size > 0 && !state["impl-audit"]) {
bootLines.push('<span class="err">✗ assertConformance failed</span>');
bootLines.push(' auth.signUp declares audits but auditLogger');
bootLines.push(' is not in resolved deps.');
bootErrored = true;
}
if (state.audits.size > 0 && !state.cores.has("audit")) {
bootLines.push('<span class="err">✗ assertConformance failed</span>');
bootLines.push(' auth manifest declares audits[] but @repo/core-audit');
bootLines.push(' is not bound in this app\'s BindContext.');
bootErrored = true;
}
if (state.publishes.size > 0 && !state.cores.has("events")) {
bootLines.push('<span class="err">✗ assertConformance failed</span>');
bootLines.push(' auth manifest declares publishes[] but @repo/core-events');
bootLines.push(' is not bound in this app\'s BindContext.');
bootErrored = true;
}
if (!bootErrored) {
bootLines.push('<span class="ok">✓ auth: 3 use cases bound, all wrapped</span>');
bootLines.push('<span class="dim"> signIn — withSpan ∘ withCapture</span>');
if (state.mutates) bootLines.push('<span class="dim"> signUp — withSpan ∘ withAudit ∘ withCapture</span>');
else bootLines.push('<span class="dim"> signUp — withSpan ∘ withCapture</span>');
bootLines.push('<span class="dim"> signOut — withSpan ∘ withAudit ∘ withCapture</span>');
bootLines.push('<span class="ok">✓ assertConformance: 0 violations</span>');
} else {
bootLines.push('<span class="dim"> pnpm dev refusing to start.</span>');
}
out.boot.innerHTML = bootLines.join("\n");
// === CI ===
let ciErrored = false;
if (state.publishes.has("auth.signed-up")) {
ciLines.push('<span class="ok">✓ event closure: auth.signed-up</span>');
ciLines.push('<span class="dim"> publisher: auth.signUp</span>');
ciLines.push('<span class="dim"> consumer: marketing-pages.on-auth-signed-up.handler</span>');
} else {
ciLines.push('<span class="warn">⚠ orphan consumer detected</span>');
ciLines.push('<span class="file"> packages/features/marketing-pages/.../on-auth-signed-up.handler.ts</span>');
ciLines.push(' consumes "auth.signed-up" but no feature publishes it.');
ciErrored = true;
}
ciLines.push('');
ciLines.push('<span class="dim">[generator drift]</span>');
ciLines.push('<span class="ok">✓ auth manifest matches turbo gen feature scaffold shape</span>');
ciLines.push('');
ciLines.push('<span class="dim">[required cores]</span>');
if (state.cores.size === 0) {
ciLines.push('<span class="warn">⚠ auth declares no requiredCores</span>');
ciLines.push(' is this intentional? Feature uses audit + events at runtime.');
} else {
ciLines.push('<span class="ok">✓ requiredCores: [' + Array.from(state.cores).join(", ") + ']</span>');
ciLines.push('<span class="dim"> all present in pnpm-workspace.yaml</span>');
}
if (!ciErrored && state.cores.size > 0) {
ciLines.push('');
ciLines.push('<span class="ok">✓ pnpm conformance — passed</span>');
} else {
ciLines.push('');
ciLines.push('<span class="err">✗ pnpm conformance — 1 violation</span>');
}
out.ci.innerHTML = ciLines.join("\n");
}
document.querySelectorAll("[data-pg]").forEach(btn => {
btn.addEventListener("click", () => {
const key = btn.dataset.pg;
const value = btn.dataset.value;
if (value) {
if (!state[key]) state[key] = new Set();
if (state[key].has(value)) {
state[key].delete(value);
setPressed(btn, false);
} else {
state[key].add(value);
setPressed(btn, true);
}
} else {
state[key] = !state[key];
setPressed(btn, state[key]);
}
render();
});
});
render();
})();
// ═════════════ §03 Layer pillbar ═════════════
(function() {
const layers = {
ts: {
title: 'TypeScript <em>brands</em>',
latency: '0s',
tag: 'compile time',
heading: 'If you forget the wrapper, you can\'t even <em>bind</em>.',
body: `
<p>Wrap helpers — <code>withSpan</code>, <code>withCapture</code>, <code>withAudit</code> — return branded function types. The DI <code>bind</code> signature requires those brands as input. A use-case factory that hasn't been wrapped is not assignable to the binding slot.</p>
<p>The manifest is consumed at the type level: a use case declared <code>mutates: true</code> demands an <code>Audited</code> brand in its binding. The IDE lights up the moment you save.</p>
<ul>
<li>No runtime cost. No test required.</li>
<li>Refactor-safe: rename a wrapper and every call site fails at once.</li>
<li>Doesn't catch behavior — only structural omissions.</li>
</ul>`,
catches: 'forgotten <code>withSpan</code>; forgotten <code>withAudit</code> on mutation; missing dependency on the binder\'s <code>ctx</code> type.',
codeLang: 'core-shared/instrumentation/with-span.ts',
code: `<span class="k">export type</span> <span class="t">Instrumented</span>&lt;<span class="t">F</span>&gt; = <span class="t">F</span> &amp; { <span class="k">readonly</span> __instrumented: <span class="b">true</span> };
<span class="k">export function</span> <span class="t">withSpan</span>&lt;<span class="t">I</span>, <span class="t">O</span>&gt;(
tracer: <span class="t">ITracer</span>,
opts: <span class="t">SpanOpts</span>,
fn: (input: <span class="t">I</span>) =&gt; <span class="t">Promise</span>&lt;<span class="t">O</span>&gt;,
): <span class="t">Instrumented</span>&lt;(input: <span class="t">I</span>) =&gt; <span class="t">Promise</span>&lt;<span class="t">O</span>&gt;&gt; { <span class="c">/* … */</span> }
<span class="c">// core-shared/di/bind.ts</span>
<span class="k">type</span> <span class="t">ProductionUseCase</span>&lt;<span class="t">I</span>, <span class="t">O</span>, <span class="t">M</span> <span class="k">extends</span> <span class="t">UseCaseManifest</span>&gt; =
&amp; <span class="t">Instrumented</span>&lt;(input: <span class="t">I</span>) =&gt; <span class="t">Promise</span>&lt;<span class="t">O</span>&gt;&gt;
&amp; (<span class="t">M</span>[<span class="s">"mutates"</span>] <span class="k">extends</span> <span class="b">true</span> ? <span class="t">Audited</span>&lt;<span class="k">any</span>&gt; : <span class="k">unknown</span>);
<span class="c">// In bind-production.ts:</span>
bind&lt;<span class="t">ISignUpUseCase</span>&gt;(SYMBOL).toDynamicValue&lt;
<span class="t">ProductionUseCase</span>&lt;<span class="t">SignUpInput</span>, <span class="t">SignUpOutput</span>, <span class="t">AuthManifest</span>[<span class="s">"useCases"</span>][<span class="s">"signUp"</span>]&gt;
&gt;(ctx =&gt; <span class="h">withSpan</span>(tracer, opts, <span class="h">withAudit</span>(auditLogger, <span class="h">withCapture</span>(logger, tags, signUpUseCase(...)))));
<span class="c">// Forget withAudit on a mutating use case:</span>
<span class="err">Type '...' is not assignable to type 'Audited&lt;...&gt;'.
Property '__audited' is missing.</span>`,
},
eslint: {
title: 'AST-aware <em>lint rules</em>',
latency: '<1s',
tag: 'on save',
heading: 'The manifest and the code must <em>agree</em>.',
body: `
<p>Custom ESLint rules in <code>@repo/core-eslint</code> read the manifest and walk the use-case AST. They flag any disagreement in either direction: a manifest entry without a matching code call, or a code call without a matching manifest entry.</p>
<p>Because rules read the manifest's declared use cases by name, they don't rely on fuzzy regex matches — false-positive rate is near zero. The Problems panel updates as you type.</p>
<ul>
<li>Surfaces in the editor with line + column.</li>
<li>Reads <code>pnpm-workspace.yaml</code> for installed-cores checks.</li>
<li>Auto-fixable for trivial cases; advisory otherwise.</li>
</ul>`,
catches: 'manifest ↔ code drift; undeclared <code>bus.publish</code>; audit calls outside the manifest; required cores not installed.',
codeLang: 'core-eslint/rules/no-undeclared-event-publish.ts',
code: `<span class="k">export const</span> rule: <span class="t">Rule.RuleModule</span> = {
meta: { type: <span class="s">"problem"</span>, docs: { description: <span class="s">"bus.publish must match manifest.publishes"</span> } },
create(context) {
<span class="k">const</span> manifest = <span class="h">loadFeatureManifest</span>(context.cwd);
<span class="k">if</span> (!manifest) <span class="k">return</span> {};
<span class="k">return</span> {
<span class="t">CallExpression</span>(node) {
<span class="k">if</span> (!<span class="h">isBusPublishCall</span>(node)) <span class="k">return</span>;
<span class="k">const</span> eventName = <span class="h">getStringArg</span>(node, 0);
<span class="k">const</span> useCase = <span class="h">findEnclosingUseCase</span>(node, manifest);
<span class="k">if</span> (!useCase.publishes.includes(eventName)) {
context.report({
node,
message: <span class="s">\`bus.publish("\${eventName}") not declared in manifest.\${useCase.name}.publishes\`</span>,
});
}
},
};
},
};
<span class="c">// On save in VS Code Problems panel:</span>
<span class="err">manifest/no-undeclared-event-publish</span>
<span class="b">sign-up.usecase.ts:42</span>
Found bus.publish("auth.signed-up") but no matching manifest entry.`,
},
boot: {
title: 'Container <em>self-check</em>',
latency: '~3s',
tag: 'pnpm dev startup',
heading: 'The dev server <em>refuses</em> to start on drift.',
body: `
<p>At the tail of <code>bindAll()</code>, after every feature has registered its bindings, the app runs <code>assertConformance(container, manifests)</code>. It walks each manifest entry, resolves the bound symbol, and inspects the resulting function for the expected brand flags.</p>
<p>This catches what the type system can't see: someone <em>bound</em> the use case but passed an unwrapped factory through a type cast, or the manifest was edited but the binder wasn't.</p>
<ul>
<li>Runs at every <code>pnpm dev</code> start, every prod boot, every test run.</li>
<li>Throws synchronously — boot fails loudly.</li>
<li>Cheap: a single walk over manifest entries.</li>
</ul>`,
catches: 'binding type-casts that hid a missing wrapper; manifests edited without rebinding; required cores missing in the bound BindContext.',
codeLang: 'core-shared/conformance/assert-bindings.ts',
code: `<span class="k">export function</span> <span class="t">assertConformance</span>(
container: <span class="t">Container</span>,
manifests: <span class="t">FeatureManifest</span>[],
ctx: <span class="t">BindContext</span>,
): <span class="t">void</span> {
<span class="k">for</span> (<span class="k">const</span> m <span class="k">of</span> manifests) {
<span class="c">// 1. Required cores present?</span>
<span class="k">for</span> (<span class="k">const</span> core <span class="k">of</span> m.requiredCores) {
<span class="k">if</span> (!ctx[<span class="h">coreKey</span>(core)]) {
<span class="k">throw new</span> <span class="t">ConformanceError</span>(<span class="s">\`\${m.name}: requires \${core} but not bound\`</span>);
}
}
<span class="c">// 2. Each use case bound &amp; wrapped?</span>
<span class="k">for</span> (<span class="k">const</span> [name, uc] <span class="k">of</span> Object.entries(m.useCases)) {
<span class="k">const</span> bound = container.get(<span class="h">symbolFor</span>(m.name, name));
assert(bound.<span class="r">__instrumented</span>, <span class="s">\`\${m.name}.\${name}: missing withSpan\`</span>);
assert(bound.<span class="r">__captured</span>, <span class="s">\`\${m.name}.\${name}: missing withCapture\`</span>);
<span class="k">if</span> (uc.mutates &amp;&amp; uc.audits.length &gt; 0) {
assert(bound.<span class="r">__audited</span>, <span class="s">\`\${m.name}.\${name}: declares audits but unwrapped\`</span>);
}
}
}
}
<span class="c">// In bindAll(): final line.</span>
<span class="h">assertConformance</span>(container, [authManifest, blogManifest, ...], ctx);`,
},
ci: {
title: 'Repo-wide <em>drift gate</em>',
latency: '~120s',
tag: 'on every PR',
heading: 'The view from <em>above</em> the codebase.',
body: `
<p>Some checks only make sense at the repo level: every event consumer must have a publisher; every manifest should look like what <code>turbo gen feature</code> would produce today; required cores must actually be in <code>pnpm-workspace.yaml</code>.</p>
<p>The <code>pnpm conformance</code> task aggregates ESLint + boot + cross-feature checks into one CI gate. Failures block merge.</p>
<ul>
<li>Closes the loop on cross-feature event contracts.</li>
<li>Re-runs the generator into a tmp dir and diffs scaffold shape.</li>
<li>Slowest layer; the backstop, not the front line.</li>
</ul>`,
catches: 'dangling event consumers; manifest shape drift from generator; manifests + workspace.yaml disagreement; allowlist growth without justification.',
codeLang: 'scripts/conformance.ts',
code: `<span class="k">import</span> { loadAllManifests } <span class="k">from</span> <span class="s">"@repo/core-shared/conformance"</span>;
<span class="k">import</span> { regenerateScaffolds, diffShape } <span class="k">from</span> <span class="s">"./regen"</span>;
<span class="k">const</span> manifests = <span class="k">await</span> <span class="h">loadAllManifests</span>(<span class="s">"packages/features/*"</span>);
<span class="c">// 1. Event closure: every consumer has a publisher.</span>
<span class="k">const</span> published = <span class="k">new</span> <span class="t">Set</span>(manifests.flatMap(m =&gt;
Object.values(m.useCases).flatMap(uc =&gt; uc.publishes)));
<span class="k">const</span> consumed = <span class="k">new</span> <span class="t">Set</span>(manifests.flatMap(m =&gt;
Object.values(m.useCases).flatMap(uc =&gt; uc.consumes)));
<span class="k">for</span> (<span class="k">const</span> event <span class="k">of</span> consumed) {
<span class="k">if</span> (!published.has(event)) {
<span class="h">fail</span>(<span class="s">\`Orphan consumer: "\${event}" has no publisher in the repo.\`</span>);
}
}
<span class="c">// 2. Scaffold drift: regenerate &amp; diff.</span>
<span class="k">for</span> (<span class="k">const</span> m <span class="k">of</span> manifests) {
<span class="k">const</span> fresh = <span class="k">await</span> <span class="h">regenerateScaffolds</span>(m.name);
<span class="k">const</span> diff = <span class="h">diffShape</span>(fresh, <span class="s">\`packages/features/\${m.name}\`</span>);
<span class="k">if</span> (diff.length &gt; 0) <span class="h">fail</span>(<span class="s">\`\${m.name} drifted from generator shape\`</span>, diff);
}
<span class="c">// 3. Cores actually installed?</span>
<span class="k">const</span> ws = <span class="k">await</span> <span class="h">readWorkspace</span>();
<span class="k">for</span> (<span class="k">const</span> m <span class="k">of</span> manifests) {
<span class="k">for</span> (<span class="k">const</span> core <span class="k">of</span> m.requiredCores) {
<span class="k">if</span> (!ws.packages.includes(<span class="s">\`@repo/core-\${core}\`</span>)) {
<span class="h">fail</span>(<span class="s">\`\${m.name} requires core-\${core} but not in workspace\`</span>);
}
}
}`,
},
};
function show(layerKey) {
const l = layers[layerKey];
document.getElementById("lb-title").innerHTML = l.title;
document.getElementById("lb-latency").innerHTML = l.latency;
const n = document.getElementById("lb-narrative");
n.innerHTML = `
<span class="scenario-tag">${l.tag}</span>
<h4>${l.heading}</h4>
${l.body}
<div class="layer-catches"><strong>Catches</strong>${l.catches}</div>`;
const c = document.getElementById("lb-code");
c.setAttribute("data-lang", l.codeLang);
c.innerHTML = l.code;
}
document.querySelectorAll("#layers-pill button").forEach(btn => {
btn.addEventListener("click", () => {
document.querySelectorAll("#layers-pill button").forEach(b =>
b.setAttribute("aria-pressed", b === btn ? "true" : "false"));
show(btn.dataset.layer);
});
});
})();
// ═════════════ §04 Mistake catalog ═════════════
(function() {
const mistakes = [
{
label: "Forget <code>withSpan</code> wrapper on a use case",
sub: "src/use-cases/<name>.usecase.ts — binder skips wrapping",
catches: { tsc: true, eslint: false, boot: true, ci: true },
details: "TypeScript catches it instantly: the binding slot demands Instrumented&lt;F&gt;, and a raw factory is not assignable. If someone uses an <code>as any</code> cast, the boot assertion catches it. CI re-runs the boot assertion as a backstop.",
errors: [
{ layer: "tsc", code: `<span class="err-line">error TS2322:</span> bind-production.ts:24
Type '(input: SignUpInput) =&gt; Promise&lt;SignUpOutput&gt;'
is not assignable to 'Instrumented&lt;(input: ...) =&gt; ...&gt;'.
Property '__instrumented' is missing.` },
{ layer: "boot", code: `<span class="err-line">ConformanceError:</span> assertConformance failed
auth.signUp: binding missing brand '__instrumented'
did you forget withSpan() at bind time?
pnpm dev refusing to start.` },
],
},
{
label: "Forget <code>withAudit</code> on a mutating use case",
sub: "manifest declares audits: [...], binder forgot to wrap",
catches: { tsc: true, eslint: false, boot: true, ci: true },
details: "Because the manifest type flows into the binding signature, a use case declared <code>mutates: true</code> with non-empty <code>audits[]</code> requires an Audited&lt;F&gt; brand at the binding slot. TS flags it. Boot does the runtime walk. CI runs boot.",
errors: [
{ layer: "tsc", code: `<span class="err-line">error TS2322:</span> bind-production.ts:36
Type 'Instrumented&lt;F&gt; &amp; Captured&lt;F&gt;'
is not assignable to 'Instrumented&lt;F&gt; &amp; Audited&lt;F&gt;'.
Property '__audited' is missing.` },
{ layer: "boot", code: `<span class="err-line">ConformanceError:</span>
auth.signUp declares mutates: true with audits: ["user.created"]
but binding has no __audited brand.` },
],
},
{
label: "Call <code>bus.publish</code> without a manifest entry",
sub: "developer adds an event but forgets to update manifest",
catches: { tsc: false, eslint: true, boot: false, ci: false },
details: "TypeScript can't see what the function body does; it only checks the signature. ESLint walks the AST and reads the manifest, so it catches the mismatch instantly. Boot can't help: the publish call is dynamic. CI runs the same ESLint rule.",
errors: [
{ layer: "eslint", code: `<span class="err-line">manifest/no-undeclared-event-publish</span>
<span class="file">sign-up.usecase.ts:42</span>
bus.publish("auth.signed-up") in factory body
but manifest.signUp.publishes is empty.
Fix: add "auth.signed-up" to manifest.signUp.publishes
or remove the bus.publish call.` },
],
},
{
label: "Add manifest entry without writing the code",
sub: "manifest declares audits: [...] but auditLogger is unused",
catches: { tsc: true, eslint: true, boot: true, ci: true },
details: "Multi-layer catch. TypeScript catches the binder if the factory's signature doesn't accept auditLogger. ESLint walks the use-case body and notes the missing call. Boot resolves the bound function and inspects deps. CI runs the boot assertion.",
errors: [
{ layer: "eslint", code: `<span class="err-line">manifest/no-undeclared-audit</span>
<span class="file">sign-up.usecase.ts:1</span>
Manifest declares audits: ["user.created"]
but no auditLogger.log(...) call in factory body.` },
{ layer: "tsc", code: `<span class="err-line">error TS2741:</span> bind-production.ts:36
Property 'auditLogger' is missing in factory deps
but required by manifest.signUp.audits.` },
],
},
{
label: "Remove <code>core-audit</code> while a feature requires it",
sub: "pnpm remove @repo/core-audit; some manifest still has audits[]",
catches: { tsc: false, eslint: true, boot: true, ci: true },
details: "The feature's own type-check might still pass (audit types are still resolved from cache or types-only imports). ESLint reads <code>pnpm-workspace.yaml</code> on each lint and flags the manifest. Boot catches the missing dep in BindContext. CI catches the workspace mismatch.",
errors: [
{ layer: "eslint", code: `<span class="err-line">manifest/required-cores-installed</span>
<span class="file">auth/feature.manifest.ts:7</span>
Manifest declares requiredCores: ["audit"]
but @repo/core-audit is not in pnpm-workspace.yaml packages.` },
{ layer: "ci", code: `<span class="err-line">pnpm conformance — failed</span>
auth manifest requires core-audit
but workspace does not include it.
Run: pnpm turbo gen core-package audit
or remove audits[] declarations from this feature.` },
],
},
{
label: "Skip the generator and hand-roll a new feature",
sub: "missing manifest, missing bind-production, ad-hoc structure",
catches: { tsc: false, eslint: true, boot: true, ci: true },
details: "Hand-rolled features tend to skip the manifest entirely. ESLint catches use-case files without a sibling manifest. Boot catches the absent registry entry. CI runs the generator into a tmp dir and diffs scaffold shape.",
errors: [
{ layer: "eslint", code: `<span class="err-line">manifest/feature-must-have-manifest</span>
<span class="file">packages/features/billing/src/use-cases/charge.usecase.ts</span>
Found use-case file in a feature with no feature.manifest.ts.
Run: pnpm turbo gen feature billing` },
{ layer: "ci", code: `<span class="err-line">conformance: generator drift</span>
packages/features/billing: shape does not match
output of pnpm turbo gen feature billing.
Missing files:
- src/feature.manifest.ts
- src/di/bind-production.ts` },
],
},
{
label: "Add a consumer with no publisher",
sub: "handler imported from an event name nothing emits",
catches: { tsc: false, eslint: false, boot: false, ci: true },
details: "This is intrinsically a repo-level check — no single feature can know whether someone else publishes the event. CI aggregates all manifests, builds the consumes ↔ publishes graph, and fails on dangling consumers.",
errors: [
{ layer: "ci", code: `<span class="err-line">conformance: orphan consumer</span>
packages/features/marketing-pages/...
src/events/handlers/on-billing-charged.handler.ts
consumes "billing.charged" but no feature publishes it.
Either:
- add the publish to billing.feature.manifest.ts
- or remove the handler.` },
],
},
{
label: "Bypass the bus with a direct cross-feature import",
sub: "auth imports a marketing-pages use case directly",
catches: { tsc: false, eslint: true, boot: false, ci: true },
details: "Standard cross-feature boundary rule: feature A cannot import feature B's internals. The existing ESLint workspace-boundary plugin catches the import path. CI runs <code>pnpm turbo boundaries</code> as backstop.",
errors: [
{ layer: "eslint", code: `<span class="err-line">boundaries/no-cross-feature-import</span>
<span class="file">packages/features/auth/src/use-cases/sign-up.usecase.ts:3</span>
Cannot import @repo/marketing-pages from auth.
Cross-feature reactions must go through @repo/core-events.
Replace direct call with: bus.publish("auth.signed-up", {...})` },
],
},
{
label: "Repository write without a use-case wrapper",
sub: "code path mutates DB outside the use-case → controller chain",
catches: { tsc: false, eslint: true, boot: false, ci: true },
details: "Lint rule: any call into a Payload repository's <code>create / update / delete</code> must originate from a file inside <code>src/use-cases/</code>. Otherwise the audit/observability chain is bypassed. Hard to express in TypeScript; easy in an AST rule.",
errors: [
{ layer: "eslint", code: `<span class="err-line">manifest/repo-write-only-in-use-cases</span>
<span class="file">packages/features/auth/src/integrations/api/router.ts:14</span>
Direct repository write outside src/use-cases/.
Wrap this mutation in a use case so it inherits audit
+ instrumentation + capture.` },
],
},
{
label: "Required-cores list lies",
sub: "manifest says requiredCores: [\"audit\"] but feature never injects auditLogger",
catches: { tsc: false, eslint: true, boot: true, ci: true },
details: "ESLint walks the binder file; if requiredCores includes 'audit' but no use case in the manifest declares audits[] and no binder injects auditLogger, flag it. Boot catches dead requirements at startup. CI runs both.",
errors: [
{ layer: "eslint", code: `<span class="err-line">manifest/required-core-unused</span>
<span class="file">auth/feature.manifest.ts:7</span>
requiredCores includes "audit" but:
- no use case declares audits[]
- no binder injects auditLogger
Either use it or drop it.` },
],
},
];
const layerLabel = { tsc: "tsc / IDE", eslint: "ESLint", boot: "pnpm dev (boot)", ci: "pnpm conformance (CI)" };
const table = document.getElementById("mistakes-table");
mistakes.forEach((m, idx) => {
const row = document.createElement("div");
row.className = "mistake-row";
row.setAttribute("data-row", idx);
row.setAttribute("aria-expanded", "false");
row.innerHTML = `
<div class="mistake-label">${m.label}<span class="sub">${m.sub}</span></div>
<div class="catch-cell ${m.catches.tsc ? 'yes' : 'no'}"></div>
<div class="catch-cell ${m.catches.eslint ? 'yes' : 'no'}"></div>
<div class="catch-cell ${m.catches.boot ? 'yes' : 'no'}"></div>
<div class="catch-cell ${m.catches.ci ? 'yes' : 'no'}"></div>`;
table.appendChild(row);
const detail = document.createElement("div");
detail.className = "mistake-detail";
const errorsHtml = m.errors.map(e => `
<div class="err-card">
<div class="layer-tag">${layerLabel[e.layer]}</div>
<pre>${e.code}</pre>
</div>`).join("");
detail.innerHTML = `<div>${m.details}</div><div class="errors">${errorsHtml}</div>`;
table.appendChild(detail);
row.addEventListener("click", () => {
const expanded = row.getAttribute("aria-expanded") === "true";
document.querySelectorAll(".mistake-row").forEach(r => r.setAttribute("aria-expanded", "false"));
row.setAttribute("aria-expanded", expanded ? "false" : "true");
});
});
})();
// ═════════════ §06 Milestones ═════════════
(function() {
const milestones = {
"1": {
num: "i.",
title: "Manifest helper &amp; branded wrappers",
effort: "23 days",
ships: "<code>defineFeature</code> in <code>core-shared/conformance</code>; brand types <code>Instrumented&lt;F&gt;</code>, <code>Captured&lt;F&gt;</code>, <code>Audited&lt;F&gt;</code>; update one feature (auth) to declare its manifest and consume the branded binding signature.",
files: [
{ name: "core-shared/conformance/define-feature.ts", sub: "new — manifest helper + type machinery" },
{ name: "core-shared/instrumentation/with-span.ts", sub: "extend — attach __instrumented brand" },
{ name: "core-shared/instrumentation/with-capture.ts", sub: "extend — attach __captured brand" },
{ name: "core-audit/with-audit.ts", sub: "new — wrap helper for audit emission" },
{ name: "features/auth/src/feature.manifest.ts", sub: "new — first manifest, reference impl" },
{ name: "features/auth/src/di/bind-production.ts", sub: "update — use ProductionUseCase&lt;...&gt; binding type" },
],
catches: "Forgotten wrappers, missing deps on the binder ctx, manifest declarations without matching binding signatures.",
why: "Compile-time feedback is the cheapest and the only layer that requires zero CI infrastructure or runtime check. Every later milestone leans on this primitive — without it, the others have nothing to read.",
},
"2": {
num: "ii.",
title: "Boot-time conformance assertion",
effort: "12 days",
ships: "<code>assertConformance(container, manifests, ctx)</code>, called at the tail of every app's <code>bindAll()</code>. Roll out to all three apps (web-next, cms, web-tanstack). Failure throws synchronously and prevents boot.",
files: [
{ name: "core-shared/conformance/assert-bindings.ts", sub: "new — the runtime walker" },
{ name: "core-shared/conformance/registry.ts", sub: "new — manifest aggregation" },
{ name: "apps/web-next/src/server/bind-production.ts", sub: "extend — call assertConformance at tail" },
{ name: "apps/cms/src/server/bind-production.ts", sub: "extend — same" },
{ name: "apps/web-tanstack/src/server/bind-production.ts", sub: "extend — same" },
],
catches: "Type casts that masked unwrapped factories, manifest edits without rebinder updates, missing core packages in the bound BindContext.",
why: "Highest signal-to-noise after milestone i. Catches the failure mode the type system can't see — and does so before any test runs. Forces the manifest to be consulted at every boot, which prevents the manifest from going stale silently.",
},
"3": {
num: "iii.",
title: "AST-aware ESLint rules",
effort: "34 days",
ships: "Three new rules in <code>@repo/core-eslint</code> that read each feature's manifest and check the use-case + binder AST. Surface in the editor's Problems panel; run in <code>pnpm lint</code>; same plugin as existing custom rules.",
files: [
{ name: "core-eslint/rules/manifest-usecase-signature-matches.ts", sub: "new — manifest deps ↔ factory signature" },
{ name: "core-eslint/rules/no-undeclared-event-publish.ts", sub: "new — bus.publish ↔ manifest.publishes" },
{ name: "core-eslint/rules/no-undeclared-audit.ts", sub: "new — auditLogger.log ↔ manifest.audits" },
{ name: "core-eslint/rules/required-cores-installed.ts", sub: "new — manifest ↔ pnpm-workspace.yaml" },
{ name: "core-eslint/index.ts", sub: "extend — register new rules in preset" },
],
catches: "Manifest ↔ code drift in either direction (most common drift class), undeclared event publishes, calls into non-installed cores.",
why: "Editor-level feedback is what teams notice during normal work, not in a CI gate they've already merged past. Shipped after milestone i because the rules read the manifest the helper defines.",
},
"4": {
num: "iv.",
title: "CI drift gate",
effort: "2 days",
ships: "<code>pnpm conformance</code> task aggregating event-closure check, generator-drift check, required-cores-installed check, and an allowlist-growth check. Hooked into the existing CI workflow alongside lint and tests.",
files: [
{ name: "scripts/conformance.ts", sub: "new — repo-level aggregator" },
{ name: "scripts/regen-and-diff.ts", sub: "new — turbo gen into tmp + diff" },
{ name: ".github/workflows/ci.yml", sub: "extend — add pnpm conformance step" },
{ name: "turbo.json", sub: "extend — register conformance pipeline" },
],
catches: "Orphan consumers (no publisher), scaffold drift from the generator, manifests + workspace.yaml disagreement, escape-hatch growth without justification.",
why: "Cross-feature checks only make sense at the repo level. Cheap to add last because by then the manifest is real, the brands exist, and the boot assertion has been catching wiring mistakes for weeks.",
},
};
function show(key) {
const m = milestones[key];
document.getElementById("ms-num").innerHTML = m.num;
document.getElementById("ms-title").innerHTML = m.title;
document.getElementById("ms-effort").innerHTML = m.effort;
document.getElementById("ms-ships").innerHTML = m.ships;
document.getElementById("ms-catches").innerHTML = m.catches;
document.getElementById("ms-why").innerHTML = m.why;
const files = document.getElementById("ms-files");
files.innerHTML = m.files.map(f => `<li><code>${f.name}</code><span class="sub">${f.sub}</span></li>`).join("");
}
document.querySelectorAll("#ms-pill button").forEach(btn => {
btn.addEventListener("click", () => {
document.querySelectorAll("#ms-pill button").forEach(b =>
b.setAttribute("aria-pressed", b === btn ? "true" : "false"));
show(btn.dataset.ms);
});
});
show("1");
})();
</script>
</body>
</html>