Commit Graph
51 Commits
Author SHA1 Message Date
TheRON eb537768ef ENVIRONMENT 5.4: add the DIVERGENCE marker, and record five unmet requirements
The marker vocabulary had six entries - REQ, PREF, PROVEN, ASSUMED, DEFERRED, INSTANCE. They could say how strong a requirement was and where it came from. None could say it was not being met. That gap is why five unmet REQs in this document went unrecorded for weeks: there was no notation to write them in, so nobody wrote them.

DIVERGENCE is added as the seventh, with the rule attached. A REQ that stops being met does not become a PREF and is not rewritten to describe what was built. It stands, the gap is recorded, the correction is owed by the thing. A specification that agrees with whatever exists specifies nothing. No requirement in this revision was lowered.

DIV-001 at section 14 item 11: the AGPL section 13 source link is a numbered constraint the application code will follow, and it is not met on a public deployment. DIV-002 at section 9 twice and section 8.3: the FastAPI control plane, the worker unit and the SQLite queue do not exist, and five declared dependencies are imported by nothing. DIV-003 at sections 1.3 and 5.2: the instance is publicly reachable against three REQs. DIV-004 at section 11: eight declared keys are read by nothing.

Section 11 also gains MECHCOMP_MAX_EXPORT_MM, read by the code since cdde394 and declared nowhere. That is the divergence running the other way and the harder one to notice, because a missing key looks like nothing at all.

Section 5.2 records that the divergence may be a misfiling rather than a violation. WORK-ORDER-004 section 3 says dev abbreviates Mechanical Compiler Developers, that the name is production, and that it appears on printed material. If that reading holds, 5.2 never applied to this instance and 5.3 does. Written as a decision, not a correction.

Section 1.1 gets a note that it was right about CadQuery all along. DIV-005 was opened against this paragraph rather than against HANDOFF section 5, which had it backwards for three weeks.

Section 15 gate 3 now states its status: dependencies, toolchain image and scaffolding all met, worker unit not met, gate does not pass. Section 19 records the port complete since 20 AUG, retained because the rejected cases being part of the contract is why the oracle means anything.

STAGING-STATE specification reference updated from revision 5 to 5.4.

Applied by anchored patcher. The first attempt failed on one anchor - the env block comment column was 35, not 39 - and nothing was written, including the thirteen correct patches. Suite 643 passed, oracle intact. Documentation only.
2026-09-14 04:26:44 -05:00
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
TheRON 61b8f1eb4c HANDOFF: reconcile against the code, and delete what git already reports
Section 3 said main was at ee3fedf with 547 passing. Section 12 transcribed a commit log ending at a8081e1, ten commits behind. Neither is corrected; both are removed. git rev-parse, git describe and git log report them, and the header already explained why the hash line could never be right - it is not known until the commit is made, so the value was always the previous commit. The same reasoning applies to the rest and nobody had applied it.

Section 4 item 2 listed STL export as forthcoming work. It landed at 0545b79 and the bounded route at cdde394. The length_view gap that would have made every export a silent 100 mm preview was fixed before the route shipped. The roadmap entry still said the composer does not export anything.

Section 3 module table was missing stl.py entirely. The baseline run is now dated 2026-08-18 and marked overdue against DIV-006, since it predates both the DNAT rule and the placeholder service replacement, which PROCESS section 9a names as requiring a run.

Section 7 recorded the F-034 per-profile breakdown as Three-Fin 9, Y 7, A Frame 5, Rectangle 5, T 1, Four-Fin 1 - which sums to 28 against its own stated total of 30. The measured distribution had been recorded correctly in ACCEPTANCE section 7 and in F-034 resolution for three weeks, while the wrong numbers stayed in the document a successor reads first.

Section 0 reading order gains DIVERGENCES.md and ENVIRONMENT.md. ENVIRONMENT.md was missing from it for three weeks while PROCESS section 8 carried ENVIRONMENT and omitted HANDOFF instead. Two lists, neither complete, each looking authoritative. Both now carry both.

PROCESS correction: the amendment rule added at 15e90fd claimed a document replaced rather than amended loses content, citing HANDOFF.md. Checked today against both archived handoffs - it had lost nothing. The dihedral gap, the three artifact classes, the cross-project mail question and the toolchain probe recipe are all still present. The hazard is structural, a rewrite has no diff, but the loss I asserted did not happen. Corrected rather than left standing.

Applied by anchored patcher, all-or-nothing across both files. Suite 643 passed, oracle intact. Documentation only.

Known cosmetic defect: the baseline paragraph in section 3 now has two adjacent bold spans and a line over 80 columns, because the anchor stopped mid-paragraph. Renders correctly. To be rewrapped with the next patch that touches the file.
2026-09-13 15:39:01 -05:00
TheRON 15e90fd8cf PROCESS: write down the delivery chain, and stop transcribing derivable state
New section 3a records the whole path from an assistant producing something to it existing in Gitea. It has been explained conversationally to every new assistant for months and was written in no document, which is the reason it is now here. Section 0 claims this document describes how work gets done; the part it did not describe was how work actually arrives.

3a covers: nothing but the operator writes, because the assistant reads Gitea read-only and the operator is the only party with write access anywhere in the chain. Three command groups rather than one, because section 4 requires a suite result the assistant has actually seen before a commit exists. The shell-free idioms - make -C, git -C, runuser - because pct exec runs no shell. Commit messages parsed by the host shell before pct sees them, so repeated -m flags and no dollar sign, backtick or exclamation mark. Which failures are passes, because an operator who cannot tell success from failure cannot report usefully.

3a also records the amendment rule: do not ship a rewritten file to change a few passages of a large one. Ship an anchored script that validates every anchor matches exactly once and writes nothing if any does not. This commit was made that way. A rewrite regenerates the document from the assistant reading of it and a transcription error is silent - the same hazard as rewriting HANDOFF.md in place, one level down.

Section 4: the oracle check and git status are one check, not two (F-033). And the two interpreters are deliberate - verify-oracle runs python3 because make_fixtures.py imports nothing outside the standard library, test runs venv/bin/python because the suite imports mechcomp, Shapely and pytest. Neither is a mistake to be tidied into consistency.

Section 8: the reading order was missing HANDOFF.md for three weeks while HANDOFF section 0 carried a different order missing ENVIRONMENT.md. Two lists, neither complete, each looking authoritative. Both now name DIVERGENCES.md. The authority ladder gains the rule underneath it: a document that describes something yields to the thing it describes, a document that prescribes something does not. That dissolves the apparent contradiction with deploy/README.md, which is correct to claim the repository wins for the files it owns.

Section 8 also qualifies promote-the-deviation. It applies to facts about a host, not to a requirement the code has not met. A REQ is never lowered to match what was built; the gap is recorded in DIVERGENCES.md and the correction is owed by the code. A specification that agrees with whatever exists specifies nothing.

Section 10 described 18 AUG: repository at its seed commit, port not started, 3 passed and 236 skipped. It now states that it no longer records a commit, a version or a test count, because git rev-parse, git describe and make test report those and every attempt to hold them current in prose went stale.

Suite 643 passed, oracle intact at 113 accepted and 10 rejected. Documentation only.
2026-09-13 15:17:28 -05:00
TheRON 3ccec9ab40 Record six divergences between the documents and what is running
A register of requirements that stand and are not met. FAILURES.md records something that went wrong: an action was taken and it did not work. This records something that is wrong now: a requirement was written, it stands, and the thing it governs does not comply. Nothing failed and nobody was surprised, which is why none of it was written down.

That absence has a mechanical cause. ENVIRONMENT.md carries six markers for a requirement 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.

DIV-001: the AGPL section 13 source link is absent from a public deployment, verified against web/app.py. DIV-002: the two-unit control-plane and worker architecture in ENVIRONMENT.md section 9 was never built. DIV-003: the instance is publicly reachable against three REQs. DIV-004: eight declared MECHCOMP keys are read by nothing, and MECHCOMP_MAX_EXPORT_MM is read and undeclared. DIV-005: requirements-cad.txt against CT 100, unverified, do not close. DIV-006: ct-baseline.sh has not run since two host changes.

DIV-001 first. Smallest remedy in the register, and the only entry whose consequence falls outside the project. It is also the only one where the document is right and the code does not comply.

Three of six are the same shape: a manifest or specification describing an architecture that was not built, with nothing recording the difference.

