Architecture

The front end — five layers, the template, the two registers, and the bundled explorer

references/cockpit.md — rendered straight from the skill doctrine.

On this page
More architecture topics

This is the home for everything you render: the layer model that says which file you’re allowed to touch, the copy-the-template-fill-it build recipe, the two registers (the narrated Journey — four canonical stations — and the Exploration deck of analyst instruments), the two-tier brand nav that separates them, and the bundled tool-level explorer. For a solution-design / buy-vs-build engagement the deliverable is a cockpit — a scored, walkable comparison of solutions on Gate · Fit · Cost · Risk under one shared Scenario, with a client walkthrough. For a single tool pick the deliverable is the lighter bundled explorer (last section). Both read their data as JSON; neither is built from scratch and neither is forked from another engagement.

Schemas for every JSON file live in contract.md — the always-open lookup from Phase 2; the mount(spec) / ctx contract lives in shell/README.md. This file cites both rather than restating them.

The five layers — know which one you’re touching

This file owns the five-layer model. (CLAUDE.md only routes up to here; if it carries a copy, it’s a stale duplicate — slim it to a pointer.) The model exists to keep one rule enforceable: you never edit the layer you depend on.

Layer What Your relationship to it
Contract the data schemasconfig / mission / criteria / matrix / landscape / research-index / scenario / decisions / architectures → solutions / fillers.json (all defined in contract.md) you fill the data
Kernel pure compute — engine/kernel.js (gate/fit/cost/risk, fork→architecture narrowing) pinned, never edited
Shell the mechanism — shell/shell.js (DecisionShell.mount, render/URL/keyboard loop, walkthrough frame, fork/architecture renderers, FLIP, copy accessors, the solution detail pane) pinned, never edited
Template template/ — a born-split ES-module graph (plan-14): app.js (the thin entry = STATIONS + the DecisionShell.mount spec) + state.js + data/ (constants leaf) + logic/ (cost · scoring · knowledge · status · solutions) + views/ (arbeitsbrett · verdikt · schaerfe) + app.css + the deploy pair (index.njk + eleventy.config.reference.js) + standalone index.html + stub data copied once, then customized
Instance the filled template — the engagement’s content + any bespoke later views you own it

The one rule (D4): you pin the kernel + shell — via the _methodology submodule, exactly like brand → website/_brand — and never touch them; you copy the template once and then it’s yours. Improvements to kernel or shell flow back to the decision-methodology repo and you re-pin; they are never patched in place in a consumer.

Copy the template, never fork another engagement. A new cockpit is built by copying template/ and filling it — never by cloning customers/skischule or customers/ehimare and deleting their content. Forking an instance drags its case-specific cost engine, its bespoke copy, and its half-finished later views into a job they don’t fit.

Reference instances are not templates. demo/ (the cats-off-counter walkthrough) and the live cockpits (customers/skischule, customers/ehimare) are filled templates — read them for ideas, but copy template/, not them. Treating a filled instance as the scaffold is what produces improvised, half-built early pages.

Station renderers live in the template, not the shell. The shell owns the invariant mechanism (mount, the frame, the fork/architecture renderers that are identical everywhere). The station renderers (the use-case grid, criteria-by-axis, the architecture finding+rail) live in the template, because engagements visibly customize the early pages — Ehimare’s Mission is an elaborate layered card set; the demo’s is a simple grid. Copy the canonical version, then adapt it.

