Archive — history, not state. Kept for its reasoning and its evidence; its plan is closed.

Edicts

Standing rules for this work, from John. Every subagent brief points here. When code and this file disagree, this file wins — and when in doubt, the simpler reading is the correct one. This is a simple system that grew out of hand; the job is to put it back.


The system, in full

Read this before deciding anything. If a change makes the description below longer, it is probably wrong.

  • Programs import modules; modules import other modules.
  • Programs and modules use endowments. An endowment is a name the shop may deny.
  • An executable is a list of things to run in order to start an actor — endowments and modules and such.
  • There is machinery to branch module use on the target platform. Use it; do not invent a second mechanism.
  • mcode source files are lowered into single mach images that share a stone pool of constant shapes, text, bytecode, and numbers.
  • Those are objects, just like any other object on an actor’s heap.

That is the whole runtime. Complexity beyond it needs a reason you can state in a sentence.


1. Delete, do not fall back

We do not need compatibility. Once the new way boots, the old way goes. This is not yet version 1; nothing outside this repo uses it; this is all of the code that exists.

So: no compatibility arms, no transition readers, no legacy opcodes kept alive for artifacts we can simply regenerate, no kind tags whose only job is to tell old from new. When you find a fallback, the default action is delete it, not preserve it.

This overrides the instinct to be careful with existing behaviour. Being careful means the suites stay green, not that old shapes survive.

Known instances to delete rather than respect (verify each, then remove):

  • MACH_RESERVED_OP0/OP1 (slots 43/44) — “legacy boot compatibility: former delete-by-constant-key opcode”. They exist for committed artifacts we reseed constantly.
  • The DYNAMIC record-shape kind — a tag distinguishing legacy descriptors from image ones. A shape is a stone object; losing a shape is losing the pointer.
  • The transitional pmac reader, once the pool reader has callers.
  • Any transition_* / legacy_* helper.

2. As much in Pit as possible

C is the audit surface; Pit is safe by construction, because something bad can only happen where something holds a powerful endowment. Minimal C is a prime edict.

If you see an opportunity to remove C, do not take it silently and do not skip it — say so. Report the work item; John shapes it; it gets its own agent.

3. Do not port anything from 9466791a without checking it

That 47k-line commit is directionally right in places and against these rules in others. It also never built — it added a translation unit without regenerating the build manifests, and uses assert() without including <assert.h>. Treat every line as unverified.

Specifically: do not carry over its defects “faithfully”, and do not carry over its compatibility arms at all. If a ported block contains a fallback, drop the fallback.

4. Follow the docs; ask when the work is not scoped by them

docs/ is the target description and is normative. John’s calibration: it is about 80% right, and it should simply be followed. Before deciding, read the page that covers it, then build what it says. You are implementing, not auditing.

Ask when you start on work the docs do not scope, or do not make clear enough that two readings would build the same thing. That is the trigger — not “I noticed something odd.” Those questions are worth interrupting for because they are rarely local: a doc is usually silent about something because it touches another part of the system, and that is where the design actually moves.

A blocked agent that asks costs minutes; a guess costs a rewrite. But an agent that queues up observations instead of working costs the whole session.

When a ruling comes back, the doc is made explicit first and the code aligns to it. That is part of the work item, not follow-up. Code that implements a ruling nobody wrote down has already started to rot.

5. Files carry their own names; there is no table mapping files to keys

A module’s name is where it is: <package>/<file>. An endowment’s name is its filename in a package the shop draws endowments from — <package>/metal.c provides $metal. A shop names the packages that may provide endowments; the packages name themselves by their contents. Listing the directory is reading the list.

So a hand-maintained manifest mapping file paths to module keys should not exist. The root package.json’s natives array is exactly that and must go: 26 rows of {"file": …, "key": …}, including {"file": "internal/vm.c", "key": "endowment/vm"} — which means $vm’s name is a table entry today rather than a filename.

Two things fall out with it:

  • Its per-target arms ({"file": {"emscripten": false, "*": "internal/sysinfo.c"}}) are a second target-branching mechanism, which the system description above forbids. They also become unnecessary: a target has $memfs precisely because memfs.c is in that platform’s package and nowhere else. Selection needs no arms.
  • The reason the table exists — core’s C lives in internal/ and platform/posix/, named for the repo’s layout rather than for the package owning the surface. The table is the adapter between those two namings. Move the files and it has nothing left to say.

The unit of “the system can pull this in when requested” is the package. Adding hardware support is publishing a package and a shop naming it, never editing a build table.

The shop’s three lists, and internal/

John, 2026-07-26, completing this edict. The manifests go away entirely and what replaces them is not another manifest — it is three sets of package names the shop holds:

listsays
endowment packagesthese packages provide endowments
packages allowed to make C modulesfor now, all of them
use fallback packagesan unqualified use('time') looks here

A package’s internal/ directory is package-private, and the import written inside that package is the bare, package-relative use('internal/time'). That makes privacy structural: you cannot name another package’s internal at all, because the name is always your own package’s. This replaces native/, which was importable from anywhere and so private in name only — anybody could use('pitlib/native/time').

The whole chain, with no table anywhere in it:

pitlib/internal/time.c   provides the native functionality
pitlib/time.cm           use('internal/time')   — allowed: pitlib may make C modules
                                                — findable: it is right there
use('time')              → pitlib, because pitlib is a use-fallback package

Whether that C arrives as a dynamic library or linked into the executable is the shop’s business and changes nothing above.

A module that only wraps a C file should not exist. pitlib/json.cm is three lines — use('core/json') and return it. Delete it, put json.c at the package’s top level, and let use('json') reach it directly. internal/ is for C that a real module wraps; today that is time alone.

The test for all of it: look at any file, see what it imports, find that thing immediately.

shoplib — reachable, but never a fallback

John, 2026-07-26. The files that are neither the runtime’s nor a platform’s — nota, wota, qop, kim, deflate, crypto, eddsa, mach, shop_paths, sysfacts, bootgate, wildstar, libgit2, qopfs — go into one new package, shoplib. Split it later if it earns it.

The shop uses it, and packages the shop uses to get work done may use it. It is not on the use fallback list, so nobody’s code picks it up by writing use('deflate'). That is not a danger claim — a game may legitimately use a shoplib module. It just has to say where it is getting it from, with the full locator.

So the fallback list is not a permission boundary; it is the answer to “what does an unqualified name mean.” Reachability and defaulting are different questions, and this is what keeps them apart.

The resolution model — ruled in full, 2026-07-26