From a documentation audit reading all 18 documents against cdde394. No code, tests, fixtures or configuration changed. Suite 643 passed, oracle intact at 113 accepted and 10 rejected.
2026-09-13 15:01:37 -05:00
TheRON cdde394bd8 composer: /m/stl, a bounded export route
The route lives under the gated prefix from IDENTITY-CONTRACT.md section 5, so
gating it later is a proxy change and not a code change. It ships OPEN, because
/m/ is not yet gated and no membership system exists to gate it -- recorded in
section 8 of that document and cited in the module docstring, so a successor
reads it as a decision rather than an oversight.

model/stl with a Content-Disposition filename carrying the input_id. The
browser saves it without JavaScript assembling a blob on the happy path. The
route rebuilds from the query parameters rather than caching what /api/build
just made: the file is a function of the URL, and the composer goes on holding
no state between requests. Persistence is its own piece of work and should not
arrive here by accident.

MECHCOMP_MAX_EXPORT_MM bounds it, defaulting to 3048 -- one ten-foot member.
The ceiling exists because this endpoint is unauthenticated on a public name
and member_length_ft is an unbounded number whose value alone decides the size
of the computation and the download. /api/build has the same exposure with a
constant-size answer; export is where bounding becomes worth it.

3048 is not a claim that longer members are wrong. A hundred-foot member is a
real artifact and nobody prints one in a piece -- it gets sectioned. The export
ceiling and the member catalogue answer different questions and conflating them
would be the mistake. The limit is configuration rather than a query parameter
because a limit the caller can raise is not a limit, and an unparseable or
non-positive setting falls back to the default rather than disabling the bound:
a typo must not leave a limit that exists in the documentation and nowhere else.

Over the ceiling is a 413 naming the length, the limit and the key, and it
refuses rather than truncates. A truncated export would ship a 3048 mm file
whose design record describes a 30480 mm member -- the same silent wrongness
the length_view control was added to prevent, arriving by a different door.

ProfileRejected is a 422 with the reason intact, an unknown family a 404, and
only a genuinely unexpected exception a 500. Refusals are text/plain so the
download control can show them and a person who hit the URL by hand can read
it. A rejection is the compiler working.

Two things changed while building rather than after. The type coercion was
extracted from build_payload into typed_overrides and is now shared: had the
export coerced differently from the view, the downloaded file would not be the
part on screen, and neither would have looked wrong on its own. And the button
re-sends the query the current drawing came from rather than reading the
controls when pressed, so a half-typed number in a text box cannot export
something that was never displayed.

The page's JavaScript was parsed with node --check before landing. The
download handler rebalanced braces around the data.ok block, which is exactly
the kind of edit that compiles as a Python string and breaks in a browser.

16 tests. Mutation-proven: never applying the ceiling fails 4, truncating
instead of refusing fails 4, a bad environment value disabling the bound fails
5, the export using its own coercion fails 1, a rejection becoming a 500 fails
2. Restoration verified by checksum, PYTHONDONTWRITEBYTECODE=1 throughout.

One narrow margin worth recording: the shared-coercion guarantee rests on a
single test, test_the_export_is_the_part_the_view_shows. It is the only thing
that failed under M4. Deleting it would silently remove the only check that the
file matches the drawing.

Suite 643 passed.
2026-09-12 11:00:11 -05:00
TheRON 0545b79674 stl: export a member as a sealed mesh
prism_mesh takes a region and a length, not a Result. A member, its support, or
the support alone are then three calls with different regions rather than three
special cases inside one function -- support is a designed part of an artifact
where no printable orientation exists, not something a slicer adds, and
flattening everything into one mesh now would be expensive to undo later.

Named and documented as a MEMBER exporter. ROADMAP section 2 has three artifact
classes: members are prismatic and exist, nodes are non-prismatic and do not.
A prism sweep will never produce a node and no amount of extending this should
be attempted. A module called stl.py would invite exactly that.

No CAD kernel. Caps are a constrained Delaunay triangulation of the section
with its holes, walls are a quad strip per ring. That is why STL costs no new
dependency while STEP would.

Winding is forced, never assumed. region_parts returns each part outer-clockwise
with holes counter-clockwise because region_area depends on that to make holes
subtract; a prism swept along +Z needs the opposite, since traversing the outer
boundary counter-clockwise puts material on the left and the wall normal on the
right, which is outward. Both are reversed here.

The identifiers in the 80-byte header are PROVENANCE, NOT A CHECKSUM. Principle
4: mesh bytes are not reproducible across toolchain versions, so two exports
carrying the same build_id may differ byte for byte and both be correct -- a
GEOS release can move a triangulation without moving the geometry, which is the
F-034 mechanism applied to tessellation. The docstring says so where someone
diffing two exports will find it. Facet normals are written as zeros: every
consumer recomputes from vertex order, and a stored normal would be a second
source of truth for which way a face points.

A section failing is_region_simple is refused rather than exported. Its own
docstring names the case: an outline that measures perfectly, touches itself at
a point, and cannot be sealed. Emitting it would produce a file that opens in a
slicer and prints wrong.

member_stl defaults to the report's LENGTH_MM, the value the record's
VOLUME_MM3 and MASS_G were computed against. Any other sweep makes the record
describe a different object than the file beside it.

61 tests across all eleven profiles in both families, four properties each,
every negative assertion with a positive control. Edge pairing catches a missing
wall and inconsistent winding; Euler catches a filled or invented hole; area
overlap catches a bore paved over; volume against region_area times length
catches inverted normals and a wrong length together.

Two test defects found by mutation and fixed before landing, both worth
recording because both passed while being wrong.

The hole check originally tested triangle centroids. Ignoring holes at
triangulation time slipped through it -- two triangles can pave a bore with
both centroids outside it. Replaced with intersection area, which catches it.

The sweep-length test built at the family defaults, where preview_length_mm is
100, so a mutation hardcoding 100.0 passed the entire suite: the assertion was
comparing a value against the constant that had replaced it. Now parametrised
over 37.5 mm and 3048 mm, and proven against hardcoded mutations at both
values, each caught by the other case.

Mutation results against the real sections: holes not reversed fails 23,
exterior-only triangulation fails 45, outer ring not forced CCW fails 25,
simplicity precondition removed fails exactly 1 -- the refusal test written for
it. Restoration verified by checksum rather than by a green run, after a
same-length mutation plus a cached .pyc produced a false clean reading earlier
in the session. PYTHONDONTWRITEBYTECODE=1 throughout, per HANDOFF section 8.

Suite 627 passed. None of the 566 moved.
2026-09-12 10:40:35 -05:00
TheRON ecac644204 composer: enumerated parameters, and length_view becomes reachable
length_view was in both families' defaults and in neither COMMON_GROUPS, so the
Full Length branch of model_length_mm could not be reached from the composer.
Every model was a 100 mm preview whatever member_length_ft said. Found while
specifying STL export: the sweep must equal model_length_mm(p), the value
already published as LENGTH_MM, or the record's VOLUME_MM3 and MASS_G describe a
different object than the file beside them.

Adding it to the list would have been one line and the wrong fix. The type
coercion falls through to the raw string for anything that is not a bool, int
or float, and model_length_mm compares against the literal "Full Length" and
silently falls back to preview for anything else -- the reference behaved the
same way, so the port is right to keep it. A free-text box would have replaced
an unreachable control with a silent one: "Full Length " with a trailing space
builds a 100 mm model and says nothing.

So ENUM_PARAMS declares which parameters take one of a fixed set of values.
The browser renders a select instead of an input, and a value outside the set
is dropped before the build rather than passed through -- the family default
stands, which is a valid model. The guard sits before the coercion, not after,
where it would be dead code. The next enumerated parameter needs no new
machinery.

Deliberately not mirrored as a check in the families. geometry_checks is shared
with the oracle and the reference accepted any string here; adding a check
there would put the 123 frozen cases at risk to fix a user-interface problem.
The guard belongs in the composer, which is not oracle-bearing.

19 assertions, mutation-proven: removing the guard fails 8 of them. Six are the
parametrised bad values. The other two assert the mechanism rather than its
effect -- that the string never enters values at all, and that the fallback
reaches input_id. Without them, a pass-through that happened to be ignored
downstream would look identical to a working fallback, and a later change to
model_length_mm would turn a passing test into a defect somewhere else.

