Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Porting an app onto CVN

The port CVN came from moved a 7,400-line application runtime into this shape without a rewrite. The order below is the order the obstacles fell; each step was blocked by the previous one.

0. Publish the measure

Add a declaration to the runtime: owned, delegated, hostCapabilities. Make delegated a list the code produces, and make the loop that drives the port read that list — not a plan file. Twice the port "finished" because every box was ticked while the work behind one was half done.

1. Move decisions, one verb family at a time

For each action family, extract the decision — expression in, patch or Cmd out — into a pure kernel module. Leave effect execution where it is. Measure inline state rebinds inside the action body; they fall from dozens to one.

2. Stop reading and assigning the host's state

A function that assigns to a variable it does not own cannot move to another module. Replace every state = … with cell.commit / cell.replace, every read with cell.value. Count the sites from the function body, not the file — the plan's "≈430 reads" was a whole-file count; the body had 78.

3. Move the body; inject what it imports

The action body becomes a kernel module taking an ActionPorts object. Anything it imported from the host tree that node cannot load (a directory import, a DOM-only module) becomes an injected port. Declare the interface; the contract gate keeps it honest.

4. Let the kernel build the dispatch

createRuntime builds the runner inside the dispatcher, with _perform bound to it. No fallback to a host action: a fallback is how a method is counted as owned while something else still does the work.

5. Shrink the port surface by disposition, not by count

Each remaining port has a written reason: platform, host data, session state, blocked on another move. Trade behaviour ports for data ports. Refuse repackaging.

The traps, so they are not re-derived

  • let state = getState() in an async function is a stale snapshot after the first await.
  • A kernel module that imports a host-tree module with a directory import is one node cannot load.
  • A port the kernel declares and never travels stays wrong forever.
  • A rt: any ports bag hides a missing port until a branch throws inside a catch.
  • The runner must use the dispatcher's perform, or the dispatcher records nothing.
  • Suite "contention" is an app loop until a profile of the page says otherwise.