This subsumes the partial statements above. It is the target; the docs restate it and the code aligns to it.

  • A locator is <package>::<path>. :: marks the package boundary — it is load-bearing, not punctuation, because the internal/ rule below cannot be stated without an explicit boundary. A name without :: is a path, searched through one chain: the caller’s own package → the caller’s package aliases → the shop’s aliases → the fallback packages, in list order.
  • One chain for use, $start, and the command line. The command line is not special; the shell’s cwd supplies the starting package, exactly as the importing file’s package does for use. After the chain misses, a start request additionally: walks the bin packages (shop_tools first) as <bin pkg>::<path>; then tries the whole text as a full locator, fetching if needed; then, finally, treats the text as a script, owned by the calling package. That script fallback works from the command line too.
  • The clerk consults four lists of package names: endowments, c_native (allowed to compile C — for now, everyone), fallback (unqualified use), bin (the actor PATH). Exactly like bin paths in an environment or lib paths in a compiler, applied to use, endowments, and starting actors.
  • Endowment packages: only top-level files become endowments; a .c endowment additionally requires the package on the C list; and an endowment package cannot be used at all — the shop blocks it.
  • internal/ is package-private: bare use('internal/x') in-package works; <package>::internal/x from outside is refused.
  • A package manifest is: aliases (the field once called dependencies), modules (target arms), compilation (C flags). Nothing else. name is dead (a package’s name is its path), natives is dead, and members dies the moment the build discovers packages by walking folders for package.json. The repo root stops being a package and becomes a collection of packages, each eventually its own repository.
  • engine is its own package: one unit, imports nothing, compiled into the cart, found by the cart’s fixed header — on no list, resolved by no chain.
  • cake, target-final: given a list of actors, compute the closure of files, compile the C the lists permit, and link it — statically or as dylibs — naming symbols from one authority. No mapping manifests anywhere in the system.

The package-identity invariant (John, 2026-07-26). Deriving the package from a locator is a semantic step, never a text rewrite: every resolution lands on a package identity first, and candidates carry it. This is what makes versioning enforceable later — one executable cannot contain the same file from the same package at two different versions; the closure builder must decide or refuse. That enforcement is phase C’s (the executable manifest), but nothing built now may re-parse locator text to guess a unit’s origin, or the collision becomes undetectable. Aliases are the version knob, and they fold into the realization key.

A target is a recipe — targets are not coded into cake

John, 2026-07-26. Nothing in cake, the shop, or anywhere else may take a target name to mean “these things.” Building is pulling levers: these packages available as endowments, these embedded whole, these actors in the cart, this garbage collector, this value representation, these fallback and bin lists. A target is a recipe — a named bundle of lever settings — and the name is something the recipe declares, never something cake interprets. “I want to compile for ios” must never reach code that knows ios means darwin_file.

The one sanctioned place a target name appears outside a recipe: a package’s own module arms ("file": {darwin: "internal/file_darwin", …}) — the package declaring how it varies, matched against the name the recipe supplies. The package speaks; cake only carries.

The build products and the recipe — ruled 2026-07-26, “everything hangs off this”

John confirmed the full articulation (conversation record, “The system as I understand it” + “The build products”); future phases hang off it. The load-bearing pieces:

  • Three products: the runtime (the C binary), the cart, the bundle. The cart and bundle are described by an actor list — the shop computes the closure. The runtime is described by a runtime recipe = a platform package + the free levers + the package lists. The platform package declares its requirements (startup file, scheduler impl, paging facts — package data, not cake knowledge) and its constraints (levers it forbids); the recipe picks what is genuinely free; the shop validates.
  • Levers split into two groups that never mix: stamp-entering (rep, text, GC-ABI, endian, wota width — they change interpretability) and non-stamp (budgets, scheduler arrangement, linked packages, endowment lists and whole, capability levers, log stripping). A target is a named recipe file, committed, ordinary; inventing a target is writing a file.
  • Tool symmetry: pit source has a compiler and an assembler (cement); C source has a compiler (cake — one file in, one object out, flags from the package) and an assembler (the runtime builder — NEW, replaces meson: reads the recipe, derives the TU list, drives cake, links, renders the static table from the symbol authority).
  • The two builds meet at the extension spec: the cart build emits, from its closure, the exact set of C modules and endowment providers its actors can reach — (package, file, full symbol, facts) rows. A ship runtime links exactly that spec (claims-trimmed); a dev runtime links from the whole-lists with no spec. Bootstrap order: dev runtime (lists) → cart (closure) → ship runtime (spec) → console image. The CART3 -Dextensions_spec lane is the half-built precedent.
  • c_native, final form (clarifying the earlier “for now, everyone”): it is a REAL gate — C compiles only from blessed packages. The dev shop blessing everyone is the dev shop’s stance, exactly like internal/ being unguarded during the move: permissive while building, structural once built. The implementation should eventually make dev blessing explicit rather than empty-means-all, so absence is never permission.
  • Platform package names are flat and bare: posix, windows, apple, web, playdate — they appear only in recipes, never in use().

Three refinements, ruled 2026-07-27 after landing 1 of the recipe arc:

  • The recipe describes the runtime, and only the runtime. Which payload form a cart or bundle ships, and which service actors ride along, are the cart/bundle build’s inputs — chosen per build by whoever composes it. A playdate cart carrying a minimal compiler plus game source, compiling on-device at first boot, is composed with the same shop tools and no code change. payload and app_fleet are therefore not recipe fields, and no per-target payload table exists anywhere.

    Refined 2026-07-27: composition parameters are ARGUMENTS, not a schema. John: these “look like command line args… it would be preferable to do that with the agility of a makefile.” A builds/<name>.json description file briefly existed and is deleted — it was a parameter file for a single verb. pit bundle takes --payload and --actors directly; the committed defaults are the makefiles and scripts that invoke it, which are exactly as committed as a schema file and infinitely more agile. The line that earns a FILE is: data several tools must agree on, that keys artifacts (recipes). One invocation’s parameters never qualify — everything they decide is already recorded in the product’s own manifest.

  • There are no permission lists — the toolchain is the gate. A platform package knows names and build data: its entry file, its providers. It does not say what is possible. Compiling for the target with the target’s toolchain answers that: pulling macos_endowments into a playdate build fails because the headers do not exist, and that failure is the signal — no list to maintain, no list to drift. The same holds for levers: nan64 doubles on the ps1 is expressible, and the toolchain plus the frame rate are the answer. Committed recipes are suggestions — worked positions that suit a machine — never permissions. This is “presence means compiles here” applied to levers: one principle, not two. (constraints blocks in platform manifests are deleted; the loader validates schema shape, not possibility.)

  • Recipe format ratified: JSON — data validated before anything acts on it, read by the same pair that reads package.json. sched stays in the stamp for now because PIT_PROFILE_STRING carries it; whether it should is a checkable open question (no byte’s meaning is known to change with it), and evicting it later is one routine reseed.

$image — ruled 2026-07-27 (“brilliant”)

The endowment over the image provider: open a mach pool by content hash, validate, pin its placement, bind (entries + cross-image imports), resolve entry functions, unpin/close. Held by boot. C keeps the mechanics — mmap, validation, the pin table, everything that touches address space; Pit holds all policy. The runtime pins the boot cart’s blob at cold boot (on XIP targets it is resident by construction); every later image is brought in from Pit by boot/the clerk following realization rows — the realization, built by the shop at build time, is the “list of modules + entrypoints”; $image is how the runner makes the plan real, never how the plan is made. A pool’s bytes are ordinary store content anyone may read; what $image gates is executable placement.

Consequences ruled with it:

  • Eviction is the absence of traced references. Closures trace their binding, bindings retain their pin; an image is evictable exactly when the last actor holding anything from it dies. No stop-the-world, no quiescence — pins are refcounts over immutable pools.
  • Bank switching is dispatcher mechanics, not a boot turn. By switch time there is no decision left (cement’s placement rows / boot’s mount already decided), so the scheduler writes the bank register as part of turn dispatch — and mid-turn cross-image calls make a boot-turn design impossible anyway. Boot gets a turn only for genuine decisions (mount, fetch, evict), arriving as ordinary messages. One-actor-per-bank via deliberate placement-row duplication is the intended pattern on banked targets.
  • An executable stays nebulous; a realization freezes one covering. The manifest names modules with zero blob knowledge; which blobs cover them is chosen per realization (dev fine-grained for hot reload, ship fused), and grouping is a LINKING choice (fusing turns import edges into direct indices), which is why the covering is picked ahead of time and starting is following rows.

The pool emitter is a pure linker — ruled 2026-07-27

One fix answers both open questions (instruction provenance and stone-header authority): C lowers, Pit links. CORRECTED same day after take-3’s reconnaissance proved the original premise false: C’s per-unit blob carried tagged UTF-8 C-strings materialized onto the heap at load — the exact “per-load materialized constant tree” the pool spec forbids — so “C already produced target layout” was untrue. The design stands; the C lowering GROWS to make it true (~200 lines, said out loud per edict 2): the per-unit lowering emits target-final constants — STONE object bytes (objhdr + fash64 + packed UTF-32) for text and shape keys, 8-byte target immediate words for numbers — plus a sidecar carrying the only two things the linker cannot know: shape-operand relocation positions (VALUES and FUNCTION_REFS are function-relative, so constant operands never need rewriting — proven; only pool-global shape indices do) and the mach-pc → mcode instruction index provenance column the origin map needs (the lowering already stamps line/col per pc; this rides beside it). Object-layout authority stays in mach.c — the alternative (a shoplib/stone.c layout accessor) would be the second authority the ruling exists to prevent.

mach_pool_emit.cm is then a pure linker: lays out sections, copies stone objects verbatim and dedups them by bytes, gives each function a contiguous VALUES range in its own cpool order, recomputes the documented self-relative displacements, patches only the shape operands, and emits IMPORT rows. Pit never learns an opcode and never constructs an object header; what it writes is only the public byte contract the spec documents.

Two sequencing rulings made with it (orchestrator, under standing rules): the import op does not exist anywhere yetuse() is resolved by static linking at compile time, no compiler emits ["import", …], and the two module-result opcodes have readers but no producer — so IMPORTS emission lands spec-conformant, proven against synthetic fragments, and the import lane’s production wiring is the executable-manifest landing’s first customer. And the pool oracle fixture is one host-profile fixture regenerated by a make target like the boot artifacts, its C sub-test compiled only when the ABI/rep/text axes match (self-skip precedent: check-playdate); the hand-built cases keep every other arm green.

fash64 in Pit — ruled 2026-07-27: required minimal C

The pool emitter must write the stone-text/shape hashes its output carries; fash64 needs exact 64×64 multiply, which Pit numbers (doubles) and 56-bit fit cannot do. John: “that is part of the minimal C; simply required to be in C” — a small accessor beside crypto/blake2, the established hash-in-C lane. fash64 stays the stone-text hash (it is the record-lookup hot path’s choice); the mach ABI’s stone.text axis already lets a future target declare a different hash.

Number spelling — ratified “fine”, REVISIT flag (John, 2026-07-27)

The shortest-decimal-that-number()-recovers rule stands ratified. John wants to explore the canonicality question further and consult again later; surface it when he asks, never relitigate it in an agent brief meanwhile.

The sync file surface is GONE; $platform does not exist

John, 2026-07-26. The 10 bootstrap_* functions are deleted, not migrated to a smaller surface: the shop store, bundles and source resolution move to file; if the logger wants sync writes it waits for the planned c-lib endowment family ($stdio etc.) rather than keeping a private lane. And there is no $platform — the logic was in the wrong place. System facts (memory, cores) are std::sysinfo, the std::file pattern exactly: one interface, per-platform arms over per-platform endowments. That decides internal/sysinfo.c’s fate — it dissolves into per-platform endowment files + the arms — and with it the last cross-package internal/ import other than os.

Endowment presence means “compiles here” — absence honesty is runtime’s job

John, 2026-07-26, revising the package-split idea (one package per missing capability would explode). posix_spawn is present on ios: it compiles, it is a thin wrapper, and it returns -1 exactly as the OS does. The posix endowment package is anything that compiles with posix; windows does not list it because it does not compile there — and an msys2-flavored recipe legitimately could. So the package boundary is the compilation boundary; what a call does on a given OS is the OS’s answer, passed through honestly. No per-file subsetting, no capability packages. Preference (darwin_file over posix_file on ios) is a module arm or the programmer’s own choice — an ios-specific program using $posix_file directly is fine.

Noted for later: c_endowments — one endowment package per C standard header (stdio.h, …), giving a third choice beside the posix and platform families.

Three follow-on rulings, 2026-07-26:

  • lane and lease are macros in the C filePIT_USE_LANE(main) beside PIT_USE_PROBE, scanned by cake at manifest time exactly as probes already are, rendered into the manifest as data. Verified before ruling: the fact already travels as manifest field 5 (endowment_appkit_window|main|…); only where cake reads it from changes. The logic “this C shim needs pinning to a thread” lives in the C that needs it.
  • endowments_whole is a second list, companion to endowments — a disposition of the package, holding package names like every other list.
  • The script fallback needs no flag. An argument that is locator-shaped (identifiers, /, ::, dots, dashes — no whitespace, parens, operators, quotes) is resolution-only, and a miss reports the full chain; only non-locator-shaped text runs as a script, owned by the calling package. There is no -e — it was already removed from the CLI — and no special path: pit <arg> passes straight to start. The escape hatch for a deliberately locator-shaped script is making it not locator-shaped: pit '(blob)'.

