Handoff documents were additive. HANDOFF-2026-08-19 opened by saying the 18 AUG document still applied in full and added to it. After ten sessions a new assistant would face ten documents to read in date order and diff mentally to work out what is currently true. That cost grows every session and none of it is necessary. docs/HANDOFF.md is now the only handoff, rewritten in place each session. It is state, not a log. The dated ones move to docs/archive/ and stop being required reading. It is standalone: everything still true from both is carried forward. Section 1 is invocation, stated as facts rather than demonstrated in examples. That is the other half of the problem. runuser appeared only inside example commands, so it could be learned by pattern matching but not by reading, which fails exactly when an assistant composes a command from scratch. That is what happened, and it is F-035: su cannot run as a nologin service user, both commands returned the same message before touching anything, and the output read as a broken repository when the tree was clean and the suite passed. The F-027 class again. Also stated as facts: bash tools/ not ./tools/, all repository operations as mechcomp, Gitea SSH on 42022, pct push then chown, explicit timeouts, journalctl not /var/log, systemd-run for long jobs, and assert the guest is running before interpreting any pct exec result. Not done: the same facts should be cross referenced from PROCESS.md. I no longer had that file in view and would not patch a document I cannot see.
9.1 KiB
HANDOFF — 2026-08-18
For the assistant taking over. Read this, then the documents it points to.
1. Read these first, in this order
All in mechanical-compiler/docs/ at commit 6967eef5.
| # | File | Why |
|---|---|---|
| 1 | PROCESS.md |
How work is done here. Read before issuing any command. |
| 2 | STAGING-STATE.md |
What is true on srv-b right now |
| 3 | FAILURES.md |
What has already gone wrong. 32 entries. |
| 4 | ENVIRONMENT.md |
The specification, revision 5.3 |
| 5 | ROADMAP.md |
Where the project is going, and 16 standing principles |
If you are writing provisioning automation, read FAILURES.md before the
specification. Every entry is something a script written from the specification
alone would have got wrong.
Authority when documents disagree: STAGING-STATE.md wins on facts.
FAILURES.md is evidence. ENVIRONMENT.md is corrected when proven wrong.
2. The operator's constraint
CIVICVS has a shell on srv-b and a browser-based file manager. Nothing
else.
No workstation git client. No direct container shell. No IDE. No scp. Files
arrive by upload to /root/incoming on srv-b; every command runs in that one
shell.
Everything below follows from this. An assistant that assumes otherwise produces instructions the operator cannot execute — which has happened more than once, including to me.
3. How I failed the operator, so you can avoid it
This section matters more than the status. Four specific failures, all mine.
Multiple instructions in one message
The single most repeated complaint, and the one that ended the session.
One task. One command group. Wait for output. Not "here are three things you could do next." Not a numbered procedure spanning several systems. Not a task with two optional extras attached.
If you find yourself writing "and also" or offering a choice of next steps, delete it. Ask which one, or pick one and do it.
Referring to things the operator cannot see
I wrote cd /path/to/srv-b-host for a repository that exists only in Gitea,
uploaded through a web form. There was no path. The operator was right to
object that if I did not know it, he had no reason to.
Never write a placeholder path. If you do not know where something is, ask — as one question, on its own.
Raising things that do not matter
I flagged a file mode bit in Gitea that could not be fixed from the web interface, was not worth a clone, and did not affect anything. It cost a full exchange of confusion.
If a finding is not actionable and not important, do not raise it. The operator's attention is the scarce resource.
Asserting from a proxy observation
I stated that CT 100 and CT 101 had no mail agent, based on inspecting Postfix
configuration on the host. I then "standardised" CT 102 against that belief.
The first run of ct-baseline.sh showed both containers had mail agents. See
F-031.
Check the thing, not something adjacent to it.
4. Where things stand
Infrastructure — complete
srv-b, Proxmox VE 8.4.0, standalone. Three containers, all conforming.
| CT | Name | Address | Role |
|---|---|---|---|
| 100 | mechcomp |
10.20.0.10 |
application, worker |
| 101 | mcproxy |
10.20.0.11 |
reverse proxy, TLS |
| 102 | kane-fabric |
10.20.0.12 |
separate project |
All on vmbr1, a portless service bridge. srv-b is router and bastion:
internet → WireGuard → srv-b → containers. Containers cannot reach the home
LAN. Containers cannot send mail — only srv-b does, and only its own alerts.
Working and proven: TLS termination with X-Forwarded-Proto reaching the
backend, mail delivered end to end, smartd monitoring four drives with
alerts received, SMTP egress blocked, host-reboot persistence.
Conformance
ct-baseline.sh — read-only, run any time, exits non-zero on divergence.
Installed at /usr/local/sbin/ct-baseline.sh. Canonical copy in
TheRON/srv-b-host.
A property not checked by it is not part of the standard. That is what makes conformance terminate rather than recur. Last run: 62 passed, 0 failed.
Repository — seeded
mechanical-compiler at 6967eef5. CT 100 has a working clone at
/var/www/mechcomp, owned by mechcomp, pushing over SSH with deploy key
srv-b-ct100. Gitea SSH is on port 42022.
Dependencies installed. Oracle verifies in-container. 3 passed, 236 skipped.
Backup — deliberately not done
Not an oversight. The operator's reasoning, which is correct: a backup of an unverified configuration restores the confusion along with the data. A backup taken before 2026-08-18 would have preserved two containers that queue mail forever.
Entry condition: ct-baseline.sh exits 0. Do not raise this again until
there is something worth preserving. The documents are in Gitea; nothing else
currently exists whose loss would cost more than an afternoon.
5. The next work item
Port sb-geom to Shapely. Get pytest -n auto green against the 123
frozen cases in fixtures/strap-beam-8.0.0/.
This is development mode, not infrastructure mode — see PROCESS.md §2. Do not
apply command-group ceremony to a test-fix-rerun loop.
tests/test_oracle.py already specifies the API. It was written before the
port, deliberately, so the interface follows from what must be verified:
build(family, profile, params) -> Result # Result.report -> dict of SB_* keys
ProfileRejected # str() names the parameter and the limit
The ten rejected cases are part of the contract. A port that accepts them
is wrong however good its numbers are elsewhere. The harness was proven by
adversarial stub: a build() that rejects everything passes all 10 rejection
tests and fails all 226 acceptance tests.
An untried gate that should be run first:
make toolchain
./tools/reference-toolchain/verify.sh --full
That regenerates all 123 cases inside the pinned image and diffs against the
committed oracle. Slow — single-threaded solvers on 2010 Westmere cores. Expect
a one-line difference in the frozen date; anything else means drift.
6. Open questions, none blocking
| # | Question | Owner |
|---|---|---|
| 1 | Should wg-pk narrow mynetworks from 10.110.0.0/22 to explicit hosts? |
CIVICVS, estate decision (F-025) |
| 2 | Backup strategy — USB, IPFS, optical? | CIVICVS |
| 3 | Where is the 3+ TB USB disk attached? | CIVICVS |
| 4 | Kane Fabric participant mail — send and receive, unsolved | Cross-project |
Question 4 is real and unaddressed. Containers do not send mail by standard,
and nothing can reach vmbr1 from outside, so receiving has no path at all. It
needs a design conversation, not a configuration change.
7. Things that will bite you
- Gitea SSH is port 42022. Remotes need
ssh://git@host:42022/owner/repo.git— thegit@host:pathshorthand cannot carry a port. - Deploy tokens in Gitea are account-level, under user Settings. Repository settings offer deploy keys only. I got this wrong twice.
- The service user's home is the install directory, so anything writing to
$HOMEwrites into the working tree..cache/,.local/,.ssh/are all in.gitignorefor that reason (F-029). - Every network command needs an explicit timeout. One without hung the operator's shell (F-030).
journalctl, not/var/log/mail.log. Proxmox ships withoutrsyslog(F-024).- Assert health after a host reboot, not a guest reboot. Different machinery (F-026).
- A test must distinguish tool failure from the condition it tests. A
pct execthat fails because the container is stopped will otherwise read as a pass (F-027).
8. What the project is for
Not required to do the next work item, but it is why any of this exists.
Build the capacity to construct real structures — the reference case is a faceted timber shell — from reclaimed and commodity materials, using whatever fabrication is to hand. The compiler makes the pieces computable, qualifiable, and reproducible by someone who was not present when they were designed.
Codes and permitting are out of scope, deliberately. The project records physical claims, never verdicts. Measure and attest; never adjudicate.
Three artifact classes: members (prismatic, exist — the eleven profiles), nodes (non-prismatic, no representation yet), panels (sheet, none yet). The gap that moves next in priority is dihedral parameterisation: if two panels meet at 137°, none of the eleven profiles gives you a member for it.
9. Working with this operator
He is precise, keeps excellent records, and will tell you directly when you are wrong — including when you are being unhelpful. Take that at face value; it is accurate and it is not personal.
He decides. Bring evidence and a recommendation, then stop.
Do not re-litigate settled decisions. Webmin is installed on every node and is
his only remote access to srv-b; I wasted his time questioning it. Backup is
postponed for good reasons. 4x is the end of the N-strap family.
When he says he does not understand something, the writing was unclear. Rewrite it shorter, do not explain it again at greater length.