length_view is a build parameter and stays in both hashes, unlike the author.
Two members of different lengths are different designs, and a test asserts it.

Suite 566 passed, of which 19 are new. None of the 547 moved.
2026-09-12 10:19:47 -05:00
TheRON 56e0570f10 roadmap: two principles, and the contradictions reading it turned up
ROADMAP.md was described as a reference rather than a commitment -- something
that documents intent and will eventually become a roadmap. The header now says
so, and says that HANDOFF section 4 holds the actual order. Sections 1, 2, 3 and
7 are durable and still correct; section 4's sequence and section 5's production
assumptions have drifted since 18 AUG.

Principle 17: nothing approximate may write to anything reproducible. Principle
4 governs what is hashed; this governs what may reach the thing being hashed. A
search index, a documentation system or a language model may read design records
freely and may suggest anything to a person, but none may originate a parameter
that lands in a record. The failure is silent and compounding -- if suggestions
become records and records become the corpus, the corpus teaches itself its own
guesses, and every record remains perfectly correct about what it was built
from. Where a suggestion informed a build, the record's note field says so,
being already excluded from both hashes. No new field is needed and the
discipline is written down before there is anything to guard against.

Principle 18: a contribution is admitted by the oracle, not parsed by the
compiler. Author in whatever notation suits you, freeze the cases inside the
pinned toolchain, port, and admit when the oracle passes byte-identically. This
has happened twice, with both .scad generators -- it describes what was done
rather than what is planned. Two consequences: no second geometry engine is ever
kept in step with the first, and legacy/ holds the reference tier rather than
superseded code. When someone asks for another input format, the answer is that
notation never runs in production.

A third principle was drafted and dropped -- that artifacts cross a service
boundary and geometry does not. Section 4a's own rule is that use cases precede
interface proliferation, and writing a principle about a kernel service boundary
while the kernel is not a service is precisely that. It is a gate, not a
principle, and the kernel can acquire one when it acquires a boundary.

HANDOFF section 11 gains rows 12 through 16, all found by reading ROADMAP.md
properly rather than grepping it. Section 5 names a production FQDN that the
printed name contradicts. Section 5 says the production proxy is not on the
WireGuard side, for a reason the DNAT at 1fdb115 removed. Section 4a says no use
case crosses the Kane Fabric boundary and that neither project defines the
interface -- membership-gated export is that use case, and IDENTITY-CONTRACT.md
defines the identity half, while the siting interface 4a is actually about
remains undefined and should stay so. ROADMAP section 4 and HANDOFF section 4
are different lists. And legacy/ is misnamed.

None are resolved here. Two are CIVICVS's, two want the whole repository in view
at once, one is cosmetic. Recorded so the reconciliation pass finds them written
down rather than rediscovering them cold.
2026-09-12 10:07:04 -05:00
TheRON c72036cbbb docs: the ACL leaves the roadmap, and the development-name excuse is withdrawn
Four documents brought into line with what landed at 54f0296 and ee3fedf.

HANDOFF section 4 item 5 said the ACL was deliberately last. It is deleted, not
deferred. Authorisation lives upstream at the last proxy hop, decided against a
membership system this repository knows nothing about, so the compiler will
never 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. An item left the roadmap rather than moving down it.

Item 4 claimed persistence was the prerequisite for a library of saved designs
and for access control. The second half has been false since the identity
contract landed and was found by reading the anchor rather than recalling it.
Corrected, and item 4 now states the position that follows: 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. 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 walking files cannot
answer -- "every design by this author since March" is that query, and it
arrives with membership, not before.

"Acceptable for a development name" is withdrawn from HANDOFF section 4 and
WORK-ORDER-004 section 3. The name is production, dev abbreviates Mechanical
Compiler Developers, and it appears on printed material. The posture is
unchanged -- world-reachable, unauthenticated, /m/ not yet gated, STL export
will ship open -- but it is carried openly as open question 10 instead of
excused. The phrase survived three documents and two earlier corrections
because it was plausible and nobody challenged it, which is the same mechanism
that produced the stale ingress paragraph.

Section 0 gains the two new documents, with CONSUMER_INTERFACE_GATES.md marked
read-before-proposing-an-integration: several tempting cross-project moves are
recorded there specifically as things not to build yet, and a successor who
finds them independently will be tempted to solve them. Also a note that
deploy/ now holds the unit and the vhost as they actually run, and that the
repository is the single source of truth -- read a container when you suspect
drift, then fix the drift here rather than on the host.

HANDOFF section 1 and PROCESS section 9 gain the same rule: pct exec runs no
shell. A glob, redirect, pipe or && is expanded by the host shell against the
host's filesystem and the container receives whatever literal survives, so an
unwrapped glob reports "No such file or directory" and reads as a broken
container. Third instance of this class after F-035 and F-036 -- each time the
tool was invoked wrongly and the error named the wrong subject. Not filed as a
new failure: it is the same finding as those two, and a third entry would
record the instance rather than the pattern.

STAGING-STATE records the deployment configuration as committed. The unit had
been marked delivered at deploy/mechcomp.service on 11 SEP while existing only
on the host.
2026-09-12 09:48:22 -05:00
TheRON ee3fedf794 docs: our side of the cross-project gate register
Kane Fabric recorded a gate register against this project at its 5df8801. This
is the same instrument pointed the other way, plus the defects that review
found in our own contract.

A gate is a disagreement, an unmade decision, or an assumption two projects
hold differently and have not had to reconcile. Recording one is not scheduling
work on it. The failure this prevents is specific: when two systems meet, the
pull is to fix the mismatch immediately, usually by one side quietly adopting
the other's model in a commit that looks like integration and is actually a
surrendered boundary.

G-1 and G-2 are the two defects, corrected in the previous commit and recorded
here because the class of mistake will recur.

G-3 is the one that does not dissolve. This compiler records a stable
identifier in a document built to stay legible for years; the civic direction
is address-bound, opaque, epoch-scoped participation that deliberately avoids
durable person identity. The mechanical part is already free -- Author.email
takes an opaque token without a schema change. What remains is that a record
naming a token whose epoch has closed has an author line nobody can ever
resolve again. That may be exactly right as privacy policy and is still a
permanent loss of provenance for a physical object somebody is holding. Two
goods in conflict, owned jointly, settled by neither side writing its document
second.

G-4 and G-5 are the residue of the two new invariants: nothing verifies that
exactly one hop decides, and fail-closed makes the eligibility endpoint a hard
dependency of every gated route. Both recorded rather than designed around.
G-6 notes that a shape is not a vocabulary -- two deployments can comply and
still disagree about what "verified" means, which is tolerable only while the
record states the method verbatim and claims nothing beyond it.

N-1 through N-4 record what was raised against us that is correctly not ours:
buildings against delivery-point geography, the local secure origin MS5-004
needs, the wg-pk fleet question, and credential hierarchies. Written down so a
successor does not mistake them for work or rediscover them as new.

Section 4 records that both projects independently drew the same junction --
geography, then a membership system, then a minimal decision, then this
compiler, one direction only. That is the strongest evidence available that the
shape is right. It also states where the two must not meet: if this compiler
ever reads parcels, delivery points or buildings, the membership layer has been
bypassed and every gate here is void.
2026-09-12 09:35:00 -05:00
TheRON 54f0296e6f identity contract: neutral headers, and the chain is not one hop
External review of bac6120 by the Kane Fabric project found two defects, both
mine, both cheap now and expensive once anything implements against them.

The contract gave kane-fabric/oidc as an example authentication method. That
named a capability in another project which has no OIDC service, no user
database and no person-authentication role at all. An example in an interface
document is read as an expectation by the next person to implement it -- the
same failure as "acceptable for a development name", a plausible clause nobody
challenged hardening into a constraint. Method is now specified by shape rather
than by example, and the document names no system outside itself.

The headers were X-Kane-Auth-Email and X-Kane-Auth-Method. Two things wrong: a
jurisdiction in a header name is a deployment fact in an invariant place, in a
document that spends a section insisting the compiler must never learn a
deployment's membership concepts; and -Email named a format in a field the
contract explicitly allows to hold something else. Now X-Mechcomp-Auth-Id and
X-Mechcomp-Auth-Method. The receiver is the invariant, the jurisdiction is not.