Move first, guard second

internal/ is not actually guarded today, and that is fine. Move the files to their right packages first, then activate the guard. Enforcing privacy before the move makes 88 imports unnameable and turns a file move into a design argument about what to re-export; after the move those same imports are ordinary qualified names like use('shoplib/crypto') and there is nothing left to argue about. Do not enforce a rule whose only effect is to block the work that makes it satisfiable.

One source/ per package

src/ and source/ become one folder. The rule is simple on purpose: if a package compiles any C module, it is compiled with everything in that package’s source/. No per-file support lists, no distinction between a module and its helpers — the folder is the unit.

os is decomposed, not ported

os was one C module every platform had to provide, and nobody could say what it should contain — the six copy-pasted if (tc.system == …) branches in cake/plan.cm are that failure written down. It goes. Its 28 functions are at least six unrelated surfaces, and each becomes a raw per-platform endowment named for what it actually is: $dlfcn for dynamic linking on posix, whatever Windows calls its own, and so on for the rest.

wgpu becomes a package. That also retires the one row discovery could not reach.

Errors: log and disrupt — the log surfaces, values do not carry errors

John, 2026-07-27, refusing the return-the-error-text proposal (c2): “The pattern that has been established is log and disrupt. We don’t really return errors, except through callbacks perhaps. What needs to happen is the log that the C writes to needs to surface instead of die in a log file.” So: C raises precise text on its error channel and disrupts; the fix for invisible errors is DELIVERY — the failing path’s recent error-channel lines reach the user with the failure — never a new error-return shape. (Queued work: the compile/start failure paths surface their error-channel lines to the CLI client; the logger’s ring replay is the existing mechanism to extend.)

$runtime holds suspend/resume; the hook is a ring — ratified 2026-07-27

suspend_actor/resume_actor live on $runtime beside stop_actor. Confirmed mechanics: NO new actor state — an atomic flag on the context plus the existing VM pause flag and two scheduler gates; a frozen actor is an ordinary actor that is currently idle. Killing beats pausing (John): halt clears the freeze, death notices thaw, $stop always lands — a freeze holds ordinary work only.

$hook is the call/return event stream: the VM announces every call and return, and the installer is a fixed C recorder into a bounded ring the holder drains on its OWN turns. Counting (how many times a function fired), timing (return minus call — the recorder stamps times in C, which is exactly why fixed-C beats a callback), tracing, and breakpoints are all DERIVED by the drainer from that one stream. Its sibling $pgo is the cheap aggregate flavor — always-counting site counters baked into instrumented images, no per-event record. A user Pit callback inside the dispatch loop stays refused: GC hazard plus write-power-as-observation.

Ratified with it (John, “that sounds fine to do”): wake-on-site is a LETTER, not a callbackinstall may carry a match set, and when a matched site fires the recorder nudges the HOLDER’s mailbox with ordinary actor mail, so pinpoint breakpoints lose the drain-interval latency while the debuggee still never runs foreign code. The nudge must respect the recorder’s constraints (no allocation in the debuggee’s heap, no blocking in the raw-frame window — deferring the enqueue to turn end is acceptable and invisible).

Static over-application is a COMPILE refusal — ruled 2026-07-27 (“keep the compile refusal”). A call the compiler can prove over-applies is refused at compile time with the location; the runtime arity disrupt remains for what the compiler cannot prove.

c1 (hoist the C’s scratch estimate into the first slot check) is ACCEPTED as a stopgap — John: “we’re going to be changing how this works altogether; fine to eliminate this bug for now.”

A resolved provide beats a bootstrap default (ruled 2026-07-28, FLAGGED for John)

A provides row (a resolved unit’s result published under a name) is AUTHORITATIVE over a C-boot-injected bootstrap default — start_plan.c’s provides application becomes overwrite, not set-if-null. Derived, not chosen: a resolved binding is the answer, a bootstrap default is scaffolding for the pre-module world; and it PRESERVES base behavior — log already had overwrite semantics at base via its BIND_FLAG_OVERWRITE binding row, and moving it to a provides row is what dropped the flag. Provably scoped: overwrite differs from set-if-null only for a name C boot pre-injects (today only log); for every other name the slot is null so the two coincide. Double-provides of one name is already forbidden by one-selection-per-package, so no first-wins-vs-last-wins behavior is lost.

DEEPER QUESTION ESCALATED TO JOHN (does NOT block the landing — (A) works under either answer): is log AMBIENT or CLAIMED? Under (A) a claim-less actor still gets the C-boot log, because the default remains for non-claimers. If log is ambient (the language hands every actor log the way it hands text()), that C pre-injection is correct and the module merely upgrades it. If log is claimed (only actors that reference it get it, per unused-means-absent), the pre-injection is eventually WRONG and should be removed — with (A) as the bridge that keeps the tree green until then. This is the crux under the (A)/(B) fork and it is genuinely John’s; (A) is the safe move regardless.

log is CLAIMED — ruled by John 2026-07-28

“log is not an endowment, but is also claimed, as we want the builds to be as thin as possible; if an actor did not use log, or the log statements are stripped out of it, the log function should never be instantiated on the actor’s heap at all.” So: log is a claimed GLOBAL (bare name, not deniable, but present only when referenced). Consequences: C boot’s pre-injection of log (pit.c:180) is a BRIDGE, not the design — it gets DELETED once the module lane fully lands and every log user claims it (a 4a-continued follow-up); the provides-overwrite ruling is what keeps the tree green until then. Log-stripping a build must also strip the claim, so a stripped actor carries no logging unit at all.

Also ruled same message: the keystone’s RESIDUAL FLOOR TABLE is blessed IF MINIMAL — after all families move, the ~4 engine-behavior names ($trace, $couple/$stop, $log_enabled, $letters) may remain as a small floor list; 4b deletes the membership LOGIC and keeps only that. And the standing flags (provides-overwrite, log-gate-in-floor, bare-names, pit.realize.entry, two-phase crossing) are RATIFIED.

The log gate is the floor’s because the sink is (ruled 2026-07-28, FLAGGED for John)

Deriving the log-control seam for landing 4a-continued, not choosing it: log-control must work on an actor that claimed nothing. pit log <channel> is a SYSTEM letter precisely so it reconfigures emission on any actor — including one not listening for user mail, which is exactly what “unused means absent” produces. So log-control cannot become a user letter (it would be undeliverable to a claim-less actor), and it cannot live entirely in the logging module (a minimal actor never loaded it, yet its floor $log_sink still emits).

