Commit Graph
5 Commits
Author SHA1 Message Date
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 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 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 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
civicus-build c7e32d8e07 Seed repository: rev-8.0.0 reference, frozen oracle, toolchain, test harness
Reference implementation of the strap-beam generators at revision 8.0.0, kept
so the acceptance oracle can be regenerated. Not a live target; the running
application has no OpenSCAD dependency.

The oracle holds 123 frozen cases, 113 accepted and 10 rejected, produced by
OpenSCAD 2021.01 with BOSL2 at 92d697c2. The ten rejections are part of the
contract: a port that accepts them is wrong.

tests/test_oracle.py specifies the port API and was written before the port,
so the interface follows from what must be verified rather than what is
convenient to implement. Proven by adversarial stub: a build() that rejects
everything passes all 10 rejection tests and fails all 226 acceptance tests.
2026-08-18 07:30:17 -05:00