anvil + validate — describe a mechanical part, get back a physics-validated pass/fail where every check cites the code it came from.
Anvilate is a local-first, open-source design tool for mechanical, structural, and industrial engineers. It runs the analytical screens you'd otherwise do by hand in a spreadsheet — bending, deflection, buckling, resonance, bolted and welded connections, contact, thick-wall pressure, tolerance stack-ups — and rolls them into one scorecard that won't hand you a silent green. No cloud, no LLM required, no account.
Status: pre-alpha (v0.0.1). The deterministic engineering core is real, tested, and runnable today. The natural-language front end, 3D geometry, FEA, and STEP export described under Where this is going are still being built.
Python 3.11+.
git clone https://github.com/clay-good/anvilate.git
cd anvilate
python -m venv .venv && source .venv/bin/activate
pip install -e ".[export]" # drop [export] if you don't need DXF outputRun any of the worked examples — each is self-contained, needs no network, and prints a scorecard:
python examples/cantilever_bracket_check.py[PASS] bending yield: safety factor 1.84 vs required minimum 1.50
[FAIL] tip deflection: deflection 36.284 mm vs limit 15.000 mm
scorecard FAIL (2 checks); governing: tip deflection
The aluminum bracket is strong enough but too bendy — the deflection screen catches what a yield-only hand check would wave through.
The whole flow is: pull a material, describe the geometry and load, roll the checks into a Scorecard.
from anvilate.analysis import (
cantilever_end_load, rectangular_second_moment,
strength_scorecard, deflection_scorecard,
)
from anvilate.scorecard import Scorecard
from anvilate.standards import default_materials_db
from anvilate.units import Quantity
al = default_materials_db().get("AA-6061-T6")
I = rectangular_second_moment(Quantity.parse("20 mm"), Quantity.parse("10 mm"))
beam = cantilever_end_load(
force=Quantity.parse("100 N"),
length=Quantity.parse("500 mm"),
second_moment=I,
extreme_fibre=Quantity.parse("5 mm"),
elastic_modulus=al.elastic_modulus.quantity,
)
card = Scorecard(entries=(
strength_scorecard("bending yield", stress=beam.max_bending_stress,
allowable=al.yield_strength.quantity, required=1.5),
deflection_scorecard("tip deflection", deflection=beam.max_deflection,
limit=Quantity.parse("15 mm")),
))
print(card) # scorecard FAIL (2 checks); governing: tip deflectionUnits are first-class (SI and US customary — mix kip, ksi, in, mm, MPa freely); materials come from a provenance-tagged database; safety factors and citations travel with every result.
484 runnable examples, each executed in CI so they stay honest. A few:
| Run this | What it shows |
|---|---|
machine_on_floor_beam.py |
Declaring where a load actually sits recovers real margin the worst-case mid-span guess throws away (FAIL 1.19 → PASS 1.58). |
beam_bearing_web_checks.py |
A beam's end reaction is checked two ways (AISC §J10.2 web yielding and §J10.3 crippling); the thin web buckles at 213 kN before it crushes at 316 kN, so crippling governs. |
hss_beam_flexure_shear.py |
A square HSS with a noncompact flange: the naive plastic moment reads 394 kN·m, but AISC §F7 flange local buckling cuts the real capacity to 368 kN·m (7% lower). See hot-rolled steel. |
bolted_tension_splice.py |
Gross yielding (621 kN) and net-section rupture (544 kN) both pass, but AISC §J4.3 block shear tears the end block out first at 450 kN — the limit state a member-only check never sees. |
plate_girder_design.py |
A deep welded girder: the slender web docks bending 5.6% (AISC §F5 R_pg) yet, once stiffened, nearly doubles the shear via §G2.2 tension-field action (832 → 1468 kN). |
spur_gear_agma_check.py |
An AGMA spur gear checked for both tooth-root bending and surface pitting — pitting runs the higher utilization (0.69 vs 0.39), the mode a Lewis bending-only check never flags. |
pipe_expansion_loop.py |
A B31.3 thermal-expansion bend: the elbow's stress-intensification factor makes it work 73% harder than the straight-pipe stress calc predicts (S_E/S_A 0.84 vs 0.48). |
gear_shaft_assembly.py |
One gear shaft, three coupled subsystems: DE-Goodman fatigue sets the 28.5 mm diameter, then the key length and bearing L10 life follow from it — no single check is the design. |
rc_t_beam_floor.py |
A monolithic RC floor beam: counting the slab as a compression flange (ACI T-beam) adds strength and drives the net tensile strain to 0.024 — far past the 0.005 ductility limit the bare web barely meets. |
lug_drawing.py |
Code-check a lifting lug (ASME BTH-1), then export its outline to a fabrication-ready DXF. |
column_base_plate.py |
A base plate checked for concrete bearing (AISC J8) and plate bending (Design Guide 1) — bearing passes, the thin plate fails. |
motor_mount_resonance.py |
A mount that's statically bulletproof but resonates below running speed — the dimension a static hand calc never sees. |
hydraulic_cylinder_wall.py |
The thin-wall formula reads a comfortable pass; the exact Lamé solution says the barrel fails. |
tolerance_stackup.py |
A 1D stack-up worst-case rejects the design, yet Monte Carlo predicts 99%+ assembly yield. |
lifting_lug_calc_report.py |
The same padeye screening rendered as a submittal: formula, substituted values, result, and clause for every check. See calculation reports. |
sheave_repair_from_inverse.py |
A failing bending check that carries its own fix: a design inverse names the sheave diameter that lands the margin in one solve. See typed repair feedback. |
bracket_load_scatter_fragility.py |
A bracket that passes at SF 1.70 nominal but falls below the required 1.5 one run in five once the load scatters ±15% — a shortfall probability no single-point check reports. See uncertainty margins. |
feature_control_frame_legality.py |
Five drawing callouts that do not parse — flatness to a datum, perpendicularity to nothing, Ⓜ on a surface, symmetry on a 2018 drawing, a fourth datum — refused with the reason, plus what a position tolerance contributes to a 1D stack. See semantic GD&T. |
feature_control_frame_drawing.py |
One declaration, three consumers: the same feature control frame as text, as a QIF characteristic definition, and as DXF geometry on its own annotation layer — every symbol drawn as lines and arcs, because a viewer without a GD&T font renders a Ⓜ as a missing glyph and the callout silently loses its modifier. See semantic GD&T. |
agent_driving_eval.py |
Two models over the same tasks, and the one that looks better is worse: averaged over every run the model that gave up posts the lower iteration count, and only the completion rate says so. See agent-driving evals. |
mcp_server_session.py |
Drives the MCP server as a real subprocess over stdio: a compile that works, a document that fails as a result rather than a transport error, and three refusals that each say a different thing. See MCP tool contracts. |
branch_reinforcement_zone.py |
A B31.3 §304.3.3 branch whose reinforcement zone height is set by the branch, not the run it sits on — reading L4 as 2.5(T_h − c) credits 67% more branch area than it earns. See process piping. |
timber_beam_lateral_stability.py |
A 2x12 rafter that passes bending stress at 1.42 and fails at 0.57 once NDS §3.3.3 lateral stability is applied — one strut at midspan still is not enough. See timber screening. |
frame_member_forces_to_checks.py |
A Pynite frame export screened by cited AISC checks: the axis mapping and the axial sign convention are declared, not inferred — unflipped, a 180 kN compression reads as tension and the column is never checked for buckling. See analysis interop. |
lifter_verification_matrix.py |
The calculation is not the evidence: a passing BTH-1 lifter's plan asks for a 125% proof load and a dimensional inspection, counts the check verified by analysis alone, names the one that did not run — and reports not_evaluated until a result is actually recorded. See verification planning. |
attested_evidence_bundle.py |
A screening result sealed so somebody else can re-check it: the same inputs rebuild the identical bundle digest, a materials-database bump moves it, a one-byte change to the drawing fails verification by name, and — since a release, and found by reading the requirement's own three named checks against what the code did — the predicate is validated against its schema rather than only its type label, because a predicate of {"anything": "at all"} verified PASS while the type string and the subject digests matched, so an envelope carrying no scorecard, no citations and no bill of materials came back clean, and a signature nobody checked reports not_evaluated rather than pass. All of which is now reachable from the shell: anvilate verify is the command evidence-attestation names, and the library had done all three checks since the attestation layer shipped with nothing calling them. It refuses three things on purpose — a signature nobody could check exits 2 rather than 0, a subject with no file is reported unchecked instead of assumed to match, and a symmetric signature is not attestation, so a fully checked HMAC envelope reads PASS with attested=False and the command prints why, because that pair without the reason invites exactly the wrong conclusion. It also reports the toolchain the envelope attests — the requirement's own scenario asks for that alongside the signature and the digests, and it is read out of the verified statement rather than out of the machine, because a verifier elsewhere with different versions installed still has to be told what produced the artifact. See attested evidence. |
plated_shaft_callouts_change_the_verdict.py |
Three drawing callouts that are check inputs, not annotations: reading the as-forged finish drops a shaft journal from a comfortable SF 2.52 to 1.08 — a FAIL, plating moves a 60° thread's pitch diameter by four times its thickness, and a heat-treat condition no material record backs reports not_evaluated instead of screening the untreated row. See typed callouts. |
lug_evidence_bundle_roll_up.py |
One lug in four states, and only one of them is verified: checks-only passes while naming what it does not cover, a written-but-unperformed proof-load plan drops the same scorecard to not_evaluated, performing it earns test-verified, and a review the design moved under pulls it back down. See the evidence bundle. |
lug_scorecard_as_qif.py |
The same lug handed to quality software as QIF Results (ISO 23952): five checks cross as five characteristics, the over-margin weld reads PASS with the finding in its description, and the tear-out check that never ran crosses as NOT_ANALYZED carrying the requirement it would have been judged against — omitting it would have turned a file with one honest gap into a file of four characteristics every one of which was evaluated — a part whose failure mode nobody looked at, reported as one fully examined. A verdict-only deflection check becomes QIF's attribute gauge rather than getting an invented threshold. See quality interchange. |
measured_shaft_from_certificate.py |
The other direction: a Digital Calibration Certificate read as a measured input. A 25 mm shaft called to ISO 286 h6 measures 25.0004 mm and fails by 0.4 µm — but the laboratory's own expanded uncertainty is ±1.2 µm at k=2, three times the overshoot, so the measurement is consistent with an in-tolerance shaft a quarter of the time and the screen says so. The certificate's value is a draft until somebody named confirms it, its certificate is unsigned and the provenance says so — had it been signed, it would read present and not verified, because there is no third state an offline tool can honestly claim. See quality interchange. |
rfq_sheet_to_confirmed_inputs.py |
A customer sheet that contradicts itself: five quantities taken from eight labelled lines and three recorded as not taken, a design load stated as both 50 kN and 45 kN with neither chosen, and a release that stays blocked even after both sides are confirmed — two values for one field is not a field. See requirements ingestion. |
lightest_passing_bracket.py |
Eighty-one brackets swept in milliseconds: the lightest one in the box fails, the lightest that passes is 3.75x heavier, and a 20-point budget spent on a grid finds nothing where the same budget on a Halton sequence finds seven. See design-space exploration. |
canopy_beam_load_combinations.py |
A light canopy whose bending is sized by one ASCE 7-22 combination and whose hold-down by another — a wind uplift the gravity cases never show. See load combinations. |
braced_frame_column_seismic.py |
A gravity column comfortable in compression whose base connection is governed by the net tension an ASCE 7-22 §2.3.6 seismic reversal produces — a load the gravity cases never reveal. |
spec_load_combination_check.py |
A Design Spec whose load cases are classified by nature, aggregated into a demand mapping and screened against the governing ASCE 7-22 combination — load combinations as part of the same validated flow, not a separate spreadsheet. |
welded_bracket_fatigue.py |
The same stress spectrum passes on a category-90 weld detail and fails on a category-56 one — the EN 1993-1-9 detail category, not the stress, decides fatigue life. See weld fatigue. |
isolator_amplifies_at_running_speed.py |
A 1450 rpm pump whose "reassuringly firm" 0.5 mm pad sits at f/f_n = 1.08 and passes 5.7x what a rigid bolt-down would — and whose 11 ms transport shock then inverts the question, since the half-sine shock spectrum peaks at 1.77 and softening a mount helps on one side of that peak and hurts on the other. See thermal screening. |
lipped_channel_dsm.py |
One cold-formed lipped channel, three unbraced lengths, three different governing buckling modes — distortional at 1 m (150.8 kN), local at 3 m (82.5 kN), global at 6 m (21.9 kN). A thicker web fixes the first and does nothing for the third. See cold-formed steel. |
power_device_heatsink.py |
A 30 W device whose junction cooks in still air (145 K rise) and survives with a fan (44 K) — a thermal resistance network where the convection to air governs. See thermal screening. |
process_pipe_schedule.py |
An ASME B31.3 process line where Schedule 10 fails and Schedule 40 passes the service pressure once mill tolerance and corrosion are taken off the wall — rate the wall you keep, not the one stamped on the pipe. |
floor_joist_wet_service.py |
An NDS timber joist that passes dry and fails wet — the wet-service factor C_M in the adjustment chain is the whole difference. See timber screening. |
timber_header_bearing_governs.py |
A short header whose bending (SF 1.25) and shear (1.14) both pass while it crushes at its support (0.96) — bending demand falls with L², the bearing stress at the support doesn't fall at all. |
timber_post_slenderness.py |
The same 4x4 under the same 4,000 lb passes at 8 ft (SF 1.70) and fails at 12 ft (0.82) as the NDS column stability factor collapses from 0.41 to 0.20 — and at 16 ft the §3.7.1.4 slenderness cap makes the screen refuse rather than quote a plausible number. |
cold_formed_stud_flange.py |
A cold-formed flange that is only 59% effective at 1.5 mm and fully effective at 3.5 mm — the AISI Winter effective-width reduction that sets cold-formed design apart. See cold-formed steel. |
aluminum_ladder_rail.py |
A 6061-T6 strut whose low modulus makes it buckle (ADM §E.3) at 91 MPa — giving away 62% of the 240 MPa strength it reaches in tension. The 0.85 out-of-straightness knockdown in that branch is 17.6% of the answer. See aluminum screening. |
spreader_beam_bth1_category.py |
The same 3 m spreader beam at 107.6 MPa in bending: ASME BTH-1 Category A allows 124.0 MPa and Category B allows 82.7 MPa, so it passes at SF 1.15 and fails at 0.77 on identical steel under an identical load — and its 50,000-cycle life is Service Class 1, so the fatigue row is not evaluated rather than passed. See lifting devices. |
pressure_vessel_nozzle_and_flange.py |
The same 800 mm vessel at two wall thicknesses: at 14 mm everything passes, and at 8 mm the shell still passes (SF 1.11) while the 6-inch opening fails at 0.49 — UG-37 credits the wall's excess over what pressure alone needs, and the excess vanishes faster than the wall: the shell's own margin falls 1.9x (2.14 to 1.11) while the opening's falls 3.4x (1.66 to 0.49). Its flange shows the second trap: the seating load is larger (400.0 vs 218.7 kN) and the operating condition still governs once each is divided by its own allowable, so the one-allowable shortcut lands 36% short. See pressure equipment. |
bracket_reviewer_dossier.py |
Four checks reordered for the engineer who has to decide where to look: the unevaluated fatigue check sorts ahead of the failing deflection one, and a check passing at SF 3.0 is still surfaced because nobody recorded where its allowable came from. A recorded exception never turns the failure into a pass, and trimming the section invalidates the prior review rather than carrying it across. See responsible-charge review. |
retrofit_two_code_editions.py |
A 2018 frame designed to AISC 360-16 with a new mezzanine to -22: the new work alone passes, the combined bundle fails naming both editions, and it passes once the engineer of record records who accepted the mix and why. Nothing about the structural checks changed — what failed is the claim the bundle was making about itself. See standards effectivity. |
bracket_redesign_embodied_carbon.py |
A 12 kg steel bracket machined at a 35% yield starts as a 34.3 kg billet: the swarf carries 65% of the 53.1 kgCO2e cradle-to-gate estimate, and a near-net stamping at 88% yield lands at 16.7 — the lighter part is not automatically the lower-carbon one, the yield is. Mixing EN 15978 module scopes is refused and a material with no factor comes back not evaluated, never zero. See embodied carbon screening. |
vessel_surface_flaw_fad.py |
A 4 mm x 40 mm surface flaw in a 20 mm vessel shell placed on the BS 7910 failure assessment diagram: K_r 0.367 at service looks like 2.73 in hand and the real load-line margin is 1.71, because L_r rides out with it — and the same flaw on a Charpy-correlated toughness comes back not evaluated rather than passing. See fitness-for-service screening. |
welded_aluminum_platform_beam.py |
The same 6061-T6 platform beam under the same 100 MPa: passes unwelded (SF 1.79, 178.5 MPa allowed) and fails welded (SF 0.87, 87.4 MPa) — welding halves the temper permanently, and a member declared welded with no weld-affected properties comes back not evaluated rather than falling back to parent metal. See aluminum screening. |
cfrp_ply_anisotropy.py |
A unidirectional carbon/epoxy ply is 139 GPa along the fibers but only 8.6 GPa across (16:1) — the rule of mixtures that explains why laminates cross-ply. |
rc_floor_beam.py |
A reinforced-concrete floor beam whose reinforcement develops 321 kN·m, and the ACI 318 design inverse for the steel a 400 kN·m demand needs. See reinforced concrete. |
retaining_wall_stability.py |
One retaining wall, three external-stability checks (TMS/geotech): overturning and sliding both pass, but the resultant leaves the middle third so the heel lifts and the toe pressure climbs to 148 kPa — no single number says the wall stands. |
slope_stability_rain.py |
A 35° cut steeper than its friction angle: friction alone can't hold it, cohesion does, and saturation (pore pressure) nearly undoes it — why slopes stand for years then fail in a storm. |
pump_selection_from_line.py |
The whole hydraulics chain — Darcy friction + fittings + static lift → total head → hydraulic and shaft power → specific speed → centrifugal — from pipe geometry to a motor nameplate. |
vfd_pump_energy_saving.py |
The pump affinity laws: backing a pump to 80% speed with a VFD trades a fifth less flow for nearly half the power — the cube law that is the whole case for variable-speed drives. |
masonry_wall_slenderness.py |
A TMS 402 masonry wall its gravity check passes at f_a/F_a = 0.52, but adding out-of-plane wind drives the combined unity ratio past 1.0 — the interaction, not either stress, sizes it. |
Full annotated gallery: examples/README.md.
What's implemented: a units layer, the typed Design Spec IR, a standards/materials database (materials, fasteners, bearings, NEMA, dowels, T-slot, ASME B36.10M pipe schedules), the T1 analytical library above (236 closed-form modules and 1,818 public symbols, each dimension-checked and hand-verified, 4,157 tests), ISO 286 fits + tolerance stack-ups + DFM process-capability checks, an auditable evidence/provenance roll-up, DXF export, and discipline packs that turn a declared element into a cited PASS/FAIL scorecard — structural (beams, columns, beam-columns, bolted/welded connections, base plates, lugs, gussets — AISC 360 / ACI 318 / ASME BTH-1), industrial (pressure-loaded covers and panels — a cover that passes on stress and fails on stiffness is the ordinary case, since stress goes as t² and deflection as t³, so one chosen on strength alone is chosen on the wrong one; and the edge condition is the biggest lever on the page, cutting the deflection by more than three, which is why CLAMPED is a claim about the hardware rather than a default. That pack was excused from the pack-documentation gate with a reason that turned out to be false — the page named covered no cover plate — so an exemption now has to name pages that exist and that mention a symbol the pack exports), geotechnical (shallow footings, retaining walls, slopes, piles), hydraulics (pump duties and pipe runs), and masonry (TMS 402 walls), every check citing its clause. The analytical library reaches past the mechanical/structural core into neighboring disciplines the same engineers work in — A rendered detail must not argue from a figure it then rounds away: the isolator screen printed "transmissibility 1.00 > 1" — a sentence contradicted by the number inside it — for every mount just under the isolation onset and every one tuned far below the forcing frequency, since TR is exactly 1 at r = √2 and approaches 1 from above as r falls to 0. The sweep for that shape found a worse one in the scorecard itself — safety factor 2.50 exceeds target band 1.50–2.50 by 0.00 — over-engineered, three contradictions in one sentence, in the library's central rendering and on the status the repair loop acts on. Two refusal messages had it too — a round-HSS guard reading D/t = 200.0 exceeds the §F8 applicability limit 0.45E/F_y = 200.0, and a masonry cap reading overstates the column by 1.00x inside the sentence refusing it. All four widen the precision until the value supports the claim, and the one point where the claim was simply false, r = 0, now says the mount neither isolates nor amplifies because a static load passes straight through. geotechnical (Rankine earth pressure, Terzaghi bearing capacity, consolidation settlement, retaining-wall stability, slope stability — see geotechnical screening), hydraulics (Darcy-Weisbach pipe flow, open-channel Manning flow, pump sizing and affinity laws, differential-pressure metering, fluid statics — see hydraulics screening), building services — four packs that shipped, were tested, and appeared in no documentation at all until a gate went looking: a worker's OSHA noise dose (two machines at 92 and 90 dBA combine logarithmically, so six hours of the pair is a factor of 0.75), an IES lighting layout against its energy-code power density (two checks that pull against each other, so the same edit fixes one and breaks the other), an ASHRAE 62.1 zone whose air-distribution effectiveness is a divisor — a poorly distributed zone needs 25% more air, not less, and it is the term most often left at 1.0 by accident — and an NEC feeder whose current comes from the power, the voltage and the power factor together, since using the kW figure directly is the classic undersizing; see building-services screening, where every figure on the page is recomputed from the pack and every clause it cites is one an entry actually names. What found them is a ratchet on the pack contract itself: discipline-packs says a pack missing citations, tests or documentation is rejected with the missing items enumerated, and four packs were missing the third, masonry (TMS 402 allowable-stress design — see masonry screening, now joined by NDS timber's beam stability factor C_L: a 2x12 rafter with 42% in hand on bending stress has C_L = 0.402 unbraced and fails at 0.57, and one strut at midspan still is not enough. A timber reference design value is a record too, carrying which of the seven properties it is — a stress and a modulus are both [pressure], so the unit cannot tell them apart — and NDS Table 4.3.1 is enforced on the factor chain rather than described: the load duration factor C_D applies to neither modulus nor to bearing perpendicular to grain, and applying it to a modulus at a snow load makes the beam 15% stiffer than the standard allows on exactly the deflection check that governs — the F_bE coefficient is 1.20 where the column's Euler stress uses 0.822, and a test pins the ratio so the two cannot be confused quietly), and process piping (ASME B31.3 pressure design on the wall you can rely on, and miter bends that rate well below the pipe they are made from — see process piping, and a branch connection whose reinforcement zone height is set by the branch rather than the run it sits on: L4 is the lesser of 2.5(T_h − c) and 2.5(T_b − c) + T_r, so reading it off the run alone credits a thin branch with 67% more area than it earns, and a reinforcing pad raises A3 as well as its own A4 until the run's cap binds) — each closed-form, dimension-checked, and hand-verified. Every check also renders as a reviewable calculation report — whose units are chosen to compose rather than to look familiar, and two families were still missing: an SI report printed σ = M / Z = 169477.24 N·mm / 3.00 in³, every other factor converted and the section modulus not, with line loads left in lbf/ft beside lengths in millimetres. All six families convert now, held by a gate that asserts moment ÷ section modulus is exactly the stress unit and line load × length² exactly the moment unit, each factor exactly 1 — and writing that gate corrected its own author, since 1 kN/m is 1 N/mm, so the "familiar" spelling composes too and the choice between them is legibility — where every assumption now carries who put it there, [engineer stated] / [resolved from bundled data] / [library default: <reason>], the field having been a plain tuple of strings while the model's own docstring said "with their origin", so an assumption the engineer asserted and one the library supplied were the same bullet in a document somebody signs; a bare string is refused rather than tagged with a guess, because defaulting an untagged one to "engineer stated" puts a claim about provenance into a signed document on nobody's authority — formula, substituted values, result, and clause — with the formulas typeset as MathML the browser lays out, so the document stays one air-gapped file with no script and no bundled font, and a formula whose parse does not write back out as the string the check cited falls back to plain text rather than stacking a fraction of something else — and what a citation means and how to check it says exactly what that clause reference does and does not claim — and every one of the 1,818 public analysis symbols now names one, the citation debt having gone from 47% of the surface to zero, with the empty ledger kept as a ratchet so a new check that names no source fails the build — including, now, the allowable basis every bundled strength carries: a handbook mean and a specified minimum were always different numbers, but the difference lived in prose inside a source string, and a check that needs a design allowable now does demand one — for one release the distinction was recorded and nothing consumed it, so every code-cited check ate a mean strength silently, and a 6061-T6 screen ran on 276 MPa where the specification guarantees 240. Eight of the seventeen bundled materials carry a specification minimum and screen unchanged; the other nine report not_evaluated naming the material and its basis until the caller declares that this screen accepts a typical value — a declaration that then lands on every entry the screen produced, including the passing ones. Fatigue data now has the same distinction and a sharper one: a fatigue record carries its curve, its survival level, what it was measured on, and where it came from, and cannot be built without any of the four — a mean curve asked for a design answer returns nothing rather than a value with a caveat somewhere, because the design curves are drawn a stated number of standard deviations of log N below the mean, and reading the mean as the design curve hands back exactly the margin that offset was there to provide; the stress ratio R is a required field, since the difference between an R = 0 and an R = −1 curve is the whole subject of mean-stress correction; and a curve declines outside the cycle range its method covers rather than extrapolating, so the EN 1993-1-9 curve returns nothing below 10,000 cycles where the standard sends you to a strain-based assessment. The schema is anchored by agreeing to 1e-12 with the same standard's curve computed independently elsewhere in the library, at forty (detail category, life) pairs. A weld's detail category is a record too, not a number: EN 1993-1-9's category 90 and IIW's FAT 90 are the same label and a different curve, so the standard, edition, table and detail description are required fields, the EN ladder is discrete and a value between rungs is refused with the two nearest named, and a shear category refuses the direct-stress curve outright because EN's Δτ family runs a single m = 5 with no knee. And EN 1993-1-9 §8 caps the nominal range at 1.5·f_y — a limit the S-N formula does not know about, which returns a few thousand entirely ordinary-looking cycles for a 600 MPa range on a category-90 detail. What is bundled is now gated by what the wheel ships rather than by where datasets happen to live: the licence sweep walked two directories, so a .csv beside a module or a payload under a third would have shipped with no licence record and nothing would have noticed. Every non-Python file in the package must now be a dataset with a redistributable SPDX identifier or an exemption with a written reason — and, separately, must survive packaging: every test here runs against src/, so a dataset that stopped being shipped would keep passing and fail for the first person who pip installed it, on a materials lookup that works for every contributor. The wheel target must ship one whole package directory with every dataset under it and no key that could narrow it, since building a wheel fetches the backend and this suite runs with the socket layer closed; the fresh-install check that closes the rest is a written procedure rather than a claim — there is one, the agent skill — and an adversary test writes an unlicensed data file into the installed package and requires the sweep to see it. Data this library may read but may not ship — a publisher's section database, a benchmark's case archive — goes through fetch-on-first-use: consent is an argument rather than a default, the checksum is verified on download and on every read, the cache carries a provenance record naming the licence and the retrieval date the caller stated, and releases contain the recipe and the digest rather than the payload. That flow is also the only way anything here reaches the network, and it is now held that way rather than believed: the golden path — spec bytes to scorecard to DXF to evidence bundle to a verified attestation, plus a rendered report and a compile over the MCP handler — runs in the suite with the socket layer closed, the block is proved by making real calls through it rather than by patching a name nothing calls, fetch refuses without consent before it reaches the transport, and a second module importing a network client fails the build. A model that refuses to be constructed in a broken state could still be copied into one — pydantic runs no after-validator on model_copy, so the copy is a fully typed instance every downstream check accepts, and Normal(mean=1.0, std=0.5) was one call away from a negative standard deviation reaching the sampler. Its sibling was measured at the same time and was worse: frozen=True stops a field being rebound and does not reach inside the value, so a dict field on a frozen model is writable by anyone holding it and the writes land after every validator has run. CompilationTask.reference names the spec fields a correct compilation must carry and its constructor refuses a task naming none — and del task.reference["material"] turned a compilation that got the material wrong into one scoring 1 of 1 fields correct, deleting the wrong-but-valid case the module exists to report. Nine frozen models carried such a field; eight are frozen mappings now, reading exactly like a dict, refusing every write, and serializing as a plain object so nothing downstream sees the difference. The ninth is an exemption with a reason: the MCP tool schemas are arbitrarily nested JSON documents, where freezing the top level would read as a guarantee it does not make. This library found it three times in three unrelated modules and wrote the same comment each time; it is one base class now, inherited by every model that declares an invariant, with a ratchet that reads the declarations out of the source so a new model cannot be added without it. A copy with no update still costs nothing, because it cannot have moved — and that half is pinned by counting the validator rather than comparing the copy, which is true either way. There is a quickstart that is a front door rather than a section of this file — install, screen a lifting lug, read a cited FAIL with its governing check named, in under ten minutes of reading, with the page's own example executed in CI and its output compared byte for byte. The forty-five pages behind all of this have an index organized by what you are trying to do rather than by what the modules are called — start-here, screening by discipline, deciding what the answer is worth, declaring the part, getting the answer out, driving it from an agent — with a ratchet that a new page absent from it fails, and that no single section may hold more than half the pages, because a list with a heading on it is the alphabetical file listing again. Contributors start at adding a check — which now also carries a second sweep — cross the public-surface manifests against every identifier in tests/ and examples/, and 54 of 2,019 symbols are named nowhere. Most are result types reached by attribute access and not by name; the subset worth reading is the constants, and only a mutation settles one. That found BELLEVILLE_PLATEAU_RATIO, √2, published and used nowhere but its own module's prose: changing it to √2.2 failed nothing. It is pinned by its property rather than its digits — the load-deflection curve rises everywhere below the ratio, touches zero at it, and turns over above, which is what the constant means and also catches a change to the curve it describes. And the sweep that asks a harder question of a documentation page than the existing ratchet does: not "is this page named in a test", which a test can satisfy without reading a number off it, but "change a number on the page and does anything fail". Run at HEAD it found two pages arguing from figures nothing checked — typed-callouts.md's "a safety factor of 2.52 against 1.08", the whole argument of the page and stated twice, and analysis-interop.md's "416,231 times larger (25.4⁴)", which is not a fixture at all but a conversion the unit layer can do. Both are gated now, and a second pass over the same sweep gated five more — the aluminium column curve's 0.85 out-of-straightness knockdown, AISI's 1.052 in the plate slenderness, EN 1993-1-9's 0.737 and 0.405 curve anchors (recomputed from the curve, with the cycle counts the sentence names read out of it too, because the right ratio at the wrong life is still wrong), BTH-1's two design factors (whose ratio is checked as well as their values, since a page listing 2.00 twice satisfies every equality), and the GD&T half-band a Ø0.2 position contributes at RFS and at MMC, plus the uncertainty page's whole worked block (its distributions read off the page, the sampler run, and the printed line compared) and the pressure-vessel page's two-thickness table (compared against the run rather than only against the ratio sentence beside it — a cell and a ratio edited together leave the page internally consistent and wrong, which is the failure mode a self-referential gate has by construction), the seven contract rules — and which of them a gate actually enforces, said plainly rather than implied: rules 1, 5 and 6 carry named gates, rule 2 says outright that it has none beyond review, and 3, 4 and 7 name no gate at all. On top of the scorecard sit three cross-cutting layers that keep a green from being a silent one: typed repair feedback (a failing check names the parameter and the value that fixes it; a two-sided band flags over-engineering), uncertainty-aware margins (input scatter propagated to a shortfall probability and a sensitivity ranking) — and the label travels with the number now: a rendered annotation names the sampling method and prints the screening citation beneath it, where for a release both renderings showed the probability and the sample count and dropped the other two, MarginUncertainty having carried method and citation all along with nothing consuming either. The one place a reviewer meets that number is the document they sign. The gate replaces both values with sentinel strings and requires the renderings to show those, since a hardcoded "Monte Carlo, screening only" line passes every naive check and becomes a lie the day a second method exists, and ASCE 7-22 load combinations (the governing combination named, including the counteracting uplift case a gravity-only check misses) — and, because a combination treats a nature nobody supplied as zero, a load case that carries a force with no declared nature is now named rather than skipped: in the worked example a 130 kN girder passes at 1.52 on a demand that never saw a 25 kN conveyor reaction and fails at 1.04 once it does, so the check and the evidence bundle both report not_evaluated before a number is computed, and a green scorecard under a partly classified load set is a not_evaluated bundle. Because every check is closed-form and evaluates in microseconds, design-space exploration sweeps them exhaustively and returns an exact Pareto front — the lightest design that passes, which in the worked bracket is 3.75x heavier than the lightest one in the box. Drawing callouts are typed too: a semantic GD&T layer holds a feature control frame as data with Y14.5's grammar enforced in the constructor — flatness cannot reference a datum, Ⓜ cannot sit on a surface, and symmetry cannot appear on a 2018 drawing, because the 2018 edition removed it. That one declaration now drives three consumers — the text form, the QIF characteristic definition, and the drawn frame itself — with the propagation tested rather than assumed, because a consumer that quietly ignores a modifier renders a callout looser on the drawing than the one declared and tighter in QIF. The drawing consumer emits the boxed callout as DXF geometry on its own annotation layer, and every symbol is drawn as lines and arcs rather than typeset: a ⌖ or an Ⓜ written as text renders only where the viewer happens to have a GD&T font, and where it does not the modifier silently disappears. The proportions were read out of a published symbol chart rather than recalled — three would have been wrong from memory, including a Ø whose 1.5h is the symbol's height and not its slash's length, a defect rendering a real frame caught and no unit test would have. Externally computed member forces and section properties come in through a typed doorway that makes every convention explicit — analysis interop, where the axis mapping, the axial sign convention, and every component you chose not to screen are declared rather than inferred. A meshed sectionproperties result now imports through an adapter whose value is what it refuses: the length unit is required rather than defaulted (the package returns bare floats, and a section drawn in millimetres and declared as inches has its second moment read as 416,231x larger, so the part screens as immensely stiffer than it is), a composite section is refused because its constants are modulus-weighted, the extreme fibre is taken from the smaller section modulus, and the shear form factor is deliberately left unset — get_as() is the Timoshenko shear area, whose A/A_s is 1.2 for a rectangle, while the peak-over-average ratio the screen wants is 1.5, so importing one as the other would understate the peak shear stress by 20% with every dimension check satisfied. A finite-strip run comes in the same way, and refuses two things a signature curve makes easy to get wrong: a load factor is not a load (CUFSM's y-axis multiplies the applied reference distribution, and the DSM formulas take ratios, so a factor imported where a load belongs is wrong by the reference and dimensionally unremarkable), and which minimum is which mode is a reading rather than a lookup — declare the constrained modal decomposition, or declare a signature-curve reading and carry the half-wavelength each minimum was read at, with the pair refused when the local one is not the shorter, because swapping them moves strength between a curve anchored on P_ne and one anchored on P_y and nothing looks wrong. And once the physics passes, verification planning emits the physical test each check implies — a BTH-1 lifter's 125% proof load, a vessel's UG-99 hydrostatic — with the rule that a plan is never evidence: nothing performed reports not_evaluated, never a pass. Drawing callouts that are not geometry are typed too — typed MBD callouts makes surface finish, plating, and heat treatment the check inputs they always were: the finish derives the Marin surface factor, plating moves a fit by twice its thickness and a 60° thread's pitch diameter by four times it, a declared heat-treat condition no material record backs reports not_evaluated, and every callout carries a persistent characteristic identifier derived from what it is rather than what it says, so a revision reads as one change rather than a deletion and an addition. Work starts from a document, so requirements ingestion reads an RFQ sheet into a draft spec and refuses to release it while any load-bearing value is unconfirmed — and there is now a checklist to work from rather than a count: every value with the line it came from, the excerpt included because a reader holding the sheet open matches on text faster than on a line number, a conflict showing both readings rather than naming the field, and every heading present even when empty. summary() said "3 unconfirmed", which is the one thing the confirmer already knew; the SourceLocation was on every value from the first release and nothing rendered it: no confidence scores (every value carries the line it came from instead), a bare number recorded as not-extracted rather than guessed at, and a sheet that contradicts itself reported rather than silently resolved. Every layer's output then assembles into one evidence bundle with a single roll-up that is never better than its worst section: an absent layer is named rather than assumed, an unperformed verification plan drops a green scorecard to not_evaluated because a plan is not evidence, and a review the artifact moved under counts for less than no review at all. The whole result then seals as attested evidence: an in-toto statement whose subjects are the artifact digests and whose predicate carries the scorecard, the citations, a CycloneDX inventory of the environment that is now read rather than typed — every caller hand-wrote it, and two attested pint 0.24.4 and pydantic 2.9.2 against an environment running 0.25.3 and 2.13.5, which is a false toolchain record inside the one document whose purpose is provenance and the one part of an attestation nobody can catch by reading it; the list is derived from the declared dependencies so a new one appears unbidden, an uninstalled optional extra is left out rather than given a placeholder version, dev tooling is excluded because pytest did not produce your bundle, and a test requires every reported version to equal what the environment actually has, and a machine-readable AI-involvement disclosure — content-addressed, so the same inputs rebuild the identical digest and a materials-database bump visibly does not, and honest, because an unsigned bundle says so and a signature nobody checked reports not_evaluated. And because a scorecard is structurally a set of characteristics with requirements and actuals, the whole thing exports as QIF Results (ISO 23952) for CMM and quality software — with the tri-state carried across rather than flattened: a check that could not run crosses as NOT_ANALYZED holding the requirement it would have been judged against, because a format conversion that quietly drops it turns a file with an honest gap into one that reads as fully examined. Drawing callouts cross the same way: a feature control frame maps to a QIF characteristic definition with every name read out of the published XSD rather than recalled — three of which would have been guessed wrong, including a material-modifier enumeration that reads MAXIMUM where the drawing says Ⓜ and a non-diametral zone element that is spelled differently for position than for the orientation characteristics — and a modifier the target type has no element for is refused rather than dropped, because a Ⓜ that vanishes on the way out crosses as a tighter requirement than the drawing granted. The same page opens the other direction: a Digital Calibration Certificate (the open PTB schema) read as a measured input, so a provenance chain that used to end at a handbook table can end at a calibrated instrument — with the laboratory's stated uncertainty handed to the margin sampler as a typed distribution, a unit outside the declared D-SI table refused rather than guessed at, and a signature reported as present and unverified rather than as checked, because there is no third state an offline tool can honestly claim. And because the agents driving all of this need to be taught what correct use looks like, Anvilate ships a first-party agent skill in the open SKILL.md convention — retrieval not recall, read the scorecard, not-evaluated is not a pass, inverse-first repair, confirm before use, screening not certified — bound to the library by CI rather than by good intentions: every symbol it names is imported, every worked example is executed and its claimed output compared byte for byte, every rule is anchored to an example whose own assertions carry the claim, and guidance that would bypass a gate or overstate a verdict fails the build. The module that produced that census — anvilate.specbench, the suite reader whose whole subject is the denominator, since a benchmark score over a set the pipeline cannot accept is a number about the benchmark — was itself on no page until the same discoverability sweep found it, and is now documented with the three things it refuses to do: an out-of-scope verdict cannot be built without a reason, so "0 of 106" comes with 106 reasons rather than a count; part count binds before material, because a 44-part PLA bookshelf is not a materials problem and reporting it as one suggests adding PLA would fix it; and a document missing one of the suite's twelve headings is refused rather than read leniently against something else. Whether a given local model can actually drive it is a question only an eval over Anvilate's own tool surface answers, so that measurement ships before the server it will measure — completion, iterations and tool-call errors as three numbers with no fourth that averages them, because a model that abandons the hard tasks drives its iteration count down and its error count with it and only the completion rate says so; iterations are reported over completed runs, a run that made no call has no error rate rather than a clean one, and the task set is refused if it names an operation the tool catalog does not expose or leaves any of the eight required ones untouched. Every artifact that leaves the library now passes the export gate: a DXF or a QIF document is written only when the scorecard passes, or under an explicit override that stamps UNVALIDATED and the blocking checks into the file's own metadata — because a cut file that does not say it came out of a T1 screen is indistinguishable from a released drawing, and a check that could not run blocks it exactly as hard as one that failed. The requirement had said so since the first spec and no exporter had implemented it; what holds it now is a ratchet rather than a habit — every public export entry point that emits an artifact must take a mandatory authorization, every saveas in the package must sit inside one that does, and the MCP tool declaring the watermark gate is resolved through its backing symbol so "the MCP surface grants no bypass" is a claim that can fail. The one declared gate with nothing behind it, build_part's sandbox, is asserted to be undischarged, so the day an implementation lands the test fails rather than the tool quietly acquiring code with no sandbox. The requirement watermarks the evidence bundle too, so the bundle now records what was emitted and under what authorization — and that changes the roll-up: a part whose every check passes reads NOT_EVALUATED once a drawing has left it authorized from no card at all, because nothing failed and something is in the world with no verdict behind it, while an empty export record is named as a layer the bundle does not speak to rather than read as nothing having been exported. The requirement's other half — that every evidence bundle carry the screening disclaimer and the modelling assumptions — is now a constant on the rendering rather than a field, because in a library "non-dismissable" can only mean there is no call that renders a bundle without it, and an empty assumptions list reads none declared in the bundle and in the calculation report both: a document whose author declared none and one whose author forgot the section used to be the same document. Where the export layer is pointed — STEP AP242 per the CAx-IF Recommended Practices, 3MF as ISO/IEC 25422, refereed in CI by the free NIST analyzer against the free CAx-IF test models — is written down in export targets — a roadmap page kept as a verification record, with each claim marked confirmed or not and how it was checked, including the two that did not survive checking. None of it is shipped yet: no STEP or 3MF writer exists, which is what made re-aiming free. The two load-bearing data contracts — the Spec IR going in and the scorecard coming out — are published as JSON Schema 2020-12, generated from the models and held against them by a gate with two halves: the artifact must match the model, and a changed artifact must carry a moved version, because a client pinned to 1.1.0 fetching different content under the same identifier is the breaking change nobody can see. The tri-state is in the enumeration, so a consumer cannot model the result as a boolean without noticing what it is dropping. Those same artifacts are now the MCP tool contracts: the pipeline's eight operations are published as tool definitions whose schemas $ref the spec and scorecard at their versions rather than paraphrasing them, so the tool surface an agent reads and the structured-output constraint a compiler is decoded under cannot drift apart — and the reference is written out as a literal on purpose, because one computed from the same call it is compared against agrees with itself at every version, including the one where the tool surface should have moved and did not. Two dispatch modes, decided by one rule rather than tool by tool: bounded work (parsing a spec, the closed-form T0/T1/T2 checks, reading a scorecard, writing an export) answers in the reply, and unbounded work — a full build, an FEA-class run whose stopping condition is a convergence tolerance — goes through the Tasks extension, with a T3 tool that claims bounded cost refused in the constructor. Four of the eight operations are backed by shipping code and name the symbol, which CI resolves against the live surface; the other four say so rather than naming something that does not exist. Writing the request handler against those contracts then found what publishing them early is for: four of the eight tools name nothing in their input to act on — read_scorecard takes no arguments at all and returns a scorecard — so each is asking the server to remember what the last call produced, and that is a session rather than the stateless skeleton the spec describes. Every tool now declares the required input property carrying its subject, the constructor refuses one the schema does not require (an optional subject is state for exactly the calls that omit it), and the unservable list is derived from those declarations rather than written down. The two surfaces are now held against each other, which only became possible once a CLI existed: one spec screened over MCP and at the shell must produce the same scorecard document rather than the same status, since two cards agreeing on PASS and differing on which checks ran is the drift a status comparison cannot see. The single divergence is asserted rather than smoothed — the CLI exports an evidence bundle from a spec file and MCP refuses export_artifact — together with its cause, which is that the tool names nothing in its input to act on while the shell command takes the path of what it exports; the test fails the day the tool is given a subject, which is exactly when it should be revisited. Every operation that is servable at all is now dispatched — compile_spec and run_validation, the two tools that are backed, bounded and servable statelessly at once; the other six are refused for a structural reason rather than for want of a handler, and the "not dispatched yet" branch no tool reaches any more stays as the net for the next one, asserted unreached in both directions. A document that fails compile_spec comes back as a result carrying its error paths rather than a JSON-RPC error, because the request was fine and the document was not — while the same document sent to run_validation is a malformed request, since that tool's input property is declared as the published Design Spec schema rather than as a candidate. Behind run_validation is the piece the pipeline was missing: screening a spec on its own terms — every discipline pack screens a typed element you build by hand, and nothing screened the spec document, so a spec compiled to the IR and stopped there. It now screens what the document supports (tolerance achievability against the declared process's floor, every declared stack-up chain on its worst case rather than its statistical spread, and whether every force-carrying load case is classified) and names what it cannot: a DesignSpec states no structural element type, so no pack screen can be selected from one and the T1 analytical tier reports not_evaluated on every spec with that reason — a gap in the IR rather than in the screen, and closing it means a published-schema change rather than more analysis code. The rule that makes the card safe to read is that a tier the spec demanded always produces an entry, including when the document carries nothing to run it against, because a demanded tier that quietly produced none would leave the card green on the checks that happened to exist. Array elements are held to their items schema too, which the surface uses in exactly one place and where it mattered: run_validation.tiers names three tiers because the fourth is task-dispatched, and until that was enforced T3_fea was accepted on the synchronous tool while a misspelled tier came back as spec.acceptance.tiers.0, sending a client to look at its document for a problem in a different argument. The gate that was supposed to have caught that compared two sets of keyword names, so adding pattern to the known set satisfied it while nothing enforced a pattern — and items had sat there unenforced the whole time; it is a table of probes now, each keyword carrying a value its schema accepts and every value it must refuse. A call is now checked at both ends against the same document the client was handed: the arguments against the published inputSchema on the way in, and the structuredContent against the published outputSchema on the way out, with a non-conforming result refused as an internal error naming the property rather than sent for the client to choke on — because a client that validates against the contract, which is the point of publishing one, would reject the payload without knowing whether the server or its own pin was wrong. Both dispatched handlers already conformed, which is the only state in which that gate can be written and stay green; what CI adds on top is the half the in-process check deliberately skips, resolving the spec and scorecard $refs against the released artifacts and validating a real result of every dispatched tool whole. And a backing symbol that merely resolves is not evidence a handler goes near it — run_validation named the evidence-bundle assembler for as long as nothing was wired and went on resolving after it was dispatched to the spec screen — so the named symbol is now replaced with one that raises and the call has to raise through it. The transport is newline-delimited JSON over stdio, where a notification produces no output line (a client waiting one-for-one stalls otherwise) and a line that is not JSON gets a parse error without taking the stream down. It runs as anvilate-mcp or python -m anvilate.mcp. At the shell there is now anvilate itself, which there was not: headless-automation names build, check, export and diff and the only console script was the MCP server. check compiles a spec document, screens it and prints the card; export renders the evidence bundle, which is assembled from a scorecard and so needs no geometry — that one was refused whole for a commit, on a reason true of a DXF and false of the bundle, and a refusal wide enough to cover something that works is as misleading as a missing one. diff compares two spec documents and the verdicts they screen to — its requirement says "two builds of a part (or a spec change)", and the parenthesis is both what is possible without a geometry kernel and the half a merge gate reads, since the scenario is a commit that changes a shared pattern and makes a downstream part fail; its exit code is about what got worse, so a part that was already failing and still fails does not fail the build, while a check that used to run and now cannot does. Only build is refused by name with what it is waiting on, because unknown command: build tells a script author they typed it wrong when the operation is specified and unbuilt. The exit code is the interface and it is not a boolean: 0 passed, 1 failed, 2 could not be evaluated, 3 bad request, 4 unbuilt. check takes a directory as well as a file, because the requirement is to revalidate all specs in a repository on push: a document found by searching that is not a spec is skipped with a line saying so, one you named is an error, an empty search is a bad request rather than a pass, and the exit code over many is the worst verdict found. export takes the same paths check does — a file, several, or a tree — because CI publishing evidence bundles for a repository should not be a shell loop in a script nothing type-checks. There is a reusable CI action too, which writes both the scorecard report and the bundles — install, screen the repository, fail the run — whose allow-not-evaluated is off by default and says why in its own description, since a merge gate treating "could not run" as a pass is the silent green the tool exists to avoid. An action's shell script is the least-tested code in most repositories, so this one is resolved against the CLI: every flag it passes must exist, every variable it reads must be bound, and the exit codes its comment documents must be the ones the CLI can return. The container image the same requirement names is not shipped, and the page says why rather than leaving a gap: a docker build is a network operation no offline gate can check, and an image published without a gate on what it contains is a claim the rest of this project refuses to make. Every blocking check goes to stderr with the spec it came from — including the ones that could not run, labelled as such rather than called failures — which is the half of the requirement the first version missed. An audit an hour after the first version also found the hole that a bare exit code makes: ArgumentParser.error exits 2 hardcoded, so every usage error came back with the code the docs tell a CI job it may accept, and a typo read as a screen that ran and could not conclude. A usage error is a bad request — a merge gate must not go green on a screen that never ran, and the map is total over the four statuses so a fifth is a decision rather than a silent zero. What an agent does with all of that is its own page, and the first thing it says is which half of the loop is missing: the shape everyone writes is build, validate, read the scorecard, repair, and two of those four are not callable — build_part is task-dispatched with the Tasks extension unbuilt, and read_scorecard takes no arguments and returns a scorecard, which is a session. So the loop that works is two calls and the card is read out of the validation reply, and the page's four worked examples are executed in CI with their printed output compared byte for byte rather than described. And before the intent compiler exists, its measurement does: constraining a small model's output to a schema is measured to take validity from ~62% to 100% while taking accuracy down from ~20% to 11%, so a valid spec can still be the wrong spec — schema validity, field correctness and the wrong-but-valid rate are three separate numbers with deliberately no fourth that averages them, because a single figure over a constrained decoder rises while the thing a user cares about falls.
The screens above are the trustworthy core. The end goal is to wrap them so a plain-English request compiles into that same validated scorecard and a parametric solid you can open in CATIA, SolidWorks, or NX:
natural language ──► typed Design Spec ──► parametric B-Rep geometry
▲ │
│ ▼
human review ◄── validation report ◄── physics + DFM + FEA checks
│ │
└───────── agent self-corrects ◄───────────┘ (until checks pass)
│
▼
STEP AP242 · DXF · 2D drawing · source code
The LLM is a replaceable component that only writes the spec and proposes edits; the geometry and validation pipeline is deterministic and runs identically with or without any AI. Nothing unvalidated leaves the tool.
The behavioral contract for every subsystem is specified up front in openspec/specs/ — that's the authoritative design reference, including the roadmap, non-goals, and risk analysis.
MIT — see LICENSE. GPL-licensed analysis engines (Gmsh, CalculiX) are invoked as separate subprocesses with file-based interchange, keeping Anvilate's own code MIT.