Therefore the split is by concern, not by line count: the FLOOR owns the log-SELECTOR state and the tiny sysym handler that sets it — the on/off gate for $log_sink, which the floor’s own error/disrupt emission already consults regardless of claims. The MODULE (lang/logging.cm) owns rich formatting, channel routing, and the logger-actor path. A claim-less actor is still log-controllable because the floor always answers the sysym; a claiming actor gets the rich surface on top. The wire protocol is untouched (senders keep sending sysym). This is neither the agent’s option A (floor extension point — a claim-less actor couldn’t answer) nor pure B (which mis-framed the gate as movable module state); it is the derivation the “claim-less actor” invariant forces.

THE LOGGING MODEL + the inject-row trims — ruled by John 2026-07-28

Logging, target-final (John: “commit this to memory… easier than what I think is going on today”): a system has a logger actor, well known to the boot actor (the one which spawns all actors and receives $start). When boot starts an actor, if the new actor has a log statement in it, boot also sends along the logger actor’s token (which already exists); the log function the new actor binds simply forwards to that logger actor. “There is no ‘gate’ because if the actor has no log statements, it just doesn’t get the logger token nor the log function.” Consequences: $log_enabled is NOT target (“I’m not even sure about” it — “what the logging should be is totally removed”); the claim decision replaces the gate; per-channel filtering is the logger’s business, not a per-actor flag. This SUPERSEDES the target-finality of the “log gate is the floor’s” derivation above — that derivation stands only as the correct BRIDGE seam while the boot actor doesn’t exist yet. Known wart John wants cleaner: the brief startup window before the logger actor exists (today: the C boot sink). C code must be able to log too — that is what the boot sink is for (see finding below).

The floor layer is confirmed real and stays: all actors must $couple even when it is not exposed to the executable; all actors must have $stop listening for stop from their overling; and so on. The floor is conduct, not a passing list.

Inject-row trims (4b input — every survivor must be justified, not grandfathered). Most of the list existed “because we had engine_lite do too much work — it had to know the shop path and runtime path because it needed to do use itself,” and several names were never meant to be actor-exposed at all — they “hitchhiked that mechanism to get to engine_lite to use, and then it would kill it.” Per-name:

  • $overling — real, requestable, stays. $overling_id — DIES (existed so the engine could mint its overling’s token from an id itself; token minting is now properly only with the root creator that passes tokens out).
  • $self — stays. Bare self — “shouldn’t be there at all.” $self_id — DIES (same reasoning as $overling_id).
  • $fd — a REAL endowment that engine_lite shouldn’t need; not every platform has files. Out of the engine floor, ordinary endowment entry.
  • $root — “probably the same as $shop”; the engine only knew these to do $start. Merge or kill, verify first.
  • $shop_path / $runtime_path — never supposed to be actor-exposed; existed for the engine’s own use. Die from injection.
  • $mach_load / $mach_compile_mcode_bin — SUSPECT: “totally handled by the root now”; hitchhikers. Verify and kill. CONSTRAINT that survives them: engine_lite still needs the ABILITY to make C functions from C modules, because functions cannot be passed between actors — the capability stays engine-side even as the endowment names die.

Findings recorded with the ruling (established 2026-07-28, facts not rulings):

  • $actorsym IS: a stoned record minted once by C boot (pit.c:147-149) used as a hidden property key — actor[ACTORDATA] = desc brands a record as an actor token and hangs its system metadata (id, runtime_key, address, port, reply routing) off it; MACH_IS_ACTOR tests for the property. It is therefore TOKEN-FORGERY-ADJACENT in ordinary hands (brand any record an actor; read any token’s runtime key) — it must not remain a requestable inject row; its fate is floor-internal.
  • $log_sink IS: not a new thing — the floor’s existing boot_context.log (pit_engine_log, pit.c:85): the C-side proxy that routes log(channel, msg) through Pit_Log, which falls back to stderr before the logger exists and forwards to the engine’s log function after. It is exactly the “C must be able to log too” peculiarity plus the startup window, and it is boot-private, never an endowment.

An unresolved claim is a compile refusal; claims are ordinary fulfillment (ruled 2026-07-28)

John, on the 4a-continued blocker: “the shop understands how to fulfill requests. An executable has requested the ‘send’ ability… It knows that ‘send’ it fulfills with ::send.cm… in my head, send was just a module; it’s thus injected through the same mechanism as ‘use’ and the endowments. They are all, at the end of the day, unbound names in the program… A ‘cart-only’ shop can thus make them if it at least has the ability to fetch.” And: “the unresolved claim shouldn’t fall back.”

Consequences: (1) a claim the shop cannot fulfill is a compile refusal naming the missing unit — never a silent fallback to an engine/C answer (the fallback the landing found is a defect, deleted); (2) a hermetic shop (no packages, no fetch) failing to build a mail-using program is CORRECT behavior — tests provision the lang packages like any dependency; (3) bundles that want offline building ship the standard units as ordinary content (“values arrive as constants or realization units”) — no special cart obligation, no engine retreat.

The lever lives at shop CONSTRUCTION, ruled same thread: making a shop that CAN compile send/log “should be easy… a lever… probably the standard option, where the shop carts you ship, if they don’t have fetch, perhaps they know how to fulfill log, send, and other top level functions; plus an assortment of endowments… something like the bash script command line thing where you make it; it’s not this layer that it’s appropriate.” So: the resolution layer stays dumb (find-or-refuse); universe content is decided by a composition command whose package list is an ARGUMENT (makefile agility); its DEFAULT ships the toplevel functions + an endowment assortment — a bare shop is something you ask for. State 2026-07-28: mechanics exist (content dir = bundle@1 manifest

  • objects + catalog, pit qop archives it, publish computes closures); the one-command pit bundle shop-style verb does NOT — queued as a non-gating landing in bundle@2 territory.

Also directed same message: defects that cost agent time are NOT chipped — fix them directly in-lane and note them in chat. Applied immediately to the two the landing found: the realization key must fold which endowment-fulfilling files exist (third key-missing-an-input instance this arc, after the toolchain stamp and the canonical salt), and make nuke must actually escape (it left .pit/objects and .pit/state/realize-index.json, and the stale realize index was the real wedge).

“I actually question the necessity of this at all… before we used symlinks; we would copy entire git repos into packages in the shop… But now, we’re package first. What does it even mean to link a file inside a package? Aren’t all of our files content hashed at this point?… The OS file capabilities should include symlinking, under the idea that the endowments that are grantable are just thin wrappers over OS capabilities; but I wonder if the shop we’ve been working on needs to utilize them at all.”

