This notebook introduces a real traceability graph over two of the model’s three named requirement usages (the third, added later in this notebook, closes a different gap this same graph finds -- see below); after running it you can tell, for each of the two, what functional intent it expresses, what allocation and realization carry it forward, and what verification evidence, if any, actually exists, and you will have found one of this tutorial’s own strongest pieces of formal evidence tied to no requirement at all, then closed that gap directly.
Traceability, and loading the model¶
SEBoK defines traceability as “the degree to which a relationship can be established between two or more products of the development process” (see the traceability glossary entry); Douglas’s own story names what it is for: “we’re left with a traceability map that connects the as-designed system with the requirements,” used to audit missed requirements, unjustified widgets, and to drive verification tests. Chapter 9’s own coverage report joined one pair of surfaces (RequirementUsage, SatisfyRequirementUsage) for one question: has a positive claim ever been made? This notebook extends that single join into the full chain Douglas’s own idea names: from a requirement’s own functional intent, through whatever allocation and realization carry it forward, to the verification evidence that actually exists, built from real queries (model.query(), get_satisfy_relationships(), requirement_coverage(), allocations_for(), supertypes_transitively()), not a hand-typed table. This chapter adds exactly one new named model element, and only because this notebook’s own traceability analysis, below, finds a real gap it is positioned to close directly: otherwise, models/ch10-cumulative.sysml carries models/ch08-cumulative.sysml’s content forward unchanged, the same design choice Chapter 9 made (see this chapter’s own Overview for why, unlike Chapter 9, this chapter still commits its own fixture file). models/ch10-cumulative.sysml -- the file loaded fresh below, and again by notebook 03 -- already carries that one new element forward, since it is a real, committed fixture, not assembled at runtime; the notebook’s own “unjustified widget” search below is run against a deliberate reconstruction of the state before this chapter’s own fix, precisely so the finding that motivated the fix can still be shown honestly, the same way Ch8-03 reconstructs a different model state via source.replace(...) rather than a second committed file.
from pathlib import Path
import opensysml
from toaster.report import format_diagnostics
conn = opensysml.connect(version="v0.9.0")
source = Path("../../models/ch10-cumulative.sysml").read_text()
model = conn.load_from_content(source, strict=False)
assert model.ok, f"Model failed: {format_diagnostics(model.diagnostics)}"
Check the guard and see the whole model¶
The model loads cleanly. Before tracing anything through it, the same language-tier control every chapter carries, chosen here to match what this chapter actually traces: an allocate naming a feature that was never declared still fails to load, which matters directly for a chapter about following allocation and realization chains -- an allocation naming a feature that does not exist can never silently stand in for a real one. This control does not test a different, real gap: whether an allocation naming a feature that DOES exist, but is the wrong one (a genuine mismatch, not an undeclared reference), would be caught the same way. That case is untested here.
# An allocate naming an undeclared feature fails to load; it can never silently
# stand in for a real allocation link this chapter's own traceability graph follows.
bad_source = """
package Bad {
action def A;
part def W { perform action a : A; }
part w : W;
allocation badAlloc allocate A::missing to w;
}
"""
bad = conn.load_from_content(bad_source, strict=False)
assert not bad.ok, "Expected failure for an allocation naming an undeclared feature"
print(f"Negative control ok: bad.ok={bad.ok}")
Negative control ok: bad.ok=False
The full part hierarchy underneath both traced chains, drawn whole rather than rooted at either one -- nominal and slow are typed by, but not owned by, Toaster, so no single root reaches both heatGen’s own chain and theirs.
from toaster.render import model_to_dot, render_dot
from IPython.display import SVG
dot = model_to_dot(model, title="Ch10")
out_path = Path("../../figures/ch10-structure.svg")
out_path.parent.mkdir(exist_ok=True)
render_dot(dot, out_path)
SVG(filename=str(out_path))Trace the two requirement chains¶
With the model loaded, the graph starts from two of the model’s three named requirement usages and, for each, the requirement definition’s own declared subject feature: not read off the source text by eye, but found the same way any other query in this tutorial finds a real fact, by reading each candidate feature’s own sysx:sourceText in the API-JSON export for the literal subject keyword SysML v2 itself requires there (SysML v2 formal/2026-03-02 SS8.3, RequirementDefinition). This is a workaround for a gap in the OpenSysML runtime’s API-JSON export, which has no structural subjectParameter key on RequirementDefinition (DEFERRED.md D-038).
from toaster.query import ApiIndex, find_requirements
idx = ApiIndex(model)
# Workaround for DEFERRED.md D-038: no structural subjectParameter key on RequirementDefinition.
def requirement_subject(idx, req_def_qn):
"""The (name, type) of req_def_qn's own declared `subject` feature, found by
reading each of its owned ReferenceUsage features for the literal `subject`
keyword in its own sysx:sourceText, not assumed from the requirement's name."""
for e in idx.of_type("ReferenceUsage"):
qn = e.get("qualifiedName", "")
if qn.startswith(req_def_qn + "::") and e.get("sysx:sourceText", "").strip().startswith("subject "):
return e["declaredName"], idx.type_names(qn)[0]
return None, None
requirements = sorted(r.id for r in find_requirements(model))
for r in requirements:
req_def = idx.type_names(r)[0]
subject_name, subject_type = requirement_subject(idx, req_def)
print(f"{r}: def={req_def} subject={subject_name} : {subject_type}")
ToasterDemo::energyConservationReq: def=ToasterDemo::EnergyConservationReq subject=None : None
ToasterDemo::heatGenerationReq: def=ToasterDemo::HeatGenerationReq subject=heatGen : ToasterDemo::HeatGenerator
ToasterDemo::timely: def=ToasterDemo::TimelyToast subject=toaster : ToasterDemo::Toaster
heatGenerationReq’s own subject is heatGen : HeatGenerator, an engineering rating on the logical carrier one level below the heating system (Chapter 6). timely’s own subject is toaster : Toaster, the toaster as a whole. Both are real functional intents the requirement definitions themselves state; the next cells trace what carries each one forward. A third requirement usage, energyConservationReq, also printed above, prints subject=None : None: it declares no explicit subject line at all (see the remediation cells below for why -- a real spec question, not an oversight), and it is not a functional-decomposition requirement like the other two. This notebook’s own traceability graph below (functional intent, allocation, realization) is not built for it -- it exists to close a different gap this same notebook finds later, addressed directly where that gap is found.
from toaster.query import allocations_for, supertypes_transitively
heatgen_allocations = allocations_for(model, "ToasterDemo::HeatingAssembly::heatGen", inherit=True, index=idx)
heatgen_realizers = supertypes_transitively(model, "ToasterDemo::ResistanceCoil")
print("heatGenerationReq's subject is allocated from:")
for a in heatgen_allocations:
print(f" {a}")
print(f"ResistanceCoil (the realizer chosen for HeatGenerator) itself specializes: {heatgen_realizers}")
heatGenerationReq's subject is allocated from:
{'id': 'ToasterDemo::HeatingAssembly::heatGenAllocation', 'type': 'AllocationUsage', 'ends': [['ToasterDemo::HeatingSystem::applyHeat', 'ToasterDemo::ApplyHeat::generateHeat'], ['ToasterDemo::HeatingAssembly::heatGen']]}
ResistanceCoil (the realizer chosen for HeatGenerator) itself specializes: {'ToasterDemo::HeatGenerator'}
heatGenAllocation allocates applyHeat.generateHeat to heatGen, declared inside HeatingAssembly where both resolve, the functional action Chapter 4 defined reaching the logical carrier; ResistanceCoil specializes HeatGenerator (Chapter 6’s own mechanism selection, AS-C06), and rated/weak are its two physical candidates. The same pattern, traced for timely’s own subject next.
Toaster itself, unlike HeatGenerator, has no further physical subtype: its own candidates are usages typed directly by it. specializes_transitively() reports every usage that specializes or is typed by Toaster, which also catches the two requirement definitions’ own internal subject placeholders (TimelyToast::toaster, TimelyToastTest::toaster, nested inside the requirement definitions themselves, not real candidates); filtering to elements owned directly by the top-level package leaves the two real ones.
from toaster.query import specializes_transitively
toaster_allocations = allocations_for(model, "ToasterDemo::Toaster::heating", inherit=True, index=idx)
toaster_candidates_raw = specializes_transitively(model, "ToasterDemo::Toaster")
toaster_candidates = sorted(
qn for qn in toaster_candidates_raw if idx.by_qn[qn].get("owner", {}).get("@id") == "ToasterDemo"
)
print("timely's subject is allocated from:")
for a in toaster_allocations:
print(f" {a}")
print(f"specializes_transitively(Toaster), raw: {sorted(toaster_candidates_raw)}")
print(f"filtered to real, top-level candidates: {toaster_candidates}")
timely's subject is allocated from:
{'id': 'ToasterDemo::Toaster::heatAllocation', 'type': 'AllocationUsage', 'ends': [['ToasterDemo::ToastingSystem::toastBread', 'ToasterDemo::ToastBread::applyHeat'], ['ToasterDemo::Toaster::heating']]}
specializes_transitively(Toaster), raw: ['ToasterDemo::TimelyToast::toaster', 'ToasterDemo::TimelyToastTest::toaster', 'ToasterDemo::nominal', 'ToasterDemo::slow']
filtered to real, top-level candidates: ['ToasterDemo::nominal', 'ToasterDemo::slow']
heatAllocation allocates toastBread.applyHeat to heating, declared inside Toaster where both resolve; Toaster’s own real candidates are nominal and slow. Both chains reach real physical candidates. What verification evidence actually exists for each is the last, and most consequential, link.
from toaster.query import requirement_coverage
coverage = {c["requirement"]: c for c in requirement_coverage(model, idx)}
for r in requirements:
print(coverage[r])
{'requirement': 'ToasterDemo::energyConservationReq', 'satisfied_by': [], 'failed_by': [], 'covered': False}
{'requirement': 'ToasterDemo::heatGenerationReq', 'satisfied_by': ['ToasterDemo::rated'], 'failed_by': ['ToasterDemo::weak'], 'covered': True}
{'requirement': 'ToasterDemo::timely', 'satisfied_by': [], 'failed_by': ['ToasterDemo::slow'], 'covered': False}
heatGenerationReq is covered on both sides: rated really satisfies it, weak really fails it, and both claims are genuine positive/negative evidence about a real candidate. timely is not covered: the only claim against it is negative (slow fails it), and TimelyToastTest’s own verify timely; objective names the requirement without binding any subject (Chapter 9’s own finding, reproduced here directly against the same real model). This is an absence of a claim, not evidence that nominal fails timely: Toaster::cycleTime is still a settable attribute, not derived from anything, so no one has ever actually checked whether nominal satisfies timely in the first place. energyConservationReq’s own coverage entry, also printed above, reports covered=False too (satisfied_by=[], failed_by=[]) -- but for a structurally different reason than timely’s: not an omission anyone could still close with more querying, but the deliberate result of a design choice explained in the remediation cells below (and in AC-C10), where this requirement is tied to its own evidence by subsetting an already-proved lemma, not by any instance-level assert satisfy.
def format_allocation(alloc):
"""Render an allocations_for() entry ({id, type, ends}, per find_allocations's
own docstring) as "name: source.chain -> target (nested in Owner)" -- generated
from the real query result, not a fresh literal."""
name = alloc["id"].rsplit("::", 1)[-1]
source_chain = ".".join(qn.rsplit("::", 1)[-1] for qn in alloc["ends"][0])
target_qn = alloc["ends"][1][-1]
target_name = target_qn.rsplit("::", 1)[-1]
nested_in = target_qn.rsplit("::", 2)[-2]
return f"{name}: {source_chain} -> {target_name} (nested in {nested_in})"
traceability_graph = [
{
"requirement": "ToasterDemo::heatGenerationReq",
"functional_intent": "heatGen : HeatGenerator (a heat generator rated for at least 600 W)",
"allocation": format_allocation(heatgen_allocations[0]),
"realization": "ResistanceCoil (:> HeatGenerator); candidates rated, weak",
"verification_evidence": (
f"satisfied_by={coverage['ToasterDemo::heatGenerationReq']['satisfied_by']}, "
f"failed_by={coverage['ToasterDemo::heatGenerationReq']['failed_by']} "
"(bidirectional: a real positive claim AND a real negative claim)"
),
},
{
"requirement": "ToasterDemo::timely",
"functional_intent": "toaster : Toaster (a toasting cycle completing in at most 180 s)",
"allocation": format_allocation(toaster_allocations[0]),
"realization": "Toaster itself; candidates nominal, slow",
"verification_evidence": (
f"satisfied_by={coverage['ToasterDemo::timely']['satisfied_by']} (none), "
f"failed_by={coverage['ToasterDemo::timely']['failed_by']} "
"(one-sided: only a negative claim, plus an unbound verify objective)"
),
},
]
for row in traceability_graph:
print(row["requirement"] + ":")
for key, value in row.items():
if key != "requirement":
print(f" {key}: {value}")ToasterDemo::heatGenerationReq:
functional_intent: heatGen : HeatGenerator (a heat generator rated for at least 600 W)
allocation: heatGenAllocation: applyHeat.generateHeat -> heatGen (nested in HeatingAssembly)
realization: ResistanceCoil (:> HeatGenerator); candidates rated, weak
verification_evidence: satisfied_by=['ToasterDemo::rated'], failed_by=['ToasterDemo::weak'] (bidirectional: a real positive claim AND a real negative claim)
ToasterDemo::timely:
functional_intent: toaster : Toaster (a toasting cycle completing in at most 180 s)
allocation: heatAllocation: toastBread.applyHeat -> heating (nested in Toaster)
realization: Toaster itself; candidates nominal, slow
verification_evidence: satisfied_by=[] (none), failed_by=['ToasterDemo::slow'] (one-sided: only a negative claim, plus an unbound verify objective)
heatGenerationReq traces all the way from a stated functional intent through a real allocation and a real mechanism selection to genuine, opposite-polarity verification evidence. timely traces just as far through intent, allocation and realization, but stops one link short: no candidate has ever been positively checked against it. Both are real findings about the model as it actually stands, not a contrast built to make one requirement look better than the other.
A real gap: a proof tied to no requirement¶
One more real finding this graph surfaces, in the other direction: does the model’s own strongest formal evidence trace back to any requirement at all? deliveredEnergyBoundedBySupply (Chapter 8) is the one property in this whole tutorial proved by Z3 for every value its unbound features admit, not merely evaluated at one point. This check went through several rounds before settling: a bare test of whether it is named as the subsets of any SatisfyRequirementUsage is structurally vacuous (SysML v2’s own assert satisfy grammar requires a satisfy’s subsets target to itself be a requirement usage, formal/2026-03-02 SS8.3, so a bare AssertConstraintUsage’s own id can never appear there for any model that loads at all); two rounds of hardcoding one more named field each still missed a real, constructible tie; a field-agnostic scan of every element’s every field closed most of those gaps but still missed a connection-end typing (end e1 ::> lemma;) and raised a genuine, unresolved question -- does a dependency/allocate/metadata about relationship naming both the lemma and a requirement as two SIBLING elements, neither owning the other, count as a “tie”? Rather than keep broadening an open-ended search, that question was escalated and settled by deciding to stop chasing completeness and replace it with two small, explicitly-named, narrowly-scoped checks instead (toaster.query.requirement_ties, described in the case study):
Satisfy-by-subject: is the lemma ever named as the SUBJECT of a real
assert satisfy <requirement> by <lemma>;relationship -- the chapter’s own idiom, used above forrated/weakagainstheatGenerationReq?Direct reference from within a requirement’s own body: does any element owned (at any nesting depth) by a
RequirementDefinitionorRequirementUsage(an EXACT type match: aConcern/Viewpointowner does not count, even though both are genuine metaclass subtypes of one or the other) have its ownsubsets,redefines,referencesorreferentfield pointing at the lemma’s id directly?
This is a deliberate, narrow pair of checks, not an attempt at completeness, and it says so plainly: by design, it does not detect a connection-end (end e1 ::> lemma;, port/connector typing); a dependency/allocate/metadata about/connection/#derivation-style relationship naming the lemma and a requirement as two sibling elements where neither owns the other; Concern/Viewpoint ownership; a transitive chain through an intermediate element not itself owned by a requirement; or any other relationship shape not named above.
from toaster.query import requirement_ties, tied_to_any_requirement
delivered_energy_bounded = idx.by_qn["ToasterDemo::deliveredEnergyBoundedBySupply"]
# models/ch10-cumulative.sysml (https://github.com/Open-MBEE/toaster/blob/main/models/ch10-cumulative.sysml) -- the exact file `model` above was loaded from --
# already carries this chapter's own remediation forward (see below): a genuine
# committed fixture, not assembled at runtime. To show honestly what this search
# found BEFORE that remediation, reconstruct that earlier state from `source`
# itself, the same way Ch8-03's own `source.replace(...)` reconstructs a different
# state for its own demonstration, rather than checking a second committed file.
before_marker = "\n requirement def EnergyConservationReq {"
source_before_remediation = source[:source.index(before_marker)] + "\n}\n"
model_before = conn.load_from_content(source_before_remediation, strict=False)
assert model_before.ok, f"Model failed: {format_diagnostics(model_before.diagnostics)}"
idx_before = ApiIndex(model_before)
ties = requirement_ties(model_before, "ToasterDemo::deliveredEnergyBoundedBySupply", idx_before)
tied_to_a_requirement = tied_to_any_requirement(model_before, "ToasterDemo::deliveredEnergyBoundedBySupply", idx_before)
print(f"deliveredEnergyBoundedBySupply @type: {delivered_energy_bounded['@type']}")
print(f"Satisfy-by-subject or direct-reference-from-a-requirement-body hits, before remediation: {ties}")
print(f"deliveredEnergyBoundedBySupply tied to any requirement, before remediation (by these two narrow checks): {tied_to_a_requirement}")
assert not tied_to_a_requirementdeliveredEnergyBoundedBySupply @type: AssertConstraintUsage
Satisfy-by-subject or direct-reference-from-a-requirement-body hits, before remediation: []
deliveredEnergyBoundedBySupply tied to any requirement, before remediation (by these two narrow checks): False
It was not, under these two narrow checks’ own stated scope, before this chapter’s own remediation (the reconstructed state checked above). Neither check found an element referencing deliveredEnergyBoundedBySupply’s own id: no assert satisfy named it as a by-subject, and no element owned by a requirement’s own body subset, redefined, referenced, or bare-named it directly. This was not a claim that no tie could exist by any conceivable mechanism -- only that these two specific, narrow, well-understood checks found none. Douglas’s own traceability concern names the inverse of this: a design element (“widget”) with no requirement to justify it (an unjustified widget). Here the shape is reversed but the concern is the same: a genuine piece of verification evidence with no requirement to justify it, the strongest formal proof this tutorial has built connecting to no stated need at all, at least not through either of the two ways this check knows to look. Toaster::cycleTime is not derived from anything and deliveredEnergyBoundedBySupply was not tied to any requirement (by this narrow check) are two different gaps in the same graph. The cycleTime gap stays open (a non-goal of this chapter, and not something a traceability graph alone can close); the deliveredEnergyBoundedBySupply gap is closed directly below, precisely because finding it is what this chapter’s own analysis did -- not papered over by forcing a connection the model does not actually make, but remediated the same way any other real gap this tutorial has found gets remediated: construct, then re-analyze.
The graph above, and the search that follows it, say the same thing two different ways: a traceability graph is not just a map of what connects, it is just as much a map of what does not, and both kinds of gap are real findings, not defects in the query.
This check is now genuinely trustworthy (after the narrowing described above), so the gap it just found above is a real gap in the model, not an artifact of an under-powered search. Douglas’s own traceability concern names an unjustified widget: a design element with no requirement behind it. Here the shape is reversed -- the tutorial’s own strongest formal evidence, with no requirement in front of it -- but the concern is the same, and it deserves the same treatment: close it, not just note it. models/ch10-cumulative.sysml -- the same file model above was loaded from -- already carries the fix that closes it, added directly because this notebook’s own traceability graph found this gap and is positioned to close it.
Closing the gap¶
Before printing the construct, one more real spec question needs settling: what should this requirement’s own subject be? SysML v2 formal/2026-03-02 SS7.21.1 ties a requirement’s subject to what any satisfy relationship may bind to it (“A requirement usage can only be satisfied by an entity that conforms to the definition of its subject”), but the real reason this requirement declares no subject here runs deeper than conformance to any one binding: its own required constraint, c :> deliveredEnergyBoundedBySupply, never references a subject anywhere in its own body -- it is a closed, already-proved proposition over its own free-standing elements. Declaring a subject type would commit to an arbitrary, unused type that nothing in the requirement’s own content ever reads, not state anything real. Two different earlier drafts hit two different problems here, and it matters which is which: the reverted Approach A typed the subject HeatGenerator but never added an assert satisfy line at all, so its own defect (a declared, unused subject with nothing live bound to it) was found by direct spec reading, not by any tool diagnostic -- no warning from the OMG SysML v2 Pilot Implementation (the pilot) ever fired against it, because nothing in that draft ever exercised the subject. A real pilot warning (“Bound features should have conforming types”) did fire, but against a different, separately-built draft’s own first commit: Approach B’s own earliest version paired a typed subject (HeatGenerator) with a real assert satisfy line binding the lemma -- a constraint, not a HeatGenerator usage -- against it; that draft’s own author fixed it by dropping the subject two commits later, before this reconciliation began. This design has no assert satisfy at all, so that particular mechanical trigger does not even apply here either way, but the deeper reason for leaving the subject undeclared is unchanged, and stronger: there is nothing in this requirement’s own body for a declared subject type to mean. Confirmed directly below, not assumed: what does the base requirement def itself declare as its own default subject?
from toaster.tools import resolve_library
requirements_lib_path = resolve_library() / "Systems Library" / "Requirements.sysml"
requirements_lib_text = requirements_lib_path.read_text()
assert "subject subj : Anything[1]" in requirements_lib_text, (
"expected the base requirement def RequirementCheck to still declare "
"subject subj : Anything[1] as its own default -- confirm directly, not assumed"
)
print("Confirmed: the base `requirement def RequirementCheck` (Systems Library/Requirements.sysml) "
"declares `subject subj : Anything[1];` as its own default subject.")
Confirmed: the base `requirement def RequirementCheck` (Systems Library/Requirements.sysml) declares `subject subj : Anything[1];` as its own default subject.
Anything is exactly what a bare constraint genuinely conforms to, and the honest reflection of a requirement whose own body never references a subject at all. So the construct below declares no subject line for EnergyConservationReq, inheriting RequirementCheck’s own default rather than committing to an unused, arbitrary type. models/ch10-cumulative.sysml -- the file this notebook loads -- already carries this fix: confirmed directly against the real pilot for this exact file (hasErrors=False, hasWarnings=False, no “Bound features should have conforming types” warning, which only ever applies to a declared, typed subject bound by an assert satisfy this design does not have) -- the pilot itself stays toolchain, never called from this notebook.
ENERGY_CONSERVATION_REQ_DEF = """\
requirement def EnergyConservationReq {
doc /* A heat generator shall never deliver more energy than it is supplied:
* energy conservation, a physical law any real heat generator design
* must obey, independent of any one proof of it. No subject type is
* declared here: this requirement's own required constraint never
* references any subject at all -- it is a closed, already-proved
* proposition over its own free-standing elements -- so declaring
* one would commit to an arbitrary, unused type, not state anything
* real; the subject is left to inherit RequirementCheck's own
* default, `subject subj : Anything[1]` (Systems
* Library/Requirements.sysml). Deliberately no `assert satisfy`
* line either. Confirmed directly, not merely argued: attempting
* `assert satisfy energyConservationReq by
* deliveredEnergyBoundedBySupply;` makes model.verify_satisfaction()
* error identically no matter what is bound as the satisfying
* feature ("require condition evaluation failed: no value for
* feature heatGenCheck.efficiency"), not pass -- demonstrated
* directly as a negative control in notebook 01, and recorded in
* full in
* docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md.
* See AC-C10 (this chapter's own judgment record, notebook 01) for
* how this requirement's own need was actually identified, and for
* the honest distinction between a tie and a solver-level proof. */
require constraint c :> deliveredEnergyBoundedBySupply;
}
requirement energyConservationReq : EnergyConservationReq;"""
print(ENERGY_CONSERVATION_REQ_DEF)
requirement def EnergyConservationReq {
doc /* A heat generator shall never deliver more energy than it is supplied:
* energy conservation, a physical law any real heat generator design
* must obey, independent of any one proof of it. No subject type is
* declared here: this requirement's own required constraint never
* references any subject at all -- it is a closed, already-proved
* proposition over its own free-standing elements -- so declaring
* one would commit to an arbitrary, unused type, not state anything
* real; the subject is left to inherit RequirementCheck's own
* default, `subject subj : Anything[1]` (Systems
* Library/Requirements.sysml). Deliberately no `assert satisfy`
* line either. Confirmed directly, not merely argued: attempting
* `assert satisfy energyConservationReq by
* deliveredEnergyBoundedBySupply;` makes model.verify_satisfaction()
* error identically no matter what is bound as the satisfying
* feature ("require condition evaluation failed: no value for
* feature heatGenCheck.efficiency"), not pass -- demonstrated
* directly as a negative control in notebook 01, and recorded in
* full in
* docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md.
* See AC-C10 (this chapter's own judgment record, notebook 01) for
* how this requirement's own need was actually identified, and for
* the honest distinction between a tie and a solver-level proof. */
require constraint c :> deliveredEnergyBoundedBySupply;
}
requirement energyConservationReq : EnergyConservationReq;
EnergyConservationReq restates exactly the property deliveredEnergyBoundedBySupply proves, as a stakeholder-facing requirement rather than the lemma’s own real-arithmetic form, with no explicit subject line (the fix confirmed above); its own require constraint c :> deliveredEnergyBoundedBySupply; is itself a direct reference from within a requirement’s own body -- Check B’s own idiom, made real rather than hypothetical. Deliberately absent: an assert satisfy energyConservationReq by deliveredEnergyBoundedBySupply; line, Check A’s own idiom, the same pattern already used above for rated/weak against heatGenerationReq. The next cells show directly why it is absent, rather than only asserting it in the doc comment above.
ENERGY_CONSERVATION_SATISFY_ATTEMPTS = {
"the lemma itself": "deliveredEnergyBoundedBySupply",
"the lemma's own free-standing usage": "heatGenCheck",
"an unrelated real candidate": "rated",
}
# Three short inline strings, the same way every other negative control in this
# tutorial is built, each loaded as its own one-off model (not the committed
# fixture, which deliberately omits this line) so the real, current model
# loaded above stays exactly what models/ch10-cumulative.sysml (https://github.com/Open-MBEE/toaster/blob/main/models/ch10-cumulative.sysml) commits.
attempt_models = {}
for label, binding in ENERGY_CONSERVATION_SATISFY_ATTEMPTS.items():
attempt_line = f"assert satisfy energyConservationReq by {binding};"
attempt_source = source.rstrip()[:-1] + f"\n {attempt_line}\n}}\n"
attempt_model = conn.load_from_content(attempt_source, strict=False)
assert attempt_model.ok, f"Model failed ({label}): {format_diagnostics(attempt_model.diagnostics)}"
attempt_models[label] = (binding, attempt_model)
print(f"{label} ({binding}): {attempt_line}")
the lemma itself (deliveredEnergyBoundedBySupply): assert satisfy energyConservationReq by deliveredEnergyBoundedBySupply;
the lemma's own free-standing usage (heatGenCheck): assert satisfy energyConservationReq by heatGenCheck;
an unrelated real candidate (rated): assert satisfy energyConservationReq by rated;
These all load cleanly -- grammatically legal, and the OpenSysML runtime raises no diagnostic against any of them (SS7.21.1’s own binding rule is satisfied once the subject is left undeclared, as confirmed above, regardless of what is bound). Legality is not the question; whether it does the work the construct is for is. Run each the same way a reader would confirm any other assert satisfy claim in this model, model.verify_satisfaction() -- the same call already used above for rated/weak against heatGenerationReq.
for label, (binding, attempt_model) in attempt_models.items():
attempt_verdicts = attempt_model.verify_satisfaction()
attempt_verdict = next(
v for v in attempt_verdicts
if v.element == f"satisfy energyConservationReq by {binding}"
)
status = "holds" if attempt_verdict.holds else "FAILS"
print(f" [{status}] {label} ({binding}): {attempt_verdict.element} ({attempt_verdict.error})")
assert attempt_verdict.holds is False
assert "require condition evaluation failed: no value for feature heatGenCheck.efficiency" in attempt_verdict.error
[FAILS] the lemma itself (deliveredEnergyBoundedBySupply): satisfy energyConservationReq by deliveredEnergyBoundedBySupply (satisfaction satisfy energyConservationReq by deliveredEnergyBoundedBySupply: require condition evaluation failed: no value for feature heatGenCheck.efficiency)
[FAILS] the lemma's own free-standing usage (heatGenCheck): satisfy energyConservationReq by heatGenCheck (satisfaction satisfy energyConservationReq by heatGenCheck: require condition evaluation failed: no value for feature heatGenCheck.efficiency)
[FAILS] an unrelated real candidate (rated): satisfy energyConservationReq by rated (satisfaction satisfy energyConservationReq by rated: require condition evaluation failed: no value for feature heatGenCheck.efficiency)
Not a pass, an error, for all three bindings actually tested above: require condition evaluation failed: no value for feature heatGenCheck.efficiency, identical whether the satisfying feature is the lemma itself, the lemma’s own free-standing usage (heatGenCheck), or a totally unrelated real candidate (rated). This is not a quirk of one binding -- it is demonstrated directly, across three different ones, for the structural reason already named in the doc comment above: heatGenCheck/heatGenCheckDuration are deliberately left free (the whole point of the Z3 proof is that it holds for every value, not one), so the construct cannot be evaluated at all, regardless of what is bound as the satisfying feature. requirement_coverage()'s own covered=True/False only ever checks whether a non-negated SatisfyRequirementUsage exists, never whether it actually evaluates -- these cells are the one place in this whole model that actually runs the check, and it fails identically every time. This is why models/ch10-cumulative.sysml carries no assert satisfy for this requirement: not because a tie is missing, but because satisfy is the wrong register for what this model actually has. EnergyConservationReq’s own required constraint already evaluates true, by construction (it is the proved lemma) -- SS7.21.1’s own words, “a requirement is satisfied when it evaluates to true” -- with no satisfy usage needed at all. The subsetting construct shown above is how that is stated.
models/ch10-cumulative.sysml already carries this construct forward: it is not assembled from the fragment above at runtime, it is the chapter’s own committed fixture, the same load-the-committed-fixture idiom every construction-zone cell in this tutorial uses. model (loaded once, at the top of this notebook) already reflects it, unlike the reconstructed model_before used above: the same two narrow checks, run again, unchanged, this time against the real, current model.
ties_after = requirement_ties(model, "ToasterDemo::deliveredEnergyBoundedBySupply", idx)
tied_after_remediation = tied_to_any_requirement(model, "ToasterDemo::deliveredEnergyBoundedBySupply", idx)
print(f"Satisfy-by-subject or direct-reference-from-a-requirement-body hits, after remediation: {ties_after}")
print(f"deliveredEnergyBoundedBySupply tied to any requirement, after remediation: {tied_after_remediation}")
assert tied_after_remediation
assert len(ties_after) == 1
assert ties_after[0]["requirement"] == "ToasterDemo::EnergyConservationReq"
assert ties_after[0]["field"] == "subsets"
Satisfy-by-subject or direct-reference-from-a-requirement-body hits, after remediation: [{'tying_element': 'ToasterDemo::EnergyConservationReq::c', 'field': 'subsets', 'requirement': 'ToasterDemo::EnergyConservationReq'}]
deliveredEnergyBoundedBySupply tied to any requirement, after remediation: True
It is True now, genuinely, and precisely so: ties_after carries exactly one entry, not the three a design that also kept an assert satisfy line would show. It is Check B’s own hit -- EnergyConservationReq::c’s own require constraint c :> deliveredEnergyBoundedBySupply; subsets the lemma directly from within the requirement’s own body -- and it is attributed to the DEFINITION, ToasterDemo::EnergyConservationReq, not the usage energyConservationReq: _nearest_requirement_owner’s owner-chain walk finds c’s nearest exact-type RequirementDefinition/RequirementUsage ancestor, and c is owned by the definition, not the usage. Check A finds nothing here, by design -- there is no assert satisfy for it to find, for exactly the reason the negative control just above demonstrated. This is the construct-and-analyze loop this whole tutorial teaches, run once more, start to finish: build, query, find a gap, remediate, re-query, confirm. The gap this chapter’s own traceability graph found is closed by this same chapter, because finding it is what this chapter’s own analysis is for.
The tie is real, and the subject-type fix keeps it spec-legitimate. One honest question is still open: is this tie -- a requirement whose own formal condition is defined by subsetting the very lemma it restates -- actually a legitimate way to close this chapter’s own “unjustified widget” finding, or is it circular, a requirement manufactured FROM the evidence rather than one that independently motivates it? A doc comment cannot carry that answer honestly; a judgment record can. Build one the same way AS-C06 (Chapter 6) is built: name each group of fields, narrate what it’s for, print it, then assemble.
Recording the judgment (AC-C10)¶
Before AC-C10 states its claim, the next cells give it a real anchor in the model: a ReviewRecordRef tag naming EnergyConservationReq as what AC-C10 is about.
# what is being claimed, and what, specifically, it is about
ac_c10_subject_ref = "ToasterDemo::EnergyConservationReq"
AC_C10_TAG = """\
metadata acC10Tag : ReviewRecordRef about EnergyConservationReq {
identifier = "AC-C10";
}
"""
TOASTER_INCREMENT = f"{ENERGY_CONSERVATION_REQ_DEF}\n{AC_C10_TAG}"
print(AC_C10_TAG)
metadata acC10Tag : ReviewRecordRef about EnergyConservationReq {
identifier = "AC-C10";
}
This fragment is the same text now committed in models/ch10-cumulative.sysml, alongside the ReviewRecordRef definition (carried forward since Chapter 2) and EnergyConservationReq itself (printed above); loading the cumulative model (the model.ok cell at the top of this notebook, unchanged) is what makes this a real, checkable tag rather than an assertion the reader has to trust. AC-C10 states the claim next: what this tie is actually being framed as.
from toaster.evidence import ReviewRecord, validate_record, hash_content
ac_c10_claim = (
"EnergyConservationReq/energyConservationReq's own tie to "
"deliveredEnergyBoundedBySupply is a spec-legitimate use of SysML v2's own "
"require-constraint-subsetting idiom (SS7.21.2), and its own general "
"motivation -- energy conservation, a physical law any real heat generator "
"design must obey -- is genuine and independent of any one proof of it. But "
"this requirement's own NEED did not predate Chapter 8's proof: it was "
"identified retroactively, specifically to close the gap this chapter's own "
"traceability analysis found, and the tie itself (requirement_ties()'s own "
"Check-B hit on the DEFINITION) is a structural fact about the model, not a "
"solver-level proof that this requirement's own formal condition holds for "
"every value -- that proof is AS-C08's own, cited and not repeated here. "
"Deliberately absent is any assert-satisfy claim: this requirement's own "
"required constraint never references its subject, confirmed directly by "
"the negative control above, so requirement_coverage() correctly reports "
"covered=False for it -- by design, not as a residual gap of the same kind "
"as timely's."
)
ac_c10_model_ref = "ToasterDemo::EnergyConservationReq"
print(ac_c10_claim)
EnergyConservationReq/energyConservationReq's own tie to deliveredEnergyBoundedBySupply is a spec-legitimate use of SysML v2's own require-constraint-subsetting idiom (SS7.21.2), and its own general motivation -- energy conservation, a physical law any real heat generator design must obey -- is genuine and independent of any one proof of it. But this requirement's own NEED did not predate Chapter 8's proof: it was identified retroactively, specifically to close the gap this chapter's own traceability analysis found, and the tie itself (requirement_ties()'s own Check-B hit on the DEFINITION) is a structural fact about the model, not a solver-level proof that this requirement's own formal condition holds for every value -- that proof is AS-C08's own, cited and not repeated here. Deliberately absent is any assert-satisfy claim: this requirement's own required constraint never references its subject, confirmed directly by the negative control above, so requirement_coverage() correctly reports covered=False for it -- by design, not as a residual gap of the same kind as timely's.
What standard this claim is checked against: SysML v2’s own account of what a requirement is, and what conformance its own satisfy relationship requires.
ac_c10_scope = (
"EnergyConservationReq/energyConservationReq and its own require-constraint "
"subsetting tie to deliveredEnergyBoundedBySupply, as added by this notebook. "
"Not a claim about deliveredEnergyBoundedBySupply's own proof (AS-C08 already "
"states that proof's real limits) or about any other requirement in this "
"model."
)
ac_c10_criteria = (
"Per SysML v2 formal/2026-03-02 SS7.21.1/SS7.21.2: (a) does the requirement's "
"own choice to leave its subject undeclared honestly reflect that its "
"required constraint never references a subject at all, rather than hiding "
"an arbitrary, unjustified type choice? (b) is the requirement's own need "
"independently motivated -- a real, antecedent stakeholder concern "
"(Douglas's own need/rationale/verification anatomy) -- or does its content "
"trace back only to the evidence it was built to accommodate? (c) does "
"requirement_coverage()'s own covered=False result for this requirement "
"honestly reflect a deliberate design choice -- tied by subsetting an "
"already-proved universal lemma, not by a point check -- rather than "
"reading as an undiscovered gap of the same kind as timely's?"
)
print(ac_c10_scope)
print(ac_c10_criteria)
EnergyConservationReq/energyConservationReq and its own require-constraint subsetting tie to deliveredEnergyBoundedBySupply, as added by this notebook. Not a claim about deliveredEnergyBoundedBySupply's own proof (AS-C08 already states that proof's real limits) or about any other requirement in this model.
Per SysML v2 formal/2026-03-02 SS7.21.1/SS7.21.2: (a) does the requirement's own choice to leave its subject undeclared honestly reflect that its required constraint never references a subject at all, rather than hiding an arbitrary, unjustified type choice? (b) is the requirement's own need independently motivated -- a real, antecedent stakeholder concern (Douglas's own need/rationale/verification anatomy) -- or does its content trace back only to the evidence it was built to accommodate? (c) does requirement_coverage()'s own covered=False result for this requirement honestly reflect a deliberate design choice -- tied by subsetting an already-proved universal lemma, not by a point check -- rather than reading as an undiscovered gap of the same kind as timely's?
What is being taken as given: the base library’s own default subject type, already confirmed directly above, plus AS-C08’s own already-recorded limits on the proof this tie is built from, which trace to two solver gaps, DEFERRED.md D-030 and DEFERRED.md D-031.
ac_c10_premises = [
"SysML v2.0 formal/2026-03-02 SS7.21.2 explicitly supports "
"reference-subsetting an existing constraint as a requirement's own "
"required constraint (`require <name>;` / `require constraint c :> "
"<name>;`) -- the spec's own intended idiom for reusing an "
"already-declared constraint, confirmed by reading the spec directly.",
"SS7.21.1: 'A requirement usage can only be satisfied by an entity that "
"conforms to the definition of its subject.' The base `requirement def "
"RequirementCheck` (Systems Library/Requirements.sysml) declares "
"`subject subj : Anything[1];`, confirmed directly above; "
"EnergyConservationReq leaves its own subject undeclared, inheriting that "
"default, since its own required constraint never references a subject "
"at all. Two different earlier drafts hit two different problems: the "
"reverted Approach A typed the subject `heatGen : HeatGenerator` but "
"never added an assert-satisfy line at all, so its own defect (a "
"declared, unused subject with nothing live bound to it) was found by "
"direct spec reading, not by any tool diagnostic. A real pilot warning "
"('Bound features should have conforming types') did fire, but "
"against a different, separately-built draft's own first commit: "
"Approach B's own earliest version paired a typed subject with a real "
"assert-satisfy line binding the lemma against it; that draft's own "
"author fixed it by dropping the subject two commits later, before "
"this reconciliation began. This design has no assert-satisfy at all, "
"so that specific mechanical trigger does not even apply here either "
"way, but the deeper reason for leaving the subject undeclared stands "
"regardless -- recorded in full in "
"docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md.",
"Direct test, not argument alone (the negative control above): "
"`assert satisfy energyConservationReq by "
"deliveredEnergyBoundedBySupply;` makes model.verify_satisfaction() error "
"identically regardless of its own binding, because this requirement's "
"own required constraint never references its subject at all -- the "
"register `satisfy` is built for (a candidate's own values substituted "
"into a formula that depends on them) is not what this construct offers, "
"so no `assert satisfy` is given for it.",
"Douglas's own requirement anatomy (AGENTS.md SS1, Part 4): a need, a "
"rationale, and a means of verification. This requirement supplies a "
"real, general rationale (energy conservation) and a real means (the "
"subsetted, Z3-proved lemma itself), but its own need was identified "
"only after Chapter 8's proof already existed, in response to this "
"chapter's own traceability search -- not before it.",
]
ac_c10_assumption_refs = [
"AS-C08's own residual_uncertainties (Chapter 8): deliveredEnergyBoundedBySupply "
"is a hand-restated companion lemma, not a solver-checked reference to "
"HeatGenerator's own efficiencyBounded/deliveredEnergy (D-030, D-031); this "
"record does not strengthen that proof, only frames how its tie to a "
"requirement should be read.",
]
print(ac_c10_premises)
print(ac_c10_assumption_refs)
["SysML v2.0 formal/2026-03-02 SS7.21.2 explicitly supports reference-subsetting an existing constraint as a requirement's own required constraint (`require <name>;` / `require constraint c :> <name>;`) -- the spec's own intended idiom for reusing an already-declared constraint, confirmed by reading the spec directly.", "SS7.21.1: 'A requirement usage can only be satisfied by an entity that conforms to the definition of its subject.' The base `requirement def RequirementCheck` (Systems Library/Requirements.sysml) declares `subject subj : Anything[1];`, confirmed directly above; EnergyConservationReq leaves its own subject undeclared, inheriting that default, since its own required constraint never references a subject at all. Two different earlier drafts hit two different problems: the reverted Approach A typed the subject `heatGen : HeatGenerator` but never added an assert-satisfy line at all, so its own defect (a declared, unused subject with nothing live bound to it) was found by direct spec reading, not by any tool diagnostic. A real pilot warning ('Bound features should have conforming types') did fire, but against a different, separately-built draft's own first commit: Approach B's own earliest version paired a typed subject with a real assert-satisfy line binding the lemma against it; that draft's own author fixed it by dropping the subject two commits later, before this reconciliation began. This design has no assert-satisfy at all, so that specific mechanical trigger does not even apply here either way, but the deeper reason for leaving the subject undeclared stands regardless -- recorded in full in docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md.", "Direct test, not argument alone (the negative control above): `assert satisfy energyConservationReq by deliveredEnergyBoundedBySupply;` makes model.verify_satisfaction() error identically regardless of its own binding, because this requirement's own required constraint never references its subject at all -- the register `satisfy` is built for (a candidate's own values substituted into a formula that depends on them) is not what this construct offers, so no `assert satisfy` is given for it.", "Douglas's own requirement anatomy (AGENTS.md SS1, Part 4): a need, a rationale, and a means of verification. This requirement supplies a real, general rationale (energy conservation) and a real means (the subsetted, Z3-proved lemma itself), but its own need was identified only after Chapter 8's proof already existed, in response to this chapter's own traceability search -- not before it."]
["AS-C08's own residual_uncertainties (Chapter 8): deliveredEnergyBoundedBySupply is a hand-restated companion lemma, not a solver-checked reference to HeatGenerator's own efficiencyBounded/deliveredEnergy (D-030, D-031); this record does not strengthen that proof, only frames how its tie to a requirement should be read."]
What supports the claim, and how: the base library’s own text and the negative control, both already confirmed directly above, plus a direct, live check of what sysml-toolkit’s sysmlv2 verify --solve -- the Z3-backed solver used for deliveredEnergyBoundedBySupply’s own proof in Chapter 8, a different tool from the OpenSysML runtime’s model.verify_satisfaction() -- actually reports for this requirement, run below directly via subprocess, bypassing toaster.modelcheck’s own wrapper: that wrapper’s own line parser cannot read a verdict line for a constraint that is also the subject of an assert satisfy/assert not satisfy declaration at all (DEFERRED.md D-029), a gap this model’s own existing timely/heatGenerationReq declarations already trip regardless of this chapter’s own construct -- the same reason Ch8-02 shells out directly too. Not assumed, not guessed, and not copied from what a design that kept Check A would have found.
import os
import subprocess
from toaster.tools import resolve_library, resolve_sysmlv2, tool_env
# Shelling out directly rather than toaster.modelcheck's own wrapper: that
# wrapper's own line parser cannot read a verdict line for a constraint that is
# also the subject of an assert satisfy/assert not satisfy declaration at all
# (DEFERRED.md D-029) -- this model already has two such declarations
# (timely, heatGenerationReq), so the wrapper would fail before ever reaching
# this chapter's own construct.
SYSMLV2_BINARY = resolve_sysmlv2()
SYSMLV2_LIB = resolve_library()
os.environ["PATH"] = tool_env()["PATH"] # the next cell shells out to the CLI too; it finds z3 on PATH
solve_result = subprocess.run(
[str(SYSMLV2_BINARY), "verify", "../../models/ch10-cumulative.sysml",
"--lib", str(SYSMLV2_LIB), "--solve"],
capture_output=True, text=True, timeout=30, env=tool_env(),
)
solve_lines = solve_result.stdout.splitlines()
energy_conservation_lines = [
line for line in solve_lines
if "EnergyConservationReq" in line or "energyConservationReq" in line
]
lemma_line = next(line for line in solve_lines if "deliveredEnergyBoundedBySupply (AssertConstraintUsage)" in line)
print(f"Lines mentioning EnergyConservationReq/energyConservationReq: {energy_conservation_lines}")
print(lemma_line)
assert energy_conservation_lines == []
assert "satisfied" in lemma_line
Lines mentioning EnergyConservationReq/energyConservationReq: []
../../models/ch10-cumulative.sysml:323:9 deliveredEnergyBoundedBySupply (AssertConstraintUsage): satisfied (z3: holds for all values of unbound features)
Confirmed by contrast, not just by absence, and run for real rather than described in prose: what would the solver report if Check A’s own assert satisfy idiom were added back, as a one-off? A fixed, repo-relative scratch path (mirroring Ch8-02’s own scratch-file idiom), not models/ch10-cumulative.sysml itself, which deliberately omits this line.
SCRATCH_DIR = Path("companion-check-scratch")
SCRATCH_DIR.mkdir(exist_ok=True)
with_satisfy_source = source.rstrip()[:-1] + (
"\n assert satisfy energyConservationReq by deliveredEnergyBoundedBySupply;\n}\n"
)
with_satisfy_path = SCRATCH_DIR / "ch10_with_satisfy.sysml"
with_satisfy_path.write_text(with_satisfy_source)
with_satisfy_result = subprocess.run(
[str(SYSMLV2_BINARY), "verify", str(with_satisfy_path),
"--lib", str(SYSMLV2_LIB), "--solve"],
capture_output=True, text=True, timeout=30,
)
with_satisfy_lines = with_satisfy_result.stdout.splitlines()
with_satisfy_energy_line = next(
line for line in with_satisfy_lines
if "EnergyConservationReq" in line or "energyConservationReq" in line
)
print(with_satisfy_energy_line)
assert "undecided" in with_satisfy_energy_line
with_satisfy_path.unlink(missing_ok=True)
companion-check-scratch/ch10_with_satisfy.sysml:323:9 c (ConstraintUsage, satisfies ToasterDemo::energyConservationReq): undecided (result is indeterminate over unbound features)
The solver reports nothing at all for EnergyConservationReq::c in the current, satisfy-less model -- not undecided, not anything. A bare :> subsetting reference to an already-checked element is not itself a new thing for the solver to check, so it emits no separate verdict for it; the only solver-level fact in scope here is deliveredEnergyBoundedBySupply’s own already-cited result (AS-C08, confirmed again live above): satisfied. Confirmed by contrast above, run for real, not merely described: adding assert satisfy energyConservationReq by deliveredEnergyBoundedBySupply; back DOES make the solver emit a line -- c (ConstraintUsage, satisfies ToasterDemo::energyConservationReq): undecided (result is indeterminate over unbound features) -- a real verdict, not nothing, but not satisfied either: the same indeterminate result every other requirement’s own unbound required constraint already gets in this model. So dropping assert satisfy does not leave an unresolved verdict hanging; it removes the only thing that would have generated one here in the first place, and either way the solver never reports this requirement’s own required constraint as independently satisfied on its own terms. This requirement’s own tie to the lemma’s real satisfied result is a structural fact about the model -- the subsetting relationship itself, confirmed by requirement_ties()'s own Check-B hit above -- not a second, independently re-verified claim layered on top of it, under either register. That line only exists at all because of the very assert satisfy idiom this chapter deliberately does not use, and the negative control above already showed that idiom fails outright under model.verify_satisfaction(), across three different bindings.
ac_c10_evidence_refs = [
"Systems Library/Requirements.sysml, confirmed directly above: "
"`subject subj : Anything[1];` is RequirementCheck's own default subject.",
"model.verify_satisfaction() against assert satisfy energyConservationReq by "
"<candidate> (the negative control above, three real bindings tested -- the "
"lemma itself, its own free-standing usage, and an unrelated real "
"candidate): holds=False, error='require condition evaluation failed: no "
"value for feature heatGenCheck.efficiency' in every case.",
f"sysmlv2 verify --solve, real output captured live above: no line at all "
f"for EnergyConservationReq/energyConservationReq in the current, "
f"satisfy-less model ({energy_conservation_lines}), only the lemma's own "
f"already-cited result ({lemma_line.strip()}); confirmed by contrast, also "
f"run live above, that adding assert satisfy back in makes the solver emit "
f"a real verdict after all ({with_satisfy_energy_line.strip()}) -- just an "
f"undecided one, not a satisfied one.",
"requirement_ties(model, LEMMA, idx)'s own Check-B hit, reproduced above: "
"exactly one entry, attributed to the DEFINITION (EnergyConservationReq).",
]
ac_c10_rationale = (
"Three things are true at once here, and none should be softened into the "
"others. First, energy conservation really is a general physical constraint "
"on any heat generator, independent of whether Chapter 8 happened to prove "
"one instance of it, so once stated this way EnergyConservationReq is a "
"legitimate requirement, and SS7.21.2's own subsetting idiom is a "
"spec-sanctioned way to state it against an existing constraint. Second, "
"that legitimacy does not by itself make this requirement's own NEED "
"independently motivated: it is honest to say the need was not antecedent "
"-- it exists because this chapter's own traceability analysis found "
"deliveredEnergyBoundedBySupply tied to nothing, and EnergyConservationReq "
"was built specifically to close that gap. Third, the tie this requirement "
"actually has is purely structural -- the subsetting relationship itself, "
"detected by Check B -- and deliberately NOT reinforced by an assert-satisfy "
"claim: two separate, direct tests confirm that register would not do real "
"evaluative work here, run above for real rather than merely argued -- "
"model.verify_satisfaction() errors outright on all three bindings tested, "
"and sysmlv2 verify --solve, while it DOES emit a real verdict once "
"assert satisfy is added back (undecided, not nothing), never resolves it "
"to satisfied either. requirement_coverage()'s own covered=False for "
"energyConservationReq is therefore correct and expected, not a residual "
"gap: there is no assert-satisfy declaration for it to find, by design."
)
print(ac_c10_evidence_refs)
print(ac_c10_rationale)["Systems Library/Requirements.sysml, confirmed directly above: `subject subj : Anything[1];` is RequirementCheck's own default subject.", "model.verify_satisfaction() against assert satisfy energyConservationReq by <candidate> (the negative control above, three real bindings tested -- the lemma itself, its own free-standing usage, and an unrelated real candidate): holds=False, error='require condition evaluation failed: no value for feature heatGenCheck.efficiency' in every case.", "sysmlv2 verify --solve, real output captured live above: no line at all for EnergyConservationReq/energyConservationReq in the current, satisfy-less model ([]), only the lemma's own already-cited result (../../models/ch10-cumulative.sysml:323:9 deliveredEnergyBoundedBySupply (AssertConstraintUsage): satisfied (z3: holds for all values of unbound features)); confirmed by contrast, also run live above, that adding assert satisfy back in makes the solver emit a real verdict after all (companion-check-scratch/ch10_with_satisfy.sysml:323:9 c (ConstraintUsage, satisfies ToasterDemo::energyConservationReq): undecided (result is indeterminate over unbound features)) -- just an undecided one, not a satisfied one.", "requirement_ties(model, LEMMA, idx)'s own Check-B hit, reproduced above: exactly one entry, attributed to the DEFINITION (EnergyConservationReq)."]
Three things are true at once here, and none should be softened into the others. First, energy conservation really is a general physical constraint on any heat generator, independent of whether Chapter 8 happened to prove one instance of it, so once stated this way EnergyConservationReq is a legitimate requirement, and SS7.21.2's own subsetting idiom is a spec-sanctioned way to state it against an existing constraint. Second, that legitimacy does not by itself make this requirement's own NEED independently motivated: it is honest to say the need was not antecedent -- it exists because this chapter's own traceability analysis found deliveredEnergyBoundedBySupply tied to nothing, and EnergyConservationReq was built specifically to close that gap. Third, the tie this requirement actually has is purely structural -- the subsetting relationship itself, detected by Check B -- and deliberately NOT reinforced by an assert-satisfy claim: two separate, direct tests confirm that register would not do real evaluative work here, run above for real rather than merely argued -- model.verify_satisfaction() errors outright on all three bindings tested, and sysmlv2 verify --solve, while it DOES emit a real verdict once assert satisfy is added back (undecided, not nothing), never resolves it to satisfied either. requirement_coverage()'s own covered=False for energyConservationReq is therefore correct and expected, not a residual gap: there is no assert-satisfy declaration for it to find, by design.
What could be wrong, and what is still open (trustworthiness): named plainly, not folded into a tidier-sounding conclusion.
ac_c10_counterevidence = (
"This record's own framing could be read as too generous: a requirement "
"invented after the fact to absorb existing evidence is exactly the shape "
"Douglas's own traceability concern warns against in reverse -- a "
"requirement manufactured FROM the evidence rather than one that "
"independently motivates it. That reading is not wrong; it is why this "
"record exists rather than a cleaner doc comment alone. What keeps this "
"from being circular in the way Douglas warns against is narrow: the "
"underlying physical law (conservation of energy) would be a real "
"constraint on any heat generator design whether or not Chapter 8 had ever "
"proved deliveredEnergyBoundedBySupply -- the requirement's own general "
"content is not invented, only the TIMING of its discovery is retroactive. "
"A reader who judges retroactive discovery disqualifying regardless of the "
"underlying law's own independence would reasonably disagree with this "
"record's own engineering_conclusion. Separately: this tie rests entirely "
"on a structural check (Check B's subsetting hit), with no solver-level "
"re-verification of the requirement's own required constraint at all -- "
"the solver simply does not generate one for a bare subsetting reference "
"(confirmed above). A reader could reasonably want more than structural "
"detection before treating a requirement as genuinely tied; this record "
"does not manufacture an assert-satisfy claim to supply that, because the "
"one tested above does not hold up (it errors, not passes)."
)
ac_c10_residual_uncertainties = (
"Whether a future chapter would accept a requirement whose own need was "
"identified after its own evidence, or whether this tutorial should adopt "
"a stricter rule requiring every requirement's need to predate its own "
"evidence, is not settled here -- an open question this record surfaces "
"rather than resolves. Whether this tie should ever be strengthened beyond "
"structural subsetting -- a genuinely parametrized, per-candidate check, "
"rather than a closed, already-proved universal lemma -- is also open, and "
"would require re-authoring deliveredEnergyBoundedBySupply as an open "
"template (SS7.21.1's own massLimit idiom) rather than the closed "
"proposition Chapter 8 deliberately built."
)
print(ac_c10_counterevidence)
print(ac_c10_residual_uncertainties)
This record's own framing could be read as too generous: a requirement invented after the fact to absorb existing evidence is exactly the shape Douglas's own traceability concern warns against in reverse -- a requirement manufactured FROM the evidence rather than one that independently motivates it. That reading is not wrong; it is why this record exists rather than a cleaner doc comment alone. What keeps this from being circular in the way Douglas warns against is narrow: the underlying physical law (conservation of energy) would be a real constraint on any heat generator design whether or not Chapter 8 had ever proved deliveredEnergyBoundedBySupply -- the requirement's own general content is not invented, only the TIMING of its discovery is retroactive. A reader who judges retroactive discovery disqualifying regardless of the underlying law's own independence would reasonably disagree with this record's own engineering_conclusion. Separately: this tie rests entirely on a structural check (Check B's subsetting hit), with no solver-level re-verification of the requirement's own required constraint at all -- the solver simply does not generate one for a bare subsetting reference (confirmed above). A reader could reasonably want more than structural detection before treating a requirement as genuinely tied; this record does not manufacture an assert-satisfy claim to supply that, because the one tested above does not hold up (it errors, not passes).
Whether a future chapter would accept a requirement whose own need was identified after its own evidence, or whether this tutorial should adopt a stricter rule requiring every requirement's need to predate its own evidence, is not settled here -- an open question this record surfaces rather than resolves. Whether this tie should ever be strengthened beyond structural subsetting -- a genuinely parametrized, per-candidate check, rather than a closed, already-proved universal lemma -- is also open, and would require re-authoring deliveredEnergyBoundedBySupply as an open template (SS7.21.1's own massLimit idiom) rather than the closed proposition Chapter 8 deliberately built.
kind="asserted_context" is the deliberate choice here: Hawkins’ own words, “it is being asserted that the context is appropriate for the argument elements to which it applies” (Hawkins et al. 2011, Sec. 3.2, p. 9; see the asserted context glossary entry), name exactly what this record does -- it frames how this tie should honestly be read, not a new piece of supporting evidence (asserted_solution) or a synthesis of child claims (asserted_inference, AI-C10’s own kind in notebook 03). With every part named above, the record assembles from them directly.
from toaster.query import get_review_record_refs
ac_c10 = ReviewRecord(
identifier="AC-C10",
kind="asserted_context",
claim=ac_c10_claim,
subject_ref=ac_c10_subject_ref,
model_ref=ac_c10_model_ref,
content_hash=hash_content(source),
scope=ac_c10_scope,
criteria=ac_c10_criteria,
premises=ac_c10_premises,
assumption_refs=ac_c10_assumption_refs,
evidence_refs=ac_c10_evidence_refs,
rationale=ac_c10_rationale,
counterevidence=ac_c10_counterevidence,
residual_uncertainties=ac_c10_residual_uncertainties,
disposition="pending",
dependency_freshness="current",
engineering_conclusion="undetermined",
record_kind="worked_example",
)
errors = validate_record(ac_c10, model=model)
tag = next((t for t in get_review_record_refs(model) if t["identifier"] == ac_c10.identifier), None)
print(f"Model tag: {tag}")
print(f"Validation errors: {errors}")
assert errors == []
Model tag: {'tag': 'ToasterDemo::acC10Tag', 'identifier': 'AC-C10', 'annotated_element': 'ToasterDemo::EnergyConservationReq'}
Validation errors: []
The tag printed above is now part of the loaded model, and validate_record confirms the Python record and the model’s own tag agree about what AC-C10 is actually about, with no errors reported. AC-C10 is what this tie actually is: a spec-legitimate construct, a genuine general motivation, and an honestly disclosed assurance deficit (Hawkins’ own term; see assurance deficit) about when its own need was actually identified and how far a purely structural tie actually reaches -- not a defect papered over, a real judgment an accountable engineer should weigh. Notebook 03’s own synthesis cites this record by identifier, the same way it already cites AS-C06/AS-C08/AI-C06.
Try the chapter exercise in exercises/ch10/exercise.ipynb: it asks you to build this same traceability graph over your own coffee-maker model, reconstruct a judgment ledger over three of your own already-built records, and synthesize both into one honestly-scoped sign-off record.