It read my commit
Part 2 of five in StellarView Alpha, a series about delivering a solved application as an operated platform.
My code looked fine on my laptop. That was the only place it looked fine.
This is part two of five. The thread through the series is a single step: everything works on the machine it was built on, and the first move away from that machine is where things break. Every example of it failing here is ours.
The question that stopped me

Verbatim from the register:
docker-compose.yml declares build: . for the api service but no Dockerfile exists in the repository. Confirm whether authoring it belongs to this work unit’s runnable-skeleton scope or is deferred to the deployment phase, since docker compose up is a documented cold-start path in the README.
I had committed that file an hour earlier and had not finished it. Nobody told the plan. It opened the repository, followed the reference, and asked.
You can check the claim in three commands:
cd stellarview-aqueduct
git show 9ba136b --stat # seven files. No Dockerfile.
git show 9ba136b:docker-compose.yml | grep -n "build" # 16: build: .
The seed commit landed at 03:45:29. The epic was staged at 05:38. The requirements document says nothing about Docker builds.
Look at the shape of the question, because the shape is the point. It is not a warning and it is not a fix. It names two candidate owners for the work, gives the reason each might be right, and cites the README as the evidence that the question matters at all. Then it leaves the box empty.
Three hundred and fifty seven of these

They are not filed off to one side either. Every phase card in the epic carries its own count, so the plan and everything unresolved about it are the same screen. Thirty nine on phase one, thirty eight on phase two, thirty five on phase three, thirty on phase four:

Counted from the epic’s own staging file, not quoted from a slide:
| total | 357 |
| critical | 8 |
| resolvable from the repository | 68 |
| refused outright | 281 |
| answered | 0 |
A sample, verbatim:
- Which database role runs migrations. WU-1-4 introduces a non-superuser
aqueduct_approle that must not own the tables it is restricted by, so the runner may need a separate owner or admin connection string distinct from the runtimeDATABASE_URL. - Should
app.tenant_idbe set transaction-local so it unwinds automatically on rollback, or session-level on the connection so non-transactional leases are also covered? The two differ in cleanup obligations and in what a rolled-back transaction leaves behind. - Which operations are legitimately permitted to run outside a tenant context, and how do they reach the database?
That last one is the entire tenancy boundary, asked as a question, before anybody wrote a line. Answer it wrong and the isolation is decoration.
The rollback one is the question I would have got wrong, and I would have found out during an incident.
The eight critical ones are business decisions
This surprised me more than the count. Every one of the eight is a decision a person has to make, not a technical detail. Phase 5, work unit 5-1:
Which cloud provider and which region are we standing Aqueduct up in? Nothing in the repository or in docs/requirements.md names one, and the pinned provider, the remote state backend type and every later Work Unit in this phase depend on the answer.
Note the clause in the middle. It checked both sources before it asked.
The buttons above the register
Here is the objection this series had not answered, and it is visible on the same screen as the proof.

Sitting directly above a register that refused 281 questions are four buttons: Suggest All, Suggest All (AI), Auto-Resolve, Accept All. Sixty eight of the 357 are classified resolvable, meaning the machine believes it could settle those by reading the repository rather than by asking a person.
So the fair question is what happens when a delivery manager under pressure presses the one that answers everything. We pressed it, on camera. It took about a minute and a half.

It did not guess. It went and read what was installed and answered with what it found:
Pin exactly the runtime set already installed under `node_modules/` (which
contains no dotenv): `express@4.22.2`, `pg@8.13.1`, `zod@3.24.1`, plus
`jsonwebtoken@9.0.2` and `cors@2.8.5` for the Conduit `Authorization: Token`
scheme, with `eslint@10.9.1` as the only devDependency; declare them as exact
versions so `npm ci` is reproducible.
One of the four it drafted was the Dockerfile question this article opens with, and it reached the same conclusion the operator did:
In scope for this work unit. docker-compose.yml is already committed (in the
single existing commit, alongside README, package.json, and the domain) and its
api service declares build: ., so the compose path is broken today. No
Dockerfile or .dockerignore exists anywhere in the repo.
Phase one moved from five resolvable to three, and three answered. Three hundred and fifty three questions are still open.
And look at what the answer is labelled. ai-suggestion, with an edit
control and a reject control beside it. It is a draft in the box, not a decision
in the plan. That is the whole distinction the register is built on: the machine
will do the reading, and it will not sign your name to the result.
Two honest notes. The global counter at the top of the screen still reads 0
answered while phase one reads three, which is a real inconsistency in the
product and it is in the picture above. And hasConnectivity came back false on
this run, so it resolved only what it could settle without a live database.
Four, out of sixty eight it thought it might manage.
It read the code, not only the document

Two findings were in neither the requirements nor the plan, only in the repository.
A route answering 403 where it should answer 404, which tells an attacker the
record exists. And a token parser that would accept alg: none.
What the questions were protecting
Here is the answer to the migration-role question, as it eventually shipped. This is the whole reason the question was worth asking:
// migrations/002_app_role_and_rls.mjs
await client.query(`ALTER TABLE ${tableIdentifier} FORCE ROW LEVEL SECURITY`);
await client.query(`
CREATE POLICY tenant_isolation ON ${tableIdentifier}
USING (tenant_id = nullif(current_setting('app.tenant_id', true), '')::uuid)
WITH CHECK (tenant_id = nullif(current_setting('app.tenant_id', true), '')::uuid)
`);
And the comment above it, which is better engineering writing than most of what gets published about multi-tenancy:
* - is NOSUPERUSER and NOBYPASSRLS, so it cannot opt out of row security
* - owns none of the tables (this migration runs as the owner role) —
* table ownership is what a same-role migrate+runtime setup would have
* given away, silently voiding every policy below
*
* The policy reads app.tenant_id with missing_ok=true and nullif's the empty
* string, so an unset setting compares to NULL and matches no rows — fails
* closed, not open.
Table ownership is what a same-role setup would have given away, silently voiding every policy below.
That is the failure mode nobody writes down. By the time you find it, the policies have been decorative for months.
You can prove it in one session. Same connection, five contexts:
SELECT count(*) FROM articles; -- 0 no tenant set
SET app.tenant_id = '<tenant-a>';
SELECT count(*) FROM articles; -- 2
SET app.tenant_id = '<tenant-b>';
SELECT count(*) FROM articles; -- 1
SET app.tenant_id = '';
SELECT count(*) FROM articles; -- 0 empty is not a wildcard
SET ROLE aqueduct; -- ERROR: permission denied
The last line is the one that matters. The application cannot opt out of its own isolation, because Postgres will not let it.
Why this is the interesting part
A plan that guesses looks finished. That is the problem with it. It reads as complete, gets approved, and the guesses surface in week six as rework nobody budgeted for.
Three hundred and fifty seven empty boxes look worse and are worth more. Each one is a decision that has been found, named, and handed to the person who should make it, before it can become a defect.
The step off the laptop is not writing the code. It is finding out which of your assumptions were assumptions.
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 , The run: ten issues, three passes each, and why the branches are stacked.