How to build one

  1. Copy template/ into <engagement>/site/. Copy site/eleventy.config.reference.js up to <engagement>/.eleventy.js — it carries the module-graph cache-bust + passthrough conventions so you inherit them instead of reinventing them (plan-14). site/index.njk is the built deploy entry; site/index.html stays the no-build / copy-me dev artifact.
  2. Pin the kernel + shell via the _methodology submodule (the reference config already wires the _methodology + _brand passthroughs and the app.js ES-module graph; customers/ehimare is the live worked example). Load order + exact paths: shell/README.md § Consuming it. The brand base.css is REQUIRED in the chain (tokens → base → components → utilities → shell → app): it carries the heading contract (default weights + .h1/.h2/.h3/.display + .eyebrow + the fluid --fs-* tokens). Instance heading elements wear a brand class (<div class="h3 vl-name">, <div class="eyebrow cl-head">) and the app.css rule keeps only the size/rhythm delta — never re-set font-family/font-weight per bespoke class (that’s how headings silently drifted to 400).
  3. Fill the data in phase order, each file replacing its PLATZHALTER stubs: mission.json (Phase 1 — use cases with currentState/improvementLevel/enables, stakeholders, stress test) → criteria.json (Phase 2 — each criterion on one axis, Fit ones carrying useCase + area) → decisions.json + architectures.json (Phase 3) → solutions.json
    • fillers.json (Phase 4 — the scored compositions, their coverage, and the gap-filler library) → optionally landscape.json + research-index.json (the Tools + Report surfaces, the latter carrying the rounds[] research log). Shapes are the JSON Schema in contract/schema/ (doctrine in contract.md). Validate with node contract/validate.mjs <engagement>/data after edits.
    • When solutions.json / research-index.json grow too big to hand-edit (an engagement can reach 700+ KB / hundreds of solutions / 1000+ facts), shard them (Plan 24): author small per-record YAML under data/solutions/<key>.yaml + data/rounds/<roundId>.yaml
      • data/research-index.yaml, and the two .json files become a generated, gitignored artifact rebuilt by the pinned assembler. One-shot node _methodology/contract/assemble.mjs explode data, then add assemble ahead of every build (assemble → validate → 11ty). The shards are the source of truth — never hand-edit the assembled JSON. Full doctrine + the surgical data-edit.mjs editor: contract.md § Authoring at scale; customers/ehimare is the live worked example.
  4. Customize the stations in app.js — keep the canonical shape, change the content.

Register-3 copy overrides (B8). The mount spec’s copy: { forks } override map polishes client-facing strings; it keys by data id (fork id, option key) and falls back silently to register 2 on a miss. Keys MUST be a subset of the data’s ids/option-keys — a renamed id leaves an orphaned override key that silently stops applying. The shell emits a dev-mode warning for orphans (guarded by a dev flag, silent in production). Where the override map lives + the closed-set rule: model.md § Three registers; the mount(spec)/copy contract: shell/README.md.

The two registers — the Journey and the Exploration deck

A cockpit holds two registers, and the nav makes the seam legible:

  • The Journey — a curated story you narrate: a linear, paced walkthrough through Mission → Requirements → Architecture → Solutions, with prev/next, dots, and the presenter keyboard. It is the four canonical stations, and it is the whole story — nothing else is a chapter. (Station 4 · Solutions is interactive — it carries the same live knobs/FLIP it had as a view; the shell scope-refreshes its body just like a view’s.)
  • The Exploration — a non-linear analyst deck: the Board, the Verdict, the Sharpness Matrix, the Matrix, the Tools landscape, the Report. You jump between these at will to work the solution space. They are instruments, not chapters.

This is one model, one URL space, one data spine — every register reads the same solutions.json + ctx.kernel.*. There is no second URL space and no client/consultant mode split (the old continuum rule’s real content, which still holds). The split only names the spine’s two halves so the nav can show them — it does not reintroduce a peer-app or an audience mode. Drill-downs (a solution’s full detail opened in the shell detail pane from any surface; the Sharpness Matrix) still live inside Exploration.

