Skip to content

RFC-037 · Physics Kernel — the technical architecture for the Lab subsystem + MCP server ​

Status: Shipped (updated 2026-10-16: src/lib/physics/ + server/mcp; the closing ADR it names was never written (ADR-087 covers only the systems layer)) · was: Draft · 2026-08-08 · Closes into: ADR-TBD (physics-kernel package boundary + FigureSpec + MCP hosting) · Related: PRD-033 (vision), UXS-015 (look & feel), ADR-030 (fly-physics pure-function isolation), RFC-034/#412 (launch-ascent engine), RFC-024 + RFC-035 (containerized VPS stack + nginx/subdomain pattern) · Tracking: #458 (epic) · slices #459–#464

Why this is an RFC. PRD-033 sets the vision (liberate the physics into a Lab + an MCP server) and UXS-015 the look & feel; this RFC is the how it's built, and four load-bearing contracts have to be frozen before any code because every downstream slice and both consumption surfaces depend on them: (1) the kernel package boundary — what is "kernel" vs "app", enforced so the pure core can't regain a framework dependency, imported unchanged by both the SvelteKit build and a standalone Node process; (2) the FigureSpec contract — the single declarative figure description that the Lab renderers and the MCP responses both emit, so "draw the result" is defined once; (3) the card / serialization model — the portable atom that lets one card live in Notebook, Focus, and Canvas and persist across sessions; (4) scientific credibility as a public contract — exposing formulas to the world forces choosing one authoritative ephemeris and capping unbounded compute. Getting any of these wrong bakes a shape we can't cheaply walk back into a public API and a persisted document format.


1 · Scope ​

This RFC owns the technical spine shared by every surface: the kernel, the compute/figure/card contracts, the MCP server, the data strategy, and the slice plan. The product vision lives in PRD-033; the interaction/visual design in UXS-015. Where this RFC says "Notebook / Focus / Canvas," those are the three views defined in UXS-015 over one card model.

2 · Grounding — what already exists (inventory, 2026-08-08) ​

A two-agent sweep read ~45 modules. Verdict: the physics is overwhelmingly pure already (no three, no Svelte stores, no DOM, deterministic). The kernel is ~80% lift-and-shift.

DomainModulesRepresentative formulas
Ascent / launchorbital/ascent-physics.ts (+constants)2-DOF gravity-turn integrator, Tsiolkovsky Δv, pressure-interpolated thrust/Isp, staging, closed-loop insertion
Descent / EDLorbital/descent-physics.ts (+constants, 11 bodies)1-DOF entry integrator, drag, dynamic pressure, Mach, per-body atmospheres
Transfer / windowslambert.ts, lambert-grid.ts, orbital.ts, orbital/mission-arc.tsLambert (Lagrange-Gauss), porkchop grid, vis-viva, Kepler position, Hohmann + Keplerian transfer ellipse
Cislunarorbital/cislunar/cislunar-geometry.tsECI translunar/trans-Earth coasts, lunar orbit/flyby, spiral burns
Interplanetaryinterplanetary-geometry.tsAnalytic + waypoint-replay heliocentric trajectories
Ephemeris / skyastronomy/{time,planets,moon,horizontal,index}.tsJD/GMST/LST/obliquity, JPL Standish planet positions (Newton Kepler solve), Schlyter Moon, ecliptic→equatorial→horizontal, alt/az sky-pointing
Satellite / TLEsatellite/{tle,propagate,look-angles,index}.tsTLE parse, Kepler + J2 propagator (own, not satellite.js), look-angles, next-pass prediction
Propulsion / enginesorbital/{engine-registry,launcher-engines}.ts~22 engines: thrust/Isp/mass/cycle; launcher↔engine cross-refs
Utilityparse-delta-v, scale, planet-stats, moon-projection, earth-regimes, orbit-regime-matchΔv parse, escape-velocity/gravity tables, light-time, regime bounds

This table is the formula-registry seed (§5): each row becomes one or more registered formulas that the palette, the views, and the MCP tools all derive from.

3 · Contract 1 — the kernel boundary (D1: resolved) ​

