Committing, and changing your mind#
Week 12
Where we started#
Week 1 ended on a problem we have not solved since:
You will have to choose between them before you have the evidence you would need to choose well.
Twelve weeks of options later, that is still true. What has changed is that you now know which evidence is cheap, which is expensive, and which you only get by building the thing.
A defensible commitment is a boring document#
Not an argument. A table.
| Tried | Cost | What the result eliminated |
|---|---|---|
| the obvious approach | measured, in your own units | named, and narrower than you hoped |
Three columns, and the third is the one people leave out. A thing you tried and abandoned is only evidence if you can say what it ruled out — and for whom.
A cost estimate you cannot source is a preference.#
What a result that eliminates something looks like#
An interpreter running an unmodified riscv64 build of Node. It works: it starts, it runs a real agent non-interactively.
Then fork. V8’s six threads block on per-thread futexes, the scheduler’s
find_runnable returns −1, the wait returns ETIMEDOUT, glibc starts
reporting errors, a Node CHECK fails, abort(), SIGABRT — exit 134.
Twenty-odd fixes. All of them dead ends.
What it actually eliminated#
Not interpretation. Not that guest. Not even fork.
It eliminated this interpreter, for a guest with a userspace thread scheduler, with no kernel underneath it.
That is a narrow result, and narrowness is the point. A finding that eliminates a whole technique is almost always a finding you have over-read.
Why “make it work, then iterate” does not apply here#
The advice is sound everywhere else because it assumes your logic is separable from the thing running it. Ship, learn, refactor, keep the valuable parts.
In this field the assumption is false.
You did not build an application. You built an execution environment, tuned to one target’s requirements.
So a change of mind is not a refactor#
Change what the target needs and you do not keep the valuable parts. There are no valuable parts — there is one artefact, shaped end to end by requirements that just moved.
You throw the environment away and build a different one.
This is why the course spent four weeks on characterising a target before it spent a week on building anything.
Scope decisions are the expensive ones#
Not the implementation decisions. The ones you made in Assignment 1, from your assumptions about what the execution environment would have to provide.
And the requirements that decide your fate are not the likely ones. They are the outer reaches — the capability you assumed was out of scope, which turns out to be load-bearing.
A requirement for a desktop, or for native applications, does not extend your approach. It invalidates it.
One that arrives late#
Take week 9’s ahead-of-time route. You trace a real workload, translate every block it executed, and the cold start comes down by the order of magnitude the technique promises. Correct work, honestly measured.
Then the target reaches the part of itself that writes code at runtime — an embedded scripting engine, a regular-expression JIT, anything with a compiler inside it. There is nothing to translate ahead of time, because the code has not been written yet.
That property was in your characterisation the whole time, as one line about what the target is made of that nobody put a price on.
The requirement that ends you is rarely the one you wrote down.#
Nothing was implemented wrongly#
That is what makes it a scope failure rather than an engineering one. There is no commit that introduced it and no test that would have caught it, because the code was correct.
A property of the target invalidated the technique, at the point in the calendar where discovering it was most expensive.
The fallback was a real kernel underneath, which does this without being asked — and which was in the option set you wrote in Assignment 1, with a price already next to it.
Priced-in replacement is not rework#
A deliberately temporary layer — chosen knowing you would replace it, with the migration costed at the time you chose it — is a plan. It is often the right call, and it is not a compromise.
Discovering the same fact in week nine is not a plan. It is the same code, the same replacement, and a different admission.
The difference is entirely in when you knew, and whether you wrote it down.
The smallest answer usually wins#
A runtime two major versions behind. The obvious move is the upgrade: newer edition, newer minimum toolchain, everything upstream has fixed since.
The upgrade cost ten workarounds and still could not run a subprocess.
The measurement that settled it#
The newer SDK’s worker ran substantial code over one shared linear memory, and
that build had never been linked for threading: 8,733 atomic instructions,
and no __wasm_init_tls. Every thread-local aliased every other.
Reproducible, every time, as RuntimeError: function signature mismatch.
Three different dispatch strategies were tried. All three failed identically — which is what upgraded a symptom into a general claim:
a second runtime instance cannot execute guest code while sharing the scheduler’s linear memory
What shipped instead#
Fifteen lines. A syscall that delegates to the one next to it.
Then three more of the same shape, because once the boundary was the thing being fixed, each addition was small and each was testable on its own.
The upgrade was not wrong. It was a larger bet, made on less evidence, that would have been harder to abandon.
When to stop, re-scope, or ask upstream#
Week 5’s broken execve is the model. Three invocations established the
shape of it, and the diagnosis was one line of an interface definition
declaring the wrong return type.
That combination is what makes a defect somebody else’s:
- reproducible — three commands, not a story about a flaky run
- minimal — no part of your system is required to demonstrate it
- attributable — the wrong line is in a component you do not own
Why it matters which kind you have#
A performance characteristic you can design around, you design around. A contract stated wrongly, you escalate — because working around it means encoding someone else’s error into your own design, permanently, where the next person will find it and assume you meant it.
Knowing which of your problems are actually yours is most of knowing when to stop.
Most engineers escalate too late, and the reason is almost never technical.
Auditing what you decided in week 4#
Your final task asks you to go back to the reduction you wrote at the start and mark it yourself.
It is not marked on whether you were right. Being wrong about a target you had spent two weeks with is expected, and the people who were right were mostly lucky.
It is marked on calibration — whether the confidence you expressed then matches what you now know, and whether the thing that surprised you was something your own evidence should have shown you.
In the session#
Five minutes each, no slides.
The commitment, the evidence that licensed it, and the one thing you now know that would have changed it.
The room’s job is to find the step in your reasoning that your evidence does not actually support. Somebody will. That is the exercise working.