The nav is the brand “Navigation E” two-tier docnav, rendered by the shell VERBATIM from the view registry (not a hand-rolled toggle). Each view declares a group ('reise' | 'exploration'); the spec’s nav block lists the groups (tier-1 = Journey · Exploration, the topbar main-links), and the shell renders tier-2 from the active group’s members into the brand left sidebar (.docnav-side) — the Journey’s four stations as jump-links, or the Exploration screens as view-switches. The wordmark cell carries the EV logo + the page title (the brand .docnav-brand-tag, e.g. “Template”/“Demo”) — there is no separate title bar. (The topbar util slot can hold an optional .docnav-cta link/button — util:{href,label} or util:{action,label} — but the template/demo declare none.) This is the engine’s own docs layout used as-is; you don’t touch any of it, you just load the brand CSS + shell and supply the skeleton (#shellNav topbar, then a .docnav-shell with <aside id="shellSide" class="docnav-side"> beside <main class="docnav-page"> holding #app). A consumer that declares no groups keeps the old flat .view-btn behaviour. Replaces the per-instance .view-toggle. (Detail: shell/README.md § nav.)

The Journey — four canonical stations (shape fixed, content yours)

Stations 1–4 mirror the first four methodology phases (process.md) and live in the template’s STATIONS array (titled 1 · Mission / 2 · Requirements / 3 · Architecture / 4 · Solutions). Stations 1–3 are inline render functions; Station 4 reuses the solutions renderers (renderLoesungen/loesungenBody) and declares body/bodySelector/flipRows + onClick/onInput so the shell scope-refreshes it like an interactive view. They are the entire narrated arc — Phase 5 renders as Exploration instruments (below), not as a station. Keep the shape and the data sources; adapt the voice and depth. The canon is grounded in the two real cockpits (Skischule ⋂ Ehimare) — when you customize, match what they actually do; don’t invent a new layout, and don’t let the template/demo drift from the real instances.

  • Station 1 · Mission — backed by mission.json. The tool-neutral use cases (what the system must do, never a product), each carrying its transitioncurrentState (none/manual/digitized) → improvementLevel (enable/digitize/improve/ai-assist) — plus the stakeholders (decides / uses-daily / sceptic) and the one stress test. Use the word use cases, not “jobs” (the use-case spine is model.md). Ehimare dresses this as an elaborate layered card set — that’s instance copy over the same mission data; the canonical generic is the use-case grid.
  • Station 2 · Requirements — backed by criteria.json. All criteria, grouped by axis (Gate / Fit / Risk) and, within Fit, clustered by area (the use-case clusters). The canonical way to show a functional use case is its current-state → target-state ladder: what the team does today (currentState) → how the solution lifts it (improvementLevel), with the optional ✨ AI-assist tier where an ai-assist level applies. This is the generalization of Ehimare’s hand-built “Today → Digital → AI” ladder — same shape, now driven by the use-case + criterion fields instead of hardcoded. Plus the cost-is-its-own-axis note (cost must never contaminate Fit — model.md). The requirements are the criteria, each on exactly one axis (single-placement — model.md).
  • Station 3 · Architecture — backed by decisions.json + architectures.json. Leads with the structural finding (a closed fork’s closedBy) and the architectures along the buy↔build spectrum (ctx.forks.rail, with the “← the core carries everything / you build more yourself →” framing). It names the forks; it does not parade them — the interactive value-fork belongs in the Verdict screen, where it resolves. (Ehimare renders each architecture as a rich position card with a role badge + wins/gives-up tradeoffs; that’s a richer skin over the same rail — adopt it when the architectures deserve the room. Putting the interactive narrowing control here inverts the phase: the space comes before the lever that reshapes it. Architecture-is-not-solution: model.md.)
  • Station 4 · Solutions — backed by solutions.json + the kernel verdict/ranking. The evolved Cockpit as the closing chapter (Phase 4 · Solutions): the verdict-ranked table (Fit / Year-1 / Risk / Verdict columns) with live day-rate + preset knobs and FLIP re-rank, topped by the verdict banner (names #1 at the current weighting, invites override) + the ★ recommended preset. Unlike Stations 1–3 it is interactive — it declares body/bodySelector/flipRows
    • onClick/onInput, so the shell scope-refreshes just #loesungenBody (with FLIP) on a knob, the same machinery a view uses. Each row is click-to-open in the detail pane. One ranking path powers both this station and the Verdict screen (the two-surface proof — model.md). Two distinct elements, never conflated (B4): (a) the authored verdict — reads the data’s recommended + verdictNote (contract.md) — is the lead; (b) the kernel ranking sits below it, labelled “the instrument (turnable)”. When the authored favourite ≠ the instrument’s current #1 (a legitimate divergence — model.md § the verdict is authored), show both and say why. The banner must never pass the instrument’s top row off as the recommendation.

Bespoke later views (a configurator, a presentation mode) are pure instance code.

The Exploration deck — the analyst instruments

These are the Exploration register (above) — peer screens reached from the Exploration menu, not chapters of the Journey. They share one data spine. Anywhere a solution is listed — a Solutions row, a Verdict card, a Board card, a Matrix column — the row/card is click-to-open: it emits data-shell-action="opensol" data-sol="<key>" and the shell slides in the solution detail pane (below). The Sharpness Matrix keeps its own inline rail.

  • Board (view arbeitsbrett) — backed by solutions.json (+ architectures.json for the columns). The per-architecture board: one column per architecture (grouped off each solution’s architecture field), a card per solution carrying the four lens icons (☑ Gate · ▥ Fit · € Cost · ⚠ Risk) each with a knowledge-state dot (offen/geschätzt/bekannt), an N gaps count, a status pill (live/offen/neu/raus), the best-leverage card flagged (“one move here sharpens the most”), and eliminated cards shown as eliminated with their cause named — a failed gate, or the decision that ruled them out (the two elimination causes — model.md; eliminations show, never vanish — P1; a decision-elimination is reversible and is never rendered as a fabricated gate-0). Clicking a card opens that solution in the detail pane (below) — which carries the Composition the card used to expand inline, plus the full Gate·Fit·Cost·Risk picture. The Journey’s exit (and Esc) lands here. A shape with no solutions — deferred or not yet surveyed (status:"deferred", contract.md) — still renders as a labelled empty “deferred” column, never dropped, so the architecture set matches the Journey (B7).
  • Verdict (view verdikt) — backed by the kernel verdict/ranking. The top solution + 2 alternatives + their tradeoffs (each card click-to-open in the detail pane); the live value-forks resolve here (turn a hinge → the architecture rail narrows); a banner names #1 at the current weighting and invites override; the verdict reads off the same rankAll the Solutions list uses (the two-surface proof). The verdict is the consultant’s judgment instrument: the consultant authors it, the agent (and the cockpit) presents it and never decides or eliminates on its own (the doctrine is model.md; eliminations show as a hard-filter 0, never a removed row).
  • Solution detail pane (per-solution)renderSolutionDetail(sol, ctx) rendered into the shell-owned slide-in pane (spec.solutionDetailctx.openPane; scrim/✕/Esc close). Opened from any surface that emits data-shell-action="opensol" (Solutions row, Verdict card, Board card, Matrix ⓘ). For one solution, with room to breathe: the verdict score + the three axis bars (fit/cost/risk at the live weighting), the Fit breakdown, the 4-bucket Year-1 cost, the risk dimensions, the tradeoff, the knowledge lenses, and finally the Composition (renderComposition(sol, ctx) — the same coverage detail the Arbeitsbrett used to expand inline). It reads the same rankAll(ctx) the lists rank by. The pane header carries an ⤢ enlarge button (next to ✕) that promotes the same body to a full-page detail screen with a breadcrumb back to the originating view (URL-synced as ?sol=, closeable via breadcrumb/Esc/browser-back). Supply spec.solutionTitle(key, ctx) so the screen heading + breadcrumb label survive a cold reload — see shell/README.md § Solution detail pane.
  • Composition detail (per-solution)renderComposition(sol, ctx), now rendered inside the detail pane (it was reached by selecting a Board card). For one solution: its coverage rows over the criteria (clustered by area/axis)
    • ✨ assists + the building block (filler) cards from fillers.json + the “core + building blocks + integration layer = one solution” equation. Render coverage as a glyph for the qualitative shape with the Fit score on hover — the two are distinct fields and a mismatch (in but Fit 2) must stay visible, never collapsed (the coverage→Fit bridge, model.md). Pre-score like Tools — no scoring leak. This is Ehimare’s solution explorer generalized (de-hardcoded CLUSTERSarea, SIDECARSfillers.json). The ✨ assist row is solution-aware (B6): it renders for this solution only when the solution’s own coverage references the assisting filler (aug:/part:) — a filler the solution can’t carry shows no ✨ row (contract.md § fillsAssistFor). A filler’s eliminated[] ways render struck under its building-block card (the never-deleted rule, below solution level).
  • Sharpness Matrix (view schaerfe) — renderSchaerfe, an Exploration instrument: the candidates × five-facet knowledge grid (open, high-separation cells light up) beside a ranked “suggested moves · by information gain” rail. A 🎯 fork move deep-links the real decisions.json fork into the Verdict screen (where it resolves); facet moves only highlight (no auto-action — P1). (The verdict-ranked solutions list — the evolved Cockpit — is no longer an Exploration screen; it is Station 4 · Solutions of the Journey, above.)
  • Matrix (view matrix) — renderMatrix, the full scoring grid: criteria × solutions, grouped by tier (Hard Filters / kritisch / standard / nice-to-have), sortable (click a solution column → by Total, a criterion row → by that criterion; survivors always rank above gate-eliminated columns, which render struck + dimmed), live tier weights (the per-tier <input> re-ranks subtotals + Total in place — config.tierWeights are just the starting values), tier show/hide toggles, per-tier Fit subtotals + a grand Total (Fit %), and reasoning on hover (solutions[].reasoning[critId], graceful fallback to the criterion description). Below the grid, a generic cost/effort block (Aufwand PT · Projekt-Kosten · laufend/Jahr · Jahr-1) from the kernel TCO — no case-specific work-package steps; that detail stays in an engagement (e.g. Skischule’s Chatwoot configurator). Subtotals + Total sum Fit-axis only (Risk is its own axis, shown not rolled up; Cost lives in the cost block). Sticky frozen header + first column; the active sort round-trips in the URL. Each solution column header carries a small button that opens that solution in the detail pane (the header name stays the sort handle — the ⓘ never sorts).

The Sharpness Matrix is grid-only (Plan 25). The ranked next-move „rail" — the separation/leverage heuristic infoGain (gain = w_open × separation × leverage) that once drove the rail and the Board best-leverage flag — was retired: no engagement kept it, so the Sharpness Matrix is now the knowledge-state grid alone (candidates × facets, ● bekannt · ◐ geschätzt · ◌ offen). Coverage reads in the Lücken-Schnitt; ranking in the Verdikt/Lösungen.

The Tools + Report views (optional, copied like the stations)

Beyond the scoring surfaces (Station 4 · Solutions / the Matrix view), the template ships two more that surface the Phase-4 research itself. Both are optional — drop the data file and the view shows an empty note — and both are copied template code (renderLandscape / renderReport in app.js), not shell.

  • ToolsrenderLandscape, backed by landscape.json. The raw, pre-score candidate list from the landscape survey — filter / search / click→detail, with gate badges and the filter chips derived from the engagement’s own criteria.json Gate-axis entries (and the category buttons + capability chips auto-derived from the data). It is the unscored surface; the scored one is Solutions/Matrix (or, for a pure tool pick, the bundled explorer below). It carries no ranking of its own — a landscape report’s offered winner is discarded on ingest (the ingest/audit procedure is research-briefs.md).
  • ReportrenderReport, backed by research-index.json (the append-only rounds[] log — contract.md). The research narrative: one proposal sketch per architecture (position), cross-cutting assist briefs, open meeting questions, links to the raw research files. The Report is the history — stations show the latest judgment, the Report shows how it got there (regression appends, never rewinds — process.md). A standout chip jumps into the Tools view.

Only the mechanism is canonical — the data is engagement substance. Don’t carry another engagement’s landscape.json/research-index.json content across; re-derive it from this job’s own survey. (These views graduated into the template from Skischule + Ehimare; the transfer discipline is transferring-between-engagements.md.)

The one math rule

The instance app.js carries no scoring/cost math except a single instanceCost(sol, scenario) hook — because per the kernel contract, recurring/usage/setup pricing is engagement code, not kernel code (the TCO method is pricing-tco.md; the pricing schema fields are contract.md). Everything else routes through ctx.kernel.* (ranking/fit/risk/viewCriteria/passesHardFilters) and ctx.forks.*. The cost engine is the one place a cockpit legitimately diverges per engagement (seats × month, usage, a flat retainer, a configurator) — keep it in instanceCost, out of the kernel.

The provenance invariant (Plan 23)

Every asserted value in a cockpit must let the reader instantly verify it. A score, gate, risk, coverage verdict, plan-gating, deployment/property, the €-figure (cost), knowledge facet or landscape tool property is not “clean” because it is filled — it is clean only when it ties back to the research that produced it. The cockpit makes that tie a first-class affordance, and where it is missing it says so out loud. The bar is the whole board, not the scored axes — the surfaces a reader actually chooses on (a price, a “Data residency: Germany”) are held to the same standard as a Fit score (Plan 23 §G).

  • Carry the refs in the data. Each fact-bearing carrier is a Plan-19/20/23 object that co-locates the value with its provenance facts[] (coverageEntry, scoreEntry, deploymentMode, planGating[], the descriptive property objects (dataResidency, crmCustomTypes, …), costInputs (the price basis, §G1), knowledgeFacet, and a landscape tool’s properties). The facts[] ids resolve into research-index.json facts{} ({claim, research, confidence, links[], revisedIn}). The criterion/facet id appears once (as the map key), so a value and its provenance can never drift apart. Latest research round wins (^r(\d+) in the fact id) — the freshest fact that states a thing is the one shown.
  • Property + cost cells verify EXACTLY like a score cell (§G). There is ONE matrix verify vocabulary, not a per-surface one: every asserted cell — score, descriptive property (“Properties” rows, via spec.properties prov(s,ctx)+provRef+anchor), and the cost figures (every “Cost”/“recurring” row, carrier costInputs) — is mx-cell-open with the claim+evidence in its hover tooltip and a click that lands on the detail-pane section it’s explained in (anchordata-sd-anchor/-crit); ungrounded shows the same honest . No bespoke dot/badge per surface. The detail-pane fact-sheet still carries the full provBadge as the landing target.
  • Clickability ≠ groundedness — don’t couple them. The cell-nav (hover-tooltip + click-to-pane) is the cfg.openParts/cfg.provenance gate and applies to EVERY non-empty asserted cell. A descriptive property with no prov carrier (Architecture, Connection) and EVERY cost row (not just the grounded “Cost/year” headline) are still click-to-detail — they just carry no (they assert no researched fact). Provenance only layers the claim+evidence / on top when a prov carrier exists. So every property/cost cell needs a real, existing pane anchor (anchor/data-sd-anchor), or its click lands nowhere; landing on a generic catch-all (the baseline spezif) for a value shown elsewhere reads as a broken click. All gated by cfg.provenance/cfg.openParts (default off → byte-parity).
  • The shell verifies, uniformly. One primitive — DecisionShell.factProvenanceprovBadge — renders the same affordance on every surface: hover shows the claim, the cell is click-through to the solution-detail point where it’s explained, and the evidence (source links ↗) + research (the research unit) are uncollapsable. Turn it on with the matrix / solution-detail provenance: true cfg slot (default off → parity-safe; an instance opts in).
  • Ungrounded is shown, never hidden. A value with no resolving facts[] renders an explicit unbelegt chip — an honest, surfaced research gap, not a blank. The validator (scripts/provenance-report.mjs) counts grounded vs unbelegt per surface and emits the backfill worklist; it warns, never blocks (progressive enforcement).
  • Aggregates stay fact-free (transitive provenance). Facts attach to the value cells only; derived rollups (Verdict %, weighted Fit, composite Risk) inherit provenance through the cells they sum — never carry their own facts[]. The kernel reads .val/.cov/.level and ignores facts[], so provenance is purely a contract+shell concern (parity guardrail).
  • Never invent a source. Backfilling provenance means finding the round that already states the value; if no past research states it, it stays unbelegt. A fabricated link is worse than an honest gap.

The bundled tool-level explorer

When the job is a single-category tool pick (not a composed solution), use the bundled explorer.html instead of a cockpit. It is a single-file, dependency-free HTML app that renders a tool-selection matrix as three views:

  • Ranked — who survived the hard filters, ordered by weighted Fit score.
  • Matrix — the full grid, reasoning on hover, live tier weighting.
  • Detail — one tool at a time: verdict, strengths/weaknesses, risks, fit, pricing, full score breakdown.

Scope — this is the tool-level surface. It renders gates + the weighted Fit score for a matrix of tools. It does not render the solution-level model — the shared Scenario, the Cost/Risk axes, compositions (tools + build + integration), or the re-weightable verdict instrument. That is the shared shell + kernel; for a solution-design / buy-vs-build engagement, build a cockpit by copying template/ (above). Use this explorer for single-category tool picks and to drive the Fit axis of a larger engagement. It is project-agnostic: it reads its data from sibling JSON files; you never edit the HTML for a new engagement.

Set up an engagement:

  1. Copy explorer.html into the engagement folder.
  2. Add criteria.json (produced in Phase 2 — the requirements as data).
  3. Add one matrix-<key>.json per category.
  4. Add config.json (title, subtitle, categories, default tier weights).
  5. Serve the folder and open it:
    python -m http.server 8000
    # → http://localhost:8000/explorer.html
    
    The file:// gotcha: opening via file:// is blocked by browser CORS for fetch — always serve over http://.

The example files shipped beside it (config.example.json, criteria.example.json, matrix-booking.json) are a complete working demo — copy them next to explorer.html, rename *.example.json*.json, serve, and it runs. The schema for config.json, criteria.json, and matrix-<key>.json is defined authoritatively in contract.md — read that, don’t reverse-engineer it from the example files.

Runtime behaviours worth knowing:

  • A tool with any hard-filter score of 0 is eliminated and drops to the bottom, struck through, with the failed filter named — an elimination is a recorded hard-filter 0, never a deleted row.
  • An omitted score shows as ? (not evaluated) and doesn’t count against the tool — so the explorer is useful even half-filled, which is the normal state mid-loop.
  • Tier weights are editable live in Matrix view; config.tierWeights are just the starting values — good for sensitivity-testing a recommendation in front of a client.
  • pricing renders generically: every key you include shows as a labelled row in the detail view, and annual (if present) becomes an informational matrix row. Set verified: true once a human confirms a figure. (Pricing runs after scoring and never enters the Fit number — model.md.)

Contract details for every JSON file: contract.md. The mount(spec) / ctx contract: shell/README.md. The TCO method behind instanceCost: pricing-tco.md. The discipline if you ever move a pattern between instances: transferring-between-engagements.md.

← Architecture overview