Decision: src/lib/physics/ subtree, not a separate workspace package (revisit only if the MCP build forces it). Rationale: single tsconfig, no monorepo wiring, and the pure modules already live under src/lib — this is a move, not a restructure.

  • Move the PURE modules from §2 under src/lib/physics/{ascent,descent,transfer,cislunar,ephemeris,satellite,propulsion,util}/, keeping their sibling .test.ts.
  • Enforce purity with an ESLint no-restricted-imports boundary on src/lib/physics/**: forbid three, svelte, $app/*, $lib/* (app internals), DOM globals. This is the guard that keeps the kernel re-couplable-proof — the thing ADR-030 did by convention becomes a lint gate.
  • Public index src/lib/physics/index.ts — the only entry both consumers import. Per-export units + citations in doc-comments (public-contract grade).
  • Impure code stays out: satellite/tle-source.ts, the fetch-based loaders, the /science cross-ref lookups (see §7 decoupling).

4 · Contract 2 — FigureSpec (the draw-once contract) ​

Every formula result may carry a declarative figure description, not a rendered image. One shape, consumed by the Lab's SVG renderers and returned verbatim by the MCP server so an agent can render it anywhere.

ts
type FigureSpec =
  | { kind: 'transfer-ellipse'; frame: 'heliocentric'; bodies: {...}; arc: Vec2[]; marks: Annotation[] }
  | { kind: 'porkchop'; depDays: number[]; arrDays: number[]; grid: number[][]; units: 'km/s' }
  | { kind: 'dv-waterfall'; segments: {label; dv; kind}[] }
  | { kind: 'force-diagram'; vectors: {label; dir; magN}[]; body: 'rocket' }
  | { kind: 'curve'; x: {label;units;vals}; y: {label;units;vals}; marks?: Annotation[] }
  | { kind: 'orbit' | 'ground-track' | 'sky-chart' | 'entry-corridor'; ... };
  // NOTE: 'cislunar-eci' is emitted by an APP-SIDE geometric producer (see Amendment 01
  // A01.2a), NOT the kernel — it carries fidelity 'geometric', never 'computed'.
// every FigureSpec carries (UPDATED by Amendment 01 D11/D12):
//   provenance: { fidelity: 'computed' | 'geometric' | 'replayed-published'; module: string }
//   assumptions: string[]   // e.g. ['two-body only','no drag','non-rotating frame']
// The kernel emits ONLY fidelity:'computed'; 'geometric'/'replayed-published' come from
// app-side producers and render in a visually distinct register. NEVER 'illustration' here.
  • Renderers draw on the station-blueprint.ts teal-grid substrate (UXS-015 §figure language). The sketchy-but-exact styling lives in the renderer, never in the spec — the spec is pure data.
  • Provenance is intrinsic and multi-fidelity (Amendment 01 A01.2a/D11). Kernel-emitted specs are always computed; app-side producers may emit geometric (analytic shape for continuity) or replayed-published (interpolated authored data) — the kernel may NOT, and renderers MUST keep the three registers visually distinct (enforced by a register-distinction golden-master in S2.5, Fable-5 round-2 NEW-2). The generative/illustration layer (PRD-033 T3) is a separate artifact type nothing in the kernel produces — this is the honesty line made structural.

5 · Contract 3 — the formula registry + card model ​

The single source that the palette, all three views, and the MCP tool generator derive from.

ts
interface FormulaDef<I> {
  id: string;                       // 'tsiolkovsky', 'lambert-transfer', ...
  schema: JSONSchema;               // typed inputs + UNITS + server-side caps (e.g. steps ≤ N)
  compute(inputs: I): FormulaResult;
}
interface FormulaResult {
  values: Record<string, { value: number; units: string }>;
  figure?: FigureSpec;
  status: { ok: true } | { ok: false; reasonKey: string };  // fail-honest: infeasible carries an i18n reason KEY
  // UPDATED by Amendment 01 (all user-facing text is i18n-keyed — Fable-5 r5 MAJOR-2):
  assumptions: string[];        // D12 — message KEYS naming what the model ignores (teaching-honesty payload)
  epochAgeDays?: number;        // D13 — TLE/ephemeris-derived results carry data-staleness age (numeric, not text)
}

The card (the UXS-015 atom) is the serializable instance:

ts
interface Card { id: string; formulaId: string; inputs: Record<string, number|string>;
                 wires?: { fromCard: string; output: string; toInput: string }[] }
  • A Notebook = { title, cards: Card[] } (order = narrative; a card may reference an upstream card's output).
  • A Canvas = { cards: Card[], positions, edges } — same cards, plus layout + explicit wiring.
  • Serialization (D-serialize, new): compact form in the URL (shareable, matches IA §state-persistence's URL-encoded exception) for small worksheets; localStorage for the working session; a .orrlab.json export/import for durable documents. One codec, all three views.
  • One registry → MCP tools auto-derived. Each FormulaDef.schema + compute generates one MCP tool. The registry is the single place a new formula is added; every surface picks it up.

6 · Contract 4 — MCP server (hosting, transport, auth) ​

  • Subdomain / infra. mcp.orrerylearn.com, a new container behind the Caddy edge on the VPS (ops/deploy_caddy_vhosts.sh — Caddy is the actual edge; nginx is only the inside-container static server). Reuses the RFC-024/035 compose + subdomain-vhost pattern; no new infra class. (Fable-5 r5 MINOR-5: was wrongly "behind the existing nginx".)
  • Transport. MCP Streamable HTTP (current remote-server transport; Claude.ai custom connectors speak it).
  • Auth (D3). "Anybody with credentials logs in" → OAuth 2.1 (the spec's authorization framework) for public multi-user; a shared bearer token / nginx-gated credential for the private beta first. Highest-uncertainty area — verify against the current MCP auth spec before the beta slice.
  • Statelessness = security asset. Pure compute, no per-user state, no mutation, read-only. Nothing to leak; horizontally scalable.
  • Abuse bound. computePorkchopGrid and the integrators are unbounded-cost at large steps — the schema (§5) caps iteration counts server-side; this cap is part of the tool contract, not an afterthought.

7 · Decoupling items (the non-clean six) ​

  1. Static-JSON coupling — lambert-grid.constants, mission-arc, cislunar-events, satellite/stations Vite-import planets.json / small-bodies.json / cislunar-phase-science-map.json / station-tles.json. Resolve per §8.
  2. Fetch/loader boundary — loadLaunchProfile / loadDescentProfile call fetch; pure helpers move to the kernel, loaders stay app-side adapters.
  3. Genuinely impure — excluded: satellite/tle-source.ts (localStorage + fetch + Date.now + memo).
  4. Two ephemerides — §9.
  5. Non-determinism to scrub — earth-sidereal.ts (new Date() default); porkchop.ts locale-dependent date/colour helpers move out of the physics namespace (rendering, not physics).
  6. App-domain /science lookups — scienceRefsFor et al. stay app-side.

8 · Data strategy (D2) ​

  • D2-a inline (bake ephemeris rows into .ts) — portable, but duplicates source-of-truth JSON.
  • D2-b co-bundle (ship the JSON, resolve in both consumers) — single source; build-config cost.
  • D2-c inject (caller passes the data) — purest boundary; ripples to call sites.
  • Decision: D2-b for ephemeris tables — the JSON stays the single source of truth (PA §"data over code") + a codegen drift-check that fails CI if the baked .ts diverges from the JSON; D2-c where the caller already holds the data.

9 · The authoritative ephemeris (public-contract critical) ​

Two implementations exist: orbital.ts#keplerPos (first-order mean-longitude approximation, /fly's cheap smooth path) and astronomy/planets.ts#heliocentric (JPL Standish + Newton solve, ~arc-minute, 1800–2050). Decision: the Lab and MCP expose the JPL-Standish path as authoritative; the approximation is either unexposed or labelled fast with a documented error bound. /fly internals unchanged. Verified 2026-08-08 by source read: orbital.ts#keplerPos advances mean longitude linearly and uses it directly as the true anomaly (docstring: "no eccentric anomaly solve … accurate to first order"); astronomy/planets.ts#heliocentric uses the JPL Standish ELEMENTS table + an 8-iteration Newton solveKepler (docstring: "~arcminute accuracy, valid 1800–2050"). Authoritative planet positions for the Lab + MCP = astronomy/planets.ts#heliocentric.

10 · Rollout — slices (rollback-safe, Notebook-first per PRD-033) ​

  1. S1 — kernel boundary. Move pure modules to src/lib/physics/, add the ESLint purity gate, resolve JSON coupling (§8). Prove npm run preflight green + app behaviour unchanged. No new surface.
  2. S2 — registry + FigureSpec + renderers. The formula registry (§5), the FigureSpec type (§4), and the SVG renderers on the teal grid. Public-contract tests + citations.
  3. S3 — Lab: Notebook + Focus (v1 home). The /lab route, card model + serialization codec, Notebook and Focus views over the registry, honesty line + fail-honest states (UXS-015). This is the in-app payoff and it de-risks the kernel in-process.
  4. S4 — MCP server, private beta. Standalone Node process, tools auto-derived from the registry, one domain first (transfer: Lambert + vis-viva + Tsiolkovsky), bearer auth, mcp. subdomain.
  5. S5 — Canvas + wiring + promote (T2). The graph engine (sockets, edges, cycle-check, recompute), Canvas view, promote-to-Notebook. The expensive/novel slice — deliberately after S3 proves the card model.
  6. S6 — MCP OAuth + full tool surface, then the generative/illustration layer (T3) and the agentic ask-box (T4) as later, separately-gated work.

Each slice is inert until wired. S1 has no external exposure and can land independently of auth/hosting.

Tracked as: epic #458 · S1 #459 · S2 #460 · S3 #461 · S4 #462 · S5 #463 · S6 #464. Scheduled v0.9 — parked behind v0.8 completion; do not start S1 until v0.8 closes.

11 · Alternatives considered ​

  • A — reimplement formulas in the MCP server. Rejected: duplicates the source of truth, guarantees drift, discards the unit tests. The value is one registry.
  • B — expose the whole app over an API. Rejected: drags Three/Svelte/DOM headless, couples the public contract to route internals, can't serve the in-process views.
  • C — publish the kernel to npm. Compatible, natural post-S6 follow-on; out of scope (the ask is a hosted MCP surface).
  • D — per-view bespoke data models. Rejected: the whole subsystem coherence comes from one card model across three views (UXS-015).

12 · Non-goals ​

  • Changing extracted physics accuracy (extraction is behaviour-preserving) — but Amendment 01 adds new closed-form mechanics + per-milestone astrodynamics formulas (A01.1/A01.2b). No N-body/CFD/higher-precision numerical propagation.
  • Moving impure data layers (TLE live-fetch, profile fetch) into the kernel.
  • The Canvas graph engine in v1. NOW IN v0.9 (operator "all of it", A01.5/A01.7) — T2 Canvas + wiring, T3 generative layer, T4 ask-box, and MCP OAuth all ship in v0.9 (gated beta).
  • Still out: npm publication of the kernel (§11-C).

13 · Decisions ​

Resolved:

  • D1 — kernel home → src/lib/physics/ subtree + ESLint purity gate (§3).

  • D5 — route/views → /lab; Notebook / Focus / Canvas (UXS-015).

  • D2 — JSON-data strategy → D2-b co-bundle, JSON stays the source of truth + a CI drift-check (§8).

  • D-serialize — card codec → one codec, three outputs: URL-compact (shareable) + localStorage (session) + .orrlab.json (durable export/import); freeze the URL grammar before S3 (§5).

  • D-verify — ephemeris fidelity → confirmed by source read (§9); authoritative = astronomy/planets.ts#heliocentric.

  • D3 — MCP auth model → shared bearer token (private beta) → OAuth 2.1 (public multi-user); verify vs the current MCP auth spec before S4. (decided 2026-08-08)

  • D4 — new dependency @modelcontextprotocol/sdk (+ an HTTP server dep) → approved, MCP process only, gated to S4; S1–S3 add no new runtime deps (KaTeX already present). (decided 2026-08-08)

All kickoff decisions are resolved; slices S1–S6 are cleared to be tracked as issues.


Amendment 01 (2026-08-29) — mechanics domain, the Goal model, full-coverage registry ​

Status · Shipped v0.9.1 (A01 realized: src/lib/physics/mechanics/, registry/, systems/, server/mcp; result of the adversarial review not verified) · was: Draft, operator-approved shape · Pending adversarial review (advisor / Fable-5) before re-slicing · Implements the technical spine for PRD-033 Amendment 01 (first-principles / milestone ladder / full coverage) · Companion notes · docs/wip/_archive/2026-08-29-kernel-scope-expansion-classical-mechanics.md, docs/wip/_archive/2026-08-29-s1-kernel-boundary-manifest.md

PRD-033 A01 widened the product to a first-principles learning environment organized as a ladder of mission-goal milestones exposing the entire built physics library. This amendment adds the technical pieces that requires. Contract 1 (§3) is unchanged; Contracts 2 (§4 FigureSpec) and 3 (§5 FormulaResult) are AMENDED in place by D11/D12/D13 (provenance fidelity axis, assumptions, epochAgeDays) — the §4/§5 code blocks have been updated so an implementer freezing them verbatim gets the honest contract (Fable-5 round-2 NEW-2). S1 (kernel carve) is unaffected. Scope note (operator 2026-08-29): the v0.9 MVP is the ENTIRE goal ladder + kernel + mechanics + Lab + MCP — no subset; M1 is built first, but everything ships.

A01.1 · New kernel subdomain — physics/mechanics/ (greenfield) ​

A new pure subdomain under the §3 boundary: src/lib/physics/mechanics/{kinematics, dynamics,momentum,energy,gravitation}.ts. Same purity gate, same FormulaDef registration, same citation grade as the extracted modules. Greenfield content (not a move), so it is out of S1 and lands with S2 (registry).

Compose honestly, don't overclaim (Fable-5 MAJOR 4). The reuse story is per-body helpers, not "the same integrator." A drop from rest is the singular case of the EDL integrator — its dγ/dt divides by velocity, and the code clamps Math.max(v, 1) (descent-physics.ts:542), so a drop from rest doesn't crash — it returns silently wrong numbers (a stronger reason for the purpose-built integrator, not a weaker one); and SURFACE_BODY_KINEMATICS (planet-stats.ts) covers only moon/mars/earth/venus (no Titan — Titan's constants live in descent-physics-constants.ts). Correct model: a purpose-built free-fall integrator in mechanics/ that reuses the clean per-body exports bodyGravity / bodyAirDensity (descent-physics.ts:253/260). Fidelity ceiling unchanged (§12 holds for N-body/CFD/numeric propagation); see A01.2b for the sideways per-milestone new-physics budget.

D10 — one authoritative per-body constants home (Fable-5 MAJOR 4 back-pressure). μ_Earth is currently defined in ≥3 kernel-bound places in different units (cislunar-geometry.ts:14, lambert-geocentric-grid.constants.ts:18, ascent-physics-constants.ts:22) plus planet-stats. S1 moves them as-is (fine), but mechanics/ must NOT add a 4th table — S2 designates util/constants (or util/planet-stats) as the single per-body source and mechanics imports it. Decide the home before S2; this is the one real S2→S1 back-pressure.

A01.2 · Contract 5 — the Goal / Milestone model (the load-bearing new contract) ​

The registry (§5) gains a Goal as a first-class, ordered, serializable entity — the atom of the milestone ladder. This is the "milestones-as-data, not code" bet PRD-033 A01.5 holds the MVP accountable to.

ts
interface Goal {
  id: string;                    // 'launch-a-rocket', 'get-to-the-moon', ...
  titleKey: string;              // i18n message key, e.g. 'lab.goal.launch-a-rocket.title'
  family: 'spaceflight' | 'observe';
  tier: number;                  // difficulty / ladder rank (M1=1 … M6=6, G7…)
  prereqs: string[];             // Goal ids that should precede this one
  path: GoalStep[];              // the ORDERED critical path — the curriculum
}
interface GoalStep {
  formulaId: string;             // a FormulaDef in the registry (§5)
  narrativeKey: string;          // paraglide message KEY, not raw prose (Fable-5 r5 MAJOR-2):
                                 // all user-facing text is i18n-keyed (×14 locales) + lives in
                                 // the message bundle, never baked into the serialized Goal /
                                 // .orrlab.json. Same for Goal.titleKey, assumptions (key[]),
                                 // and FormulaResult.status.reasonKey. FREEZE this at S2a/S2c.
  // Fable-5 MAJOR 2: mirror Contract 3's `Card.wires` EXACTLY — a single string
  // can't express M1's own payoff rung (a comparison fed by TWO upstream steps:
  // Δv-capacity from Tsiolkovsky vs Δv-to-orbit from the integrator). Freeze this
  // shape before D7, not after.
  wiresFrom?: { fromStep: number; output: string; toInput: string }[];
}
  • A Goal renders as a seed Notebook (UXS-015 §Notebook): path → ordered cards, narrative → the prose between them, wiresFrom → the "↳ result feeds below" reference. No new view primitive; the Notebook already is a lesson.
  • FormulaDef gains tier + prereqs too (per-formula), so the concept graph exists below the goal level. The visual skill-tree (PRD-033 A01.5: deferred) is a later consumer of this graph; v1 renders a Goal as a flat Notebook and ships no tree UI.
  • Adding a milestone = adding one Goal + any missing FormulaDefs — for the registry/formula work. Honest caveat (Fable-5 BLOCKER 2 / additivity verdict): this is NOT free of structural change at every rung. Additivity holds cleanly for M2–M3 once their figures are honest (A01.2a); it first strains at M4, whose porkchop card needs an interactive cell-selection-as-wire-source primitive no current contract expresses (freeze how a selection becomes a card output at S2, even if built at M4); and each milestone may carry a bounded new-physics budget (A01.2b). "M2 costs a fraction of M1" is the claim; since the MVP ships all milestones (A01.5), M2 is built for real right after M1 and is the first additivity checkpoint — a contract change there (vs just new FormulaDefs + figure kinds) means the architecture failed, caught after one milestone.

A01.2a · Provenance is a fidelity axis, not a binary (Fable-5 BLOCKER 1 · operator-approved) ​

RFC §4's "kernel-emitted specs are always computed" is too coarse and actively dangerous under a teaching mandate. Some kernel-classified geometry produces scene shapes, not integrated truth: cislunar-geometry.ts's translunarCoast/lunarOrbit build a two-body ellipse pinned behind the Moon for visual continuity ("inclination_deg is then ignored" to close the scene gap — no lunar gravity in the coast); interplanetary-geometry's Tier-2 mode replays authored waypoints. Stamping either computed · orrery-kernel tells a schoolkid "this is computed truth" when it is a cartoon — the PRD's exact make-or-break.

Decision (D11): FigureSpec.provenance.fidelity: 'computed' | 'geometric' | 'replayed-published'.computed = closed-form / integrated physics. geometric = analytic shape for continuity (cislunar Tier-1). replayed-published = interpolated authored data (Tier-2). Renderers MUST visually distinguish the three registers; geometric/replayed may never share the computed register. Paired with the reclassification: cislunar Tier-1 shape-generators move app-side (S1 manifest revised); the real patched-conic TLI/LOI compute (lambert-geocentric.ts, ADR-085, unit-tested vs Apollo bands) stays kernel and is what M2's honest figure derives from.

A01.2b · Per-milestone new-physics budget (Fable-5 BLOCKER 2) ​

The ladder needs bounded new astrodynamics sideways, not only the mechanics floor. Verified absent from the kernel today: SOI/patched-conic radius (M2), ignition-altitude / hover-thrust inverse (M3), gravity-assist v∞-rotation + Oberth (M6 — assists exist only as authored waypoints + labels), eclipse geometry (G8 — only Moon phases exist). Each is a small, named, closed-form formula (not N-body), budgeted per milestone and pulled demand-driven (A01.3). This is the honest scope, and it must read that way in the PRD.

A01.3 · Full-coverage requirement on the registry ​

Bidirectional coverage — reconciled with "MVP = the whole ladder" (Fable-5 MAJOR 3 + round-2 NEW-1). Two rules, both hard, that together dissolve the round-1 contradiction (export-vs-FormulaDef granularity, a taxonomy shipping formulas no goal uses) without letting demand-driven quietly drop built capabilities:

  • Coverage direction (built → goal), the hard MVP requirement. Because the MVP ships every goal (A01.5), every computable kernel capability must be pulled by some goal — including the ones the first draft's ladder missed: gas-giant-probe and micro-g small-body descent (DescentBody has jupiter, comet_67p, ryugu/bennu/eros/itokawa). Full coverage is the definition of done, so where a capability has no goal, we author the goal, not drop the capability. The export→FormulaDef→goal audit is a real S2 deliverable.
  • Demand direction (goal → formula), the anti-speculation rule. No FormulaDef exists unless a goal's path needs it — nothing speculative. This is what keeps the classical taxonomy bounded: friction, collisions/restitution, rotational, fluids, thermo stay OUT until a goal pulls them. The cut falls wherever no goal reaches — and since coverage is bidirectional, "no goal reaches it" now means "it's genuinely not a built capability."
  • Ledger granularity = FormulaDef, not raw export (infra like pegSolve, commandedPitchRad, TLE-parse internals are not formulas). CI gate: every registered FormulaDef reachable from ≥1 Goal, AND every kernel capability on the S2 coverage manifest has a goal. Both directions enforced; neither perverse, because the MVP genuinely ships all.

The seed Goal set: Family A (M1–M6 spine + M-return + "land on any world" incl. small-body/ gas-giant) · Family B (G7–G10 observe/orbit) · cross-cutting (engines, scales-time). Editorial /science domains are excluded by construction (not computable → no FormulaDef).

A01.4 · FigureSpec additions (§4) ​

Mechanics needs a few FigureSpec kinds; most already exist or are minor: projectile/ trajectory arc (reuse transfer-ellipse's arc renderer), v–t / a–t curve (curve, already defined), general free-body diagram (force-diagram, already defined — generalize body:'rocket' to any labelled mass), energy bar/waterfall (reuse dv-waterfall styling). Net new renderer work is small.

D12 — assumptions: string[] on FormulaResult + FigureSpec (Fable-5 MAJOR 7). Provenance labels who produced a figure; it does not label what the model ignores — and for a first-principles teaching audience, the model assumptions ARE the honesty payload. A bare "computed" caption on a two-body coast / no-drag projectile / non-rotating frame stamps institutional authority on a simplification without naming it. Every formula declares its assumptions ("two-body only", "no atmospheric drag", "non-rotating frame", "point mass"), rendered as a margin note. One field now vs a public-contract break later — and it converts every simplification into a first-principles teaching moment, which is the brand. (Credit: the kernel is better than assumed — ascent-physics.ts:677 already models Earth's rotation contribution, 465.1·cos(lat) m/s.)

A01.5 · Slice impact — re-slicing pending (do NOT reorder yet) ​

This is a scope delta to be folded in at the re-slicing step (after adversarial review), not a live reorder. Provisional shape:

  • S1 — unchanged (except the interplanetary-geometry.ts split at S1.2 — S1 manifest).
  • S2 — must SPLIT; it is now a mega-slice everything hangs off (Fable-5 round-3 problem 3). Post-amendment S2 = registry + FigureSpec + renderers + mechanics/ domain + Goal model + tier/prereqs + coverage-ledger CI + D10 constants home + freeze the M4 cell-selection contract + freeze the D13 result shape. Provisional split: S2a contracts + registry + FigureSpec/renderers (incl. the fidelity axis + assumptions); S2b mechanics/ domain (demand-driven, M1 rungs first); S2c Goal model + tier/prereqs + bidirectional coverage CI. The re-slicing step confirms the cut; the point is the six-slice structure cannot hold S2 whole.
  • S2.5 (Workbench Coverage Suite) = the milestone scenarios: M1 first (build order), then every milestone (M2…M-return, G7–G10) a golden-master scenario (FigureSpec + values snapshot) — all in v0.9, since the MVP is the whole ladder (A01.5). Includes the register-distinction golden-master (NEW-2) proving computed/geometric/replayed render in visibly separate registers.
  • S3 — Notebook renders a Goal; M1 is the first goal built. The additivity checkpoint (Fable-5 MAJOR 1, A01.6/D-gate): M2 is authored + built for real immediately after M1; if it needs a contract change (not just new FormulaDefs + FigureSpec kinds), the architecture failed and we caught it after one milestone. (A dry-run of the M2 delta while finishing M1 is a cheap early read of the same signal — draft-state goals live in a fixtures dir, not the validated shipping registry, since GoalStep.formulaId must resolve — NEW/MAJOR-1 gap.)
  • S4–S6 — MCP + the NL "ask" (T4) resolves a human goal against the Goal registry.TLE freshness (Fable-5 MAJOR 6 / D13): any satellite (G9) result carries epochAgeDays
    • a staleness bound in the result contract and the MCP tool description — a months-stale baked station-tles.json yields wrong ISS passes stamped "computed" to a learner told to go look up. The app-side adapter injects fresh TLEs where available; freeze the result shape at S2.

Re-slicing note (operator 2026-08-29): the MVP is the entire ladder + MCP, so re-slicing must size ALL of Family A + Family B + cross-cutting, not defer any as roadmap. This is a materially larger v0.9 than the original six-slice RFC scoped; the honest sizing lands at the re-slicing step.

A01.6 · Decisions (this amendment) ​

  • D6 — physics/mechanics/ subdomain → approved; greenfield, S2, purity-gated like the rest.
  • D7 — Goal/Milestone as a first-class registry entity (Contract 5) → approved; GoalStep. wiresFrom is an array mirroring Contract 3 (Fable-5 MAJOR 2). Freeze the shape before S2.
  • D8 — full-coverage CI ledger → approved, demand-driven + FormulaDef-granular (Fable-5 MAJOR 3): no formula without a pulling Goal; CI at FormulaDef level; export audit is a one-time S2 task.
  • D9 — FormulaDef.tier/prereqs concept graph → approved; v1 renders flat Notebooks, the skill-tree UI is a deferred consumer.
  • D10 — one per-body constants home → approved (A01.1); mechanics/ adds no 4th μ table.
  • D11 — provenance fidelity axis computed | geometric | replayed-published → approved (A01.2a); cislunar Tier-1 shape-gens reclassified app-side; renderers keep the registers distinct.
  • D12 — assumptions: string[] on results/figures → approved (A01.4).
  • D13 — TLE freshness in the result contract → approved (A01.5).
  • D-gate — M2 real-build additivity checkpoint at S3 (A01.5); a contract change in the M2 delta fails the MVP. (A dry-run while finishing M1 is a cheap early read.)
  • Open (for re-slicing): whether Family B ships before/after Family A's mid-milestones; the exact numeric threshold for "M2 costs a fraction" (delta-ledger size).

A01.7 · Access control + the T4 LLM proxy (operator, 2026-08-29 — "all of it," gated beta) ​

Scope on the tier axis is now settled: v0.9 ships all tiers T0–T4 + the MCP with OAuth — no deferral (supersedes PRD §Non-goals' "not v1" items + RFC §12's Canvas/T3/T4 exclusions, reconciled below). But both public-facing doors run as a controlled beta gated to one allowlist (~6 accounts: operator + a handful of testers), to learn before opening up.

Two doors, one guest list:

  • Door 2 — MCP (mcp.orrerylearn.com). Full tool surface, so an external agent (Claude.ai) does the NL/interpret/render loop with its own model; Orrery just serves the physics tools. D3 UPDATED: v0.9 auth = OAuth 2.1 with Google/Gmail as the identity provider, gated by an email allowlist — NOT the bearer-token beta the original D3 planned. Bearer degrades to an internal dev convenience. Verify the MCP-OAuth-2.1 + Google-IdP + allowlist pattern against the CURRENT MCP auth spec before S4 — this is still the highest-uncertainty corner (§6).
  • Door 1 — the in-Lab NL ask-box (T4), beta. Same allowlist, not public. Correction (Fable-5 round 4 B1): the app is adapter-static (no server runtime), so this CANNOT be a SvelteKit +server.ts. The ask-box's LLM steps (understand → select formulas → narrate; the kernel still computes — honesty line intact) run in a separate lab-api container on the VPS (Caddy vhost, RFC-024/035 pattern; operator-decided 2026-08-29) that also hosts the shared identity/allowlist — see A01.8. From there, calls go through the existing LiteLLM proxy on the prod VPS, with two swappable backends:
    1. LiteLLM → OpenRouter (new dedicated key) — always-available path.
    2. LiteLLM → a model stood up on the DGX — local path; depends on prod-VPS→DGX reachability (tailnet) + the DGX's shared-GPU coordination (gpu-mode), so it competes with other vLLM usage. OpenRouter is the fallback when the GPU's busy.

D14 (new) — T4 in-Lab LLM routing → approved: routed via the lab-api container → existing prod-VPS LiteLLM, backends {OpenRouter key | DGX model}. No +server.ts (adapter-static). D15 (new) — access = a single allowlist gating both doors; eventual direction is full public OAuth (Gmail), rethought after the beta.

A01.8 · Identity/auth architecture (Fable-5 round 4 · operator-decided 2026-08-29) ​

The app is adapter-static (svelte.config.js) — no server runtime, no login anywhere. Both public doors need identity the static app can't provide. Decision: a separate lab-api container on the VPS (Caddy vhost, reusing the RFC-024/035 subdomain-container pattern — same class as the mcp. server, no app-adapter change).

  • D16 — one identity layer, two doors. Door-1 sign-in and Door-2 MCP OAuth are the same question ("is this Google account on the allowlist?"). lab-api hosts it once: Google sign-in + session + the shared allowlist + the Door-1 ask-box endpoint; the MCP resource server consumes the same identity/allowlist. Build once, not twice.
  • D17 — auth spike FIRST. MCP OAuth 2.1 expects dynamic client registration, which Google's IdP does not offer — so an authorization-server / proxy layer federating to Google is likely required (in lab-api). This is the highest-uncertainty, highest-slip item; a scoped INFRA-auth-spike runs before Phase 1b/4 and its outcome shapes both doors. Verify against the current MCP auth spec revision before building.

Slice-plan consequences (auth infra started early, S3 owns the recompute core, Phase 2 is ~16 enumerated goal slices, S6/T4 depend on Phase-2 completion) live in docs/wip/_archive/2026-08-29-v0.9-flagship-slice-plan.md v2.

Orrery — architecture documentation · MIT · No tracking