Files
TheRON bc291822e1 STAGING-STATE: reconcile against the host, and close DIV-005 as recorded backwards
DIV-005 is withdrawn. It claimed requirements-cad.txt declared a CAD dependency set that was not installed. Verified in CT 100 today: cadquery and OCP both import from the venv. build123d does not, and correctly so - requirements-cad.txt names it as a viable alternative on the same OCCT kernel, not as an installed package. The file declares one requirement, cadquery greater-or-equal 2.4, and it is satisfied. The manifest and the container agree.

The false statement was in HANDOFF section 5, which said no CAD kernel is installed and that cadquery, OCP and build123d are all absent. True when written on 19 AUG, carried forward by hand for three weeks. STAGING-STATE section 5 recorded cadquery 2.8.0 in the venv for the same period. Two documents, each stating it as fact, disagreeing, and neither noticing.

The entry was recorded backwards: it named the manifest as diverging from the container when the container matched the manifest and a third document was wrong about both. Its evidence was a session record rather than an observation, which is why it carried do-not-close, and that marking is the only reason this was checked instead of acted on. The index note is corrected from three-of-six to two-of-six.

A roadmap item was deprioritised on the same false premise. HANDOFF section 4 listed STEP export as needing a CAD kernel that was not installed. It is installed.

STAGING-STATE section 5 opened with a transcribed repository block: HEAD at the seed commit c7e32d8, tests 3 passed and 236 skipped, and absent the Shapely port itself. All three were true on 18 AUG and none after 20 AUG, while the checklist directly below was kept current. Facts three weeks apart in one section, the maintained half lending credibility to the stale half. Removed rather than corrected. One line in it was still accurate - the venv cadquery 2.8.0 - and it was the only correct statement about the CAD kernel in the repository when it was deleted.

Section 3a said Kane Fabric is where SASE, HOA Diagnostics, the Mechanical Compiler and other SASE-consuming projects are implemented. Nothing here is implemented on Kane Fabric. Each project has its own FQDN and container and they share the bridge, the hub and the proxy. SASE was used and never defined anywhere in this repository. Section 3a also said no interface between the two projects exists and none is assumed; IDENTITY-CONTRACT.md specifies one.

Section 4 gave AllowedIPs on the hub peer entry for srv-b as 10.110.0.0/22, four paragraphs after correctly giving it as 10.110.0.12/32. The 22 is srv-b own AllowedIPs for the hub, recorded in section 1. The two ends of one tunnel were conflated, in the paragraph describing the security boundary. WORK-ORDER-004 section 0 settles it: all twenty peers carry a 32.

Also: the toolchain image box is ticked, verified present in CT 100. The baseline result is marked overdue against DIV-006. The Shapely port gates nothing. HANDOFF rewrap owed from 61b8f1e is applied.

Read-only verification was run before writing, per PROCESS section 2. Had it not been, this commit would have deleted the one true CAD statement and left the false one standing. Suite 643 passed, oracle intact. Documentation only.
2026-09-14 04:14:18 -05:00

