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.
This commit is contained in:
2026-08-24 03:58:12 -05:00
parent d2827c23b3
commit e921cacf0e
4 changed files with 256 additions and 3 deletions
+53 -1
View File
@@ -46,11 +46,17 @@ TWO IDENTITIES, BECAUSE THEY ANSWER DIFFERENT QUESTIONS
from __future__ import annotations
import hashlib
import os
from dataclasses import dataclass, field
from typing import Dict, List, Mapping, Optional, Tuple
FORMAT_VERSION = "1"
# Resolved once per process. Both are stable for the life of a run and
# looking them up per build would put a subprocess in the build path.
_CODE_REVISION = None
_TOOLCHAIN = None
# Reported values worth carrying for verification: the ones a person can put a
# caliper on, plus the counts that say the part is what it claims to be. Kept
# short deliberately -- a record nobody reads verifies nothing.
@@ -305,6 +311,47 @@ class DesignRecord:
# Construction from a build
# ---------------------------------------------------------------------------
def code_revision() -> str:
"""
The revision of the code that produced a model.
Resolved once per process, in order: an explicit ``MECHCOMP_REVISION``
in the environment, then ``git rev-parse`` against the package's own
tree, then ``"unknown"``.
Never guessed. An "unknown" in a record is honest and tells you the
part may not be reproducible; a plausible wrong revision would send
someone to the wrong commit, which is worse. A dirty working tree is
reported as such, because a model built from uncommitted edits cannot
be regenerated from the revision alone.
"""
global _CODE_REVISION
if _CODE_REVISION is not None:
return _CODE_REVISION
explicit = os.environ.get("MECHCOMP_REVISION", "").strip()
if explicit:
_CODE_REVISION = explicit
return _CODE_REVISION
_CODE_REVISION = "unknown"
try:
import subprocess
here = os.path.dirname(os.path.abspath(__file__))
sha = subprocess.run(
["git", "-C", here, "rev-parse", "--short", "HEAD"],
capture_output=True, text=True, timeout=5).stdout.strip()
if sha:
dirty = subprocess.run(
["git", "-C", here, "status", "--porcelain"],
capture_output=True, text=True, timeout=5).stdout.strip()
_CODE_REVISION = sha + ("-dirty" if dirty else "")
except Exception: # noqa: BLE001
pass
return _CODE_REVISION
def toolchain_versions() -> Dict[str, str]:
"""
Versions that can change emitted geometry.
@@ -314,6 +361,10 @@ def toolchain_versions() -> Dict[str, str]:
is precisely the F-034 mechanism. A record that omits them cannot explain why
the same parameters produced a different part.
"""
global _TOOLCHAIN
if _TOOLCHAIN is not None:
return dict(_TOOLCHAIN)
out: Dict[str, str] = {}
try:
import shapely
@@ -329,7 +380,8 @@ def toolchain_versions() -> Dict[str, str]:
out["numpy"] = numpy.__version__
except Exception: # noqa: BLE001
pass
return out
_TOOLCHAIN = out
return dict(out)
def record_for(family: str, profile: str,
+12 -1
View File
@@ -244,8 +244,19 @@ def report(family: str, profile: str, status: str, g: Geo, m: Metrics,
@dataclass(frozen=True)
class Result:
"""What ``build()`` returns. ``report`` is the dict the oracle compares."""
"""
What ``build()`` returns. ``report`` is the dict the oracle compares.
``record`` is the design record: what this model was made from, at full
precision, sufficient to regenerate it. It defaults to ``None`` so that
a ``Result`` can still be constructed without one -- but every build
through ``assemble`` carries it, because a generated model with no
record becomes unidentifiable the moment a parameter is tuned.
It is not part of the oracle comparison. ``report`` is unchanged.
"""
report: Dict[str, object]
section: List[List[Tuple[float, float]]]
members: List[Member]
geo: Geo
record: object = None
+44 -1
View File
@@ -56,6 +56,48 @@ class Family:
base_checks: Callable[[Params], List[Check]]
def _design_record(family: Family, profile_type: str, params: Params,
geo: Geo, rep: Dict[str, object]):
"""
The record that ships with this model.
Built from what the build already had: the family defaults, the caller's
overrides, and the Geo that was actually used. The stock and fit lines
come from that Geo rather than from the parameters, so they describe what
was built rather than what was asked for -- the two agree today and the
record should keep saying so if they ever stop.
Imported lazily. ``mechcomp.design_record`` is a leaf that imports
nothing from mechcomp, and keeping the import here rather than at module
scope means a failure in the record can never prevent a part being built.
"""
from mechcomp.design_record import code_revision, record_for
from mechcomp.stock import Fit, Provenance, RectStock
stock = RectStock(
designation="stock",
width=geo.width, thickness=geo.strap_t, count=geo.count,
provenance=Provenance(
source="build parameters",
note="strap_width_mm and strap_thickness_mm as supplied"))
fit = Fit(clearance=geo.clearance)
return record_for(
family=family.name,
profile=profile_type,
defaults=family.defaults,
params=params,
report=rep,
stock=["%s %g x %g mm x%d, stack %g mm -- %s"
% (stock.designation, stock.width, stock.thickness,
stock.count, stock.stack,
stock.provenance.describe())],
fit=[fit.describe()],
code_revision=code_revision(),
generator_revision=REVISION,
)
# ---------------------------------------------------------------------------
# Pieces every family shares
# ---------------------------------------------------------------------------
@@ -127,7 +169,8 @@ def assemble(family: Family, profile_type: str, params: Params) -> Result:
model_length_mm(p), p["material_density_g_cm3"], extra,
)
return Result(report=rep, section=c.section, members=c.members, geo=geo)
return Result(report=rep, section=c.section, members=c.members, geo=geo,
record=_design_record(family, profile_type, params, geo, rep))
def geometry_checks(p: Params) -> List[Check]: