Ten open pull requests, one per phase
Part ofCraft

StellarView Alpha, part three: ten issues, three passes each, and the branches are stacked

The run

Part 3 of five in StellarView Alpha, a series about delivering a solved application as an operated platform.

One person and one ticket works fine on a laptop. Ten phases that depend on each other is the step off it.

Three passes per issue

Every issue gets three. Analysis works out what the change is. Execution writes it. Forge builds and boots the result.

The analysis written back into the issue

The analysis is not kept in a log. It is written back into the issue, where the team already reads. Requirements validated against the phase, architecture validated against the requirements, then an implementation plan written before the implementation.

The architecture validation is the one nobody asked for. It reviewed its own design and reported what it did not like about it.

A branch per phase, and stacking

A branch per phase

Eleven branches. Not one long-lived branch that everything lands on, and not a branch per run.

Phase four stacked on phase three

One pull request of the ten was opened against another phase rather than against main. Phase four went at phase three, so its diff is only the work phase four did.

The other nine went straight at main. The machine did the right thing once, and it is worth being precise about that rather than describing it as a pattern.

This is the whole reason any of it is reviewable. Here is what the diffs actually look like, from the GitHub API:

PRbaseadditionsfiles
#12 Phase 1main+3,00241
#13 Phase 2main+4,74957
#14 Phase 3main+10,115114
#15 Phase 4feature/GH-4-WU-3+2,26527
#16 Phase 5main+12,700144
#23 Phase 10main+28,023270

Look at #15. Because it is based on phase three’s branch rather than on main, its diff is 2,265 lines across 27 files, the work phase four actually did. Every other row is cumulative, and by phase ten a reviewer opening the diff against main is looking at twenty eight thousand lines across two hundred and seventy files.

Nobody reviews twenty eight thousand lines. They approve it. Stacking is the difference between a review and a formality.

Stacking costs something. The base branch has to merge first, and a change low in the stack ripples. That is the trade, and it is worth it, because the alternative is a diff no human reads.

What came out

Ten pull requests, with their checks

Ten pull requests. Read the timestamps: phase one opened eighteen hours before phase ten. Then read the checks.

Green through phase eight. Red on nine and ten.

We are leaving that in. A run that reports all green is the one you should not trust.

Both failures were real, and both were fixed at the mechanism rather than around it.

The lint rule caught an != null comparison. The obvious move is to loosen the rule, since != null is a legitimate idiom for “neither null nor undefined”. Instead the check became stricter than either:

// Line numbers are numbers. `!= null` said "neither null nor undefined",
// which is a real idiom but one eqeqeq refuses, and loosening the rule to
// permit it would be an accommodation. Checking the type is stricter than
// both: a stray empty string or a stringified number no longer counts as a
// location.
const hasLocation = citation.filePath
  && typeof citation.lineStart === 'number'
  && typeof citation.lineEnd === 'number';

The secret scan flagged a documented dev-only key inside a captured evidence transcript. Editing a transcript until a scanner goes quiet turns evidence into fiction. So the value is allowed by name, with the reason written into the config file, and the scan still blocks a merge on anything else.

What CI caught that the laptop could not

The best defect of the run was found after all ten phases had merged, by a job, on a defect that did not reproduce locally:

FAIL - DELETE /api/articles/:slug removes the article
       expected 404 for a deleted article, got 200

The delete returned 204 and the article stayed readable. The cause was one missing check:

-  await client.query('UPDATE articles SET deleted_at = now() WHERE id = $1', [articleId]);
+  const result = await client.query(
+    'UPDATE articles SET deleted_at = now() WHERE id = $1 AND deleted_at IS NULL',
+    [articleId]);
+  return result.rowCount > 0;

And the reason it was silent is the multi-tenancy feature itself:

Under row-level security an UPDATE that no policy lets the caller see affects zero rows and raises nothing at all, so a delete could report 204 while the article stayed readable. Every other write in this directory already checks rowCount. This was the one that did not.

The isolation created the bug class. The isolation job caught it.

The honest ledger

Twelve hours and eighteen minutes, including two billing stops and a restart. Two hundred and sixty eight files. Twenty five thousand eight hundred and fifty seven lines. Ten pull requests, unattended.

Not “twelve hours of building”. Twelve hours and eighteen minutes of wall clock with two interruptions in it. The difference matters, because the second number is the one you can plan against.


Conduit is the reference application of RealWorld, created in 2016 by Eric Simons and Albert Pai and funded early by Thinkster. It was revived in 2021 by Gérôme Grignon and Manuel Vila. It is maintained today by c4ffein and Manuel Vila. MIT licensed, © Thinkster and © c4ffein.

We are not affiliated with the project. Passing its suite shows conformance to its specification and nothing more.

Next, Somewhere real: phase five wrote its own Terraform, and the application still is not serving.