748 lines
39 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# HANDOFF
**This document is rewritten in place each session. It is state, not a log.**
It is the only handoff you need to read. Dated handoffs in `docs/archive/` are
historical and are not required reading — do not diff them against this to work
out what is true. If something here is wrong, correct it here.
Last updated 2026-09-13, after the documentation audit.
This line used to carry the commit hash of its own rewrite. It cannot: the
hash is not known until the commit is made, so the value was always the
*previous* commit and section 3 drifted two commits behind without anyone
noticing. Date only, from here.
---
## 0. Read these first, in this order
This document is not the first thing to read, despite being the handoff.
1. **`docs/PROCESS.md`** — how work is done here: who runs what, on which
machine, with which tools. **It constrains everything below.** Reading it
eleventh cost this session three sets of instructions the operator could
not execute.
2. **`docs/STAGING-STATE.md`** — what is true on the host right now.
3. **`docs/FAILURES.md`** — what has already gone wrong and why.
4. This document — where the code stands.
5. **`docs/DIVERGENCES.md`** — requirements that stand and are not met.
6. **`docs/ENVIRONMENT.md`** — the specification an instance is built to. It was
missing from this list for three weeks while `PROCESS.md` §8 carried it and
omitted this document instead, so a successor following either list alone
missed something. Both lists now carry both.
7. `docs/ACCEPTANCE.md`, `docs/STOCK.md`, `docs/PRECISION.md` — the
specifications the code is held to.
8. **`docs/IDENTITY-CONTRACT.md`** — how a visitor's identity arrives, and the
boundary that keeps membership out of this application. The only document
here written to be read by someone who does not work on this repository.
9. `docs/CONSUMER_INTERFACE_GATES.md` — cross-project concerns that are open,
with owners. Non-normative. **Read it before proposing any integration with
a membership or geography system**; several tempting ones are recorded there
as things to specifically not build yet.
`deploy/` holds the systemd unit and the nginx vhost as they actually run,
imported verbatim on 12 SEP and checksum-proven equal to CT 100 and CT 101. The
repository is the single source of truth: read a container when you suspect it
has drifted, then fix the drift here rather than on the host.
When documents disagree, `STAGING-STATE.md` wins on facts about the host and
`FAILURES.md` wins on what was actually observed. This one is corrected.
---
## 1. Invocation — read before typing anything
These are facts, not examples. Getting one wrong produces an error that looks
like a problem with the repository or the containers.
**`mechcomp` has `nologin` as its shell.** Use `runuser -u mechcomp -- <cmd>`,
which executes directly. **`su - mechcomp` cannot work** and fails with
`This account is currently not available` — a message that looks like a broken
account and is not. See F-035.
**The interpreter is `/var/www/mechcomp/venv/bin/python`, not `python3`.**
`Makefile` line 3 sets `PY ?= venv/bin/python`, and `make deps` installs
pytest, Shapely, numpy and `mechcomp` itself into that virtualenv. System
`python3` has none of them and fails at the first import (F-036).
**All repository operations run as `mechcomp`, never root.** The clone is at
`/var/www/mechcomp` in CT 100. Never add a git `safe.directory` exception to work
around an ownership complaint; fix the ownership (F-008).
**`mechcomp`'s home *is* the working tree.** Anything writing to `$HOME` writes
into the repository. `.cache/`, `.local/` and `.ssh/` are in `.gitignore` for
that reason (F-029). Git identity is set `--local` for the same reason —
`--global` would write into the tree.
**`pct exec` runs no shell.** A glob, a redirect, a pipe or an `&&` in a `pct
exec` command is expanded by the *host* shell, against the host's filesystem,
and whatever literal survives is handed to the container as an argument. Wrap
anything that needs a shell:
pct exec 101 -- sh -c 'grep -n listen /etc/nginx/sites-enabled/*'
Unwrapped, that glob expands on `srv-b` — where the path does not exist — so the
container receives a literal `*` and reports "No such file or directory", which
reads as a broken container and is not. Third of the same kind after F-035 and
F-036: the tool was invoked wrongly and the error described the wrong subject.
**`verify.sh` is mode `100644`.** Invoke it as `bash tools/reference-toolchain/verify.sh`,
never `./tools/...`.
**Gitea SSH is port 42022.** Remotes need
`ssh://git@host:42022/owner/repo.git`; the `git@host:path` shorthand cannot carry
a port. CT 100 pushes with deploy key `srv-b-ct100`.
**Deploy *tokens* in Gitea are account-level**, under user Settings. Repository
settings offer deploy *keys* only.
**Files reach CT 100 by upload, then `pct push`, then `chown`.** `pct push`
writes as root, so `pct exec 100 -- chown mechcomp:mechcomp <path>` immediately
after, every time.
**Every network command needs an explicit timeout** (F-030). One without hung the
operator's shell.
**Logs are in `journalctl`**, not `/var/log/`. Proxmox ships without `rsyslog`
(F-024).
**Long-running jobs go to `systemd-run --unit=<name> --collect`**, not `nohup` or
`setsid`. `pct exec` tears those down when it exits; systemd owns the job and the
output lands in the journal.
**Docker runs as root in CT 100 only.** `mechcomp` cannot reach the daemon. CT 101
has no `keyctl` and cannot run it at all.
**Assert the guest is running before interpreting any `pct exec` result** (F-027).
A command that fails because the container is stopped otherwise reads as a pass.
Useful canonical form:
```bash
if [ "$(pct status 100 | awk '{print $2}')" != "running" ]; then
echo "CT 100 NOT RUNNING - stop here."
else
pct exec 100 -- runuser -u mechcomp -- git -C /var/www/mechcomp status --short
fi
```
---
## 2. The operator's constraint
**CIVICVS has a shell on `srv-b` and a browser-based file manager. Nothing
else.**
No workstation git. No SSH into a container. No IDE. No `scp`. Files arrive by
upload to `/root/incoming` on `srv-b`; every command runs in that one shell.
`pct exec` from that shell reaches all three containers — that is the container
path, and it is not a limitation.
An assistant that assumes otherwise produces instructions the operator cannot
execute. This has happened repeatedly.
**Deliver code by upload, not by paste.** A sixty-line heredoc containing
em-dashes and nested code fences was mangled by the browser terminal. Everything
delivered since as a tarball — with a stated checksum, file list, and what it
overwrites — has worked without exception. State what the archive contains, where
it expands, and what it overwrites, every time.
When a delivery supersedes an earlier one, **ship every file in the set**, not
just the changed one, so the resulting state is unambiguous.
---
## 3. Where things stand
### Infrastructure — complete, do not revisit
`srv-b`, Proxmox VE 8.4.0, standalone.
| CT | Name | Address | Role |
|---|---|---|---|
| 100 | `mechcomp` | `10.20.0.10` | application, worker, Docker |
| 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 and cannot send mail.
`ct-baseline.sh` is read-only, runs any time, exits non-zero on divergence.
Installed at `/usr/local/sbin/`. **Last run 2026-08-18: 62 passed, 0 failed.**
That predates the DNAT rule and the placeholder service replacement, both of
which `PROCESS.md` §9a names as requiring a run, so it is overdue — see DIV-006.
**A property it does not check is not part of the standard** — that is what
makes conformance terminate rather than recur.
**Settled decisions. Do not reopen any of these:**
- **Webmin is the operator's only remote access.** He works from Webmin on
`wg-pk`, the WireGuard hub, and from there into the `srv-b` shell — not the
other way round. Questioning this wasted a session once and was questioned
again on 11 SEP, because the decision had been recorded without the fact it
rests on. The fact is above. Do not ask again.
- **`dev.infra` does not exist on the internet.** It is a hosts-file name on
`srv-b`, CT 100 and CT 101 only, and CT 101's leaf is signed by a local
staging CA. Never tell the operator to open a `dev.infra` URL or a
`10.20.0.x` address in a browser: `10.20.0.0/24` is a portless bridge with
LAN traffic dropped. The public name is `dev.mechcomp.kane-il.us` and it
needs `WORK-ORDER-004`.
- Backup is deliberately postponed. Entry condition: `ct-baseline.sh` exits 0.
Do not raise it again.
- `4x` is the end of the N-strap family.
- The `--full` toolchain gate's Docker root-ownership hazard is understood and
handled by mounting read-only. See §6.
- **Accuracy criterion: 0.01 mm over the entire set.** Set by CIVICVS on 20 AUG.
This is the standard the software is held to, and it is met with two orders of
margin on every dimensional quantity. See `docs/PRECISION.md`.
### The port — COMPLETE
The suite is green and the composer is live.
**This section no longer records a commit hash or a test count.**
`git rev-parse HEAD` and `make test` report them. The line that used to carry
the hash was always the *previous* commit — the hash is not known until the
commit is made — which is how this section drifted two behind without anyone
noticing, exactly as the header says of its own date. Both are now derived
rather than transcribed.
The 30 expected failures are gone, resolved rather than suppressed, by the
tolerance model in `docs/ACCEPTANCE.md`. A red `make test` now means something
is actually wrong.
**The composer is live.** `mechcomp.service` in CT 100 serves it on
`10.20.0.10:8770`, behind nginx on CT 101, proven `HTTPS 200` end to end.
`mechcomp-placeholder.service` is disabled and retained as the rollback.
| Module | Ported from | Contents |
|---|---|---|
| `geom/primitives.py` | `sb-geom.scad` | **Pure geometry only.** Vectors, degree trig, exact polyline distance, corner-radius derivation, monotone solver. **`Geo` and `Member` are NOT here** — an earlier version of this table said they were, and two modules were read on the strength of it |
| `geom/records.py` | `sb-geom.scad` | `Geo`, `Member`, `place`, `local_rect`, sleeve and cavity paths, reach. The other half of `sb-geom` |
| `geom/rounding.py` | BOSL2 `92d697c2` | `round_corners`, `_circlecorner`, `arc`, `segs`, `deduplicate`, `path_merge_collinear`, `is_collinear` |
| `geom/region.py` | BOSL2 regions | Shapely booleans, nesting-parity decomposition, area, simplicity, hull, mitred offset, cleaning |
| `geom/join.py` | `sb-join.scad` | Butt joints, hull caps, fillets, derived bore, ring fit, section assembly |
| `geom/report.py` | `sb-report.scad` | Checks-as-values, metrics, five universal checks, `ProfileRejected`, report formatting |
| `geom/core.py` | `sb-core.scad` | PROFILE record, failure representation, centred assembly |
| `geom/arrangements.py` | `sb-profiles.scad` | Ring, spoke and fin arrangements, all N-generic |
| `profiles/_common.py` | both generators | Assembly pipeline and the eight shared base checks |
| `profiles/four_x.py` | `strap-beam-4x.scad` | Five profiles, defaults, 4x base checks |
| `profiles/three_x.py` | `strap-beam-3x.scad` | Six profiles, defaults, 3x base checks |
| `profiles/__init__.py` | — | `build()`, `ProfileRejected`, family registry |
| `stock.py` | — | COTS stock descriptor: `RectStock`, `RoundStock`, `Fit`, `Provenance`. A leaf module — imports nothing from `mechcomp`. See `docs/STOCK.md` |
| `design_record.py` | — | What a model was made from, at full precision, sufficient to regenerate it. `input_id` and `build_id`, and `Author` — recorded, rendered with its own limit, and in neither hash |
| `svg.py` | — | Cross-section as SVG, separately classed layers for material, cavities and stock |
| `stl.py` | — | A member as a sealed binary mesh: two triangulated caps and a quad strip down the boundary. **No CAD kernel** |
| `web/app.py` | — | The composer. Standard library HTTP server; binding from `/etc/mechcomp/mechcomp.env`. Serves `/`, `/api/build` and `/m/stl` |
`make deps` is complete in CT 100 and **must not be re-run**.
---
## 4. What to do next
**Nothing is blocked. The composer is live and the suite is green; everything
below is new capability.**
**CIVICVS stated the goal on 22 AUG: he will not print anything that is not
fully configurable.** The compiler is a half-visual composer — the T encloses
poly pallet straps in a T, the Y takes a metal electrical conduit core at its
centre. That reorders what follows, because a fixed-profile STL is not a
deliverable he wants. Two consequences:
- **The bore is a declared interface**, with a diameter and a fit class,
specified by what goes through it. It is not a byproduct of the inside
walls. Conduit for the Y, not only rebar.
- **The catalogue front end is the product, not a convenience** that comes
after export. `ROADMAP.md` §4 orders it third; that ordering predates this
and should be read against it.
**Priority order, decided 11 SEP when CIVICVS delegated it. Items 1 and 2 were
reversed on 12 SEP, for the reason given under item 1.** The reasoning is given
so a successor can disagree with the argument rather than only the sequence.
1. **An authorship field in the design record. Landed at `48d5665`.** One field,
and a door that narrows rather than closes. The original argument was that
records created before it exists can never be attributed. That was too
strong, and it was too strong *because* the field is excluded from both
hashes: a record regenerated later with the author filled in keeps its
`input_id`. But `build_id` covers the code revision and the Shapely and GEOS
versions, and boolean results on near-degenerate geometry can shift between
GEOS releases — the F-034 mechanism. Regenerate after a GEOS bump and you
have attribution under a different build identity, beside a printed part
nobody can now tie to either. Recoverable, not free. It still went first,
because it is one field and one parser branch, and doing it first means the
first coupon off the printer carries its author.
The person is identified by an email address. A handle may be chosen later
and does not replace it. Nothing verifies the address and the record says so
on the line it appears on.
2. **STL export. Landed at `0545b79`; the bounded export route at `cdde394`.**
No new dependency and no kernel — a member is prismatic by definition, so an
STL is two triangulated caps plus a quad strip down the boundary. It was the
only item producing *physical* feedback: everything else is verified against
a frozen oracle, and a printed coupon is the first test against reality, and
the first real measurement of whether `fit_clearance_mm` suits the operator's
printer.
**`length_view` was missing from `COMMON_GROUPS`**, which would have made the
Full Length branch unreachable from the composer and every export a silent
100 mm preview — a file that looks right, slices right, and is the wrong
object. Fixed before the route shipped. The sweep is `model_length_mm(p)`,
the value already published as `LENGTH_MM`, so the record's `VOLUME_MM3` and
`MASS_G` describe the file beside them rather than something else.
`/m/stl` ships **open**, because `/m/` is not yet gated and no membership
system exists to gate it. Carried as open question 10 rather than justified.
3. **Per-member stock — the conduit core.** What CIVICVS asked for on 22 AUG and
still cannot be done: `Geo` is global to a build, so every member is the same
rectangular strap by construction. Reaches into `join.py` and
`arrangements.py`; the first change that puts the oracle's own geometry at
risk rather than merely near it.
4. **Persistence.** `MECHCOMP_DATA_DIR=/var/lib/mechcomp` has been declared since
staging and nothing has ever written to it. Every design currently exists only
for the duration of one HTTP request. This is the prerequisite for a library
of saved designs. It is **no longer** the prerequisite for access control,
which has left this roadmap entirely — but it should be built knowing that a
saved design belongs to a person whose identity arrives from outside this
application (`IDENTITY-CONTRACT.md` §3).
The filesystem is the first store, not a database. Design records are already
plain text, already content-addressed by `input_id`, and already readable by
someone with none of this software installed. A directory of them is a store
with perfect provenance and no schema to migrate. SQL earns its way in when
there is a query that walking files cannot answer — "every design by this
author since March" is that query, and it arrives with membership, not
before.
5. ~~**ACL.**~~ **Deleted, not deferred.** Authorisation lives upstream, at the
last proxy hop before this application, decided against a membership system
this repository knows nothing about. The compiler will not have a user table,
a login form, a session, or a group name in any form — including as a
configuration value, which is how that leak arrives by the side door. See
`IDENTITY-CONTRACT.md` §9. An item left the roadmap rather than moving down
it.
The service is world-reachable and unauthenticated today, and `/m/` is not
yet gated, so STL export will ship open. An earlier version of this item
called that "acceptable for a development name". **It is not.** The name is
production, `dev` abbreviates *Mechanical Compiler Developers*, and it
appears on printed material. The posture is unchanged; the excuse is
withdrawn. Carried openly as open question 10 instead of justified.
**Assemblies are not on this list and should not be added.** See `PRECISION.md`
§7: positioning is field work, ruled out by design on 11 SEP.
Roadmap items — read `ROADMAP.md` before starting any:
- Dihedral parameterisation. If two panels meet at 137°, none of the eleven
profiles gives you a member for it. This is the highest-value gap and it
blocks a catalogue that could otherwise not serve the reference structure.
- Rebar- and conduit-core bore as a declared interface. See above.
- **Per-member stock.** This is what the conduit core actually needs. `Geo` is
global to a build, so every member is the same rectangular strap by
construction and the Y's centre cannot hold something of a different shape.
Moving stock from the build to the placement reaches into `join.py` and
`arrangements.py`, and it is the first change where the oracle's own
geometry is genuinely at risk rather than merely nearby.
- **The composer, deepened.** It exists, serves, and exports STL; it does not
persist anything or show a bill of materials. `payload["defaults"]` is sent to
the browser and nothing reads it — dead weight, and it is what made a
verification grep lie on 11 SEP.
- STEP export. **The kernel is installed** — `cadquery` and `OCP` both import in
CT 100, verified 2026-09-13. This item sat low on the list partly because §5
said no kernel was present; it is closer than that implied. STL needs none —
a member is prismatic by definition. See §5.
- `mechcomp-worker.service`. `src/mechcomp/worker/` is still a 36-byte stub and
nothing needs a worker yet.
- Nodes (non-prismatic) and panels (sheet). No representation exists for either.
**`WORK-ORDER-004` is closed. The composer is public at
`dev.mechcomp.kane-il.us`.** Executed 11 SEP at `1fdb115`: browser, DNS, TLS on
`wg-pk`, the WireGuard tunnel, a DNAT on `srv-b` scoped to the hub as source,
CT 100. No WireGuard change and no route were made — the hub's peer entry for
`srv-b` is still a `/32`, as all twenty are — because every vhost there proxies
to a tunnel address directly and following that convention removed the only step
that could have locked the operator out of `srv-b`.
An earlier version of this paragraph said the work was pending and that CIVICVS
could not see any of it. It was written thirty-two minutes after the ingress
closed, in the same session, and was wrong the moment it was committed: the §4
rewrite was made against the work order's old state rather than its new one.
Corrected 12 SEP. This is the staleness §0 exists to prevent, and it happened
anyway, inside one session.
Note for anyone tempted to shortcut a test print: the OpenSCAD reference in
`legacy/` does export printable STL today — `sb_extrude_section` does a
`linear_sweep` and `legacy/openscad/README.md` documents the invocation. That
is not what he asked for, and offering it again would be repeating a mistake
already made once.
---
## 5. Facts established by porting
### The public entry point is `mechcomp.profiles`, not `mechcomp.geom`
`conftest.py` does `importorskip("mechcomp.profiles")` and requires a `build`
attribute. `build(family, profile, params) -> Result` and `ProfileRejected` are
exported from `mechcomp.profiles`. Params use the OpenSCAD parameter names
unchanged.
**`params` carries only a case's overrides.** Everything else comes from the
family's declared defaults, which is why those defaults live in the port rather
than the test harness.
### Defaults are per-family, not shared
`ring_corner_radius_mm` is **2.00 in 3x** and **1.25 in 4x**. The two generators
declare their own parameter blocks and they are not identical. Do not assume a
value read from one file applies to the other. 4x also declares
`rectangle_aspect`, which 3x has no equivalent of, and its base-check list is
correspondingly one entry longer.
### Check order is the reporting order
The reference reports the **first** failing check, not an aggregate. Base checks
run before profile checks, and both run before any geometry is measured — a
builder that failed returns an empty profile, and `centred()` on an empty profile
has no members to measure.
### Report values are rounded to six significant figures
The oracle records what OpenSCAD's `echo` printed — C's `%g` at default
precision — not full-precision geometry. `echo_num()` does this; `echo_vec()`
handles vectors, which reach the oracle as strings like
`'[20.5209, 20.5209, 20.5209]'`.
Load-bearing, not cosmetic — and it is now the basis of the tolerance model.
Because the oracle holds six significant figures, the last recorded digit of
an area near 200 mm² is worth 0.001 mm², and the whole measured disagreement
between port and reference is one, two or three units in that place. See
`docs/ACCEPTANCE.md`.
Historical note, since it was the visible symptom for two sessions:
`VOLUME_MM3` ends in `_MM3`, so the old comparison judged it at the lengths
tolerance of 1e-4 against a magnitude near 20,000 — 5e-9 relative, demanded
of discretised geometry, by accident of key naming.
### Angles are degrees; the arbitrary constants are contract
OpenSCAD trigonometry is in degrees. The port keeps degrees throughout with
explicit `cos_d`/`sin_d`/`tan_d` so every expression matches its source line. The
44 solver iterations, the 0.999 and 0.98 scale factors, the 0.05/179.95 degree
cutoffs and the 1e9 sentinel are reproduced exactly. They produced the frozen
values.
### Environment
Shapely 2.1.2 on GEOS 3.13.1 — keep these in step, boolean results on
near-degenerate geometry can shift between GEOS releases. numpy 2.4.6. Shapely is
in `requirements-base.txt`, the 2D path's own dependency set.
**A CAD kernel is installed in CT 100.** `cadquery` and `OCP` both import from
the venv — verified 2026-09-13 by `importlib.util.find_spec` under
`/var/www/mechcomp/venv/bin/python`. `build123d` does not, and correctly so:
`requirements-cad.txt` names it as a viable alternative on the same OCCT kernel,
not as an installed package. The file declares one requirement, `cadquery>=2.4`,
and it is satisfied.
An earlier version of this paragraph said all three were absent. That was true
when written on 19 AUG and was carried forward by hand for three weeks, while
`STAGING-STATE.md` §5 recorded `cadquery 2.8.0` in the venv for the same period.
Two documents, each stating it as fact, disagreeing. See DIV-005.
**The 2D path must still import no CAD kernel**, and that is asserted by test —
`ACCEPTANCE.md` E-8. Separability is enforced by the suite, not by the kernel
being absent, and `requirements-cad.txt` says so itself: the suite must pass
with that file absent.
**STL export does not need one, and an earlier version of this section implied
it did.** A member here is prismatic by definition — a 2D section swept along a
straight axis — so an STL is two triangulated caps plus a quad strip down the
boundary. That is not a kernel problem.
Verified in CT 100 on 11 SEP: `shapely.constrained_delaunay_triangles` is
present in Shapely 2.1.2 and triangulates a polygon **with a hole** correctly —
summed triangle area equals the polygon area exactly, and no triangle falls
inside the hole. The second check is the one that matters: a triangulator that
fills bores would produce an STL that looks right in a slicer and prints solid
where the conduit goes.
STEP does need the kernel. `requirements-cad.txt` pins `cadquery>=2.4` for it.
### What is verified against the reference
**Every dimensional quantity is exact to 1e-4 mm across all 123 cases, with no
exceptions.** `ENVELOPE_X_MM`, `ENVELOPE_Y_MM`, `MIN_WALL_ACTUAL_MM`, and every
profile extra: `AF_*`, `FIN_*`, `SPOKE_*`, `RING_*`, `T_*`. Every count is exact.
**All ten rejections fire correctly**, including the bespoke A Frame and T paths,
which were written from the join primitives rather than from a shared
arrangement. That is strong evidence those two are structurally right.
Only `SECTION_AREA_MM2` and its two derivatives ever disagree. See §7.
---
## 6. Probing the reference directly
The pinned toolchain image is in CT 100 and OpenSCAD will echo whatever you ask
it. This settled F-034 in four exchanges and will settle any later disagreement
the same way. **Prefer it to reasoning.**
Mount the tree **read-only** and keep the probe on a separate writable mount —
Docker runs as root, and a read-write mount against a `mechcomp`-owned tree is
the F-033 hazard. A nested single-file mount inside a read-only mount fails with
`EXIT=125`; give the probe its own directory.
```bash
pct exec 100 -- bash -c 'mkdir -p /tmp/probe && cat > /tmp/probe/p.scad << "EOF"
include </repo/legacy/openscad/lib/sb-core.scad>
$fn = 48;
g = sb_geo(15.875, 0.508, 1, 0.25, 1.20, 1.20, 1.20, 1.20);
echo(str("PROBE=", sb_area([sb_sleeve_path(sb_member(0,0,0), g)])));
EOF
docker run --rm -v /var/www/mechcomp:/repo:ro -v /tmp/probe:/probe -w /probe \
mechcomp/reference-toolchain:8.0.0 \
openscad -o /tmp/o.stl --export-format=asciistl p.scad 2>&1 | grep -E "PROBE|ERROR"'
```
`EXIT=1` is normal — STL export fails on 2D geometry, the echoes still arrive.
**The oracle itself is stable.** The gate was run on 19 AUG: all 123 cases
regenerate byte-identically inside the pinned image, 113 accepted and 10
rejected, the only diff being `frozen` and the hash containing it.
`verify.sh --full`'s restore path was broken, is now fixed, and is **still
unexercised** (F-033).
**Instrumenting the port is equally cheap.** Monkeypatching
`mechcomp.geom.rounding._circlecorner` to print its half-angle and segment count
turned the F-034 diagnosis from inference into measurement in one command. The
module calls it through the module global, so patching the attribute works.
---
## 7. F-034 — measured, decided, closed
**The port is exact. The reference is noisy.** That is the opposite of what the
entry previously implied, and it changes what can be done about it.
`_circlecorner` computes `raw = (90 - angle)/180 * segs(r, None, fn)` and takes
`ceil(raw)`. At `$fn = 48` a 90° corner has half-angle 45, and `(90-45)/180*48`
is **exactly 12**. A ceiling on an exact integer is a knife edge.
Measured: the port prints `half=45 raw=12 ceil=12` at `%.17g` — landing dead on
the integer, every call, both families. The reference's arithmetic lands a hair
under 45° at some corners, pushing `raw` fractionally above 12 and the ceiling to
13. On Rectangle: **reference envelope 50 vertices, port 48** (`13+13+12+12`
against `12×4`).
**Do not try to fix this.** Rounding before the ceiling was tried and reverted —
it fixes Y exactly and breaks Three-Fin, because Three-Fin has the same asymmetry
and the oracle records it. There is nothing to correct on the port's side.
### Scale of the effect
30 of 113 accepted cases affected. 83 exact. Only three keys ever breach:
`SECTION_AREA_MM2` (29), `VOLUME_MM3` (30), `MASS_G` (22) — and volume is area
×100, mass is volume ×density/1000, so each case has **one** discrepancy reported
three times. **Worst relative error 2.24e-05.**
By profile: Three-Fin 10, Y 7, A Frame 6, Rectangle 5, T 1, Four-Fin 1. None on
Equilateral, General Triangle, Square, Diamond or Cross.
An earlier version of this line read Three-Fin 9 and A Frame 5, summing to 28
against the stated total of 30. Measured and corrected — `docs/ACCEPTANCE.md`
§7 and F-034's resolution carry the same figures. The correction had been
recorded in both of those for three weeks while the wrong numbers stayed here,
in the document a successor reads first.
### Why the test fails when the geometry is fine
At `$fn = 48` a chord deviates from its true arc by `r(1 − cos 3.75°)` =
`r × 2.1413e-3`: **2.68 µm at r=1.25, 4.28 µm at r=2.00.** The port and the
reference differ from *each other* by at most ~0.4 µm. Against the 0.01 mm
criterion (§3) that is two orders of margin.
The tests fail because `VOLUME_MM3` is compared at 1e-4 **absolute** against a
magnitude near 20,000 — demanding 5e-9 relative agreement from discretised
geometry. `3x/Y/steel0.79` breaches on `VOLUME_MM3` alone while its area passes:
the same discrepancy, judged by two wildly different standards by accident of key
naming.
### The options, and the constraint
**Do not edit `tolerance` in the oracle JSON.** It is inside the hashed document;
`test_integrity_hash` covers everything but `fixtures_sha256`. Editing it breaks
that test by design.
The change belongs in `test_oracle.py`, which is not hashed:
1. **Scale-aware bounds for the three discretisation-limited keys**, derived from
the 0.01 mm criterion and the section perimeter. Most faithful to where the
error originates. Recommended.
2. **A relative floor** — pass if within absolute tolerance *or* ~1e-4 relative.
Simplest; loosens `SECTION_AREA_MM2` from 0.001 to ~0.02 mm².
3. **Accept 30 known failures.** Honest but `make test` is never green and a real
regression hides among them.
**Closed 22 AUG.** Option 1's *scope* was kept — the three keys, the change in
`test_oracle.py`, the oracle JSON untouched — but its *derivation* was
rejected on measurement and replaced.
Measuring `|dA| / P`, the boundary displacement that would produce each area
discrepancy, gives a maximum of 1.434e-05 mm: one seven-hundredth of the
0.01 mm criterion. A `perimeter × 0.01` bound would have been 1.86 mm² at the
smallest section and 4.54 mm² at the largest, between 1,800 and 4,500 times
the worst real discrepancy, and would have caught nothing. Perimeter also
anti-correlates with the error — the largest discrepancy is at P = 209 mm and
the smallest at P = 454 mm — because the error is driven by how many corners
tip from 12 segments to 13, not by boundary length.
What replaced it: **eight units in the last place of the oracle's
six-significant-figure record**, for those three keys only. Worst case uses
37.5% of its bound, mutation-tested before landing. **`docs/ACCEPTANCE.md` is
the specification** — read it before touching any tolerance, and note E-4:
everything that positions material stays exact at 1e-4 mm and must never be
moved into that regime.
---
## 8. Method that has earned its keep
**Read the pinned source; do not recall it.** BOSL2 was fetched at
`92d697c2856de2fed93a33e858068589cefc2898` and read directly. Every function
examined had a detail that mattered and that recollection would have got subtly
wrong. The same applied to the generators: reading `sb-profiles.scad` before
writing `four_x.py` confirmed the call signatures rather than inferring them from
call sites.
**Check the invocation before concluding anything about state.** A `su` that
failed identically on two commands read as a repository problem and was an
invocation problem. That is the F-027 pattern and it cost a session's opening
exchange.
**Verify your own arithmetic before shipping it.** A claim in `PRECISION.md` that
a 50 mm radius needs `facets ≈ 460` was wrong — deviation falls with the *square*
of the segment angle, so the count grows with the square root of radius and the
answer is 158. A three-line script caught it.
**Mutation-test every suite before landing it.** Break the code deliberately and
confirm the tests notice. This found real gaps in five of six slices. It also
caught a malformed mutation of mine — cutting the cavities twice is idempotent.
**A surviving mutation is sometimes a bad mutation, not a test gap.**
**Set `PYTHONDONTWRITEBYTECODE=1` and clear `__pycache__` between mutations.** A
stale `.pyc` once masked a real defect, and every mutation result reported
before that was caught was optimistic by an unknown amount. A mutation that
survives because the test ran old bytecode is indistinguishable from one that
survives because the test is weak — F-027 in different clothes, and the reason
that rule is general rather than about one harness.
**Read-only before write.** Every command group where the answer was not certain
established the facts first.
**One task, one command group, wait for output.** Not a menu of next steps. If
you find yourself writing "and also", delete it.
---
## 9. What the project is for
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).
**A node is not an assembly.** It is another artifact — non-prismatic, carrying
several declared interfaces — and it stays inside scope. What is out of scope is
positioning artifacts relative to one another, which is field work and belongs
to whoever is holding them. `PRECISION.md` §7 states the ruling and the reason.
The consequence is the project's actual obligation: **make each artifact
interchangeable.** Fully specified, independently reproducible, carrying its own
declared interfaces so the field can fit it to whatever it meets. The stock
descriptor and the design record exist for exactly that, and it is the standard
any new capability should be judged against.
**The output must eventually be sealed manifolds and printable STL.** The 2D
section is where manifold validity is decided, not downstream in the CAD kernel —
`is_region_simple` is already a build-blocking check, and a self-touching outline
extrudes into something untessellatable.
**`docs/PRECISION.md` states what this compiler does not do**: no assembly layer,
no structural analysis of any kind, prismatic shapes only, no toolpaths. That
document is scope-locked to additive and subtractive manufacturing. **Requests to
widen it should be refused, not accommodated** — see its §10.
---
## 10. 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 it at face value; it is
accurate and it is not personal.
**He decides. Bring evidence and a recommendation, then stop.**
He has delegated all coding decisions — structure, algorithms, test design — and
does not want to be consulted on them. Still bring him scope, provenance, and
anything irreversible on the host. He runs every command, so nothing is
autonomous regardless.
When he says he does not understand something, the writing was unclear. Rewrite
it shorter; do not explain it again at greater length.
**He watches for scope creep and treats it as the primary risk** — "the greatest
enemy of software is not bugs, it's feature-creep". Documents and code are both
held to it. When in doubt, narrow.
---
## 11. 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 | Cross-project |
| 5 | ~~Tolerance model for the three discretisation-limited keys~~ | **Closed 22 AUG.** See §7 and `docs/ACCEPTANCE.md` |
| 6 | Is the bore's fit class per-material, or one clearance for all inserts? | CIVICVS — raised by the conduit-core requirement, §4 |
| 7 | ~~Public ingress at `dev.mechcomp.kane-il.us`~~ | **Closed 11 SEP** at `1fdb115`. Executed without the WireGuard change the work order proposed |
| 8 | TLS renewal has never been observed to succeed for this name; first due before 2026-12-10 | CIVICVS |
| 9 | The composer has no acceptance criteria of its own — only the path to it does | Architect |
| 10 | The service is world-reachable and unauthenticated | CIVICVS; §4 item 5 makes this due rather than hypothetical now that it is published |
| 11 | Should `ct-baseline.sh` check the ownership of a service's working tree? | CIVICVS — host property, raised by F-037 |
| 12 | `ROADMAP.md` §5 names the production FQDN `mechanical-compiler.manufacturing.kane-il.us`. `dev.mechcomp.kane-il.us` is production and is on printed material | CIVICVS — naming |
| 13 | `ROADMAP.md` §5 says the production proxy is **not** on the WireGuard side, because the NAT was outbound-only. The DNAT at `1fdb115` resolved that, and public TLS now terminates on `wg-pk` | Architect — reconcile |
| 14 | `ROADMAP.md` §4a says no use case crosses the Kane Fabric boundary and that neither project defines the interface, deliberately. Membership-gated export is that use case, and `IDENTITY-CONTRACT.md` defines the identity half. The *siting* interface §4a is about remains undefined and should stay so | Architect — reconcile |
| 15 | `ROADMAP.md` §4 and this document's §4 are different lists. STL export appears only here; `ROADMAP.md` §3 orders dihedral ahead of the bore and neither §4 records it | CIVICVS — priority |
| 16 | `legacy/` holds the reference tier, not superseded code — the name invites a successor to treat it as dead | Architect — cosmetic |
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.
---
## 12. Commit log
**Removed.** `git log --oneline` reports it, and the transcribed copy kept here
was ten commits behind within two days of being written — it ended at `a8081e1`
while the composer, the design record author field, STL export and the export
route had all landed above it.
Nothing derivable from the repository is kept in prose. `PROCESS.md` §10 states
the same rule for itself, and §3 above applies it to the commit hash and the
test count.