Section 3 now says plainly that the identifier need not be an email address. An
opaque or epoch-scoped token fits Author.email without a schema change; the
field is named for what this deployment holds, not for what the contract
requires. Renaming it would be a format change to every stored record and is
deliberately not done.

Two new invariants, both from taking seriously that containers owned by other
projects will insert themselves into this chain.

I-1: exactly one hop decides, and it is the last before the application. Two
intermediaries both setting the identity headers is a forgery vector wearing
the costume of a deployment change -- the later wins, the earlier believes it
decided, nothing reports the conflict. CT 101 is named in section 2 because it
is what exists, not because it is the invariant.

I-4: anything that is not an affirmative permission is a refusal. Unreachable,
timed out, malformed and 5xx all deny. Fail-open and fail-closed are both
defensible and are not the same system; finding out which one was built during
an outage is the worst way to learn it.

Section 6 gains parcels and delivery points explicitly, so the boundary holds
whichever primitive the geography layer settles on. Section 7 no longer obliges
any named project to provide anything -- the authorisation decision belongs to
a membership system between geography and this application, and nothing here
asks a geography layer to become an identity provider.
2026-09-12 09:34:57 -05:00
TheRON bac6120605 deploy: the running configuration, and the identity contract
STAGING-STATE recorded mechcomp.service as delivered at deploy/mechcomp.service
on 11 SEP. The unit was real; the directory was not. It existed only on CT 100,
so the repository claimed to hold something it did not, and the only way to
answer a question about the service was to read the container. The nginx vhost
had never been committed at all.

Both imported verbatim as they ran on 12 SEP, with their host checksums proven
equal at import. A first commit of a configuration file records reality, not
intentions -- every later improvement is then a diff against a known-good
starting point rather than a rewrite nobody can check.

mechcomp.env is deliberately absent. It is the one file that is supposed to
differ between instances, it is root:mechcomp 0640 because a deployment's
bindings belong to the deployment, and a committed copy would create a second
source of truth for exactly the wrong file. ENVIRONMENT.md documents the keys.
Certificates and keys likewise.

IDENTITY-CONTRACT.md is the boundary between this application and the
membership system, and the only document here written to be read by someone who
does not work on this repository. Two systems that must agree on an interface
need it written once, somewhere both can point at.

The compiler does not authenticate anyone; it is told. CT 101 decides, against
the membership system, and sets X-Kane-Auth-Email and X-Kane-Auth-Method. They
map onto Author.email and Author.method, which already exist and are tested. The
parser's refusal to recover `verified` from text was built for this: a record
read back is always self-declared, because a file cannot attest to its own
verification.

The decision belongs at CT 101 and never at wg-pk. The hub carries twenty peers
and is estate infrastructure this project does not own, so authorisation there
would make every future adjustment an escalation and would teach a shared
transport about one project's membership roll. CT 101 already shares the service
bridge with CT 100 and CT 102.

One gated prefix, /m/. nginx gets one location block, written once. Gating a new
endpoint afterwards is choosing a URL in Python -- no proxy change, no
escalation. That cheapness makes the placement of the line reversible rather
than structural. Today it sits at export: the catalogue and composer are open,
and what requires membership is producing an artifact whose design record names
an author.

Section 6 is the part most likely to erode and is written hardest. The compiler
must never learn what a building is, what a membership level is, or that group
names exist -- including as a configuration value, which is how the leak arrives
by the side door. The test is that the membership system can rename every level
and replace its storage without a line of this repository being read.

Section 9 deletes the ACL from the roadmap rather than deferring it. The
compiler will not have a user table, a login form or a session.

Recorded openly rather than assumed: the service is world-reachable and
unauthenticated, /m/ is not yet gated, and STL export will therefore ship open.
Same posture the whole service already has. The earlier justification --
"acceptable for a development name" -- was wrong and is corrected separately:
dev.mechcomp.kane-il.us is production, dev abbreviates Mechanical Compiler
Developers, and the name is on printed material.

The inbound header strip depends on none of this and should land on its own. It
costs one directive and removes a forgery that becomes possible the moment the
headers mean anything.
2026-09-12 06:58:14 -05:00
TheRON 6ce8ece709 docs: F-037, the ingress corrections, and the priority order reversed
HANDOFF section 4 said WORK-ORDER-004 was pending and that CIVICVS could not
see any of this work. It was rewritten thirty-two minutes after 1fdb115 closed
the ingress, in the same session, against the work order's old state rather
than its new one -- so HEAD described a world where the thing you were already
looking at did not exist. Section 11 row 7 carried the same staleness. Both
corrected, and the correction says how it happened, because section 0 was added
to prevent exactly this and did not.

Three things 1fdb115 recorded as unsettled were never promoted into section 11
and are now rows 8 through 10: TLS renewal has never been observed to succeed
for this name and is first due before 2026-12-10; the composer has no
acceptance criteria of its own, only the path to it does; and the service is
world-reachable and unauthenticated, which section 4 item 5 called a conscious
decision to be made before publishing and which publishing has now made due.

Priority items 1 and 2 reversed. The original case for authorship first was
that records made before the field exists can never be attributed. That was too
strong, and too strong because the field is excluded from both hashes: a record
regenerated later keeps its input_id. What it does not keep is build_id, which
covers Shapely and GEOS, and boolean results on near-degenerate geometry shift
between GEOS releases -- the F-034 mechanism. Attribution is recoverable, not
free. The residual argument stands and the field landed first at 48d5665.

Recorded under item 2, because it is the first thing STL export runs into:
length_view is not in COMMON_GROUPS, so the Full Length branch is unreachable
from the composer and every export would silently be a 100 mm preview. The
sweep must equal model_length_mm(p), already published as LENGTH_MM, or the
record's VOLUME_MM3 and MASS_G describe a different object than the file beside
them. Also split the roadmap's STL/STEP bullet: STEP needs the kernel, STL does
not, and one line implying both was left behind by the section 5 correction.

F-037: a tar stream rooted at "." re-owned the repository root. tar x ran as
root and applied the "." entry's ownership to /var/www/mechcomp; the chown that
followed named only src and tests. Everything below the root was correct, so
547 tests passed and only git noticed. Corrected by chown on one path, owner
only, no -R. safe.directory was not added -- that is F-008 and would have
masked this and every later instance.

Two of F-037's three consequences are process defects of mine rather than facts
about the environment. The verification step ran before the landing that
destroyed it, so a git status ahead of the breaking step reads as a pass -- the
F-027 pattern in a new place. And section 7 says record a failure before
correcting it; I corrected first. Both recorded rather than quietly fixed.

PROCESS section 3 gains two REQs, because the delivery path as documented
produces F-037 every time: the chown must name the directory the files land in,
and a tar transport must name its top-level directories rather than root at
".". pct push is simpler for a single file and cannot reproduce it at all.

Section 8 gains the bytecode rule: PYTHONDONTWRITEBYTECODE=1 and clear
__pycache__ between mutations. A stale .pyc masked a real defect once and every
mutation result reported before that was optimistic by an unknown amount. A
mutation surviving on stale bytecode is indistinguishable from one surviving on
a weak test.

Open question 11 is new and is CIVICVS's: ct-baseline.sh exits 0 with the F-037
condition present, so by section 9a the ownership of a service working tree is
not part of the container standard. Whether it should be is a decision about
host property covering three projects.
2026-09-12 04:53:24 -05:00
TheRON 48d566574e design record: an authorship field, recorded and in neither id
The person is identified by an email address. A handle may be chosen later
and does not replace it: the handle is a display name, the address is the id.

Excluded from input_id and build_id. input_id answers whether two records
describe the same part as specified, so two people specifying the same part
must collide there; hashing the author would make identical parts claim to be
different designs. The guarantee is structural rather than careful -- the ids
are computed from the resolved parameter set and the author never enters it.

That exclusion narrows the one-way door it was scheduled against but does not
close it. 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
tie to either. Recoverable, not free. Priority order in HANDOFF section 4 is
reversed accordingly: this before STL export, so the first coupon off the
printer carries its author.

The rendered line carries its own limit -- "(self-declared, unverified)".
Nothing checks that the address belongs to whoever typed it, and a bare email
in a field called author reads as identity to someone finding the file in five
years. The record is meant to outlive everyone present, so it states what it
knows. When an authenticated identity exists the qualifier changes and the
distinction stays legible. Same discipline as Provenance.describe(), a separate
type: provenance is about measurement, and design_record imports nothing from
mechcomp, which is what keeps a failure in the record off the build path.

