diff --git a/docs/DIVERGENCES.md b/docs/DIVERGENCES.md new file mode 100644 index 0000000..5a17d87 --- /dev/null +++ b/docs/DIVERGENCES.md @@ -0,0 +1,268 @@ +# DIVERGENCES.md + +**Requirements that stand and are not met.** + +| | | +|---|---| +| Tier | record | +| Audience | whoever is deciding what to fix next | +| Tense | past — each entry describes a moment | +| Status | 6 entries; 5 verified, 1 unverified | +| Defers to | `FAILURES.md` on what was observed; `STAGING-STATE.md` on host facts | +| Opened | 2026-09-12, from the documentation audit against `cdde394` | + +--- + +## 0. What this document is, and how it differs from `FAILURES.md` + +`FAILURES.md` records **something that went wrong**: an action was taken, it did +not work, and the entry says why. + +This document records **something that is wrong now**: a requirement was +written, it stands, and the thing it governs does not comply. Nothing failed. +Nobody was surprised. The divergence was simply never written down, because +there was no notation for it. + +That absence is the reason this file exists. `ENVIRONMENT.md` carries six markers +for a requirement's strength and provenance — **REQ**, **PREF**, **PROVEN**, +**ASSUMED**, **DEFERRED**, **INSTANCE** — and none meaning *this requirement is +not currently met*. Every entry below was visible in the documents for weeks and +recorded in none of them. + +### The rule these entries follow + +A requirement is never made true by lowering it to match what was built. The +requirement stays where it is, the divergence is recorded against it, and the +correction is owed by the thing, not by the document. + +### Form + +Append-only. Never edit an entry except to add a `Resolution` line. Each entry +carries: + +- **Requirement** — what is required, and the document and section stating it +- **Actual** — what is true at the stated commit +- **Evidence** — how that was established, and how far it was checked +- **Consequence** — what it costs. An entry with no consequence is either not + understood or not worth recording +- **Owed by** — code, configuration, host, or a decision + +Ids are assigned when an entry is recorded, not by severity. The index in §7 +carries priority. + +--- + +## 1. DIV-001 — AGPL section 13 is not satisfied on a live public deployment + +**Requirement.** Root `README.md`: section 13 obliges an offer of source to +users interacting with the software over a network, so any deployed web tier +carries a visible link back to this repository. The document calls this *a +licence obligation, not a courtesy*. + +**Actual.** At `cdde394` the served page carries no such link. + +**Evidence.** `src/mechcomp/web/app.py` read in full. The page is the single +`PAGE` constant. Its `
` holds the title and one line of description. The +document contains no anchor element except the one created in JavaScript at +download time to save the STL blob. `do_GET` serves `/`, `/api/build` and +`/m/stl`, and 404s everything else — there is no `/source` route. The composer +became publicly reachable at `https://dev.mechcomp.kane-il.us` on 2026-09-11 +(`1fdb115`, `WORK-ORDER-004`). + +**Consequence.** The obligation the front door states in its own words is unmet, +on a public name, and has been since 11 SEP. This is the only entry in this +register whose consequence falls outside the project. + +It is also the only one where the **document is right and the code does not +comply**. Every other entry is a requirement the code declined to follow for +reasons that may be sound; this one has no such reading. + +**Owed by.** Code. A visible link in `PAGE`'s header, and nothing else. + +**Priority: first.** Smallest remedy in the register, largest exposure. + +--- + +## 2. DIV-002 — the specified service architecture was never built + +**Requirement.** `ENVIRONMENT.md` §9, marked **REQ**: two units split along a +control-plane / execution-plane boundary. + +- `mechcomp.service` — FastAPI/uvicorn; serves the catalogue, accepts jobs, + returns cached artifacts, and **never runs geometry** +- `mechcomp-worker.service` — consumes the queue, runs generators, writes + artifacts + +§9 further REQs a SQLite-backed in-process job queue. §15 gate 3 requires both +units active. §1.1 calls the control-plane / execution-plane separation a +requirement *regardless of distribution*. + +**Actual.** `web/app.py` is a standard-library HTTP server that builds geometry +synchronously in the request thread. `src/mechcomp/worker/` is a 36-byte stub. +There is no queue and no database, and nothing has ever written to +`MECHCOMP_DATA_DIR`. + +**Evidence.** `app.py` read in full at `cdde394`. Its imports are `json`, `os`, +`traceback`, `http.server`, `typing`, `urllib.parse` and `mechcomp.svg`. It runs +a `ThreadingHTTPServer`; `do_GET` calls `build_payload` -> `build(...)` inline. +Its own docstring states the constraint deliberately: *standard library only — +`http.server`. No framework, no build step, no new dependency.* + +`requirements-base.txt` read: it does declare `fastapi`, `uvicorn`, `pydantic`, +`sqlalchemy` and `jinja2`, matching `ENVIRONMENT.md` §8.3 exactly. Those five are +not imported by the composer. **Not checked across the rest of `src/`** — only +`app.py` was read. + +**Consequence.** Geometry runs in the request path on a world-reachable service +with no authentication. A long build occupies a request thread; the ceiling at +`MECHCOMP_MAX_EXPORT_MM` bounds the response size but not the computation, and +`/api/build` has the same exposure with no ceiling at all. The separation §1.1 +requires is the mitigation, and it does not exist. + +Five declared dependencies are installed on every instance and appear unused. + +**Owed by.** A decision before any code. Either the architecture is built, or §9 +is formally superseded by a written decision — not edited away. + +--- + +## 3. DIV-003 — the instance is publicly reachable against three REQs + +**Requirement.** `ENVIRONMENT.md` §1.3: staging is not publicly reachable and +does not use the public FQDN, *so the promotion path is exercised rather than +assumed*. §5.2, marked **REQ** for non-production instances: no public DNS +record, and no publicly-trusted certificate — because it consumes rate limit +against a name production needs clean, and puts a development host on the +internet. + +**Actual.** `dev.mechcomp.kane-il.us` has public `A` and `AAAA` records and +terminates a Let's Encrypt certificate on `wg-pk`, expiring 2026-12-10. + +**Evidence.** `WORK-ORDER-004`, closed 2026-09-11, §1 and §2. All eight +acceptance criteria met and recorded. + +**Consequence.** Three **REQ**-marked statements are false of the running +instance, and §5.2's reasoning is a live claim nobody has rebutted: a rate limit +is being consumed against a name, and a non-production host is on the internet. + +`WORK-ORDER-004` §3 records a related open item: the first renewal for this name +is due before 2026-12-10 and **no renewal has ever been observed to succeed**. +The other four certificates on `wg-pk` are managed identically, which is +reassuring but not evidence. + +**Owed by.** A decision. Either §5.2 is superseded — the name may no longer be +"non-production", since `WORK-ORDER-004` §3 records that `dev` abbreviates +*Mechanical Compiler Developers*, the name is production, and it appears on +printed material — or the exposure is closed. The two documents currently +disagree about what kind of instance this is. + +--- + +## 4. DIV-004 — the configuration contract declares eight keys nothing reads + +**Requirement.** `ENVIRONMENT.md` §11 and `STAGING-STATE.md` §1 both carry the +`MECHCOMP_*` contract; `/etc/mechcomp/mechcomp.env` sets twelve keys. + +**Actual.** The composer reads four: `MECHCOMP_BIND` and `MECHCOMP_PORT` +(`resolve_binding`), `MECHCOMP_MAX_EXPORT_MM` (`max_export_mm`), and +`MECHCOMP_BASE_URL` (`base_url`) — each checking the process environment before +the file. + +Not read in `app.py`: `MECHCOMP_ENV`, `MECHCOMP_DATA_DIR`, `MECHCOMP_LOG_LEVEL`, +`MECHCOMP_DB_URL`, `MECHCOMP_WORKER_CONCURRENCY`, `MECHCOMP_CAD_BACKEND`, +`MECHCOMP_ARTIFACT_RETENTION_DAYS`, `MECHCOMP_SECRET_KEY`. + +The omission also runs the other way: **`MECHCOMP_MAX_EXPORT_MM` is read by the +code and absent from `ENVIRONMENT.md` §11.** It was added at `cdde394`. + +**Evidence.** `app.py` read in full. **Not checked across the rest of `src/`.** + +**Consequence.** Most of the eight belong to the architecture DIV-002 records as +never built, so this is largely the same divergence seen from the configuration +side. It is recorded separately because it is the cheapest thing here to check +and the easiest to mistake for settled: a key present in an env file, a +specification and a state document looks live from all three. + +`MECHCOMP_SECRET_KEY` deserves its own note. A secret that nothing reads is not +protecting anything, and its presence implies a session or signing mechanism +that does not exist. + +**Owed by.** Documentation, once DIV-002 is decided. §11 should state which keys +are live and which are reserved for unbuilt components, and should add +`MECHCOMP_MAX_EXPORT_MM`. + +--- + +## 5. DIV-005 — `requirements-cad.txt` declares a set that is not installed + +**Requirement.** `requirements-cad.txt` declares a CAD dependency set and, +per `HANDOFF-2026-08-19.md` §3, says it is installed by default. + +**Actual.** That same section records that `cadquery`, `OCP` and `build123d` are +**all absent from CT 100**, and notes the absence *will matter when STL and STEP +export starts*. + +STL export started. It landed at `0545b79` on 2026-09-12. + +**Evidence.** `HANDOFF-2026-08-19.md` §3, a session record, not a current +observation. **Unverified at `cdde394`.** `requirements-cad.txt` (347 b) and +`src/mechcomp/stl.py` (11,318 b) have not been read. **Do not close on this +evidence.** + +**Consequence.** Unknown until verified. Note that the absence may be correct: +`ACCEPTANCE.md` E-8 requires the 2D path to import no CAD kernel, and asserts it +by test. If `stl.py` also needs none, the divergence is only that the manifest +describes an installation that does not happen — the same shape as DIV-002 and +DIV-004, and the third instance of a manifest describing an unbuilt architecture. + +**Owed by.** Two cheap reads before anything else. + +--- + +## 6. DIV-006 — conformance has not been re-run since the host changed + +**Requirement.** `PROCESS.md` §9a, marked **REQ**: run `ct-baseline.sh` after any +change to a container or to host firewall rules. + +**Actual.** `STAGING-STATE.md` §3b records the last run as **2026-08-18**, +62 passed, 0 failed. + +Since then: a DNAT rule was added to `nat PREROUTING` and persisted +(`WORK-ORDER-004` §1, 2026-09-11), and `mechcomp-placeholder.service` was +replaced by `mechcomp.service` in CT 100. Both are changes the REQ names. + +**Evidence.** Documents only. **It is not known whether the check was run and +not recorded, or not run.** Either way the record is wrong: a conformance result +with no date after the changes it is supposed to cover is not evidence. + +**Consequence.** The conformance claim is the basis for *the three containers are +configured identically*. That claim currently rests on a run that predates two +host changes by three and a half weeks. + +`WORK-ORDER-004` §2 is partial mitigation — it verified `POSTROUTING` order +unchanged and confirmed mail still delivers after `iptables-save` rewrote the +ruleset. That is careful work and it is not the same as the baseline. + +`FAILURES.md` F-037 is also relevant: `ct-baseline.sh` exits 0 with the F-037 +condition present, so a passing run does not cover everything the standard is +believed to cover. + +**Owed by.** One run, and a dated line in `STAGING-STATE.md` §3b. + +--- + +## 7. Index + +| Id | Divergence | Verified | Owed by | Status | +|---|---|---|---|---| +| DIV-001 | AGPL §13 link absent from a public deployment | yes, `app.py` | code | **open — do first** | +| DIV-002 | Specified service architecture never built | yes, `app.py` | a decision | open | +| DIV-003 | Instance public against three REQs | yes, `WORK-ORDER-004` | a decision | open | +| DIV-004 | Eight declared config keys unread | yes, `app.py` | documentation | open, blocked on DIV-002 | +| DIV-005 | `requirements-cad.txt` vs CT 100 | **no** | verification first | open — **do not close** | +| DIV-006 | Conformance not re-run since host changed | documents only | one run | open | + +**Three of six — DIV-002, DIV-004 and DIV-005 — are the same shape:** a manifest +or specification describing an architecture that was not built, with nothing +recording the difference. That pattern is the reason this file exists, and it is +worth watching for in anything written next.