Established facts that make this cheap (verified 2026-07-28): Pit has NO symlink(2) binding at all — a symlink cannot be created from Pit. The only symlink C creates is the pit.sock compat link (host.c:333). No shop lane creates one. pit shop link is a JSON pointer map, not a link. Git tracks zero symlinks. The ONLY symlinks in the dev flow are the Makefile’s ln -s $(CURDIR) <shop>/runtime — the dev-loop convenience link.

The rule: shop-side traversal skips symlinks below the root, uniformlyrm unlinks them (landed), qop skips with a counted warning (landed), and walk/globfs/ enumerate do the same (queued). The root itself may be a link, because naming a path is choosing to follow it — this is exactly what keeps <shop>/runtime working, and it is already qop’s spelling. A link to a FILE inside a package is therefore not a case to handle correctly; it is a case to skip. Symlink creation/reading may later appear as a raw OS capability in a grantable file endowment (thin wrapper doctrine) — the shop still will not use it.

ONLY pmp1. NO CROSS-TARGET CEMENT. mcode IS the neutral thing — ruled by John 2026-07-29

PARTLY SUPERSEDED 2026-07-29 (later the same day) by “NO PORTABLE CEMENT — the finalization rulings” below. “No cross-target cement” was, in John’s words, “an error in language; what I meant was no portable cement. It’s completely expected to cement carts for other platforms.” The pmac deletion and only-pmp1 stand; the mcode-window boot lane (pmcd) now dies too. Read the new section first.

“There should be no cross target cement. There should only be pmp1. All of the code for pmac and pmcd can be removed when the boot happens correctly… mcode is the cross ABI thing. On boot, I thought the idea was that you have no real cart; you have the mcode that the runtime can lower to its specific ABI. It always goes from mcode to a specific ABI, and you can merge as many mcodes you want into one pool.”

This SUPERSEDES D4 (the provisional decision to route cross-target cement through pmcd windows). There is no cross-target cement at all: a build does not cement for a foreign target. Ship mcode; the target’s own runtime lowers it. That deletes the whole {numrep, endian} per-unit-blob arm in pit-shop/mach_lower.cm:76-78 rather than migrating it.

One precision on the wording: pmcd IS mcode-in-a-window — it is the neutral thing John describes carrying at boot — so the mcode window READER survives by necessity; what changes is its output (a pool, not a register tree). What dies OUTRIGHT is pmac: the serialized register tree, its writer (mach.c:2873-3142), its readers (3148-3355, 4482), pit_load_mach_code, mach_materialize_cpool, mach_resolve_shape_spec/_record_shapes, and the code cache that owned those trees. If the NAME pmcd should go too, that is a rename of the neutral window (to e.g. “mcode window”); the bytes and the role stay.

Merging is not a separate feature. Because lowering always targets this ABI, any number of mcode units merge into one pool. mach_lower.cm does one pool per unit today and one-pool-per-executable was recorded as needing realization images/instances rows — under this ruling it is simply what the lowering does when handed more than one unit.

Load-bearing dependency: the C mcode→pmp1 path DID NOT EXIST when this was ruled. Only shoplib/mach_pool_emit.cm (Pit) wrote pmp1. Stone 6.0 is building it, and every deletion above is downstream of it.

NO PORTABLE CEMENT — the finalization rulings, John 2026-07-29 (evening)

The correction that reframes the arc: “When I said ’no cross-target cement’, it was an error in language; what I meant was no portable cement. It’s completely expected to cement carts for other platforms.” Cement is always for one named profile; what is forbidden is a cemented artifact that pretends to be ABI-neutral. Portability ends at mcode. The goal, in John’s words: “any machine with a compiler [can] make code for any machine; some machines may not have compilers” — normal cross-compilation. Pit makes carts for other platforms from a working pit system.

Pit emits everything; C only reads

The dedup rule, ruled as an edict: C never writes what Pit can write. C reads only what must be readable before any Pit runs. Every format C reads has exactly one writer, in Pit, and CI presses each C reader’s input from that writer. Preference is always Pit. Consequences:

  • The C pool assembler (Pit_MachAssemblePool family), the on-boot mcode lowering, and pmac’s writer/reader all DELETE. The one pmp1 producer is the Pit emitter, parameterized by the target profile record. John’s shape for it: “Some pit file takes the mcode, or multiple mcode, and the profile; it dedups the mcode; profile asks for kim; it knows to write it. It’s that simple.”
  • The authority inverts. Byte-identity (C emitter proven byte-equal to the Pit linker) retires with the C emitter; the replacement gate is: every check-arms arm boots a fixture pool pressed by the Pit emitter for that arm’s profile. The C reader/executor per target IS the ground truth; the Pit emitter must satisfy it.
  • What stays in C is primitives Pit cannot express (fash64, blake2, kim packing) and full knowledge of the running build’s OWN layout — C executes the bytes; the reader half is irreducible.

Carts carry pools; mcode never boots (M1 REPEALED; pmcd dies)

root.cart’s windows become pmp1. The mcode window, the window-pool cache, and the register lane delete with the assembler. mcode remains the portable interchange artifact in the store; it never rides a boot medium. “After it’s no longer mcode, it’s not portable.”

The ABI bump stops being special: with a profile-parameterized emitter, a new ABI is just another foreign target — the OLD binary cross-presses the NEW cart. The bootstrap contract, blessed by John, which replaces M1’s protection:

  1. Every commit carries a matching (source, cart) pair; make seed verifies.
  2. Ordinary dev: the running system re-presses its own cart.
  3. An ABI/profile change lands WITH its old-binary-pressed cart in one landing; a commit whose cart stamp mismatches its source profile is a broken commit, full stop.
  4. Catastrophe: the cold floor (bootstrap.sh) builds the binary; by rule 1 the committed cart matches it. Mid-crossing wedge: check out the last green pair and press forward.
  5. Foreign targets only ever receive pressed carts; they never self-press.

Actors are the unit of code-stripping; the compiler is not shipped

John: shipping without a compiler is the point, and the compiler being in C “was locking it up.” A bare runtime with no working fleet cannot compile anything — the fleet being alive is a hard prerequisite for all compilation, forever, and that is ideal. mach_load and every compile-at-runtime surface route through the fleet’s Pit emitter.

One execution lane

pmp1 is the only payload lane. The kim8 register arm dies (D13 CLOSED): kim8’s stone layout is one more arm of the emitter’s profile parameterization, built when a recipe adopts kim8. The native/AOT lane stays parked under edict 7; when its pass comes, the one-lane rule applies to it too — it conforms to the pool world, it does not run beside it. Prerequisite work this creates: the two PIT_POOL_PAYLOAD gaps (the per-binding PGO counter array; the debug_tool assertion) are now mandatory, not optional.