parse() gets its own branch because authorship is neither input nor output and
inherits neither rule. It recovers the address and never the verification: a
text file cannot attest to its own verification, so reading a verified record
back as self-declared understates the claim, which is the only safe direction.

REVISION stays 8.0.0. The field reaches no geometry.

547 passed. The 529 are unchanged and no oracle case moved. The eighteen new
assertions were mutation-tested with PYTHONDONTWRITEBYTECODE=1 and caches
cleared between runs: leaking the author into input_canonical, deafening the
parser, trusting the adjective, dropping the qualifier, and turning an empty
author into an empty claim each fail the suite.
2026-09-12 03:56:50 -05:00
TheRON a7162bc1d5 docs: assemblies are out of scope by design, and the priority order
CIVICVS ruled on 11 SEP that the absence of an assembly layer is chosen, not unfinished. Positioning is field work. Parts made and shipped by different manufacturers have no knowledge of each other assembly tolerances, and interchangeable manufacture works because of that separation rather than in spite of it. Fit up is resolved on site against conditions no designer had. PRECISION.md section 7 now says so, and closes with read this as a boundary that was chosen, not a gap to be filled.

Section 10 corrected. Its lock is on this document subject, not on the software roadmap, and it was misread that way once. Section 7 holds two kinds of entry: limits that may be lifted by work, and boundaries that were chosen and should not be. Without that distinction section 7 reads as a to do list.

Section 7 also separates geometry interchange from machine instructions. An STL or STEP describes a shape; a toolpath describes what a machine should do. The first is in scope and planned, the second is not. The two sat one line apart with nothing saying they differ in kind.

HANDOFF section 5 corrected: STL export needs no CAD kernel. A member here is prismatic by definition, so an STL is two triangulated caps and a quad strip. Verified in CT 100 that shapely constrained_delaunay_triangles handles a polygon with a hole correctly, area exact and no triangle inside the hole. That second check is the one that matters, because a triangulator that fills bores produces an STL which looks right in a slicer and prints solid where the conduit goes. STEP still needs the kernel.

Priority order recorded with its reasoning so a successor can disagree with the argument rather than only the sequence. STL export first because it is the only item producing physical feedback. Then an authorship field in the design record, one field and a one way door. Then per member stock for the conduit core. Then persistence, which nothing has ever written despite MECHCOMP_DATA_DIR being declared since staging. Then ACL, last, because access control over nothing is machinery without a subject.

A node is not an assembly. It is another artifact with declared interfaces and stays in scope. What is out of scope is positioning artifacts relative to one another. The project obligation is therefore to make each artifact interchangeable, which is what the stock descriptor and the design record already exist for.
2026-09-11 12:38:34 -05:00
TheRON 1fdb11542e state: the composer is public at dev.mechcomp.kane-il.us
browser to DNS to Lets Encrypt TLS on wg-pk to the WireGuard tunnel to a DNAT on srv-b to CT 100. Proven 200 over v4 and v6, 301 from http, and real build JSON through the full chain.

No WireGuard change and no route were made. The hub peer entry for srv-b is still a slash 32, as all twenty peers are. An earlier plan widened it to carry the service network; that was abandoned once the hub convention was read. Every vhost there proxies to a 10.110.0.x tunnel address directly, so following the convention removed the only step that could have locked the operator out of srv-b.

The srv-b side is one DNAT rule in nat PREROUTING, scoped to the hub as source so the other nineteen peers cannot reach the service network through it. Added live, proven from the hub, then persisted. Rollback copy kept. POSTROUTING order verified unchanged after iptables-save rewrote the file, and mail from srv-b was confirmed delivered afterwards because it traverses the same interface.

WORK-ORDER-004 is rewritten as executed rather than as planned. Its section 0 states that the original proposed a route and a WireGuard change, that neither was done, and why. The original text stays in history at a8081e1.

Three things recorded as not settled: renewal has never been observed to succeed for this name and is first due before 2026-12-10; no acceptance criteria exist for the composer itself, only for the path to it; and the service has no authentication. Also filed an estate finding: shell.infra.civicus.us and corpusdb.infra.civicus.us publish an AAAA one hex digit off the address wg-pk holds, so both are broken for v6 preferring clients.
2026-09-11 12:06:40 -05:00
TheRON 39a6b02c63 docs: HANDOFF corrected against the tree, and PROCESS.md put first
A new section 0 gives the reading order with PROCESS.md at the top. The handoff never mentioned it. That is the root cause of three sets of instructions the operator could not execute in one session: an SSH to a host he does not reach that way, a LAN address behind a portless bridge, and a hosts file name that resolves on three machines.

The access model is now stated rather than only defended. The settled decisions said questioning Webmin wasted a session but never recorded what the arrangement is, so it was questioned again. It now records that he works from Webmin on the hub and into the srv-b shell, and that dev.infra names and 10.20.0.x addresses are never to be given as browser URLs.

The module table filed Geo and Member under primitives.py, where they have never been, and omitted records.py entirely. Two modules were read on the strength of it and neither held what it promised. Corrected, with the error named so it is not re-derived, plus rows for stock.py, design_record.py, svg.py and web/app.py.

Sections 3 and 4 described a world with no application in it: main two commits back, 468 passing, the catalogue front end does not exist, deployment blocked on application code. Now 529 passing at a8081e1 with the composer live behind the proxy, and the next work named as per member stock, which is what the conduit core actually needs.
2026-09-11 07:05:59 -05:00
TheRON a8081e17dc state: the composer replaced the placeholder; open the public ingress question
PROCESS.md section 8 requires host changes to reach STAGING-STATE.md before a session ends. mechcomp-placeholder.service was disabled and stopped on 2026-09-11 and mechcomp.service took 10.20.0.10:8770 in its place. nginx on CT 101 needed no change because the real service took the address the placeholder occupied. The placeholder unit stays on disk, disabled, as the rollback.

Section 5 items closed: mechcomp.service, and replacing the placeholder. Application runtime acceptance is marked partial rather than done, because no acceptance criteria have been written for the composer and it is not reachable from outside.

WORK-ORDER-004 publishes the composer at dev.mechcomp.kane-il.us. The srv-b half needs no change, verified: forwarding on, FORWARD policy ACCEPT, a direct route on vmbr1, and none of the three FORWARD rules matches hub initiated inbound traffic. The single gate is AllowedIPs on the hub peer entry for srv-b, because WireGuard drops by cryptokey routing before consulting any routing table.

Design decision recorded: the public name terminates on the hub and proxies to CT 100 directly rather than through CT 101. Routing it through CT 101 would make the public path depend on a locally signed leaf that expires 2028-11-18 with nothing renewing it. The tunnel already provides the encryption that hop would add. CT 101 keeps serving the internal name.

Section 4 not now decision on the hub route is reopened. It was correct while there was no application to reach. The consequence of leaving it closed is that the operator cannot see the application at all.
2026-09-11 06:59:53 -05:00
TheRON 14514b0bac web: the composer, served behind the existing proxy chain
A browser page with controls on one side and the cross section on the other. Three separately classed layers: printed material, the cavities the stock passes through, and the stock drawn inside its cavity so the fit gap is visible. Under it the report and the design record, so the identity of a part is visible while tuning rather than discovered afterwards. Standard library only, no framework, no build step, no new dependency.

Binding comes from /etc/mechcomp/mechcomp.env, which already declared 10.20.0.10:8770. An earlier draft invented a port on 0.0.0.0, which would have placed a second unproxied plaintext copy beside the proxied one. The fallback with no env file is loopback, never every interface: behind a proxy, binding too narrowly fails loudly as a 502 and too widely fails silently as an open service.

Controls are filtered to the selected profile, with no catch all group. A knob that moves nothing is worse than an absent one.

Renderer tests assert what an eye cannot: the vertical flip happens exactly once, holes render as holes, and the cavity is larger than the stock inside it. Seven mutations on the renderer, all caught.
2026-09-11 06:46:23 -05:00
TheRON e921cacf0e design record: every build emits one
Result gains a record field with a default. Nothing existing moves and the report dict is untouched, so all 123 oracle cases are unmoved at 506 passed. A rejected profile still raises before this point and gets no record, which is correct because there is no model to preserve.

The record learns the stock and fit from the Geo the build actually used, so it describes what was built rather than what was asked for. Those agree today and the record will keep saying so if they ever stop.

code_revision resolves once per process from MECHCOMP_REVISION, then git rev-parse, then unknown. Never a guess: unknown tells you the part may not be reproducible, while a plausible wrong sha would send someone to the wrong commit. A dirty tree is reported as such, since a model built from uncommitted edits cannot be regenerated from a revision alone. Caching matters because assemble runs 123 times in the suite and would otherwise shell out to git twice per build.

The end to end test builds a part, renders its record, parses it back from nothing but the text, rebuilds from the parsed parameters, and asserts the report is identical key for key across five profiles. If that fails the record is a description rather than a recipe.

Method correction. The mutation harness had been letting pytest write bytecode, and a write landing inside one mtime tick could be masked by a stale pyc. That made one real defect appear to survive and, more importantly, means every mutation result reported earlier in this work was optimistic by an unknown amount. Rerun with bytecode disabled, all nine mutations caught. The earlier sets are worth rerunning under the corrected method.
2026-08-24 03:58:12 -05:00
TheRON d2827c23b3 design record: what a model was made from, sufficient to regenerate it
A parametric compiler is meant to be tuned: print, measure the print, adjust, print again. That loop also destroys the value of everything already printed, because once a clearance moves the parts on the bench become unidentifiable and unreproducible. Configurability and reproducibility conflict unless something records what each part was made from.

The record carries the fully resolved parameter set at full precision, the stock and fit lines with whatever provenance exists, the code revision and the toolchain. Plain text, one fact per line, readable without this software.

It is deliberately not built from the report. geom.report rounds to six significant figures because that is what echo printed and the oracle records what was printed. A part cannot be regenerated from SECTION_AREA_MM2 equals 135.574. Inputs reproduce, outputs confirm: reported values are carried separately as verification, to be checked with a caliper against the actual part.

Two identities. input_id covers family, profile and parameters, the design intent. build_id adds code revision and toolchain. Shapely and GEOS are in build_id because boolean results on near degenerate geometry can shift between GEOS releases, which is the F-034 mechanism, and a record omitting them could not explain why the same numbers produced a different part. Timestamp, note and verification values are excluded from both.

Not on the build path. No profile imports it, so the frozen oracle cannot move. Twelve mutations tested and caught, including two defects of mine: the record carrying only overrides rather than the resolved set, and the toolchain never reaching build_id.
2026-08-23 10:37:17 -05:00
TheRON 70787ea17d stock: records delegates to the descriptor, and the descriptor stops gating
geom.records no longer computes the section, cavity or laminae. It calls mechcomp.stock, so the shape of a piece of stock is defined once. All 123 oracle cases unmoved: 482 passed. stock.py also becomes a leaf module, ending an import cycle with geom that resolved only by accident of ordering.

Three defects in f5651d3 corrected. Provenance raised on an empty source, which made an unattributed dimension unrepresentable and blocked the ordinary use of the tool: type what the caliper reads, print, measure the print, adjust. Provenance now records and travels with the output. Fit refused negative clearance on the argument that interference is not assemblable, which is a design judgement and not the compilers to make. Interference is now computed and reported. CATALOGUE read as a whitelist and is documented as starting points, with a test asserting an entry built from nothing is as valid as one pulled from the dict.

emt_template takes the diameter, fit, designation and note from the caller. There is no standards table and no lookup. A parametric compiler cannot require its subject to be catalogued before it will run.

STOCK.md section 5 amended, since the refusals were implementing it. An entry without provenance no longer fails to ship, it ships labelled unattributed. The principle that a number must not appear from nowhere looking authoritative survives; the door does not.

Tests compare records and stock against a hand transcription of the reference rather than against each other, which would be tautological after delegation. Mutation testing found four gaps before landing: a dropped lamina stacking offset, emt_template silently ignoring its fit argument, describe discarding the note exactly when provenance was unverified, and a guard on float arithmetic that asserted a tautology.
2026-08-23 10:24:39 -05:00
TheRON f5651d3a62 stock: name the COTS descriptor; the strap becomes the first catalogue entry
The compiler describes off-the-shelf hardware and generates the printed part that encloses, interfaces with, or augments it. The pallet strap is not the subject of the library, it is the first entry, and it was inlined into Geo rather than described. STOCK.md states what every entry must declare: designation, section, nominal versus actual, fit, stock tolerance, provenance.

Geo conflates three things. Width, thickness and count are the stock. Clearance is the fit, a property of the joint. The wall thicknesses are the printed part policy. This commit names the first two and leaves Geo untouched, so no profile imports the new module and the frozen oracle cannot move.

test_stock.py proves faithfulness by exact float equality against geom.records across 36 parameter combinations, 5 placements and 3 face modes. Mutation tested before landing: reversed vertex order, halved clearance, dropped lamina offset and a naive round cavity are each caught.

Round cavities are circumscribed rather than inscribed. A vertices on circle polygon lies inside the nominal diameter and bites into it by r times one minus cos 180 over n, about 9.6 micron at 9 mm radius and 48 facets, which is enough to stop a press fit. Conduit is deliberately absent from the catalogue until a measurement or citation exists.
2026-08-23 08:52:17 -05:00
TheRON 6836ece4ff tests: close F-034; bound the derived trio at 8 ULP of the oracle record
The oracle records six significant figures, so the last digit of an area near 200 mm2 is worth 0.001 mm2. Every measured disagreement between port and reference is one, two or three units in that place. SECTION_AREA_MM2, VOLUME_MM3 and MASS_G are now bounded at 8 ULP of the expected value. Everything that positions material keeps the declared 1e-4 mm and remains exact in all 123 cases.

Option 1 scope kept, derivation rejected on measurement. Max |dA|/P over the accepted set is 1.434e-05 mm, one seven-hundredth of the 0.01 mm criterion, so a perimeter x 0.01 bound would have run 1800 to 4500 times the worst real discrepancy and caught nothing. Perimeter also anti-correlates with the error.

Suite 468 passed, 0 failed. The 30 expected failures are resolved, not suppressed. Mutation tested before landing: worst case uses 37.5 percent of its bound, 12 ULP offsets and 1e-4 relative scalings are caught in all 113 cases, 1e-6 and 1e-5 correctly are not.

Adds docs/ACCEPTANCE.md as the specification. Adds F-036, the venv interpreter error, same class as F-035. Corrects the F-034 per-profile distribution to Three-Fin 10, Y 7, A Frame 6, Rectangle 5, T 1, Four-Fin 1, which sums to the stated 30.
2026-08-23 08:19:18 -05:00
TheRON 52d00b588b docs: record the F-034 decision in HANDOFF.md
CIVICVS approved option 1 on 20 AUG: scale-aware bounds in test_oracle.py for SECTION_AREA_MM2, VOLUME_MM3 and MASS_G, derived from the 0.01 mm criterion and the section perimeter. Not implemented yet. Implementation, mutation testing and a green suite are one piece of work, not a partial landing.

Section 4 item 2 is removed. The F-034 measurement rewrite in FAILURES.md landed in b255ebb, so listing it as a next step sends the next reader to redo work already done. FAILURES.md keeps status Open, which is correct until the change lands.

Header commit line moved to b255ebb. Section 7 and the section 11 open-questions table now say decided rather than ready to decide.
2026-08-20 08:06:16 -05:00
TheRON b255ebbab9 docs: handoff rewritten for the post-port state; F-034 measured
HANDOFF.md rewritten in place, as it is meant to be. The port is complete,
so the document now describes that state rather than the work leading to it.

Most important change for whoever reads it next: the suite is 436 passed,
30 failed, and section 3 says plainly that the 30 are expected and a red
`make test` is not a broken port. Without that line the next assistant
spends its opening exchange rediscovering what is already known.

Also added: the 0.01 mm accuracy criterion as a settled decision; that
defaults are per-family and differ (ring_corner_radius_mm is 2.00 in 3x
and 1.25 in 4x); that check declaration order is the reporting order; that
params carries only a case's overrides; and pointers to PRECISION.md for
what the compiler does not do.