Formats: nota metadata, binary pools, one fixed header

  • All metadata is nota: cart directory, plan/ENTRIES, names, manifests, realizations. C reads them with the nota2value it already has; bespoke section tables and their magic-header parsers delete. (The hand-decoded plan blob + string table in engine/boot_walk.cm goes with them.) wota is width-stamped and is wrong on disk; nota is the disk form. The daemon wire is already JSON lines and is fine.
  • Pools stay binary internally — the VM must index row i of a mapped section in O(1); this is the one sanctioned “magic,” because pools are the ABI-specific artifact.
  • One fixed outer header on boot media: magic, profile stamp, offset/length of the nota metadata. The stamp is checked by memcmp BEFORE any parser runs. Pool sections sit at aligned offsets addressed by the metadata, never inline as nota blob payloads (mmap/XIP alignment).
  • boot.qop (the qop archive) is bespoke but its storage question was already ruled deferred (“revisit near the end of the rebuild”); when revisited, the cart’s own nota-directory + raw-members shape is the obvious replacement.

Terminology — ratified 2026-07-29

termmeans
source.cm/.ce text
mcodeone unit’s portable compiled form; a value. Portability ENDS here
machthe instruction encoding itself
stonean immutable constant object (text/shape/number); layout is per-profile
poolthe fused ABI-specific artifact: mach bytecode + stones + tables for one profile (pmp1 at rest)
imagea pool mapped into a running system
profilethe full axis record; stamp = its hash carried in headers
manifestan executable’s target-neutral identity: program + closure + claims
realizationone profile’s freezing of a manifest: pools, entries, bindings
cementthe act: mcode → pools + realization for a named profile; press = write a cart
cartsingle-profile boot medium: fixed header + nota metadata + pools
bundlestore snapshot; may carry many realizations — the multi-target layer

Dead words: pmac, pmcd, pit.executable@1.

mach.c splits after the deletions

~3.5-4k of its 12.5k lines go with pmac/the assembler/lowering/materialize/code-cache/ shared-GC/kim8 scaffolding; what remains (“read, validate, execute pools”) splits into files (mach_vm.c, mach_pool.c, …). The one-unit rule was about the engine’s Pit, not C files in source/.

THE WALKER IS $start’s BODY — ruled by John 2026-07-29

“It seems like the walker should be in the boot actor to me… it’s not even clear to me how C could provide what’s needed to be provided, like the logger actor token and such. The boot/root actor, which has made the logger token, knows what that is; it also is the one that assigns a GUID to the newly made actor that it’s sending it to, and hence knows what its $self is; and of course what its $overling is, since the overling is where the request to $start came from. In other words, it seems natural for it to be in pit, basically as part of $start.”

Correct, and the tree already proves it: source/pit.c:311 writes logger_id: "bootstrap" — a PLACEHOLDER, because C does not have the real logger token. The ~60-line boot-wota block (pit.c:298-359) is C fabricating 13+ identity fields for actors it has no business starting. $start is ALREADY a Pit module (lang-endowments/$start.cm, moved out in 4a-continued), so the home exists.

The only two genuine C residues, neither an argument for a C walker:

  1. Actor zero — someone must make the first actor with no Pit running. One actor, once, and it is the boot actor itself.
  2. run_unit — making a code window callable (map, validate the pool header, adopt zero-copy, pin the placement) is C mechanics. That is a SHIM THE WALKER CALLS.

The refinement that is a real constraint, not a quibble: a produced value cannot cross heaps. So the walker cannot run inside the boot actor’s heap producing values for someone else — it must EXECUTE on the new actor, in the new actor’s own heap. The split is: the boot actor decides and supplies the facts (the guid it just minted ⇒ $self; the requester ⇒ $overling; the logger token it holds; whether the program mentions log at all), and the new actor’s first act is walking its own plan with those facts in hand. engine/boot_walk.cm (landed 7.2) already has exactly that shape.

Consequences for stone 7.3, which this reframes:

  • Not “move the walker to Pit and hand it four things from C” but “the walker is what $start does”. C’s job shrinks to making actor zero exist and handing over the medium.
  • The boot wota is not moved, it is DELETED for every actor but zero — the fabrication exists only because C starts actors it cannot supply.
  • It dissolves 7.2’s open question (b): whether pit cement must grow a walker for ship carts. If the walker is $start’s body it belongs to the boot actor, not to every cart.
  • It is what finally makes the ruled logging model implementable rather than aspirational: boot hands the logger token only to actors that mention log, because boot is the thing doing the starting.

What 7.3 landed (2026-07-29), and what it did not

Landed. pit_init runs the cart’s boot-walker window, projects walk, and calls it; the C walk is DELETED. start_plan.c 805 → 601 lines, and what survives is exactly the two things the ruling names as genuine C residue plus the registry: the image registry, run_unit (zero-copy pool adoption + placement pinning), and set_name. Gone with the loop: PlanView, plan_view_init, plan_project, plan_run_entry (all three entry kinds), plan_cstr, plan_bootstrap_names, and the entry/name flag vocabulary. Verified through rung 4 — reseed, recement, make check / check-cli ×2 / check-arms (14 arms) / lint / manifest ×2 / budget / check-playdate, and pit test all at 2700/2701 on a fresh daemon.

The handover is two SLICES, not the image, and this is the one design call worth carrying forward. A produced value cannot cross heaps, so the walk runs in the new actor’s heap and its bytes must be a value there — and a blob’s payload is inline, so “a blob over the image” is a per-actor-start COPY of the whole cart (4.5MB dev root cart, of which the walk reads ~27KB: the plan window and STRINGS; CODE is 98.5% and is reached by index through run_unit). So C hands over the plan window and the strings section. This is not new C parsing — the image registry already parses every registered image’s section table and survives stone 6 by the map’s own ruling; the walk was re-deriving it, and parse_image went with the duplication. Measured: no per-actor heap growth against baseline (system 22KB, policy 69KB unchanged). The alternatives were rejected on evidence, not taste: a resident non-copying blob view needs a new value representation (blob payloads are inline, the cart mapping is read-only, and on nan32 pit_ref_encode aborts for a pointer outside the registered object regions), and accessor shims put parsing back in C.

“Not to every cart” is honoured in C, not in cement. A content cart need not carry a walker: when the mounted cart names none, pit_cart_boot_walker falls back to the ROOT medium — the cart appended to this very binary, whose mcode window is ABI-neutral and lowered by this binary’s own emitter, so it always matches. cart_boot.c keeps g_root_medium beside the mount for that one field. This is why pit cement, boot.cart and every ship/app/ loader cart needed no change at all. The road not taken is worth recording: teaching the seed to emit a boot_walk.mach beside engine_lite.mach is a dead end — shop_build.cm’s bootstrap_artifacts lowers at exactly 255 of the mach VM’s 255 register slots and its module body at 254 of 255, so a new local, a module-level write (a text-keyed record access costs the one lowering scratch slot), or a second top-level binding each push it over.