F-034 rewritten around measurement rather than estimate. The original Y
evidence is preserved verbatim -- it was specific and hard-won. What is
new:

  - the same mechanism at 90 degree ring corners, where the expression is
    exactly 12 rather than exactly 8. Rectangle: reference envelope 50
    vertices, port 48.
  - which side is noisy, which was previously unstated and turns out to
    matter. Instrumented at %.17g the port prints half=45 raw=12 ceil=12
    on every call. It lands on the integer deterministically; the
    reference does not. A guard cannot make the port match noise it does
    not have, so there is nothing left to try on this side.
  - the scale: 30 of 113 accepted cases, 83 exact, worst relative error
    2.24e-05, confined to SECTION_AREA_MM2 and its two derivatives.
  - the physical magnitude: chord deviation is r*(1-cos 3.75deg), so
    0.0027 mm at r=1.25 and 0.0043 mm at r=2.00. The two implementations
    differ from each other by at most ~0.4 um. Two orders inside the
    0.01 mm criterion.
  - the constraint on any fix: the tolerance block is inside the hashed
    oracle document, so the change belongs in test_oracle.py.

Status stays Open. The decision is CIVICVS's and has not been made.
2026-08-20 06:54:11 -05:00
TheRON 114189c0cb docs: PRECISION.md -- scope, guarantees and limits, for lay readers
Answers, in plain language, what "accurate" means for this project: the
difference between model precision, machine resolution and achieved
accuracy, and why a design file should be far tighter than any machine
that will realise it.

Scoped hard to additive and subtractive manufacturing (section 0).
Formative processes, crystal growth, lithography, joining, metrology and
surface finish are explicitly out. Section 10 asks that additions widening
that scope be refused rather than accommodated -- a borrowed tolerance
figure carries no evidence from this project while looking exactly as
authoritative as a measured one.

Section 7 states what the compiler does NOT do: no assembly layer, no
structural analysis of any kind, prismatic shapes only, no toolpaths,
verified only within its tested range. A part passing every check here may
still be structurally unsound.

Records the facets limit: at facets=48, corner radii up to 4.67 mm stay
within 0.01 mm of a true curve. Above that facets must rise, growing with
the square root of radius.
2026-08-20 06:42:02 -05:00
TheRON 7b3182c0b9 port: the 3x and 4x profile catalogues; build() now exists
Completes the port. mechcomp.profiles.build and ProfileRejected are live,
so all 123 oracle cases execute rather than skip.

  _common.py   assembly pipeline shared by both families, and the eight
               base checks both generators declare identically
  four_x.py    five profiles, all direct library calls with N=4
  three_x.py   six profiles; four are library calls with N=3, while
               A Frame and T are built from join-layer primitives
               because they are specific arrangements rather than
               instances of a family

466 tests: 436 pass, 30 fail. THE 30 FAILURES ARE EXPECTED. Do not treat a
red `make test` here as a broken port.

All 30 are test_accepted_case_matches_oracle, and all 30 breach on
SECTION_AREA_MM2 alone -- VOLUME_MM3 and MASS_G are that value times 100
and times density, so each case has one underlying discrepancy reported
three times. Worst relative error 2.24e-05.

Everything that positions material is exact. ENVELOPE_X_MM, ENVELOPE_Y_MM,
MIN_WALL_ACTUAL_MM and every profile extra (AF_*, FIN_*, SPOKE_*, RING_*,
T_*) pass at 1e-4 mm in all 123 cases. Every count is exact. All ten
rejections fire correctly, including the bespoke A Frame and T paths.

Cause is F-034, now characterised precisely: the port is exact and the
reference is noisy. _circlecorner computes (90-angle)/180*segs(), which at
$fn=48 on a 90-degree corner is exactly 12. The port lands on 12.0 every
time and takes ceil 12; OpenSCAD's arithmetic lands a hair under 45 degrees
at some corners, pushing the value fractionally above 12 and the ceiling to
13. Measured on Rectangle: reference envelope 50 vertices, port 48.

There is nothing to correct on this side. Both boundaries sit within
0.0027 mm (4x) and 0.0043 mm (3x) of a true arc and within ~0.4 um of each
other -- far inside the project's 0.01 mm criterion. The tests fail because
VOLUME_MM3 is compared at 1e-4 absolute against a magnitude near 20000,
demanding 5e-9 relative agreement from discretised geometry.

Resolving that means changing the comparison policy in test_oracle.py, not
the oracle: the tolerance block is inside the hashed document and editing
it would break test_integrity_hash by design. Deferred pending a decision.

See docs/PRECISION.md for what the 0.01 mm criterion means and what this
compiler does not do.
2026-08-20 06:41:59 -05:00
TheRON af196a26f5 docs: one canonical handoff, rewritten in place; F-035
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.
2026-08-19 12:33:50 -05:00
TheRON 009fcce61c docs: handoff for the 19 AUG session
The shared layer is ported. What remains is the eleven catalogue
profiles and build().

Records what this session established that would be expensive to
rediscover: six-significant-figure report rounding and why VOLUME_MM3
makes it load bearing, the read-only Docker probe against the pinned
image, the oracle parameter defaults, and F-034 in full including why it
must not be fixed.

Also records the working method that earned its keep: read the pinned
source rather than recall it, mutation test every suite before landing
it, and deliver by upload rather than paste.
2026-08-19 08:01:06 -05:00
TheRON 86a47c3c06 rounding: record F-034, arc segment counts tip on the last bit
No behaviour change. Comments and a FAILURES entry.

The Y profile builds a section area of 135.572973 against a recorded
135.574, out by 0.001027, while every other value for that case matches
exactly. Three-Fin matches on everything including area.

Isolated by probing the reference inside the pinned toolchain image. The
hull cap is identical to nine figures, the bare union is identical, and a
single filleted pair is identical at 30 vertices and 125.699057 mm2. The
difference appears only when the three filleted pairs are combined, and
the three pairs, which are related by 120 degree symmetry and must be
identical, come back as 125.699057, 125.698029, 125.699057.

Cause proven. The arc segment count is a ceiling on a quantity that is
frequently an exact integer: a 60 degree half-angle at $fn=48 gives
exactly 8. Floating point delivers that as 8.000000000000004 on one
corner and 7.999999999999998 on the others, so one corner gets a whole
extra segment. The half-angles come from the merged polygon, whose
vertices come from the boolean kernel, and BOSL2 clipper and GEOS
disagree in the last bit.

Reproduced unguarded because the reference is unguarded. Rounding the
count before the ceiling was implemented and reverted: it makes the three
pairs identical and fixes Y exactly, and breaks Three-Fin, which had been
matching to the digit. Three-Fin has the same asymmetry and the oracle
records it. BOSL2 tipped the same way GEOS does there and the opposite
way on Y.

Two consequences for the project rather than the code. Some recorded
values encode float noise rather than geometry, so a port that is
geometrically more correct than the reference will fail those cases. And
the tolerance model may need revisiting: VOLUME_MM3 is compared at the
lengths tolerance of 1e-4 despite being area times 100 mm, so a 1e-3 area
difference becomes a 1e-1 volume difference. MASS_G is derived the same
way.

No decision yet. The number of affected cases is unknown and is the only
thing that should drive it, and that is not knowable until build() exists
and all 123 cases can run.
2026-08-19 07:20:33 -05:00
TheRON 8d79431016 geom: port sb-core and sb-profiles, the N-generic arrangements
The PROFILE record, centred assembly, and three complete arrangements:
ring, spokes, fins. Each written for N members, exercised at N=3 and N=4.

Failure travels as an empty profile carrying one failing check, as in the
reference, so profile rejections and universal-check rejections stay in
one order-sensitive list and the first failure is what surfaces.

The section is cleaned before it is measured, not after. A Three-Fin
section carries 17 collinear vertices from exact butt joints; they are
harmless in 2D and leave zero-area triangles the tessellator cannot
resolve, so measuring first would report on geometry that is not what
gets extruded.

Centred now carries the shifted members. Measuring a shifted shell
against unshifted members reports every cavity as escaping the envelope,
a leak of the whole cavity area from geometry that is fine. That trap
caught me while smoke testing, so the opportunity is removed rather than
documented.

Verified against the oracle where the fillet does not affect the result:
SPOKE_RADIUS_MM 9.1713, FIN_CORE_SIDE_MM 13.4028, FIN_SETBACK_MM
3.77783, FIN_JUNCTION_WEB_MM 2.29919, RING_CORNER_R_MAX_MM 2.87663, the
ring edge vector, and both envelope dimensions all match to the recorded
digit. The solvers are right.

OPEN: with a junction fillet of 1.5 the Y section area is 135.572973
against a recorded 135.574, off by 0.001027 and just past the area
tolerance, while every other quantity for that case matches exactly.
Either the generator fillet default is not 1.5, or there is a difference
of about seven parts per million concentrated in the fillet. The
generator settles it.

35 tests. Nine mutations, two of which found real gaps: nothing asserted
that cleaning removed anything, and the spoke zero-fillet check was
untested.

Oracle acceptance still skips; 236 unchanged.
2026-08-19 06:56:02 -05:00
TheRON b101fe6adf geom: port sb-report, validation and report formatting
Checks as values, the five universal checks, metrics, ProfileRejected,
and the report block. Completes the shared layer; only the profiles and
build() remain.

Report numbers are rounded to six significant figures on the way out,
matching OpenSCAD echo, which is C %g at default precision. The oracle
records what OpenSCAD printed, not full-precision geometry.

This is load bearing rather than cosmetic. VOLUME_MM3 ends in _MM3, so
test_oracle.py compares it at the lengths tolerance of 1e-4 and not the
areas tolerance of 1e-3. Volume is section area times a 100 mm length,
so an unrounded port reporting 13557.402 against a recorded 13557.4
fails by twenty times the tolerance while being geometrically correct.

Verified against the oracle: across all 113 accepted cases the recorded
volume equals the rounded area times length to within 3.6e-12, which
holds only if volume is computed unrounded and rounded at print. That is
what this layer does.

The consequence is that geometry must agree with the reference to better
than one part in a million before rounding. Near a rounding boundary a
smaller error can still tip the last digit, and that will show up as a
single case failing by one unit in the last place rather than as
something mysterious.

28 tests. Nine mutations, all caught first pass, including rounding to
decimal places instead of significant figures and computing volume from
the already-rounded area.

Oracle acceptance still skips; 236 unchanged.
2026-08-19 05:46:51 -05:00
TheRON ebf02d6573 geom: port sb-join, the junction and envelope strategies
Face lines, structural butt joints, hull caps, concave fillets, the
derived bore, ring fit, ring envelope and section assembly. N-generic
throughout, as the reference is.

Junctions are structural, not cosmetic: one sleeve runs through its
neighbour and is cut flush against that member the far surface, so the
two share a full-width overlap whether or not a fillet is applied on
top. The bore is derived from the members own inside-wall lines rather
than a separately scaled shape, which is what makes the declared inside
wall exactly what remains beside each cavity.

43 tests. Mutation testing found two of the reference own warnings to be
load-bearing and untested by me. A bore that has turned inside out can
carry over a square millimetre of area, so the area guard alone accepts
it and only the interior-side test rejects it. And the ring fit really
does have a spurious lower branch: a thin triangle meets a 1.2 mm web at
relative scale 0.425, where members overhang their own corners and the
solve looks converged. Both now covered.

A third mutation was malformed on my part rather than a gap -- cutting
the cavities twice is idempotent -- and was replaced with one that does
change behaviour. Nine mutations caught.

Oracle acceptance still skips; 236 unchanged.
2026-08-19 05:28:23 -05:00
TheRON 38ea024fdc geom: Shapely-backed region layer
Booleans, decomposition, area, simplicity, hull, bounds, mitred offset
and vertex cleaning. Booleans go to GEOS, which is the reason Shapely was
chosen. The decomposition does not.

BOSL2 region_parts counts by nesting parity, not connectivity: a path
takes a level from how many others contain the midpoint of its first
edge, even levels are outer boundaries, their odd children are holes.
SECTION_PARTS == 1 is an exact assertion, and Shapely agreeing with that
count is a coincidence that holds for well-formed input and not
otherwise, so the decomposition is transcribed and both the part count
and the area derive from it.

is_region_simple is treated as a manifold precondition rather than a
diagnostic. An outline that touches itself measures perfectly and cannot
be tessellated, so it must fail here and not at export.

Developed against Shapely 2.1.2 / GEOS 3.13.1, matching CT 100. Boolean
results on near-degenerate geometry can shift between GEOS releases; if
the oracle ever disagrees by one part after an upgrade, look there first.

39 tests, all arithmetic on rectangles. Mutation run found a real gap:
nothing distinguished on-boundary from outside in the nesting probe until
a shared-edge case was added. Eight mutations now caught.

Oracle acceptance still skips; 236 unchanged.
2026-08-19 03:30:03 -05:00
TheRON 545eee7217 geom: port BOSL2 round_corners and path cleanup
Shapely has no corner rounding, so the round_corners -> _circlecorner ->
arc -> segs chain is transcribed from BOSL2 at the pinned commit
92d697c2, read from source rather than recalled. Also deduplicate,
path_merge_collinear, is_collinear and approx, which the cleanup path
depends on.

Segment counts are contract, not a quality setting. An arc becomes
straight segments and the count sets the enclosed area, compared against
the oracle at 1e-3 mm2 -- and the extruded solid is those segments, so
this is the definition of the surface. Both generators set $fn = facets
with facets = 48 and no oracle case overrides it, so segmentation
depends on swept angle alone. A right angle gives 12 points.

round_corners raises where BOSL2 asserts, rather than clamping: silently
fitting a roundover the reference refused would diverge without any
visible failure. sb_corner_radii exists to derive safe radii up front.

33 tests. The exact-fit boundary raises rather than passing, because
tan(45) is under 1 in both languages -- a test asserting the tidy
behaviour would have looked right and been wrong. Mutation run found a
real gap: nothing exercised the three-point floor on blunt corners until
a 170-degree case was added. Seven mutations now caught.

Oracle acceptance still skips; 236 unchanged.
2026-08-19 03:18:52 -05:00
TheRON dfd02a4fd8 geom: port the pure-geometry half of sb-geom
Vectors, GEO and MEMBER records, member placement, sleeve and cavity
paths, exact polyline distance, corner-radius derivation, and the
monotone solver. Direct translation of legacy/openscad/lib/sb-geom.scad
at rev 8.0.0.

Angles stay in degrees, matching OpenSCAD, so every expression reads the
same as its source line. The 44 solver iterations, the 0.999 and 0.98
scale factors, the 0.05/179.95 cutoffs and the 1e9 sentinel are
reproduced exactly: they shaped the frozen oracle.

Region operations are not included -- they need a 2D boolean kernel and
follow with the Shapely layer.

49 unit tests, none of which touch the oracle. Harness proven by
mutation: radians for degrees, a shortened solver, a dropped scale
factor, a skipped crossing test and a flipped offset sign are each
caught. Oracle acceptance still skips; 236 unchanged.
2026-08-19 02:58:35 -05:00
TheRON b67cc1290e verify.sh: reach the restore on the diff branch
set -euo pipefail aborted the script on the diff pipeline one line
before the cp that restores the pre-run oracle, so --full left a
regenerated fixture file in the working tree while printing that
nothing had been overwritten. Appended || true.

Recorded as F-033. The fix is not yet exercised: the restore branch
runs only under --full and has not been entered since the change.
2026-08-19 01:10:08 -05:00
TheRON 1d3eed07cd Handover push 2026-08-18 14:36:25 -04:00
TheRON 6967eef59c Conformance updates. 2026-08-18 10:33:26 -04:00
TheRON eb0d533d86 Updated PROCESSS.md 2026-08-18 07:36:39 -04:00
TheRON 3e7e7c934d Added PROCESS.md 2026-08-17 12:50:12 -04:00
TheRON bcec11cf43 Final configuration 2026-08-17 11:48:32 -04:00
TheRON 673d5f26ac mail relay configured 2026-08-17 08:25:14 -04:00
TheRON e67988eef5 Updated 2026-08-16 20:25:18 -04:00
TheRON eac601ed99 Current state
Live state of the Mechanical Compiler staging instance on `srv-b`.
2026-08-16 12:01:20 -04:00
TheRON a00ba35451 Roadmap
What the Mechanical Compiler is for, and the order in which it gets built.
2026-08-16 12:00:42 -04:00
TheRON 1672971143 Failure records
Append-only record of every failure encountered building the Mechanical
Compiler environment.
2026-08-16 12:00:08 -04:00
TheRON 318a2e30dc Provisioning specifications
This specifies a **staging environment** on `srv-b`, plus the promotion path to
a production host that does not yet exist.
2026-08-16 11:59:08 -04:00
TheRON e85c4f4e5b Initial commit 2026-08-14 09:21:31 -04:00