Not landed, and still ahead of the end state. The boot wota is untouched — pit.c:311’s logger_id: "bootstrap" and the whole ~60-line fabrication block are exactly as they were, because deleting them is the boot-actor-starts-actors rewrite, which needs the clerk split. So the walk is $start’s body in the sense that it EXECUTES on the new actor in Pit; it is not yet the boot actor supplying the facts. $log_enabled (7.5) is untouched.

A payload-form change can wedge past make nuke (hazard, learned 2026-07-28)

Stone 5a’s landmine, worth more than the landing: reseeding with a broken payload form wedges the tree beyond every normal escape. The regenerated cart cannot boot, so it cannot reseed itself, and make nuke does not help — nuke drops caches and reseeds, and the reseed is what is broken. Escape was the cold floor (boot/manifest/bootstrap.sh) plus restoring the committed boot artifacts, which was only safe because no ABI had moved. Therefore: prove a payload-form change on a scratch program BEFORE letting it reach a reseed, and keep such changes behind a default-off lever until the walk that consumes them is proven. This is why 5a shipped with PIT_POOL_PAYLOAD off.

A format crossing should try a NEW SECTION KIND first (recipe, proven 2026-07-28)

Landing 4b’s agent found a strictly better crossing than the two-phase recipe the ledger carried. The ledger framed phase 1 as “teach the READER the new shape while the writer still emits the old” — ~230 lines of throwaway C. Instead: emit the new format as a NEW SECTION KIND beside the old one. Every reader already tolerated unknown sections (default: break; verify pins only the first seven then requires ascending kinds), so phase 1 became ~40 lines of ADDITIVE Pit in the writer alone, and phase 2 moved the readers and deleted the old writer. Retire the old kind, never reuse it — reuse makes a phase-1 artifact ambiguous. Ask this question before designing any future format move.

Three hardening rulings 2026-07-28

(1) The daemon socket gets locked down — John: “that’s OK to change. we control everything. nothing is relying on this. If it’s safer, do it.” fchmod(fd, 0700) before listen + a peer-uid check on accept (LOCAL_PEERCRED/SO_PEERCRED), and the /tmp fallback moves under a per-uid 0700 directory instead of a bare guessable path.

(2) walk SKIPS an unreadable subdirectory instead of failing the whole walk, and the callers change with it — John: “is the point that changing it to skip is the better choice, but it’s not done because callers assume it discards? If that’s the case it’s better to make it better (skip) and update the callers; sounds like an overall code reduction?” Yes on all three counts. The error contract several callers branch on goes away with it.

(3) The desktop logger’s last-resort /tmp/pit-loader.log is DELETED, not renamed. The defect explained: bootstrap_append opens O_WRONLY|O_APPEND|O_CREAT which FOLLOWS symlinks, so anyone who pre-plants /tmp/pit-loader.log as a link to a file the pit user can write gets pit’s log lines appended into that file. Renaming solves nothing (any name is equally plantable) and O_NOFOLLOW at the append seam would change a FLOOR primitive shared with observe.jsonl on every platform. The resolution the system already implies: stderr is the last resort, exactly as C’s Pit_Log falls back to stderr before the logger exists. The fixed world-writable path simply goes.

Policy allowance is per-closure and non-compositional

Recovered into the tree 2026-07-27 — this standing ruling of John’s existed only in session memory, which an agent correctly flagged as uncheckable. The ruling: a policy decision is made over an executable’s WHOLE closure and does not compose — a gate module can make a bigger closure safer, so you cannot derive a closure’s allowance from its parts’ allowances. The gate is realize-time and fail-closed; a sealed cart runs on its baked decision and re-decides nothing. (The dangerous line is native-load/native-compile plus $runtime — mach is confined; compile-to-mach is not the risk.)

6. Erlang, not OTP

The core gives you actors, isolation, message passing, and $couple — a lifetime binding where the coupled actor’s death takes you with it, automatic between a child and its overling. That is the whole of what the runtime knows about actors dying.

Supervision trees, restart strategies, heartbeats, health checks, back-off, drain protocols — all of that is application level and none of it belongs in core. They are OTP, and we are building only the Erlang part. We do not have a good OTP yet, and injecting one early would bake a policy into the layer that should have none.

So: when a problem seems to want a supervisor, the answer is that a user writes an actor that does it. $couple plus ordinary messages is the substrate; the patterns are library.

This is a specific case of the general rule — the system at its highest level stays very small. If an actor could be written to provide it, core does not provide it.

7. Ignore native

We are focusing completely on mach and the VM. Native — the AOT lane, qbe_helpers.c, native images, the native build path — gets a total independent pass later, and until then it is not a constraint on any decision.

So: do not preserve a native code path, do not weigh a native regression against a mach improvement, and do not let “but native needs this” stop a deletion. If a change degrades or breaks something native-only, note it and carry on — the later pass will pick it up with full attention rather than having it half-maintained by everyone in passing.

8. Green at every step

make check must print ALL GREEN before anything is committed, and it is re-verified independently rather than taken from a report.

It is not sufficient on its own: it compiles one point in a multi-axis profile space. text=kim8, gc=compact, value=32 and the console reps are never built by it, and that already let a real breakage through. Check the arms your change could touch.


Looking into a running system is an endowment, never an importable module

Several C files expose parts of this through plain use() today — internal/debug/debug.c and internal/debug/runtime.c are declared as the modules debug and debug/runtime in the root package.json. That is wrong. An importable module is gated by nothing, which makes docs/architecture/debugging.md’s claim — that a shipped artifact’s lack of the surface “can be checked rather than trusted” — false as written. The structural-absence property only exists once the surface is claimed, deniable, and absent from a build that does not grant it.

Superseded in shape, not in force. This edict originally named one $runtime endowment covering everything. John’s later ruling splits it four ways by what each costs the machine while present, so a build pays only for what it wants:

$vmmemory accounting — VM-wide and per-actor, walking actor memory
$hookthe call/return hook. Named $hook because $trace is already the live distributed trace-id endowment
$pgoexecution counters
$inspectread-only views of a running actor: frames, locals, closures, mailbox, bytecode, value addresses — and ref, since $inspect is how you name an actor you were not handed

$runtime keeps its name for a different and coherent idea: setting a fact in the runtime — logger, courier, node info, shutdown — held by boot or an early actor wiring the machine up.

$pgo and $hook must compile out when not granted; that is what makes the cost table in docs/architecture/debugging.md true rather than aspirational.

Source: plans/archive/edicts-2026-07.md