diff --git a/.claude/skills/ace-protocol/SKILL.md b/.claude/skills/ace-protocol/SKILL.md index 3aa1001..834a783 100644 --- a/.claude/skills/ace-protocol/SKILL.md +++ b/.claude/skills/ace-protocol/SKILL.md @@ -60,7 +60,7 @@ Frame decisions the way Z thinks: an **objective** (what is good and good enough | SA-3 | Energy model = `Q = ηPt`, sympy+numpy+matplotlib for the base tutorial. scipy is permitted if it is the right tool for the job. | | SA-4 | Single-platform CI (ubuntu-latest) | | SA-5 | Default book-theme, no custom CSS | -| SA-6 | Bounded model checking: opensysml `check` engine only | +| SA-6 | Bounded model checking: opensysml `check` engine only (the runtime's engine; for "holds" questions Z accepted sysml-toolkit's `sysmlv2 verify --solve` wrapped by `toaster.modelcheck`: DL-046, D-025) | | SA-7 | All judgment records are worked examples; `disposition` stays `"pending"` | | SA-8 | One new construct or analysis operation per sub-notebook; depth notebooks may introduce neither | | SA-9 | DOT for sequences/relationships; SysMLD first-class for interconnection; PlantUML for action flow; Matplotlib for quantitative; never Mermaid | @@ -127,9 +127,9 @@ Earlier entries with a single `Rationale:` line pre-date this format. ## Escalate to Z - SA challenge without an obvious "no" — e.g., renderer limitation that genuinely threatens a learning outcome -- Licensing questions (GPL PlantUML, pilot EPL-2.0, redistribution) +- Licensing questions (GPL PlantUML, the OMG SysML v2 Pilot Implementation's EPL-2.0, redistribution) - Spec ambiguity spanning multiple chapters, not resolvable by existing SAs -- Required opensysml capability missing from v0.9.0 with no workable simplification +- Required OpenSysML runtime capability missing from v0.9.0 with no workable simplification - A request to change a confirmed glossary definition or to approve a `differsFrom`: only Z acts. If Z's recorded positions show the change is wrong, decline it yourself and log it (nothing changes, so Z need not act); if you cannot tell whether the change would be right, escalate - Any question the frameworks and principles in `z-principles.md` do not determine (the default for the unknown) - A proposal to reopen an SA rule diff --git a/.claude/skills/ace-protocol/z-model.md b/.claude/skills/ace-protocol/z-model.md index c4ee1ae..44a6247 100644 --- a/.claude/skills/ace-protocol/z-model.md +++ b/.claude/skills/ace-protocol/z-model.md @@ -18,7 +18,7 @@ Every item below is something Z said or approved in this session. Cite an item a ## Sources and definitions - Z-11. Canonical sources take priority; own definitions appear only as contextual refinements where necessary, to make learning easier. Never make things up. Never teach something misaligned with canon. The sources are complementary KINDS of definition, not rivals: SEBoK gives the idea (conceptual, generic), the OMG specs give formal and checkable semantics, Douglas gives analogy and story (didactic, aligned with as far as possible to lower cognitive cost). They are treated as non-contradicting. Our tutorial edge is the bridge. Do not phrase a ruling as one source "governing" another; say which kind of definition is needed. -- Z-12. OpenSysML and other implementations are toolchain, cited only to flag spec gaps. Tall's three worlds and the optimization/control lens are builder-facing and never named in learner content. +- Z-12. OpenSysML and other implementations are toolchain, cited only to flag spec gaps. Tall's three worlds and the optimization/control lens are builder-facing and never named in learner content. [Reading note, 2026-10-03, DL-116/DL-117: said when this repository used "OpenSysML" for the runtime; it now names the stack whose components the tutorial uses are the OpenSysML runtime and sysml-toolkit. The position holds under both readings.] - Z-13. Douglas says what / who / where; the tutorial's what / how / where is Z's own sharpening and must be presented as such (attribute it plainly). - Z-14. SEBoK's "logical architecture" contains the functional view; the tutorial's "logical" is therefore a `differsFrom` edge, and Z APPROVED that departure in planning ("differsFrom, approved"). Learners are told the word is used more narrowly than in SEBoK. - Z-15. Mechanism and policy are grounded in public canonical texts (Astrom and Murray; Sutton and Barto), not in Z's own generalized-dynamical-systems paper (which informs Z's thinking but is not a canonical source). Neither text uses the word "mechanism" in Z's sense, so the tutorial's "mechanism" is a recorded refinement of the input/output dynamics definitions, word and determinism emphasis marked as ours. diff --git a/.claude/skills/opensysml-api/SKILL.md b/.claude/skills/opensysml-api/SKILL.md index 6859345..849b938 100644 --- a/.claude/skills/opensysml-api/SKILL.md +++ b/.claude/skills/opensysml-api/SKILL.md @@ -3,7 +3,7 @@ name: opensysml-api description: opensysml v0.9.0 interface — correct method names, return shapes, limitations, and the D-001 encapsulation rule. --- -# OpenSysML v0.9.0 API +# The OpenSysML runtime v0.9.0 API ## Connection diff --git a/.claude/skills/opensysml-query/SKILL.md b/.claude/skills/opensysml-query/SKILL.md index 172f66f..a8ecbfe 100644 --- a/.claude/skills/opensysml-query/SKILL.md +++ b/.claude/skills/opensysml-query/SKILL.md @@ -1,9 +1,9 @@ --- name: opensysml-query -description: Tested cookbook for interrogating a loaded SysML v2 model with OpenSysML v0.9.0 (three surfaces, what each sees, id formats, recipes, what does not work and the workaround). Snippets are executed by tests/test_skill_snippets.py. +description: Tested cookbook for interrogating a loaded SysML v2 model with the OpenSysML runtime v0.9.0 (three surfaces, what each sees, id formats, recipes, what does not work and the workaround). Snippets are executed by tests/test_skill_snippets.py. --- -# Querying a model (OpenSysML v0.9.0) +# Querying a model (the OpenSysML runtime v0.9.0) SysML v2 is declarative and database-like (AGENTS.md 1.4): we build a model, then ask it questions. There are three surfaces, and none of them sees everything. Pick by what you need to see. Results and dates are in `decisions/probes.md`; gap ids (G1 to G7) are in `decisions/log.md` DL-015. @@ -129,7 +129,7 @@ assert satisfies() ## Recipe 5: a staged conformance check (port types on connected ends) -OpenSysML accepts a connection between ports of unrelated types with no diagnostic (gap G4, `decisions/probes.md`), and the KerML text searched has no validation constraint for it. So this is a **project conformance check**, not a language one (AGENTS.md 1.9): apply it from the chapter and section where the connection is declared complete, keep a negative control that shows it catching a fault, and report it as *open* before then. +The OpenSysML runtime v0.9.0 and sysml-toolkit v0.9.1 both accept a connection between ports of unrelated types with no diagnostic (gap G4, `decisions/probes.md`), and the KerML text searched has no validation constraint for it. So this is a **project conformance check**, not a language one (AGENTS.md 1.9): apply it from the chapter and section where the connection is declared complete, keep a negative control that shows it catching a fault, and report it as *open* before then. ```python def feature_type_names(feature_qn): @@ -174,7 +174,7 @@ The API JSON `@id` uses `__` for `::` and escapes `_` (`named_flow` becomes `nam | `conn.load(path)` exists but does not resolve imports either | Same workaround. | | Writing these joins by hand in a notebook | Import the tested helpers from `toaster.query`: `find_connectors`, `find_allocations`, `allocations_for`, `satisfy_relationships`, `perform_relationships`, `requirement_coverage`, `specializes_transitively`, `port_type_mismatches`. The recipes above show what they do; `tests/test_query.py` covers them against `models/ch08-cumulative.sysml`. | -The sysml-toolkit Python binding (`sysmlv2.Session.from_files`) does resolve imports across files and sees unnamed elements through `elements_of_metaclass`. It is toolchain, not a chapter dependency (see `decisions/probes.md`). +The sysml-toolkit Python binding (`sysmlv2.Session.from_files`) does resolve imports across files and sees unnamed elements through `elements_of_metaclass`. It is toolchain, not a chapter dependency: every notebook loads its model through the OpenSysML runtime, and Chapters 5, 8 and 10 use sysml-toolkit only through its `sysmlv2` binary (`viz` in Chapter 5, `verify --solve` in Chapters 8 and 10; DL-118 B3/B4), not through this binding (see `decisions/probes.md`). ## Before you assert something works diff --git a/.claude/skills/sysml-diagrams/SKILL.md b/.claude/skills/sysml-diagrams/SKILL.md index c7d512e..ce7e26b 100644 --- a/.claude/skills/sysml-diagrams/SKILL.md +++ b/.claude/skills/sysml-diagrams/SKILL.md @@ -13,15 +13,15 @@ Use one default pipeline for each figure type. Read only the relevant recipe in | Question / figure type | Default pipeline | Why | |---|---|---| -| What is the system made of? Definition and decomposition view | `model_to_dot()` (in-house, `src/toaster/render.py`) → Graphviz SVG | Draws the whole model's containment graph from a full `model.query()`, not one root's direct children — a real-fixture rerun of the diagram trade study (`decisions/diagram-study-real-fixtures.md`) found the OMG pilot fails on all real chapter content (qualified-name `allocate` targets), and rendering a single root via OpenSysML's `#tree:` form only shows that root's own direct features, one level deep. | +| What is the system made of? Definition and decomposition view | `model_to_dot()` (in-house, `src/toaster/render.py`) → Graphviz SVG | Draws the whole model's containment graph from a full `model.query()`, not one root's direct children — a real-fixture rerun of the diagram trade study (`decisions/diagram-study-real-fixtures.md`) found the OMG SysML v2 Pilot Implementation fails on all real chapter content (qualified-name `allocate` targets), and rendering a single root via the OpenSysML runtime's `#tree:` form only shows that root's own direct features, one level deep. | | How do parts connect through ports? Interconnection view | Model query → `render_interconnection()` (in-house, `src/toaster/render.py`; the same real-fixture study found the actual third-party SysMLD tool cannot index real content at all) → Graphviz SVG | Draws part connectivity, port identity (as edge labels), and allocations, with zero dependency on a tool proven unreliable on real content. **Use sysml-toolkit instead specifically when port identity itself is the chapter's own pedagogical point** (e.g. a chapter introducing or exercising a conjugated port) — it draws real port names as their own boxes, not folded into one edge label, confirmed on every real fixture tested (`decisions/diagram-study-real-fixtures.md`). Otherwise default to the in-house renderer: a chapter using interconnection only to show an allocation or a connection, where port identity is not itself the point, does not need the extra external-binary dependency (`decisions/diagram-survey.md`, Ch5-vs-Ch6 example). | -| What happens next? Action-flow view | OpenSysML CLI, `-render #action:element -render-form dot` → Graphviz SVG | Confirmed directly against real chapter content (Ch6's `ApplyHeat` action): exit 0, real action-flow notation. No in-house action-flow renderer exists yet. | -| How does behavior change with events? State-transition view | OpenSysML CLI, `-render #state:element -render-form dot` → Graphviz SVG | The real-fixture study confirmed this directly against Ch7's real `Cycle` state machine — 100% success across both OpenSysML render forms. The OMG pilot (this table's earlier default) fails on all real chapter content; do not use it. | -| Who sends what, in what order? Sequence view | OpenSysML sequence query → DOT → Graphviz SVG | White background, relationship-consistent rendering, no Mermaid dependency. Provisional: no chapter's real model has a `FlowUsage` yet, so this pipeline has not been exercised against real content. Fallback: PlantUML if `opensysml -render-form dot` unsupported for sequences (confirmed at WP-1 and documented below). | +| What happens next? Action-flow view | OpenSysML runtime CLI, `-render #action:element -render-form dot` → Graphviz SVG | Confirmed against real chapter content (Ch4's `ToastBread`, Ch6's `ApplyHeat`): exit 0 and the control sequence is drawn, but the CLI's DOT omits the actions' declared typed flows (D-037); the caption must say so. No in-house action-flow renderer exists yet. | +| How does behavior change with events? State-transition view | OpenSysML runtime CLI, `-render #state:element -render-form dot` → Graphviz SVG | The real-fixture study ran this against Ch7's real `Cycle` state machine: both OpenSysML runtime render forms exit 0 and draw the states and transitions, but the `do` activity label omits the performed action's name (D-037); the caption must say so. The pilot (this table's earlier default) fails on all real chapter content; do not use it. | +| Who sends what, in what order? Sequence view | OpenSysML runtime sequence query → DOT → Graphviz SVG | White background, relationship-consistent rendering, no Mermaid dependency. Provisional: no chapter's real model has a `FlowUsage` yet, so this pipeline has not been exercised against real content. Fallback: PlantUML if `opensysml -render-form dot` unsupported for sequences (confirmed at WP-1 and documented below). | | Which requirement or function relates to which element? Traceability graph | Model query → Graphviz DOT → SVG | Explicit typed relationships and controllable grouping. Use a table when the purpose is exhaustive coverage. | | How does a modeled quantity change? Scientific plot | Model execution results → Matplotlib → SVG | Axes, units, reference values, and parameter comparisons. | -These are working defaults for this tutorial, not universal tool rankings, and are grounded in `decisions/diagram-study-real-fixtures.md` (a rerun of the original trade study against real chapter models, not the simplified toy fixture the original comparison used). Keep the same pipeline for a given figure type throughout the book. An unsupported construct warrants an explicit recipe change; a crowded figure usually warrants a smaller scope or better layout. **Never use the OMG pilot or the third-party SysMLD/sysml2d tool for real chapter content** — both are confirmed, on real content, to fail entirely (pilot: qualified-name `allocate` targets; SysMLD: an indexer bug that mis-tracks brace scope on ordinary real syntax like a doc-comment block or an `assert constraint` body). +These are working defaults for this tutorial, not universal tool rankings, and are grounded in `decisions/diagram-study-real-fixtures.md` (a rerun of the original trade study against real chapter models, not the simplified toy fixture the original comparison used). Keep the same pipeline for a given figure type throughout the book. An unsupported construct warrants an explicit recipe change; a crowded figure usually warrants a smaller scope or better layout. **Never use the pilot or the third-party SysMLD/sysml2d tool for real chapter content** — both are confirmed, on real content, to fail entirely (pilot: qualified-name `allocate` targets; SysMLD: an indexer bug that mis-tracks brace scope on ordinary real syntax like a doc-comment block or an `assert constraint` body). ## Make a figure recipe @@ -53,15 +53,15 @@ Use white backgrounds, readable typography, restrained color, and consistent nam - **DOT/Graphviz** — default for structure, interconnection, sequence, and relationship diagrams - **`render_interconnection()`** (in-house, `src/toaster/render.py`) — first-class for port-level interconnection; the intent dict is built from `model.query()` + `model.to_api_json()`. This function does not use, and never used, the third-party SysMLD tool — see the real-fixture study for why that tool is now confirmed unusable on real content. -- **OpenSysML CLI (`-render-form dot`)** — action flow and state views, confirmed directly against real chapter content +- **OpenSysML runtime CLI (`-render-form dot`)** — action flow and state views, confirmed directly against real chapter content - **Matplotlib** — quantitative figures only - **Mermaid** — NOT used anywhere in this tutorial -- **The OMG pilot** — NOT used anywhere in this tutorial; confirmed to fail on all real chapter content (qualified-name `allocate` targets) +- **The OMG SysML v2 Pilot Implementation** — NOT used anywhere in this tutorial; confirmed to fail on all real chapter content (qualified-name `allocate` targets) - **The third-party SysMLD/sysml2d tool** — NOT used anywhere in this tutorial; confirmed to fail to index any real chapter content (an indexer bug, see `decisions/log.md` DL-055) -**A note on an earlier finding, corrected by the real-fixture study (`decisions/diagram-study-real-fixtures.md`):** an earlier probe (WP-1, 2026-09-25, against a small hand-written test model, not a real chapter model) found opensysml had no native DOT or render-form CLI, and that finding drove a stopgap of generating DOT directly from `model.query()` output for the structure view. That stopgap (`model_to_dot()`) is still the right choice for structure specifically (it draws the whole model, not one root), but the earlier finding about OpenSysML's CLI itself no longer holds: the pinned binary's `-render #kind:element -render-form dot` (or `plantuml`) form is real, works on real chapter content (confirmed for both action-flow and state views), and is simply undocumented in the binary's own `-help` output. +**A note on an earlier finding, corrected by the real-fixture study (`decisions/diagram-study-real-fixtures.md`):** an earlier probe (WP-1, 2026-09-25, against a small hand-written test model, not a real chapter model) found the OpenSysML runtime had no native DOT or render-form CLI, and that finding drove a stopgap of generating DOT directly from `model.query()` output for the structure view. That stopgap (`model_to_dot()`) is still the right choice for structure specifically (it draws the whole model, not one root), but the earlier finding about the OpenSysML runtime's CLI no longer holds: the pinned binary's `-render #kind:element -render-form dot` (or `plantuml`) form is real, works on real chapter content (confirmed for both action-flow and state views), and is simply undocumented in the binary's own `-help` output. -For action flow and state: OpenSysML's own `-render` CLI, not a custom Python renderer — none exists yet for action flow, and none is needed. +For action flow and state: the OpenSysML runtime's own `-render` CLI, not a custom Python renderer — none exists yet for action flow, and none is needed. For interconnection: `render_interconnection()`'s intent dict, built from `model.query()` + `model.to_api_json()` (unchanged from WP-4; only the function's name changed, since it never depended on the tool its old name implied). Never use Mermaid as a fallback for anything. diff --git a/.claude/skills/sysml-diagrams/references/recipes.md b/.claude/skills/sysml-diagrams/references/recipes.md index 571d183..542b73c 100644 --- a/.claude/skills/sysml-diagrams/references/recipes.md +++ b/.claude/skills/sysml-diagrams/references/recipes.md @@ -2,11 +2,11 @@ **Corrected 2026-10-01** (this file was not updated when `decisions/log.md` `DL-057` corrected `sysml-diagrams/SKILL.md`'s own renderer-choice table, so it kept describing the -OMG pilot and SysMLD/sysml2d as the defaults for two view types after both were confirmed +OMG SysML v2 Pilot Implementation and SysMLD/sysml2d as the defaults for two view types after both were confirmed to fail entirely on real content, `decisions/diagram-study-real-fixtures.md`; Phase 1's own -survey, `decisions/diagram-survey.md`, caught the gap). **Never use the OMG pilot or +survey, `decisions/diagram-survey.md`, caught the gap). **Never use the pilot or SysMLD/sysml2d for real chapter content.** Run from the tutorial repository root. `$SYSML` -is the pinned OpenSysML CLI; `model.sysml` is the chapter-generated snapshot. Replace example +is the pinned OpenSysML runtime CLI; `model.sysml` is the chapter-generated snapshot. Replace example qualified names with the chosen subject. Write outputs to an ignored `build/figures/` directory. @@ -71,7 +71,7 @@ java -Djava.awt.headless=true -jar "$PLANTUML_JAR" -tsvg build/figures/interconn Confirmed on every real fixture tested (`decisions/diagram-study-real-fixtures.md`): draws real port names (e.g. `durationIn`, `durationOut`) as their own boxes inside the owning part, not -folded into one edge label the way OpenSysML's own interconnection export does. Otherwise, a +folded into one edge label the way the OpenSysML runtime's own interconnection export does. Otherwise, a chapter using interconnection only to show a connection or an allocation — where port identity is not itself the point — does not need the extra external-binary dependency; default to `render_interconnection()`. @@ -80,7 +80,7 @@ Before rendering, assert that selected relationships and endpoints match the mod parts, ports, and connections — never author a separate relationship model by hand for either pipeline. -## Action flow — OpenSysML +## Action flow — OpenSysML runtime ```sh "$SYSML" model.sysml \ @@ -89,15 +89,18 @@ pipeline. dot -Tsvg build/figures/actions.dot -o build/figures/actions.svg ``` -Confirmed directly against real chapter content (`decisions/diagram-study-real-fixtures.md`; -Ch6's `ApplyHeat` action, exit 0, real action-flow notation). No in-house action-flow renderer -exists yet. Expected output: the declared actions, initial/final nodes, and successions. Check +Confirmed against real chapter content (Ch4's `ToastBread` and Ch6's `ApplyHeat` actions, exit 0: +`figures/ch04-toastbread-flow.svg`, `figures/ch06-applyheat-flow.svg`, DEFERRED.md D-037; the +real-fixture study, `decisions/diagram-study-real-fixtures.md`, did not rerun the action-flow view, +the DL-057 probe did): the control sequence is drawn, but the CLI's DOT omits the actions' declared +typed flows (D-037); the caption must say so. No in-house action-flow renderer exists yet. Expected +output: the declared actions, initial/final nodes, and successions, without flow pins. Check decisions, guards, forks, joins, and object flows whenever the selected model contains them — every real chapter fixture tested so far exercises only a linear sequence. Distinguish a structural action-flow figure from an actual execution trace. -## State transition — OpenSysML +## State transition — OpenSysML runtime ```sh "$SYSML" model.sysml \ @@ -106,17 +109,18 @@ Distinguish a structural action-flow figure from an actual execution trace. dot -Tsvg build/figures/states.dot -o build/figures/states.svg ``` -Confirmed directly against real chapter content (`decisions/diagram-study-real-fixtures.md`): -100% success across both OpenSysML render forms on Ch7's real `Cycle` state machine, and the -mutation-control test (retargeting a transition) correctly changes the rendered output. Show -states and transitions for one behavioral question. Preserve initial entry and, when present, -event triggers, guards, effects, and entry/do/exit compartments. Change orientation or split -nested behavior into another figure when labels become crowded. +Run against real chapter content (`decisions/diagram-study-real-fixtures.md`): both OpenSysML +runtime render forms exit 0 on Ch7's real `Cycle` state machine and draw its states and +transitions, but the `do` activity label omits the performed action's name (D-037); the caption +must say so. The mutation-control test (retargeting a transition) correctly changes the rendered +output. Show states and transitions for one behavioral question. Preserve initial entry and, when +present, event triggers, guards, effects, and entry/do/exit compartments. Change orientation or +split nested behavior into another figure when labels become crowded. Check each transition's source and target against the real model, not an assumed shape — a changed target must change the corresponding arrow. -## Sequence — OpenSysML and Mermaid +## Sequence — OpenSysML runtime and Mermaid ```sh "$SYSML" model.sysml \ diff --git a/.claude/skills/sysml-v2-toaster-model/SKILL.md b/.claude/skills/sysml-v2-toaster-model/SKILL.md index 761ca7e..c43c66a 100644 --- a/.claude/skills/sysml-v2-toaster-model/SKILL.md +++ b/.claude/skills/sysml-v2-toaster-model/SKILL.md @@ -26,7 +26,7 @@ description: SysML v2 construct subset for the toaster tutorial — confirmed co No other constructs. `port def`, `interface def`, `connection def`, parametric diagrams, and `metadata` are out of scope for v0.1. -**Gap — VerificationMethodKind metadata (toaster#19 / OpenSysML#608):** The spec-defined way to annotate the verification method kind is `#verificationMethod = VerificationMethodKind::test` (SysML v2 §7.24 Table 22). This metadata construct does not parse in OpenSysML v0.9.0 (`ok=False`, error: "expected a body member"). Until fixed, document the method kind as text in the `doc` comment of the verification case definition. +**Gap — VerificationMethodKind metadata (toaster#19 / OpenSysML#608):** The spec-defined way to annotate the verification method kind is `#verificationMethod = VerificationMethodKind::test` (SysML v2 §7.24 Table 22). This metadata construct does not parse in the OpenSysML runtime v0.9.0 (`ok=False`, error: "expected a body member"). Until fixed, document the method kind as text in the `doc` comment of the verification case definition. ## Ch9–10: analysis operations (not new constructs) diff --git a/.claude/skills/tutorial-glossary/SKILL.md b/.claude/skills/tutorial-glossary/SKILL.md index 4b8edc1..2220fd6 100644 --- a/.claude/skills/tutorial-glossary/SKILL.md +++ b/.claude/skills/tutorial-glossary/SKILL.md @@ -40,7 +40,7 @@ Sources are not ranked against each other. Each supplies a **kind** of definitio 3. **Locators and quotes are checkable.** Each canonical edge has a `gl:locator` and, for file sources, a short `gl:quote` (at most 300 characters) with `gl:pdfPage`. `verify-sources` finds the quote on that page. Douglas locators are `Part N, m:ss` and were read from transcripts. Never quote at length; paraphrase in `gl:text`. **Exception (Z, 2026-09-26, DL-026):** a single definitional sentence of at most 200 characters may reproduce canonical wording in `gl:text` or `gl:gloss` when the source is attributed on the page (the Sources list) and the wording is not placed in quotation marks as if verbatim. Longer text is paraphrased. `gl:quote` is never rendered on the public page. 4. **Glosses are at most 240 characters.** A `gl:gloss` is used verbatim by `render`; without one, `gl:text` is used if it fits. 5. **Change definitions only through the graph**, then `check`, then `render`. Text between `` and `` is generated; never edit it by hand. Learner-facing pages and the skills cite terms, they do not redefine them. -6. **Builder-facing lenses are not sources or terms** (Tall's three worlds, optimization and control, generalized dynamical systems). Implementations (OpenSysML, sysml-toolkit) are toolchain, not sources. +6. **Builder-facing lenses are not sources or terms** (Tall's three worlds, optimization and control, generalized dynamical systems). Implementations (the OpenSysML runtime and sysml-toolkit, components of the OpenSysML stack, and the OMG SysML v2 Pilot Implementation) are toolchain, not sources. ## Adding a term, source or edge diff --git a/.claude/skills/tutorial-style-guide/SKILL.md b/.claude/skills/tutorial-style-guide/SKILL.md index ebe8786..93b05de 100644 --- a/.claude/skills/tutorial-style-guide/SKILL.md +++ b/.claude/skills/tutorial-style-guide/SKILL.md @@ -83,7 +83,7 @@ structure stays the same. - One code cell per fragment variable. Each is printed immediately after assignment. - Fragment variable names mirror the element: `HEATER_DEF`, `POWER_ATTR`, `TIMELY_REQ`, etc. - Fragment size: ≤5 lines of SysML per variable (ideally 1–3). Split if longer. -- Every gap construct: add a comment citing the toaster issue + OpenSysML issue + spec section +- Every gap construct: add a comment citing the toaster issue + the issue on the tracker of the component at fault (the runtime's `Open-MBEE/OpenSysML#NNN` or `Open-MBEE/sysml-toolkit#N`) + spec section directly above the string, e.g.: ```python # abstract modifier not yet supported — toaster#9 / OpenSysML#595 diff --git a/.claude/skills/tutorial-supporting-pages/SKILL.md b/.claude/skills/tutorial-supporting-pages/SKILL.md index d9f50dd..91581ef 100644 --- a/.claude/skills/tutorial-supporting-pages/SKILL.md +++ b/.claude/skills/tutorial-supporting-pages/SKILL.md @@ -12,9 +12,9 @@ description: docs/ page inventory, reproducibility statement structure, fork-and | `docs/index.md` | Opening navigation + didactic purpose statement | | `docs/setup.md` | Provisioning steps + fork-and-exercise workflow | | `docs/glossary.md` | Generated glossary of every confirmed load-bearing term (`uv run python -m glossary render`); never edited by hand | -| `docs/references.md` | Citations: Brian Douglas video, Hawkins 2011, opensysml, mystmd | +| `docs/references.md` | Citations: Brian Douglas video, Hawkins 2011, OpenSysML (the stack definition and its two components' links), mystmd | | `docs/reproducibility.md` | Closing reproducibility statement (populated from build manifest) | -| `docs/contributor.md` | Maintainer guide (4 update scenarios) | +| `docs/contributor.md` | Maintainer guide ("What we accept" policy, then 4 maintenance scenarios) | ## docs/setup.md — fork-and-exercise section @@ -29,6 +29,7 @@ Fork the repo, provision the environment (see above), then: The only tools you need are the ones introduced up to that chapter. 4. The `exercises/` notebooks are blank workspaces — they are not pre-executed and not part of the CI pipeline. +5. Your fork is where your exercise work lives; it is not a contribution path (see the contributor guide). ``` Cells 0–5 of any sub-notebook are the worked example (read-only reference). The exercise lives in `exercises/` as a separate file. Do not modify chapter notebooks while doing exercises. @@ -47,8 +48,11 @@ Author these sections with `{{manifest_field}}` template markers. A2 fills them ## Contributor guide — 4 required scenarios -1. Update a dependency and regenerate outputs -2. Add a new chapter +The guide opens with "What we accept" (AGENTS.md §1.12: keep current to the toolchain and the +specifications; strictly dominant improvements; no new content), then: + +1. Keep current: update a dependency or tool pin and regenerate outputs +2. How a change is built and reviewed 3. Change a model element and review stale judgment records 4. Run the full CI pipeline locally @@ -62,3 +66,4 @@ Practitioner prose, not tutorial. The reader is a maintainer. Assume they can re - Write contributor instructions that require unavailable tooling - Write setup instructions inside chapter narration (they go in `docs/setup.md`) - Include exercise instructions inside chapter notebooks (exercises live in `exercises/`) +- Write a contributor scenario that invites a new chapter, notebook, exercise, model element or glossary term (AGENTS.md §1.12) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..7531d70 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,25 @@ + + +## What this change is (tick one) + +- [ ] Keeps current: pin or dependency bump / workaround retired (DEFERRED entry D-___) / spec-edition re-check / drift fix +- [ ] Improves what is here (fill in the next section) + +## Strictly dominant (improvements only) + +Better on (name it, with evidence): +- [ ] (1) Spec conformance — clause(s): ___ ; check run: ___ +- [ ] (2) Didactic clarity — what gets harder for the learner without this change: ___ ; pacing check: ___ +- [ ] (3) Tool use — construct or operation run under the pinned versions: ___ + +Not worse on any priority (one line each, with how you checked): +- (1) ___ +- (2) ___ +- (3) ___ + +## Protections + +- [ ] No new chapter, notebook, exercise, construct or analysis operation, model element, judgment record, glossary term or learning outcome +- [ ] `models/`, judgment records, stored outputs and `DEFERRED.md` headings unchanged, or the change says why and recomputes `content_hash` +- [ ] `TOASTER_REQUIRE_TOOLS=1 uv run pytest tests/ glossary/tests/` passes locally, and `uv run python -m glossary lint` reports no hit the base branch does not (it is not a CI gate; see the contributor guide) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 111e4ac..1f78c4f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,51 +7,109 @@ on: jobs: build: - runs-on: ubuntu-latest + # 24.04, not latest: the pinned Linux sysmlv2 needs GLIBC_2.39 and the Linux z3 needs + # GLIBC_2.38 / GLIBCXX_3.4.32, which 22.04 does not provide. + runs-on: ubuntu-24.04 permissions: contents: read steps: - uses: actions/checkout@v4 - # Step 1: Provision environment and verify tools - name: Set up uv uses: astral-sh/setup-uv@v4 with: version: "0.5.x" - - name: Install Python dependencies - run: uv sync --locked - - name: Set up Node uses: actions/setup-node@v4 with: node-version-file: .nvmrc + - name: Install Python dependencies + run: uv sync --locked + - name: Install Node dependencies run: npm ci + - name: Record tool versions in the job summary + run: | + { + echo "### Runner toolchain" + echo '```' + uv --version + uv run python --version + echo "node $(node --version)" + echo '```' + } | tee -a "$GITHUB_STEP_SUMMARY" + - name: Install system tools - run: sudo apt-get install -y graphviz + run: sudo apt-get update && sudo apt-get install -y graphviz openjdk-17-jre-headless + + - name: Show Java version + run: java -version + + - name: Cache provisioned tools + uses: actions/cache@v4 + with: + path: .tools + key: tools-${{ runner.os }}-${{ hashFiles('scripts/tool-pins.json') }} + + - name: Provision pinned tools + run: uv run python scripts/provision-tools.py - name: Check tool versions run: uv run python scripts/check-tools.py - # Step 2: Run pytest (unit + smoke) - name: Run tests + env: + TOASTER_REQUIRE_TOOLS: "1" run: uv run pytest tests/ glossary/tests/ -v --tb=short - # Steps 3-7 (notebook execution, MyST build, Pages deploy) — WP-8 - # Placeholder: these steps are scaffolded but not active until WP-8 + # `myst build` exits 0 even when a notebook cell raises; `--strict` makes it exit 1. + # `uv run` puts .venv/bin first on PATH so the Jupyter kernel finds the toaster package. + - name: Build the book (with execution) + env: + BASE_URL: /toaster + run: | + set -o pipefail + uv run --frozen npx myst build --html --execute --strict 2>&1 | tee build.log + + - name: Check the built site (release gate) + run: >- + uv run python scripts/check-site.py + --site _build/html + --content _build/site/content + --log build.log + --base-url /toaster + + - name: Upload build log + if: always() + uses: actions/upload-artifact@v4 + with: + name: build-log + path: build.log + retention-days: 7 + if-no-files-found: warn + + # Only a push to main produces the Pages artifact; pull requests stop + # after the gate above. + - name: Upload Pages artifact + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: actions/upload-pages-artifact@v3 + with: + path: _build/html deploy: - # Disabled until WP-8 wires up the full Pages pipeline - if: false + if: github.event_name == 'push' && github.ref == 'refs/heads/main' needs: build - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: pages: write id-token: write + concurrency: + group: pages + cancel-in-progress: false environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} @@ -59,4 +117,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - run: echo "Pages deployment placeholder — implement in WP-8" + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 1f36be6..68352ec 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,6 @@ exercises/*/companion-check-scratch/ # Agent-managed git worktrees (builder/reviewer isolation during plan execution) .claude/worktrees/ + +# External tools provisioned by scripts/provision-tools.py +.tools/ diff --git a/AGENTS.md b/AGENTS.md index df85a68..9dd99bd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,6 +11,8 @@ A cold session should reach working alignment from `CLAUDE.md`, this Part 1, the # Part 1 — Foundations +"Z" in this repository is the contributor identity `mzargham` (Michael Zargham, GitHub user `mzargham`), the project's author and chief engineer. Internal files keep saying "Z"; published pages name the handle at first mention, as "mzargham (Z)". Other contributors are named by their own handles, and "Z" is never reused for another person (`decisions/log.md` DL-112). + ## 1.1 What the tutorial teaches A learner recursively breaks a system down until the leaves are concrete component definitions that perform the intended behavior, connect through the specified interfaces, and are verified. The worked example is a toaster. The tutorial teaches, in this order of emphasis: @@ -38,7 +40,7 @@ The sources are not rivals. Each supplies a different **kind** of definition, an | Story (didactic) | Brian Douglas, *Systems Engineering* playlist, Parts 3 and 4 | Analogy, example, and the toaster case; how we convey the material, aligned with as far as possible to lower the learner's cost | | Bridge | This tutorial | Contextual refinements that tie the kinds together for the learner | -**Toolchain, not sources.** OpenSysML, sysml-toolkit, the Pilot Implementation and the like execute the specs. They are cited only to flag a spec gap (§1.9), never to define a term. +**Toolchain, not sources.** OpenSysML (opensysml.org) is the open-source SysML v2 tool stack; this tutorial uses two of its components and names them by role, **the OpenSysML runtime** (Go; `Open-MBEE/OpenSysML`; Python package `opensysml`; pinned v0.9.0) and **sysml-toolkit** (Rust; `sysmlv2` binary; pinned v0.9.1). A bare "OpenSysML" is a statement about the stack; a claim that is true of, or was probed against, one component names that component, and a version number attaches to a component name. The OMG SysML v2 Pilot Implementation (EPL-2.0) is the conformance baseline and is always named as such; it is not one of the two tools the chapters run. All of these execute the specs. They are cited only to flag a spec gap (§1.9), never to define a term. **Refinement rule.** Canonical definitions come first. Our own definitions appear only as contextual refinements where needed to make learning easier, and each records the canonical edge it refines. A refinement narrows or clarifies; it never contradicts a source and never invents. One departure is approved: SEBoK's *logical architecture* contains the functional view, whereas the tutorial separates a functional layer from a logical one, so "logical" is a recorded `differsFrom` edge approved by Z (DL-015). Learners are told the word is used more narrowly than SEBoK uses it, and that Douglas's "who" is this tutorial's "how". @@ -122,7 +124,7 @@ These are tendencies, not rules. The stable distinction is *computed versus expl - Implicit parts obey the same layer rules and the glossary as everything else, and their provenance is never hidden. - Legibility comes through diagrams: every chapter shows the assembled model so that explicit and implicit parts are distinguishable without reading the Python. - Implicit parts are authored and verified before the notebooks that import them. -- OpenSysML v0.9.0 does not resolve `import` across separately loaded sources. A notebook therefore assembles the SysML text from the imported modules plus its explicit increment into one source and loads that (gap G7, §1.9). +- The OpenSysML runtime v0.9.0 does not resolve `import` across separately loaded sources (sysml-toolkit v0.9.1 does, D-017). A notebook therefore assembles the SysML text from the imported modules plus its explicit increment into one source and loads that (gap G7, §1.9). **Diagrams are views of the model, drawn like scientific plots.** The model is the data; a diagram is a selected, purpose-specific view of it, produced by query and encoding, never hand-drawn and never a second source of engineering facts. The tools supply methods. They do not decide the figure. We judge what to include and exclude and how to present it, according to what the diagram must communicate in its notebook, and record that in the figure's recipe and caption. Presentation settings (layout, orientation, short labels) never carry engineering content. A diagram of a modeled assertion is not evidence that the assertion holds: evidence comes from its own analysis. A structural diagram shows prescriptions; a plot of simulation output shows derived behavior, with units and relations read from the model. See the `sysml-diagrams` skill. @@ -134,13 +136,13 @@ Decompose until every leaf is a concrete component def that **performs** its spe Three surfaces, in order of preference for a chapter notebook (recipes and limits are in the `opensysml-query` skill): -1. `model.query()` in OpenSysML: the API Query (select, where, scope, inverse; no traversal). It sees named elements only. Name your allocations, connections and flows and it sees those too. +1. `model.query()` in the OpenSysML runtime: the API Query (select, where, scope, inverse; no traversal). It sees named elements only. Name your allocations, connections and flows and it sees those too. 2. `json.loads(model.to_api_json().content)`: the full export, including unnamed `satisfy`, `perform` and connector elements. Use it through the helpers in `src/toaster/query.py`, never ad hoc. 3. `Symbol` navigation (`model.find`, `.specializations`, `.children`). The sysml-toolkit Python binding (`Session.from_files`) is a fourth surface that reads several files at once and sees unnamed elements. It is toolchain, not part of the chapter dependencies. -**Conformance has two tiers.** *Language conformance* (parse, name resolution, typing) is always on: a declaration that violates it breaks the load. *Project conformance checks* (interface compatibility, port types, flows accounted, coverage) are **staged**, because the model emerges iteratively and is not born complete: each check is declared as applied from a chapter and section onward, has a negative control that shows it catching a fault, and is reported as **open**, not passed, until it is applied. A check has five statuses: `open` (not yet applied), `passed`, `failed`, `blocked` (cannot be applied until a stated condition holds; the result records the `unblock_when` criterion, for example a model that fails language conformance), and `wont-do` (dropped because something changed; the result records the reason and the change). The same vocabulary is used for coordination (`decisions/task-states.md`). Discovering non-conformance early and flagging it to the user is what executable specifications are for. Tools may not diagnose a fault themselves (OpenSysML v0.9.0 accepts a power port connected to a fuel port; the KerML 1.1 spec searched has no validation constraint for it), so the tutorial supplies the check (recipe 5 in `opensysml-query`). +**Conformance has two tiers.** *Language conformance* (parse, name resolution, typing) is always on: a declaration that violates it breaks the load. *Project conformance checks* (interface compatibility, port types, flows accounted, coverage) are **staged**, because the model emerges iteratively and is not born complete: each check is declared as applied from a chapter and section onward, has a negative control that shows it catching a fault, and is reported as **open**, not passed, until it is applied. A check has five statuses: `open` (not yet applied), `passed`, `failed`, `blocked` (cannot be applied until a stated condition holds; the result records the `unblock_when` criterion, for example a model that fails language conformance), and `wont-do` (dropped because something changed; the result records the reason and the change). The same vocabulary is used for coordination (`decisions/task-states.md`). Discovering non-conformance early and flagging it to the user is what executable specifications are for. Tools may not diagnose a fault themselves (the OpenSysML runtime v0.9.0 and sysml-toolkit v0.9.1 both accept a power port connected to a fuel port; the KerML 1.1 spec searched has no validation constraint for it), so the tutorial supplies the check (recipe 5 in `opensysml-query`). **Gap-tracking rule.** Use the spec-anchored construct. If a tool cannot express it, use a bare SysML fragment or custom Python. Every gap gets (a) a `DEFERRED.md` entry, (b) a toaster issue and, where the tool is at fault, an upstream issue, each citing the exact spec section and asking only for what the spec says, and (c) a comment cell wherever the workaround appears. Never work around a gap silently. Nothing is filed on a public repository until Z has reviewed the text. @@ -160,6 +162,12 @@ Learner-facing vocabulary from these lenses is allowed only where it makes a ter Alignment passes (changes to this Part 1, the glossary's confirmed definitions, or the ACE skills) are Z-initiated. The ACE triages what needs Z: it rules and logs where Z's frameworks and principles determine the answer (and shows the reasoning), and escalates to Z with a concise request where they do not. Decisions are logged in `decisions/log.md` (§7 below). To reach the ACE, route the question through the orchestrator; if there is no orchestrator in your session, state the question and your recommended default in your report and it will be triaged. Proposals to the glossary (new terms, sources or edges) go to the ACE the same way; only Z confirms. +## 1.12 What contributions we want + +The contributions we want keep this tutorial current to its toolchain (the OpenSysML runtime, sysml-toolkit and the other pinned tools) and to the OMG SysML v2 specifications; we are not adding new content. Existing content may be refined, clarified or otherwise improved against three priorities: (1) conformance with the SysML v2 specifications (the OMG SysML v2 language, API and Services, and KerML specifications, §1.2); (2) didactic clarity; (3) effective, demonstrative use of tools from the OpenSysML stack (the OpenSysML runtime and sysml-toolkit). An improvement is accepted only if it is strictly dominant: better on at least one of these and worse on none. A trade-off is not an improvement under this rule; it is proposed in an issue and Z decides. + +New content is a new chapter, notebook, exercise, construct or analysis operation, model element, judgment record or glossary term, or a new learning outcome; none is accepted by pull request. Replacing a recorded workaround with the spec-anchored construct a newer tool release accepts is keeping current (§1.9), not new content. Added text or cells count as improvement only where the learner's task gets harder without them (the earn-its-place test, `.claude/skills/ace-protocol/z-principles.md` P4; the pacing rule in `tutorial-style-guide`; SA-8). The reviewer-facing test is in `docs/contributor.md`, and the pull-request template asks for it. + --- # Part 2 — Roster and authority (partially rebuilt; content archetypes legacy, pending Pass 4) diff --git a/CLAUDE.md b/CLAUDE.md index 2b8fac9..81d1d0c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,8 +16,7 @@ glossary CLI for any term you are about to define or use: Definitions come from the glossary, not from memory. Sources in citation order: SEBoK (ideas), the OMG SysML v2 / API / KerML specs (formal semantics), Hawkins 2011 (judgment taxonomy), Åström and Murray with Sutton and Barto (mechanism and -policy only), Douglas (story and the toaster example). OpenSysML and sysml-toolkit -are toolchain, cited only to flag spec gaps. +policy only), Douglas (story and the toaster example). The OpenSysML runtime and sysml-toolkit, two components of the OpenSysML stack (opensysml.org; AGENTS.md 1.2), are toolchain, cited only to flag spec gaps. Roles (.claude/agents/): `orchestrator` (run the main session as it with `claude --agent orchestrator`), `layer-auditor`, `builder`, `reviewer`, `ace`. Each pins its model; author and reviewer run on different models; work contracts follow `decisions/work-contract-template.md` and task states `decisions/task-states.md`. diff --git a/DEFERRED.md b/DEFERRED.md index 16a64dd..2c96d97 100644 --- a/DEFERRED.md +++ b/DEFERRED.md @@ -1,5 +1,7 @@ # Deferred work +**Terminology note (2026-10-03, DL-116, DL-117).** OpenSysML (opensysml.org) is the open-source SysML v2 tool stack; this tutorial uses two of its components, the OpenSysML runtime (Go; `Open-MBEE/OpenSysML`; Python package `opensysml`; pinned v0.9.0) and sysml-toolkit (Rust; `sysmlv2` binary; pinned v0.9.1). Entries written before this note use a bare "OpenSysML" (and "opensysml") for **the OpenSysML runtime**; read them that way. Headings are unchanged because their anchors are linked from published pages, and Workaround, Resolution, Upstream-issue and Status lines are unchanged because they are dated record; `OpenSysML#NNN` references are issues on the runtime's tracker. Where an entry records a different result for sysml-toolkit (D-017, D-019, D-023, D-024, D-034, D-035, D-036), a bare "OpenSysML" in the heading is the runtime and the body states what sysml-toolkit v0.9.1 does. + ## D-001: Ch9 satisfy-coverage uses to_api_json() workaround `model.query()` does not return `SatisfyRequirementUsage` elements (Open-MBEE/OpenSysML#TBD). @@ -26,7 +28,7 @@ maintenance burden unrelated to learning outcomes in v0.1. ## D-003: efficiency typed via MeasurementReferences::DimensionOneValue, not ISQ::DimensionOneValue -`ISQ::DimensionOneValue` is not defined in opensysml v0.9.0 (Open-MBEE/OpenSysML#TBD). +`ISQ::DimensionOneValue` is not defined in the OpenSysML runtime v0.9.0 (`opensysml`; Open-MBEE/OpenSysML#TBD). `MeasurementReferences::DimensionOneValue` exists and is functionally correct, so ch03–ch08 models import `MeasurementReferences::*` and use `DimensionOneValue` directly. `SI::one` is also absent. @@ -176,7 +178,7 @@ The spec-defined way to annotate the method kind of a verification case is: #verificationMethod = VerificationMethodKind::test; ``` inside a `verification def` body (SysML v2 formal/2026-03-02 §7.24 Table 22). -In OpenSysML v0.9.0 this raises "expected a body member" and `ok=False`. +In the OpenSysML runtime v0.9.0 this raises "expected a body member" and `ok=False`. Until fixed, the verification method type is documented as text in the `doc` comment of the verification case definition (Ch3/nb04 and `models/ch03-cumulative.sysml`). @@ -189,7 +191,7 @@ with the formal `#verificationMethod` annotation and remove the gap comment. ## D-014: Mismatched port types on a connection are not diagnosed (gap G4) -OpenSysML v0.9.0 accepts `connect outlet.o to torch.fuelIn` between a `PowerPort` and a `FuelPort`, and an +The OpenSysML runtime v0.9.0 accepts `connect outlet.o to torch.fuelIn` between a `PowerPort` and a `FuelPort`, and an `interface def` with `PowerPort` ends bound to a `FuelPort`, with `ok=True` and no diagnostic. sysml-toolkit v0.9.1 `check` and `lint` (default rules) accept it too. The KerML 1.1 Beta 2 text searched has no validation constraint requiring compatible end types (`validateConnectorRelatedFeatures` requires only two related features), so this @@ -204,7 +206,7 @@ chapter and section where the connection is declared complete, with a negative c ## D-015: `model.query()` does not see unnamed connectors, `satisfy`, or metadata (gap G1; extends D-001) -Probed 2026-09-26 (OpenSysML v0.9.0): named `allocation`, `connection` and `flow` are visible to `model.query()`; +Probed 2026-09-26 (the OpenSysML runtime v0.9.0): named `allocation`, `connection` and `flow` are visible to `model.query()`; unnamed ones, every `satisfy`/`verify` (cannot be named), and `MetadataUsage` are visible only in `json.loads(model.to_api_json().content)`. A named `perform action` appears as `ActionUsage`, and inherited members are not expanded. The API spec's `getElements` returns "all the elements" at a commit (API and Services v1.0, @@ -259,7 +261,7 @@ implicit parts in notebook diagrams is therefore not available through the toolk ## D-019: OpenSysML accepts an allocate between definitions (language conformance hole) -OpenSysML v0.9.0 loads `allocate ApplyHeat to HeatingSystem;` (an action definition and a part definition) with `ok=True`. sysml-toolkit v0.9.1 with the standard library rejects it: `ReferenceSubsetting::referencedFeature must refer to a Feature`. KerML 1.1 Beta 2 8.3.3.3.9 ReferenceSubsetting (PDF p. 203) defines the referenced element as a Feature. An allocate between usages loads in both tools. The tool rejects `perform ToastBread;` naming a definition (G2), so the allocate case is inconsistent with its own handling. Found by the Ch5 audit (`decisions/audits/ch05-layer-audit.md` F-1), confirmed by a spot review. Classified as language-tier non-conformance (DL-039); affects `models/ch05-cumulative.sysml` line 53 and the same line in ch06 to ch08. +The OpenSysML runtime v0.9.0 loads `allocate ApplyHeat to HeatingSystem;` (an action definition and a part definition) with `ok=True`. sysml-toolkit v0.9.1 with the standard library rejects it: `ReferenceSubsetting::referencedFeature must refer to a Feature`. KerML 1.1 Beta 2 8.3.3.3.9 ReferenceSubsetting (PDF p. 203) defines the referenced element as a Feature. An allocate between usages loads in both tools. The tool rejects `perform ToastBread;` naming a definition (G2), so the allocate case is inconsistent with its own handling. Found by the Ch5 audit (`decisions/audits/ch05-layer-audit.md` F-1), confirmed by a spot review. Classified as language-tier non-conformance (DL-039); affects `models/ch05-cumulative.sysml` line 53 and the same line in ch06 to ch08. **Workaround:** the tutorial supplies a language-gap guard, `allocate-between-definitions` (`src/toaster/conformance.py`, `GAP_RULES`), with its own negative control -- the guard already exists (corrected 2026-09-29, DL-059 ADDENDUM: this line previously said "to be built" after the guard had already landed). Extended 2026-09-29 (DL-059 ADDENDUM 3, Task 7 F-1): originally checked only the connector end's own final target (`end[-1]`); now checks EVERY segment of a chained end, since a MIDDLE segment resolving to a Definition (e.g. `allocate doApply to toaster.Inner.heater;` where `Inner` is a nested `part def`) is the same ReferenceSubsetting violation and was previously invisible to every rule. This also makes `allocate-between-definitions` the sole owner of the chain-ROOT-is-a-Definition case: `allocate-connector-end-accessibility` (D-032) used to check that case on its own, and its own now-redundant chain-root check was removed in the same fix to avoid double-flagging -- the division of labor is now: `allocate-between-definitions` (D-019) checks whether every segment of an end is a Feature, not a Definition, regardless of position in the chain; `allocate-connector-end-accessibility` (D-032) checks whether the chain's first segment, GIVEN that it is a Feature, is actually accessible from the allocation's own context. **Resolution:** upstream fix in OpenSysML; re-test with `scripts/probes`. @@ -268,7 +270,7 @@ OpenSysML v0.9.0 loads `allocate ApplyHeat to HeatingSystem;` (an action definit ## D-020: Neither OpenSysML nor sysml-toolkit reports a part usage typed only by an item definition -`part bread : Start;` (`Start` an `item def`) loads with `ok=True` in OpenSysML v0.9.0 and passes sysml-toolkit v0.9.1 `check --lib`. SysML v2.0 (formal/2026-03-02) `validatePartUsagePartDefinition` (PDF p. 323): "At least one of the itemDefinitions of a PartUsage must be a PartDefinition" (`partDefinition->notEmpty()`). Found by the Ch5 audit (F-3), confirmed by a spot review. Classified as language-tier non-conformance (DL-039); affects `models/ch05-cumulative.sysml` lines 54 and 55 and later fixtures. +`part bread : Start;` (`Start` an `item def`) loads with `ok=True` in the OpenSysML runtime v0.9.0 and passes sysml-toolkit v0.9.1 `check --lib`. SysML v2.0 (formal/2026-03-02) `validatePartUsagePartDefinition` (PDF p. 323): "At least one of the itemDefinitions of a PartUsage must be a PartDefinition" (`partDefinition->notEmpty()`). Found by the Ch5 audit (F-3), confirmed by a spot review. Classified as language-tier non-conformance (DL-039); affects `models/ch05-cumulative.sysml` lines 54 and 55 and later fixtures. **Workaround:** the tutorial supplies a language-gap guard, `part-typed-only-by-item-def` (`src/toaster/conformance.py`, `GAP_RULES`), with its own negative control -- the guard already exists (corrected 2026-09-29, DL-059 ADDENDUM: this line previously said "to be built" after the guard had already landed). **Resolution:** upstream fix in both tools. @@ -320,11 +322,11 @@ The round-2 final ruling on a genuine no-import cross-package reference stands u **Upstream issue:** not filed — Draft 9 (`decisions/gap-issue-drafts.md`), citing SysML v2.0 formal/2026-03-02 8.3.18.8/8.3.18.9/8.3.17.2, is drafted and held for Z's review **Toaster issue:** not filed -**PASS4-007 note.** Chapter 7's own re-derivation rebuilt `Cycle` as a real `state def`, exhibited by `ToastingSystem` (the abstract subject; `Toaster` inherits it, per DL-019/DL-044), with the same `Start`/`Finish`/`Cancel` triggers this entry already covers. `chapters/ch07-execution/02-state-traces.ipynb` now demonstrates the guard directly, the first chapter notebook to do so: a scratch copy of the real, loaded `ch07-cumulative.sysml` with `Start` typo'd to `Strat` loads with `ok=True` (OpenSysML itself does not catch it), and `language_gap_findings` flags it as `unresolved-transition-trigger`. This is the same construct and mechanism this entry already documents; no new finding, no new draft. +**PASS4-007 note.** Chapter 7's own re-derivation rebuilt `Cycle` as a real `state def`, exhibited by `ToastingSystem` (the abstract subject; `Toaster` inherits it, per DL-019/DL-044), with the same `Start`/`Finish`/`Cancel` triggers this entry already covers. `chapters/ch07-execution/02-state-traces.ipynb` now demonstrates the guard directly, the first chapter notebook to do so: a scratch copy of the real, loaded `ch07-cumulative.sysml` with `Start` typo'd to `Strat` loads with `ok=True` (the OpenSysML runtime does not catch it), and `language_gap_findings` flags it as `unresolved-transition-trigger`. This is the same construct and mechanism this entry already documents; no new finding, no new draft. ## D-024: RETRACTED — OpenSysML v0.9.0's Python binding cannot ask a "holds" question (sysml-toolkit can) -**Retracted the same day it was filed.** This entry originally concluded no tool in the toolchain could ask a "holds" question and that DL-046 must fall back to DL-006 standing. That was wrong: it checked only OpenSysML. `sysmlv2 verify --solve` (sysml-toolkit v0.9.1, already rebuilt in this pass) does exactly this via Z3, verified against a constructed tautology, contradiction and a bounded-range TimelyToast-shaped requirement (`decisions/probes.md`, correction entry). OpenSysML's own gap (its Python binding is evaluate-only) still stands as a fact, but is no longer a blocking gap for DL-046 since sysml-toolkit covers it. The remaining open point is architectural, not a tool gap: sysml-toolkit's Python binding has no `verify`/`solve` method, so using it from a notebook means a `subprocess` call to the Rust CLI binary rather than a Python method call. Routed to Z as a design question, not an upstream issue. +**Retracted the same day it was filed.** This entry originally concluded no tool in the toolchain could ask a "holds" question and that DL-046 must fall back to DL-006 standing. That was wrong: it checked only the OpenSysML runtime. `sysmlv2 verify --solve` (sysml-toolkit v0.9.1, already rebuilt in this pass) does exactly this via Z3, verified against a constructed tautology, contradiction and a bounded-range TimelyToast-shaped requirement (`decisions/probes.md`, correction entry). The OpenSysML runtime's own gap (its Python binding is evaluate-only) still stands as a fact, but is no longer a blocking gap for DL-046 since sysml-toolkit covers it. The remaining open point is architectural, not a tool gap: sysml-toolkit's Python binding has no `verify`/`solve` method, so using it from a notebook means a `subprocess` call to the Rust CLI binary rather than a Python method call. Routed to Z as a design question, not an upstream issue. **Superseded by D-025** (below): Z decided the subprocess call is acceptable, wrapped in a utility function with an intent-to-deprecate record. @@ -343,7 +345,7 @@ sysml-toolkit's Python binding (`sysmlv2.Session`) has no `verify`/`solve` metho **Headline finding:** writing a bare `in` parameter's already-implicit multiplicity out explicitly, changing nothing about what the declaration means, changes whether -OpenSysML v0.9.0 can evaluate the model. `in energy : ISQ::EnergyValue;` (no +the OpenSysML runtime v0.9.0 can evaluate the model. `in energy : ISQ::EnergyValue;` (no multiplicity written) and `in energy : ISQ::EnergyValue[0..*];` (the multiplicity SysML v2.0's own default already gives the first form, §7.6.3/§7.6.4, see below) are spec-identical declarations. The tool accepts both (`model.ok == True`), but only @@ -405,7 +407,7 @@ parameter present in that run): | `[0..2]` | evaluates cleanly | | `[*]` | evaluates cleanly | -**The mechanism this evidence actually supports:** OpenSysML v0.9.0 gives a +**The mechanism this evidence actually supports:** The OpenSysML runtime v0.9.0 gives a keyword-less `in` parameter (a `ReferenceUsage` per the grammar, §8.2.2.6.3) the tighter `[1..1]` default that SysML v2.0 formal/2026-03-02 §7.6.3 reserves for "an attribute usage, an item usage, ..., or a port usage" — usages declared *with* a @@ -578,7 +580,7 @@ about at load time) two calls removed from the crash site. should tolerate what `load_from_content` already accepts with only a warning, returning some deterministic disambiguation; or (b) a same-namespace, same-name second declaration should itself be a load-time error (elevate the -warning), since two OpenSysML surfaces (load, and the API-JSON conversion this +warning), since two OpenSysML runtime surfaces (load, and the API-JSON conversion this model uses for everything else) disagreeing about whether the model is valid is the more fundamental problem, independent of which one is "right." No spec constraint naming this exact case was checked against the PDF text @@ -836,13 +838,13 @@ inline or symbolically expand a calc invocation, a solver-integration feature. ## D-032: OpenSysML accepts an allocate connector end that reaches into another type's nested feature by qualified name, with no featuring context to make it accessible (GUARDED) -OpenSysML v0.9.0 loads `allocate to ::;` (a package-level allocate whose end is a bare qualified-name reference into a feature nested inside a Definition the allocation is not itself featured within — e.g. `allocate ToastBread::applyHeat to Toaster::heating;`) with `ok=True` and no diagnostic; sysml-toolkit v0.9.1 accepts it too (its `connectors.rs` documents a "one-hop featuring lift" with no counterpart in the spec). KerML 1.1 Beta 2 validateSubsettingFeaturingTypes (8.3.3.3.4, p. 204) requires `subsettingFeature.canAccess(subsettedFeature)`, which requires the referenced feature to be `isFeaturedWithin` one of the connector end's featuringTypes (canAccess 8.3.3.3.4 p. 188; isFeaturedWithin p. 190); checkConnectorTypeFeaturing (8.3.4.5.3, pp. 214-215) finds no featuringType in common between the two definitions either, so no implied TypeFeaturing rescues it. Ruled DL-058: the tutorial's own ch05-ch08 fixtures carried exactly this construct (`ToastBread::applyHeat to Toaster::heating`) before this remediation effort; Tasks 1-3 rewrote every chapter (ch05-ch10) to the conformant nested/dot-chain idiom (`allocate toastBread.applyHeat to heating;`, nested inside the owning definition, or an equivalent same-context qualified reference). Task 4 added `allocate-connector-end-accessibility`, a `GapRule` in `src/toaster/conformance.py`, as an always-on guard against regression back into the non-conformant idiom. +The OpenSysML runtime v0.9.0 loads `allocate to ::;` (a package-level allocate whose end is a bare qualified-name reference into a feature nested inside a Definition the allocation is not itself featured within — e.g. `allocate ToastBread::applyHeat to Toaster::heating;`) with `ok=True` and no diagnostic; sysml-toolkit v0.9.1 accepts it too (its `connectors.rs` documents a "one-hop featuring lift" with no counterpart in the spec). KerML 1.1 Beta 2 validateSubsettingFeaturingTypes (8.3.3.3.4, p. 204) requires `subsettingFeature.canAccess(subsettedFeature)`, which requires the referenced feature to be `isFeaturedWithin` one of the connector end's featuringTypes (canAccess 8.3.3.3.4 p. 188; isFeaturedWithin p. 190); checkConnectorTypeFeaturing (8.3.4.5.3, pp. 214-215) finds no featuringType in common between the two definitions either, so no implied TypeFeaturing rescues it. Ruled DL-058: the tutorial's own ch05-ch08 fixtures carried exactly this construct (`ToastBread::applyHeat to Toaster::heating`) before this remediation effort; Tasks 1-3 rewrote every chapter (ch05-ch10) to the conformant nested/dot-chain idiom (`allocate toastBread.applyHeat to heating;`, nested inside the owning definition, or an equivalent same-context qualified reference). Task 4 added `allocate-connector-end-accessibility`, a `GapRule` in `src/toaster/conformance.py`, as an always-on guard against regression back into the non-conformant idiom. The algorithm has now been independently reviewed and hardened twice, each round verified against the OMG pilot as ground truth rather than merely re-derived. **Task 5 (DL-059 ADDENDUM)** found two bugs in Task 4's first cut: it did not recognize that canAccess/isFeaturedWithin treat a featuring type as accessible when it (transitively) specializes, or (for a Usage owner) is typed by, the declaring context (false positives on `part def BetterToaster :> Toaster { allocate doApply to heater; }` and on a plain `part toaster : Toaster { allocate doApply to heater; }`, both pilot-accepted); and it trusted ANY multi-segment (dot-chain) end as already accessible, when in fact only the segments AFTER the chain's first one are proven safe by successful loading — the first segment needs the same accessibility test as a bare single-segment end (false negatives on `Toaster.heater` and `Outer::box.t`-style ends, both pilot-rejected). **Task 6 (DL-059 ADDENDUM 2)** found two more bugs in Task 5's fix: `query.supertypes_transitively` (Task 5's fix for the first bug) builds its graph from NAMED elements only (`model.query(select=["name"])`), so it still returned nothing for an UNNAMED or redefining owner (e.g. `part redefines heater { ... }`, qualified name `P::Better::@0`) even when that owner clearly redefines or retypes an accessible declaring context — five more pilot-accepted constructs wrongly flagged; and a single-segment end resolving whole to a NESTED Definition (`allocate doApply to Toaster::Inner;` where `Toaster::Inner` is a `part def` nested inside `part def Toaster`) was double-flagged by both this rule and `allocate-between-definitions`, when the pilot gives exactly one error — the rule computed a declaring context for the end without first checking whether the end itself was already a Definition (a check that existed for a multi-segment chain root, but not for an ordinary single-segment end). The rule now (as of Task 6): for the end's own resolved element (single-segment case), skips entirely if it is itself a Definition (leaving it to `allocate-between-definitions`, regardless of nesting depth); otherwise accepts a declaring context equal to the owner, a plain package, or (transitively) a supertype/type/subset/redefinition target of the owner, computed via `query.supertypes_transitively_raw` (a new ApiIndex-native BFS over the raw API-JSON `type`, `subsets`, `redefines` and `specializes` reference fields, added to `src/toaster/query.py`, which works for named and unnamed elements alike — confirmed to reproduce `supertypes_transitively`'s own results exactly on every named case, so it replaces that call unconditionally rather than as a hybrid fallback); flags a chain root that is itself a Definition (never an accessible Feature); and applies the declaring-context test to the first segment of every end, single- or multi-segment alike. -**Task 7 (DL-059 ADDENDUM 3)** found two more bugs, both pre-dating Task 6 (design gaps from Task 4's original design, not introduced by Tasks 5/6). **F-1:** a Definition in the MIDDLE of a multi-segment chain (not the first segment, which this rule's own chain-root check covered, and not the last segment, which `allocate-between-definitions` covered) was invisible to every rule — e.g. `allocate doApply to toaster.Inner.heater;` where `Inner` is a nested `part def` between `toaster` and `heater`; OpenSysML accepted it with zero findings, the pilot rejected it. Fixed by extending `allocate-between-definitions` (D-019) to check every segment of a chained end, not only the last; since that now also covers the chain-root case this rule's own chain-root Definition check duplicated, the chain-root check was removed from THIS rule as redundant, closing the double-flag risk that removal would otherwise have reopened. **F-2:** the declaring-context exemption for the ordinary top-level pattern compared `@type == "Package"` by exact match, missing `LibraryPackage` (a distinct `@type` for a `library package`) — both a direct qualified reference into a library package (`L::heat`) and a `private import`-then-bare-reference form were wrongly flagged even though the pilot accepts both. Fixed by matching `.endswith("Package")` instead, this file's own established convention for metaclass-family checks. +**Task 7 (DL-059 ADDENDUM 3)** found two more bugs, both pre-dating Task 6 (design gaps from Task 4's original design, not introduced by Tasks 5/6). **F-1:** a Definition in the MIDDLE of a multi-segment chain (not the first segment, which this rule's own chain-root check covered, and not the last segment, which `allocate-between-definitions` covered) was invisible to every rule — e.g. `allocate doApply to toaster.Inner.heater;` where `Inner` is a nested `part def` between `toaster` and `heater`; the OpenSysML runtime accepted it with zero findings, the pilot rejected it. Fixed by extending `allocate-between-definitions` (D-019) to check every segment of a chained end, not only the last; since that now also covers the chain-root case this rule's own chain-root Definition check duplicated, the chain-root check was removed from THIS rule as redundant, closing the double-flag risk that removal would otherwise have reopened. **F-2:** the declaring-context exemption for the ordinary top-level pattern compared `@type == "Package"` by exact match, missing `LibraryPackage` (a distinct `@type` for a `library package`) — both a direct qualified reference into a library package (`L::heat`) and a `private import`-then-bare-reference form were wrongly flagged even though the pilot accepts both. Fixed by matching `.endswith("Package")` instead, this file's own established convention for metaclass-family checks. The rule's own remaining job, after Task 7, is now narrower and more precisely stated: not "is this end's target a Feature or a Definition" (D-019's job, on every segment) but "is the chain's first segment, GIVEN that it resolves to a Feature, actually accessible from the allocation's own context" — declaring-context equality, package/library-package membership, or specialization/typing via `supertypes_transitively_raw`. @@ -855,7 +857,7 @@ The rule's own remaining job, after Task 7, is now narrower and more precisely s ## D-033: OpenSysML's `eval()` does not simplify a product against a `DimensionOneUnit` factor, or fold an SI base-unit expansion back into its derived-unit symbol -Found while fixing a separate, real pilot warning (`efficiency`'s own bare-literal binding, no DEFERRED entry needed — that was a straightforward conformance fix, not a tool gap): once `attribute :>> efficiency = 0.7;` (`DimensionOneValue`, unbound to any measurement reference) is corrected to the conformant `attribute :>> efficiency = 0.7 [MeasurementReferences::one];`, `deliveredEnergy`'s own result — `power * duration * efficiency`, a `ISQ::PowerValue * ISQ::DurationValue * DimensionOneValue` product — prints as an unsimplified compound expression, `67200 [MeasurementReferences::one*SI::'kg⋅m²⋅s⁻²']`, instead of the clean, expected `67200 [SI::J]`. **This is a display-label defect only, not an arithmetic one**: confirmed directly, `model.eval(...) == 67200.0 [SI::J]` evaluates `True` on the fixed model (`model.eval("ToasterDemo::rated.deliveredEnergy(ToasterDemo::rated.power, 120.0 [SI::s])")`, ch07-cumulative.sysml) — the returned `Quantity`'s `scale_num` (1000.0) and SI base-unit factors (`gram`¹, `metre`², `second`⁻²) are byte-identical between the base (bare-literal) and fixed (bracketed) forms; only the `Unit.text` label differs, gaining a spurious `MeasurementReferences::one*` prefix. OpenSysML DOES normally resolve a `kg⋅m²⋅s⁻²` base-unit product to its own calc's declared return type's unit symbol (`deliveredEnergy`'s own `EnergyValue` return prints plain `SI::J` on the unfixed model, while an unlabeled raw `800.0 [SI::W] * 120.0 [SI::s]` prints the base-unit expansion, not `SI::J` — so the derived-unit naming already works when nothing else interferes); it is specifically the leftover `MeasurementReferences::one` identity factor from multiplying by a properly-bound `DimensionOneValue` that blocks that resolution. So the one missing simplification step is: drop an identity (`DimensionOneUnit`) factor from a product before naming the result's unit — not a general derived-unit-naming gap. +Found while fixing a separate, real pilot warning (`efficiency`'s own bare-literal binding, no DEFERRED entry needed — that was a straightforward conformance fix, not a tool gap): once `attribute :>> efficiency = 0.7;` (`DimensionOneValue`, unbound to any measurement reference) is corrected to the conformant `attribute :>> efficiency = 0.7 [MeasurementReferences::one];`, `deliveredEnergy`'s own result — `power * duration * efficiency`, a `ISQ::PowerValue * ISQ::DurationValue * DimensionOneValue` product — prints as an unsimplified compound expression, `67200 [MeasurementReferences::one*SI::'kg⋅m²⋅s⁻²']`, instead of the clean, expected `67200 [SI::J]`. **This is a display-label defect only, not an arithmetic one**: confirmed directly, `model.eval(...) == 67200.0 [SI::J]` evaluates `True` on the fixed model (`model.eval("ToasterDemo::rated.deliveredEnergy(ToasterDemo::rated.power, 120.0 [SI::s])")`, ch07-cumulative.sysml) — the returned `Quantity`'s `scale_num` (1000.0) and SI base-unit factors (`gram`¹, `metre`², `second`⁻²) are byte-identical between the base (bare-literal) and fixed (bracketed) forms; only the `Unit.text` label differs, gaining a spurious `MeasurementReferences::one*` prefix. The OpenSysML runtime DOES normally resolve a `kg⋅m²⋅s⁻²` base-unit product to its own calc's declared return type's unit symbol (`deliveredEnergy`'s own `EnergyValue` return prints plain `SI::J` on the unfixed model, while an unlabeled raw `800.0 [SI::W] * 120.0 [SI::s]` prints the base-unit expansion, not `SI::J` — so the derived-unit naming already works when nothing else interferes); it is specifically the leftover `MeasurementReferences::one` identity factor from multiplying by a properly-bound `DimensionOneValue` that blocks that resolution. So the one missing simplification step is: drop an identity (`DimensionOneUnit`) factor from a product before naming the result's unit — not a general derived-unit-naming gap. **Workaround:** none; the affected notebook (`chapters/ch07-execution/01-calc-energy.ipynb`, cells 14, 17 and 18) and `chapters/ch07-execution/index.md`'s "Expected result" state the physically meaningful unit ("J") in prose alongside the numeric value, with a note that OpenSysML's own printed output shows an unsimplified unit expression rather than that clean symbol. **Resolution:** upstream fix in OpenSysML's `eval()`/`Quantity`/`Unit` display logic (dropping an identity `DimensionOneUnit` factor from a product before naming the result); re-test once available. No probe committed under `scripts/probes` yet — this file's own neighboring entries point there, but this finding was confirmed via ad hoc `model.eval()` calls during review, not yet reduced to a committed regression probe. @@ -864,7 +866,7 @@ Found while fixing a separate, real pilot warning (`efficiency`'s own bare-liter ## D-034: `filter` is a reserved word in the real SysML v2 grammar; OpenSysML alone accepts it as a bare feature name with no diagnostic -Found while re-deriving the Chapter 5 exercise (exercise-track re-derivation, DL-061/062): a `part filter : FilterBasket;` declaration (a bare, ordinary-looking feature name, not qualified or unusual in any way) loads with `model.ok == True` and zero diagnostics in OpenSysML v0.9.0. `filter` is a reserved token in the real SysML v2 grammar (used for view-filter conditions, `ViewUsage`/`ViewDefinition` filtering; confirmed directly against both the pilot's own ANTLR token table and sysml-toolkit's own `SysML.xtext` grammar file). **Corrected 2026-09-29 (independent review): this is a one-of-three-tools gap, not the two-of-three "tolerate" pattern first recorded here.** sysml-toolkit v0.9.1 correctly REJECTS bare `filter` too (`sysmlv2 check`: exit 1, two real parse errors — `expected ';' or '{', found 'filter'` at the declaration, `expected a name, found 'filter'` at the reference site) — only OpenSysML silently tolerates it. The real OMG pilot also rejects it (`ERROR:no viable alternative at input 'filter'`, exit 1). So sysml-toolkit and the pilot AGREE this is invalid; OpenSysML alone is the outlier. +Found while re-deriving the Chapter 5 exercise (exercise-track re-derivation, DL-061/062): a `part filter : FilterBasket;` declaration (a bare, ordinary-looking feature name, not qualified or unusual in any way) loads with `model.ok == True` and zero diagnostics in the OpenSysML runtime v0.9.0. `filter` is a reserved token in the real SysML v2 grammar (used for view-filter conditions, `ViewUsage`/`ViewDefinition` filtering; confirmed directly against both the pilot's own ANTLR token table and sysml-toolkit's own `SysML.xtext` grammar file). **Corrected 2026-09-29 (independent review): this is a one-of-three-tools gap, not the two-of-three "tolerate" pattern first recorded here.** sysml-toolkit v0.9.1 correctly REJECTS bare `filter` too (`sysmlv2 check`: exit 1, two real parse errors — `expected ';' or '{', found 'filter'` at the declaration, `expected a name, found 'filter'` at the reference site) — only the OpenSysML runtime silently tolerates it. The real OMG pilot also rejects it (`ERROR:no viable alternative at input 'filter'`, exit 1). So sysml-toolkit and the pilot AGREE this is invalid; the OpenSysML runtime is the outlier. Confirmed empirically: `part 'filter' : FilterBasket;` (escaping the reserved word with single quotes, SysML v2's own escaped-identifier syntax) is accepted cleanly by both the pilot and sysml-toolkit — so the underlying construct is fine; only the bare, unescaped identifier collides with the grammar. Escaping was tried and rejected as the fix for the exercise's own use (see Workaround): it works, but a downstream tool (`toaster.render.build_interconnection_intent`) renders the escaped identifier's literal quote characters into its own flow-endpoint strings (`"'filter'.waterIn"`), which would appear as a stray, unexplained artifact in a rendered diagram label — worse than simply not using the reserved word as an identifier in the first place. @@ -875,7 +877,7 @@ Confirmed empirically: `part 'filter' : FilterBasket;` (escaping the reserved wo ## D-035: sysml-toolkit reports an `assert satisfy`/`assert not satisfy` naming an undeclared requirement as a warning, not an error; OpenSysML and the pilot both reject it outright -Found while re-deriving the Chapter 9 exercise (exercise-track re-derivation, DL-062). Chapter 9's own negative control (`chapters/ch09-coverage-sufficiency/01-requirement-coverage.ipynb` cell `9bae63f2`, mirrored in the exercise) asserts that a `satisfy` claim naming a requirement the model never declares fails to load — confirmed correct against OpenSysML v0.9.0 (`bad.ok == False`) and the OMG pilot 0.62.0-SNAPSHOT (`hasErrors=true`, "Couldn't resolve reference to Feature", "Must reference a constraint", "Must reference a requirement"). sysml-toolkit v0.9.1, run against the identical fixture via `sysmlv2 check --lib ...`, instead reports `warning: unresolved reference 'missingReq'` and exits 0 — the same construct that two of three pinned tools treat as a hard load failure, the third tool treats as a non-fatal warning. This is the same three-way disagreement shape as D-032/D-033/D-034 (one pinned tool disagrees with the other two on a real construct's validity), but in the OPPOSITE direction from all three of those: there, OpenSysML alone was the outlier tolerating something the other two correctly rejected; here, sysml-toolkit alone is the outlier, MORE permissive than the other two, not less. +Found while re-deriving the Chapter 9 exercise (exercise-track re-derivation, DL-062). Chapter 9's own negative control (`chapters/ch09-coverage-sufficiency/01-requirement-coverage.ipynb` cell `9bae63f2`, mirrored in the exercise) asserts that a `satisfy` claim naming a requirement the model never declares fails to load — confirmed correct against the OpenSysML runtime v0.9.0 (`bad.ok == False`) and the OMG pilot 0.62.0-SNAPSHOT (`hasErrors=true`, "Couldn't resolve reference to Feature", "Must reference a constraint", "Must reference a requirement"). sysml-toolkit v0.9.1, run against the identical fixture via `sysmlv2 check --lib ...`, instead reports `warning: unresolved reference 'missingReq'` and exits 0 — the same construct that two of three pinned tools treat as a hard load failure, the third tool treats as a non-fatal warning. This is the same disagreement shape as D-034 (one pinned tool disagrees with the other two on a real construct's validity), but in the OPPOSITE direction: there, the OpenSysML runtime was the outlier tolerating something the other two correctly rejected; here, sysml-toolkit alone is the outlier, MORE permissive than the other two, not less. **Corrected 2026-10-03 (DL-117):** this sentence originally also cited D-032 and D-033 as one-of-three cases; D-032 records sysml-toolkit accepting the construct too, and D-033 is a runtime display defect with no three-way comparison. **Workaround:** this tutorial's own negative controls (Chapter 9's own, and the exercise's mirror) assert against OpenSysML's behavior only, matching the pattern real Chapter 9 notebook 01 already uses (`bad.ok == False`); a control written to also assert against sysml-toolkit's exit code alone would need to check the warning text, not the exit code, to catch this class of error, since exit 0 alone doesn't distinguish a clean load from this one. **Resolution:** upstream fix in sysml-toolkit (report an unresolved reference in a `satisfy` claim's own `subsets` as an error, matching OpenSysML's and the pilot's own behavior); re-test once available. @@ -884,7 +886,7 @@ Found while re-deriving the Chapter 9 exercise (exercise-track re-derivation, DL ## D-036: the `connector` keyword (a KerML-only construct) is accepted in a `.sysml` file by OpenSysML; sysml-toolkit and the pilot both correctly reject it there -Found while fixing Chapter 10's own "unjustified widget" tie-search (the `requirement_ties()` redesign, `decisions/log.md` DL-071): while constructing a fixture to test a connection-end (`end e1 ::> X;`) as a possible tie shape, several independent minimal reproductions (`connector c2 { }`, `connector c2 from a1 to b1;`, `connector c2 : A to b1;`, and a `connector` NESTED inside a `part def`/`requirement def` body, not only at top level) all confirmed the same pattern. OpenSysML v0.9.0 loads each cleanly (`model.ok == True`, no diagnostics), whether the `connector` sits at top level or nested. sysml-toolkit v0.9.1 rejects every one at parse time, nested or not (`sysmlv2 check`: exit 1, `error: expected ';' or '{', found 'c2'`). The OMG pilot 0.62.0 rejects them too, nested or not (`ERROR:no viable alternative at input 'connector'`). Since even a nested `connector` is rejected, the original "bare, named, TOP-LEVEL declaration" framing was wrong — the real pattern is simpler: **`connector` is a KerML-level keyword** (`spec-refs/KerML.xtext`'s own `Connector` rule), and **SysML v2's own surface grammar uses `connection`/`connect` instead** (the construct this tutorial already uses throughout, e.g. `interface waterInterface connect pump.waterOut to filterUnit.waterIn;`). So this is likely OpenSysML being too permissive — accepting a KerML-only keyword in a `.sysml` file it's checking against SysML's own grammar — rather than sysml-toolkit/the pilot having a real gap; this reading fits the evidence better than D-036's original framing, but has not yet been checked against the formal spec's own SysML-vs-KerML surface-syntax boundary closely enough to be certain. +Found while fixing Chapter 10's own "unjustified widget" tie-search (the `requirement_ties()` redesign, `decisions/log.md` DL-071): while constructing a fixture to test a connection-end (`end e1 ::> X;`) as a possible tie shape, several independent minimal reproductions (`connector c2 { }`, `connector c2 from a1 to b1;`, `connector c2 : A to b1;`, and a `connector` NESTED inside a `part def`/`requirement def` body, not only at top level) all confirmed the same pattern. The OpenSysML runtime v0.9.0 loads each cleanly (`model.ok == True`, no diagnostics), whether the `connector` sits at top level or nested. sysml-toolkit v0.9.1 rejects every one at parse time, nested or not (`sysmlv2 check`: exit 1, `error: expected ';' or '{', found 'c2'`). The OMG pilot 0.62.0 rejects them too, nested or not (`ERROR:no viable alternative at input 'connector'`). Since even a nested `connector` is rejected, the original "bare, named, TOP-LEVEL declaration" framing was wrong — the real pattern is simpler: **`connector` is a KerML-level keyword** (`spec-refs/KerML.xtext`'s own `Connector` rule), and **SysML v2's own surface grammar uses `connection`/`connect` instead** (the construct this tutorial already uses throughout, e.g. `interface waterInterface connect pump.waterOut to filterUnit.waterIn;`). So this is likely the OpenSysML runtime being too permissive — accepting a KerML-only keyword in a `.sysml` file it's checking against SysML's own grammar — rather than sysml-toolkit/the pilot having a real gap; this reading fits the evidence better than D-036's original framing, but has not yet been checked against the formal spec's own SysML-vs-KerML surface-syntax boundary closely enough to be certain. **Workaround:** use `connection`/`connect` (the SysML-level construct) rather than the bare `connector` keyword anywhere in this tutorial's own model content — already the established pattern throughout, so no existing content needs to change; this only matters if a future notebook or exercise is tempted to use `connector` directly. **Resolution:** confirm, against the formal SysML v2 / KerML specs (not just the grammar-file excerpts referenced above), that `connector` is genuinely KerML-only and `connection`/`connect` is the correct SysML-level surface form; if confirmed, file an upstream issue against OpenSysML (not sysml-toolkit/the pilot) for accepting a KerML-only keyword in `.sysml` content. @@ -893,12 +895,12 @@ Found while fixing Chapter 10's own "unjustified widget" tie-search (the `requir ## D-037: OpenSysML's own `-render` CLI drops real content from both action-flow and state diagrams -Found during the post-Phase-2 pre-PR user-testing pass (`decisions/log.md` DL-088) and confirmed directly against the real rendered output, not assumed from a user report. `render_action_flow()`/`render_state_flow()` (`src/toaster/render.py`) both shell out to OpenSysML's own `-render '#action:...' -render-form dot` / `-render '#state:...' -render-form dot` CLI — the DOT output itself, not our wrapper, is missing content in both cases: +Found during the post-Phase-2 pre-PR user-testing pass (`decisions/log.md` DL-088) and confirmed directly against the real rendered output, not assumed from a user report. `render_action_flow()`/`render_state_flow()` (`src/toaster/render.py`) both shell out to the OpenSysML runtime's own `-render '#action:...' -render-form dot` / `-render '#state:...' -render-form dot` CLI — the DOT output itself, not our wrapper, is missing content in both cases: - **Action-flow:** two separate call sites, each with its own evidence file, both missing the same content. Ch4 (`chapters/ch04-functional-decomp/01-action-def-ffbd.ipynb`) calls `render_action_flow(model, "ToasterDemo::ToastBread", ...)`, producing a DOT graph whose only nodes/edges are the control sequence (`start -> applyHeat : ApplyHeat -> done`, with a bare "own flow" label on the action node). Confirmed by grepping `figures/ch04-toastbread-flow.svg`'s own `` elements directly: neither `ToastBread`'s own declared typed flows (`bread` in, `toast` out) nor its sub-action `applyHeat : ApplyHeat`'s own declared typed flows (`bread`, `energy`, `duration` in; `toast`, `delivered`, `loss` out) appear anywhere in the rendered SVG, even though the concept-statement cell and the balance constraint both emphasize exactly these flows. Ch6 (`chapters/ch06-recursive-decomp/01-subsystem-requirements.ipynb`) calls `render_action_flow(model, "ToasterDemo::ApplyHeat", ...)` separately, producing the same shape of DOT graph (`start -> generateHeat : GenerateHeat -> done`, with a bare "own flow" label). Confirmed the same way against `figures/ch06-applyheat-flow.svg`: neither `ApplyHeat`'s own declared typed flows nor its sub-action `generateHeat : GenerateHeat`'s own declared typed flows (`energyIn` in, `heatOut` out) appear there either. The CLI's own DOT output has no flow-pin nodes or edges to draw at all in either case. This is not a filtering choice in `render_action_flow()`, which passes the CLI's output through unmodified. - **State diagram:** `render_state_flow(model, "ToasterDemo::Cycle", ...)` (Ch7) produces a `heating` state node whose body shows a bare "do" activity label with no action name — `generateHeat`, the action the state actually performs, is never printed, even though the state machine's own SysML text declares it. Confirmed by a Ch7 chapter reviewer during Phase 2 (decisions/log.md DL-087 known gap (b)), predating this entry. -Both gaps were independently found by two different review passes (Phase 2 chapter review for the state-diagram gap; the pre-PR user-testing pass for the action-flow gap), on two different diagram types sharing the same underlying mechanism (OpenSysML's `-render` CLI), which is why they're recorded together here rather than as two separate entries. +Both gaps were independently found by two different review passes (Phase 2 chapter review for the state-diagram gap; the pre-PR user-testing pass for the action-flow gap), on two different diagram types sharing the same underlying mechanism (the OpenSysML runtime's `-render` CLI), which is why they're recorded together here rather than as two separate entries. **Workaround:** none. Both notebooks' own prose states the missing content in words (Ch4's concept statement and balance-constraint text name the real flows; Ch7's state-machine text names `generateHeat`), so a reader isn't left without the information — only the diagram itself doesn't show it. Z's own decision (asked directly, pre-PR triage): track only, do not invest in a DOT post-processing fix at this time. **Resolution:** either an upstream fix in OpenSysML's own `-render` CLI (emit flow-pin nodes/edges for an action-flow render; emit the performed action's own name on a state's "do" activity), or — if that doesn't materialize — a local DOT post-processing step that reconstructs the missing labels/nodes from the model object `render_action_flow()`/`render_state_flow()` already have in hand before shelling out; re-test and re-evaluate once either is available. @@ -907,7 +909,7 @@ Both gaps were independently found by two different review passes (Phase 2 chapt ## D-038: OpenSysML's API-JSON export has no structural `subjectParameter` key on `RequirementDefinition`; Ch10 reads each candidate feature's own `sysx:sourceText` instead -Found during DL-097's own gap analysis (`decisions/log.md`): `chapters/ch10-traceability-signoff/01-traceability-graph.ipynb` cell 8's `requirement_subject()` needs each requirement definition's own declared `subject` feature (SysML v2 formal/2026-03-02 §8.3, `RequirementDefinition`). The API-JSON export OpenSysML v0.9.0 produces carries no `subjectParameter` key on a `RequirementDefinition` element to read that structurally — confirmed directly, live probe 2026-10-02: every `RequirementDefinition` element in the cumulative model's own export was checked for a `subjectParameter` key, and none has one. `requirement_subject()` works around this by scanning each requirement definition's own owned `ReferenceUsage` features for the literal `subject` keyword in that feature's own `sysx:sourceText`, matching the text SysML v2 itself requires there, rather than reading a structural field. +Found during DL-097's own gap analysis (`decisions/log.md`): `chapters/ch10-traceability-signoff/01-traceability-graph.ipynb` cell 8's `requirement_subject()` needs each requirement definition's own declared `subject` feature (SysML v2 formal/2026-03-02 §8.3, `RequirementDefinition`). The API-JSON export the OpenSysML runtime v0.9.0 produces carries no `subjectParameter` key on a `RequirementDefinition` element to read that structurally — confirmed directly, live probe 2026-10-02: every `RequirementDefinition` element in the cumulative model's own export was checked for a `subjectParameter` key, and none has one. `requirement_subject()` works around this by scanning each requirement definition's own owned `ReferenceUsage` features for the literal `subject` keyword in that feature's own `sysx:sourceText`, matching the text SysML v2 itself requires there, rather than reading a structural field. **Workaround:** `requirement_subject()` (`chapters/ch10-traceability-signoff/01-traceability-graph.ipynb` cell 8) reads `sysx:sourceText` for the literal `subject ` prefix on each candidate `ReferenceUsage`; this is a text scrape of the exported source text, not a structural API read, but it is correct and already in place — tracked here, not blocking. **Resolution:** upstream fix in OpenSysML's API-JSON export (emit a structural `subjectParameter` reference on `RequirementDefinition`, matching the formal spec); re-test once available. diff --git a/README.md b/README.md index 6ef96dc..d7beee4 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ An executable tutorial on recursive system decomposition using SysML v2 and Open Starting from one abstract system definition, readers progressively add purpose, requirements, measures, functions, structure, and executable behavior for a domestic toaster. -No site is published yet; deployment stays off until the tutorial has complete, end-to-end content ready to publish. See [docs/setup.md](docs/setup.md) to run the tutorial or build the book locally. +CI builds the book on every pull request and deploys it from `main` to . See [docs/setup.md](docs/setup.md) to run the tutorial or preview the book locally. Adapted from Brian Douglas's [Systems Engineering Part 3](https://www.mathworks.com/videos/systems-engineering-part-3-the-benefits-of-functional-architectures-1602837771665.html) and [Part 4](https://www.mathworks.com/videos/systems-engineering-part-4-an-introduction-to-requirements-1603872564696.html). @@ -17,14 +17,23 @@ Engineering judgment records follow Hawkins et al. 2011 §§3.1–3.4. git clone https://github.com/Open-MBEE/toaster.git cd toaster uv sync --locked +uv run python scripts/provision-tools.py uv run python scripts/check-tools.py # Run tests uv run pytest tests/ -v # Preview the book locally (starts a dev server at localhost:3000) -npm install -npx mystmd start --execute +npm ci +uv run npx mystmd start --execute +``` + +`uv run` matters for the preview: it lets MyST find this project's Jupyter. Without it, MyST +looks for a Jupyter on your `PATH`, which is usually a different installation. To build the +static site the way CI does: + +```sh +BASE_URL=/toaster uv run --frozen npx myst build --html --execute --strict ``` See [docs/setup.md](docs/setup.md) for full setup instructions and the fork-and-exercise workflow, @@ -34,16 +43,18 @@ including what `uv` and `mystmd` are and why the quick start above uses them. ``` chapters/ — worked example notebooks (10 chapters, read-only for exercises) -exercises/ — parallel exercise notebooks (fork and work here) +exercises/ — parallel exercise notebooks (work them in your own fork) models/ — SysML stage model snapshots src/toaster/— Python package (bootstrap, connect, query, check, conformance, evidence, modelcheck, simulate, render, report) tests/ — pytest suite docs/ — setup, glossary, references, reproducibility statement scripts/ — pre-flight and build utilities -decisions/ — ACE decision log +decisions/ — decision log (rulings and escalations) ``` -`AGENTS.md`, `CLAUDE.md`, and `DEFERRED.md` at the repo root are not learner material — they're +[`AGENTS.md`](AGENTS.md), [`CLAUDE.md`](CLAUDE.md), and [`DEFERRED.md`](DEFERRED.md) at the repo root are not learner material — they're this project's own working contract, for the AI agents and maintainers who build and review the -tutorial's content. See [docs/contributor.md](docs/contributor.md) if you want to understand how -the tutorial is actually built, tested, and reviewed, or to contribute to it yourself. +tutorial's content. See [docs/contributor.md](docs/contributor.md) to understand how the tutorial +is built, tested and reviewed, and what contributions it wants: keeping it current to its +toolchain (the OpenSysML runtime, sysml-toolkit and the other pinned tools) and to the OMG SysML +v2 specifications, not adding new content. diff --git a/chapters/ch01-system-purpose/01-abstract-def.ipynb b/chapters/ch01-system-purpose/01-abstract-def.ipynb index 3dede7a..8260a32 100644 --- a/chapters/ch01-system-purpose/01-abstract-def.ipynb +++ b/chapters/ch01-system-purpose/01-abstract-def.ipynb @@ -86,7 +86,7 @@ "source": [ "# abstract part def : no instance may be created directly, only its subtypes.\n", "# perform ties this action to the whole that carries out the purpose.\n", - "# abstract modifier: the Editor API does not yet author it (toaster#9 / OpenSysML#595);\n", + "# abstract modifier: the Editor API does not yet author it (https://github.com/Open-MBEE/toaster/issues/9 / https://github.com/Open-MBEE/OpenSysML/issues/595);\n", "# it parses and loads correctly via conn.load_from_content(), confirmed in the next cell.\n", "# spec: SysML v2 formal/2026-03-02 §7.3.3 (PartDefinition, AbstractClassifier), §7.17.6 (perform)\n", "TOASTING_SYSTEM_DEF = \"\"\"\\\n", @@ -182,7 +182,7 @@ "id": "cell-15", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch01/exercise.ipynb`: declare item defs for a coffee maker's flows, an action def with a `doc` and typed in/out flows, and an abstract part def that performs it, and verify it loads." + "Try the chapter exercise in [`exercises/ch01/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch01/exercise.ipynb): declare item defs for a coffee maker's flows, an action def with a `doc` and typed in/out flows, and an abstract part def that performs it, and verify it loads." ] } ] diff --git a/chapters/ch01-system-purpose/02-part-def.ipynb b/chapters/ch01-system-purpose/02-part-def.ipynb index 8c1a203..e9606dd 100644 --- a/chapters/ch01-system-purpose/02-part-def.ipynb +++ b/chapters/ch01-system-purpose/02-part-def.ipynb @@ -157,7 +157,7 @@ "id": "cell-12", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch01/exercise.ipynb`: define `BrewUnit` and `HeatExchanger` as two concrete component types and verify they load." + "Try the chapter exercise in [`exercises/ch01/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch01/exercise.ipynb): define `BrewUnit` and `HeatExchanger` as two concrete component types and verify they load." ] } ] diff --git a/chapters/ch01-system-purpose/03-specialization.ipynb b/chapters/ch01-system-purpose/03-specialization.ipynb index 42e2307..a21d0e5 100644 --- a/chapters/ch01-system-purpose/03-specialization.ipynb +++ b/chapters/ch01-system-purpose/03-specialization.ipynb @@ -109,7 +109,7 @@ "id": "cell-10", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch01/exercise.ipynb`: declare that `CoffeeMaker` specializes `BrewingSystem` and confirm the specialization records correctly." + "Try the chapter exercise in [`exercises/ch01/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch01/exercise.ipynb): declare that `CoffeeMaker` specializes `BrewingSystem` and confirm the specialization records correctly." ] } ] diff --git a/chapters/ch01-system-purpose/04-composition.ipynb b/chapters/ch01-system-purpose/04-composition.ipynb index 712b3c4..5184443 100644 --- a/chapters/ch01-system-purpose/04-composition.ipynb +++ b/chapters/ch01-system-purpose/04-composition.ipynb @@ -45,7 +45,7 @@ "id": "cell-03", "metadata": {}, "source": [ - "Every notebook in this chapter loads the same already-complete `models/ch01-cumulative.sysml`, so there is no partial `Toaster` in this chapter for one notebook to hand off to the next. Notebook 03's bare `Toaster :> ToastingSystem;` and this notebook's full declaration are each an illustrative fragment, checked independently by `check_construction.py` against a minimal stub, showing what one step of Editor-API authoring would add once the API supports it, not a live patch to a running model. The real continuation mechanism is between chapters, not within one: each `chNN-cumulative.sysml` is a file on disk, and the next chapter's file is authored to include everything the previous one has, plus its own new declarations." + "Every notebook in this chapter loads the same already-complete [`models/ch01-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch01-cumulative.sysml), so there is no partial `Toaster` in this chapter for one notebook to hand off to the next. Notebook 03's bare `Toaster :> ToastingSystem;` and this notebook's full declaration are each an illustrative fragment, checked independently by [`check_construction.py`](https://github.com/Open-MBEE/toaster/blob/main/scripts/check_construction.py) against a minimal stub, showing what one step of Editor-API authoring would add once the API supports it, not a live patch to a running model. The real continuation mechanism is between chapters, not within one: each [`chNN-cumulative.sysml`](https://github.com/Open-MBEE/toaster/tree/main/models) is a file on disk, and the next chapter's file is authored to include everything the previous one has, plus its own new declarations." ] }, { @@ -173,7 +173,7 @@ "id": "cell-14", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch01/exercise.ipynb`: compose a `CoffeeMaker` from `BrewUnit` and `HeatExchanger` and verify both parts appear via `parts()`." + "Try the chapter exercise in [`exercises/ch01/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch01/exercise.ipynb): compose a `CoffeeMaker` from `BrewUnit` and `HeatExchanger` and verify both parts appear via `parts()`." ] } ] diff --git a/chapters/ch01-system-purpose/conclusion.md b/chapters/ch01-system-purpose/conclusion.md index 16062d4..ac4f978 100644 --- a/chapters/ch01-system-purpose/conclusion.md +++ b/chapters/ch01-system-purpose/conclusion.md @@ -16,4 +16,4 @@ The model answers Chapter 1's engineering question: a toaster is the subject tha Chapter 2 asks what the toaster must do. It introduces requirements, an attribute override that builds a deliberately faulty fixture, and the first engineering judgment record. The model from Chapter 1 is the starting point. -**Exercise:** The [Chapter 1 exercise](../../exercises/ch01/exercise.ipynb) asks you to model a coffee maker using the same constructs. The problem is structurally similar to the toaster but uses a different domain: state the purpose as item defs, a performed action def with a doc, and an abstract part def, add two component types with no content yet, specialize the whole (not the parts) from the concept, and compose it into the top-level system. +**Exercise:** The [Chapter 1 exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch01/exercise.ipynb) asks you to model a coffee maker using the same constructs. The problem is structurally similar to the toaster but uses a different domain: state the purpose as item defs, a performed action def with a doc, and an abstract part def, add two component types with no content yet, specialize the whole (not the parts) from the concept, and compose it into the top-level system. diff --git a/chapters/ch01-system-purpose/index.md b/chapters/ch01-system-purpose/index.md index 3de4817..bf47ec2 100644 --- a/chapters/ch01-system-purpose/index.md +++ b/chapters/ch01-system-purpose/index.md @@ -19,7 +19,7 @@ Chapter 1 asks: how do we describe a system in SysML v2 before we know how it is ## Equipment -See [setup](../../docs/setup.md) to provision Python and the OpenSysML binary before running any notebook. +See [setup](../../docs/setup.md) to provision Python and the OpenSysML runtime binary before running any notebook. ## Method @@ -41,4 +41,4 @@ The Ch1 cumulative model contains: ## Experiment -The [chapter exercise](../../exercises/ch01/exercise.ipynb) asks you to model a coffee maker using the same constructs. Work through it after completing all four notebooks. +The [chapter exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch01/exercise.ipynb) asks you to model a coffee maker using the same constructs. Work through it after completing all four notebooks. diff --git a/chapters/ch02-requirements/01-requirement-def.ipynb b/chapters/ch02-requirements/01-requirement-def.ipynb index 61faf00..d0a4673 100644 --- a/chapters/ch02-requirements/01-requirement-def.ipynb +++ b/chapters/ch02-requirements/01-requirement-def.ipynb @@ -69,7 +69,7 @@ { "cell_type": "code", "id": "976fc6bc", - "source": "# editor.add_require_constraint(owner='ToasterDemo::TimelyToast', ...) when API ships\n# require constraint: the Editor API does not yet author it (toaster#11 / OpenSysML#597);\n# it parses and loads correctly via conn.load_from_content(), confirmed below.\n# spec: SysML v2 formal/2026-03-02 §7.19 (RequirementConstraintMembership)\nCONSTRAINT_BODY = \" require constraint { toaster.cycleTime <= 180.0 [SI::s] }\"\nprint(CONSTRAINT_BODY)", + "source": "# editor.add_require_constraint(owner='ToasterDemo::TimelyToast', ...) when API ships\n# require constraint: the Editor API does not yet author it (https://github.com/Open-MBEE/toaster/issues/11 / https://github.com/Open-MBEE/OpenSysML/issues/597);\n# it parses and loads correctly via conn.load_from_content(), confirmed below.\n# spec: SysML v2 formal/2026-03-02 §7.19 (RequirementConstraintMembership)\nCONSTRAINT_BODY = \" require constraint { toaster.cycleTime <= 180.0 [SI::s] }\"\nprint(CONSTRAINT_BODY)", "metadata": {}, "execution_count": null, "outputs": [] @@ -173,7 +173,7 @@ "cell_type": "markdown", "id": "cell-07", "metadata": {}, - "source": "Try the chapter exercise in `exercises/ch02/exercise.ipynb`: add a new `brewTemp` attribute to `BrewUnit`, declare a `TemperatureReq` that requires `bu.brewTemp <= 369.15 [SI::K]` (96 degrees Celsius), and confirm it loads." + "source": "Try the chapter exercise in [`exercises/ch02/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch02/exercise.ipynb): add a new `brewTemp` attribute to `BrewUnit`, declare a `TemperatureReq` that requires `bu.brewTemp <= 369.15 [SI::K]` (96 degrees Celsius), and confirm it loads." } ] } diff --git a/chapters/ch02-requirements/02-assumptions.ipynb b/chapters/ch02-requirements/02-assumptions.ipynb index ef78fb6..4ebfef7 100644 --- a/chapters/ch02-requirements/02-assumptions.ipynb +++ b/chapters/ch02-requirements/02-assumptions.ipynb @@ -49,7 +49,7 @@ "id": "fa97b3a7", "source": [ "# editor.add_attribute_override(owner='ToasterDemo::slow', name='cycleTime', ...) when API ships\n", - "# attribute :>> redefinition: the Editor API does not yet author it (toaster#10 / OpenSysML#596);\n", + "# attribute :>> redefinition: the Editor API does not yet author it (https://github.com/Open-MBEE/toaster/issues/10 / https://github.com/Open-MBEE/OpenSysML/issues/596);\n", "# it parses and loads correctly via conn.load_from_content(), confirmed below.\n", "# spec: KerML formal/2026-03-02 §8.3.7 (FeatureChaining, anonymous redefinition)\n", "CYCLE_OVERRIDE = \" attribute :>> cycleTime = 200.0 [SI::s];\"\n", @@ -144,7 +144,7 @@ "cell_type": "markdown", "id": "cell-07", "metadata": {}, - "source": "Try the chapter exercise in `exercises/ch02/exercise.ipynb`: create a `hot` usage of your `BrewUnit` with an overridden `brewTemp` and confirm the override loads." + "source": "Try the chapter exercise in [`exercises/ch02/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch02/exercise.ipynb): create a `hot` usage of your `BrewUnit` with an overridden `brewTemp` and confirm the override loads." } ] } diff --git a/chapters/ch02-requirements/03-judgment-context.ipynb b/chapters/ch02-requirements/03-judgment-context.ipynb index 91c98dd..67859a6 100644 --- a/chapters/ch02-requirements/03-judgment-context.ipynb +++ b/chapters/ch02-requirements/03-judgment-context.ipynb @@ -94,7 +94,7 @@ "id": "cell-03", "metadata": {}, "source": [ - "The `ch02-cumulative.sysml` file adds the first requirements construct: `requirement def TimelyToast` constrains `cycleTime <= 180.0 [SI::s]` with a typed `subject` and `require constraint` body. `nominal` (`cycleTime` unset) and `slow` (`cycleTime` fixed at 200 s, a deliberately injected fault) are declared for later comparison. The `assert satisfy` pattern comes in Chapter 3; for now these usages exist without a recorded claim." + "The [`ch02-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch02-cumulative.sysml) file adds the first requirements construct: `requirement def TimelyToast` constrains `cycleTime <= 180.0 [SI::s]` with a typed `subject` and `require constraint` body. `nominal` (`cycleTime` unset) and `slow` (`cycleTime` fixed at 200 s, a deliberately injected fault) are declared for later comparison. The `assert satisfy` pattern comes in Chapter 3; for now these usages exist without a recorded claim." ] }, { @@ -227,7 +227,7 @@ "id": "35677098", "metadata": {}, "source": [ - "This fragment, together with the `ReviewRecordRef` definition above, is the same text already committed in `models/ch02-cumulative.sysml`; loading the cumulative model (the `model.ok` cell above, unchanged) is what makes this a real, checkable tag rather than an assertion the reader has to trust." + "This fragment, together with the `ReviewRecordRef` definition above, is the same text already committed in [`models/ch02-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch02-cumulative.sysml); loading the cumulative model (the `model.ok` cell above, unchanged) is what makes this a real, checkable tag rather than an assertion the reader has to trust." ] }, { @@ -528,7 +528,7 @@ "id": "cell-19", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch02/exercise.ipynb`: write an `asserted_context` record for the `brewTemp` assumption in your coffee maker model." + "Try the chapter exercise in [`exercises/ch02/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch02/exercise.ipynb): write an `asserted_context` record for the `brewTemp` assumption in your coffee maker model." ] } ], diff --git a/chapters/ch02-requirements/conclusion.md b/chapters/ch02-requirements/conclusion.md index 8b86f99..5531581 100644 --- a/chapters/ch02-requirements/conclusion.md +++ b/chapters/ch02-requirements/conclusion.md @@ -16,4 +16,4 @@ The chapter answers its engineering question: we now have a formal requirement a Chapter 3 introduces `requirement` usage (applying `TimelyToast` to the model as `timely`) and the `assert satisfy` / `assert not satisfy` idiom, which folds a satisfaction claim into a usage's own context and evaluates it against the model's own values. It also introduces `verification def`, which declares how a requirement will be checked. -**Exercise:** The [Chapter 2 exercise](../../exercises/ch02/exercise.ipynb) asks you to add a `TemperatureReq` to your coffee maker model and write an `asserted_context` record for the `brewTemp` assumption. Use the same pattern as `TimelyToast` and `context_record`. +**Exercise:** The [Chapter 2 exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch02/exercise.ipynb) asks you to add a `TemperatureReq` to your coffee maker model and write an `asserted_context` record for the `brewTemp` assumption. Use the same pattern as `TimelyToast` and `context_record`. diff --git a/chapters/ch02-requirements/index.md b/chapters/ch02-requirements/index.md index 0bbaa72..29b1918 100644 --- a/chapters/ch02-requirements/index.md +++ b/chapters/ch02-requirements/index.md @@ -40,4 +40,4 @@ After notebook 03: ## Experiment -The [chapter exercise](../../exercises/ch02/exercise.ipynb) asks you to add a temperature requirement to your coffee maker model and write the first context record for the brew-temperature assumption. +The [chapter exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch02/exercise.ipynb) asks you to add a temperature requirement to your coffee maker model and write the first context record for the brew-temperature assumption. diff --git a/chapters/ch03-measures/01-moe-definition.ipynb b/chapters/ch03-measures/01-moe-definition.ipynb index 79ddb60..34f6bbc 100644 --- a/chapters/ch03-measures/01-moe-definition.ipynb +++ b/chapters/ch03-measures/01-moe-definition.ipynb @@ -195,7 +195,7 @@ "id": "9b3b7f7c", "metadata": {}, "source": [ - "This fragment, together with the `ReviewRecordRef` definition carried forward from Chapter 2, is the same text now committed in `models/ch03-cumulative.sysml` onward; loading the cumulative model (the `model.ok` cell above, unchanged) is what makes this a real, checkable tag rather than an assertion the reader has to trust." + "This fragment, together with the `ReviewRecordRef` definition carried forward from Chapter 2, is the same text now committed in [`models/ch03-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch03-cumulative.sysml) onward; loading the cumulative model (the `model.ok` cell above, unchanged) is what makes this a real, checkable tag rather than an assertion the reader has to trust." ] }, { @@ -498,7 +498,7 @@ "id": "cell-22", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch03/exercise.ipynb`: add `requirement tempCheck : TemperatureReq;` to your coffee maker model, then record whether it is a measure of effectiveness or a measure of performance, following the pattern above." + "Try the chapter exercise in [`exercises/ch03/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch03/exercise.ipynb): add `requirement tempCheck : TemperatureReq;` to your coffee maker model, then record whether it is a measure of effectiveness or a measure of performance, following the pattern above." ] } ], diff --git a/chapters/ch03-measures/02-mop-candidate-eval.ipynb b/chapters/ch03-measures/02-mop-candidate-eval.ipynb index 0ec4f0a..58572db 100644 --- a/chapters/ch03-measures/02-mop-candidate-eval.ipynb +++ b/chapters/ch03-measures/02-mop-candidate-eval.ipynb @@ -38,7 +38,7 @@ "metadata": {}, "outputs": [], "execution_count": null, - "source": "from pathlib import Path\nimport opensysml\nfrom toaster.report import format_diagnostics\n\nconn = opensysml.connect(version=\"v0.9.0\")\n\n# editor.add_assert_satisfy(owner='ToasterDemo::slow', req='timely', by='slow', negated=True) when API ships\n# assert satisfy not yet supported by the Editor API - toaster#12 / OpenSysML#598\n# spec: SysML v2 formal/2026-03-02 section 7.19 (SatisfyRequirementUsage)\nSLOW_NOT_SATISFY = \" assert not satisfy timely by slow;\"\nprint(SLOW_NOT_SATISFY)" + "source": "from pathlib import Path\nimport opensysml\nfrom toaster.report import format_diagnostics\n\nconn = opensysml.connect(version=\"v0.9.0\")\n\n# editor.add_assert_satisfy(owner='ToasterDemo::slow', req='timely', by='slow', negated=True) when API ships\n# assert satisfy not yet supported by the Editor API - https://github.com/Open-MBEE/toaster/issues/12 / https://github.com/Open-MBEE/OpenSysML/issues/598\n# spec: SysML v2 formal/2026-03-02 section 7.19 (SatisfyRequirementUsage)\nSLOW_NOT_SATISFY = \" assert not satisfy timely by slow;\"\nprint(SLOW_NOT_SATISFY)" }, { "cell_type": "markdown", @@ -107,7 +107,7 @@ "id": "cell-11", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch03/exercise.ipynb`: fold a negated satisfaction claim into your `hot` usage of `BrewUnit`, following the pattern above; do not assert anything about `nominal`, whose `brewTemp` is unbound." + "Try the chapter exercise in [`exercises/ch03/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch03/exercise.ipynb): fold a negated satisfaction claim into your `hot` usage of `BrewUnit`, following the pattern above; do not assert anything about `nominal`, whose `brewTemp` is unbound." ] } ] diff --git a/chapters/ch03-measures/03-threshold-judgment.ipynb b/chapters/ch03-measures/03-threshold-judgment.ipynb index 7ba2505..06bd410 100644 --- a/chapters/ch03-measures/03-threshold-judgment.ipynb +++ b/chapters/ch03-measures/03-threshold-judgment.ipynb @@ -63,7 +63,7 @@ "id": "cell-03", "metadata": {}, "source": [ - "The `ch03-cumulative.sysml` file applies `TimelyToast` to the model as `timely : TimelyToast`, folds `assert not satisfy timely by slow` into `slow`'s own body, and adds `TimelyToastTest`, a verification case that declares how `timely` will be checked (notebook 04)." + "The [`ch03-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch03-cumulative.sysml) file applies `TimelyToast` to the model as `timely : TimelyToast`, folds `assert not satisfy timely by slow` into `slow`'s own body, and adds `TimelyToastTest`, a verification case that declares how `timely` will be checked (notebook 04). Its `doc` comment's note `not yet supported in OpenSysML v0.9.0` predates this tutorial's component naming and refers to the OpenSysML runtime v0.9.0, which does not accept the formal `#verificationMethod` metadata; notebook 04 explains ([toaster#19](https://github.com/Open-MBEE/toaster/issues/19))." ] }, { @@ -205,7 +205,7 @@ "id": "d2c9c179", "metadata": {}, "source": [ - "This fragment is the same text now committed in `models/ch03-cumulative.sysml` onward, alongside the `ReviewRecordRef` definition (carried forward from Chapter 2) and `acC03Tag` (notebook 01); loading the cumulative model (the `model.ok` cell above, unchanged) is what makes this a real, checkable tag rather than an assertion the reader has to trust." + "This fragment is the same text now committed in [`models/ch03-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch03-cumulative.sysml) onward, alongside the `ReviewRecordRef` definition (carried forward from Chapter 2) and `acC03Tag` (notebook 01); loading the cumulative model (the `model.ok` cell above, unchanged) is what makes this a real, checkable tag rather than an assertion the reader has to trust." ] }, { @@ -470,7 +470,7 @@ "id": "cell-19", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch03/exercise.ipynb`: write an `asserted_solution` record for your satisfaction claim." + "Try the chapter exercise in [`exercises/ch03/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch03/exercise.ipynb): write an `asserted_solution` record for your satisfaction claim." ] } ], diff --git a/chapters/ch03-measures/04-verification-case.ipynb b/chapters/ch03-measures/04-verification-case.ipynb index 87d8e98..93e91fa 100644 --- a/chapters/ch03-measures/04-verification-case.ipynb +++ b/chapters/ch03-measures/04-verification-case.ipynb @@ -50,13 +50,13 @@ "metadata": {}, "outputs": [], "execution_count": null, - "source": "# editor.set_doc(owner='ToasterDemo::TimelyToastTest', text=...) when API ships\n# spec: SysML v2 formal/2026-03-02 §7.21.2 (informal text applies to all elements including verification defs)\n# Note: #verificationMethod = VerificationMethodKind::test metadata not yet supported (toaster#19 / OpenSysML#608)\n# spec: SysML v2 formal/2026-03-02 §7.24 Table 22 (Verification Methods Compartment)\nDOC_COMMENT = \"\"\"\\\n doc /*\n * Verification method: timed test of three consecutive toasting cycles at\n * nominal input power; all must complete within 180 seconds.\n * Method type: test (VerificationMethodKind::test, SysML v2 §7.24 Table 22).\n * Note: formal #verificationMethod metadata not yet supported in OpenSysML v0.9.0;\n * tracked at toaster#19 / OpenSysML#608.\n */\"\"\"\nprint(DOC_COMMENT)" + "source": "# editor.set_doc(owner='ToasterDemo::TimelyToastTest', text=...) when API ships\n# spec: SysML v2 formal/2026-03-02 §7.21.2 (informal text applies to all elements including verification defs)\n# Note: #verificationMethod = VerificationMethodKind::test metadata not yet supported (https://github.com/Open-MBEE/toaster/issues/19 / https://github.com/Open-MBEE/OpenSysML/issues/608)\n# spec: SysML v2 formal/2026-03-02 §7.24 Table 22 (Verification Methods Compartment)\nDOC_COMMENT = \"\"\"\\\n doc /*\n * Verification method: timed test of three consecutive toasting cycles at\n * nominal input power; all must complete within 180 seconds.\n * Method type: test (VerificationMethodKind::test, SysML v2 §7.24 Table 22).\n * Note: formal #verificationMethod metadata not yet supported in OpenSysML v0.9.0;\n * tracked at toaster#19 / OpenSysML#608.\n */\"\"\"\nprint(DOC_COMMENT)" }, { "cell_type": "markdown", "id": "e5f6g7h8", "metadata": {}, - "source": "The `doc` block holds the informal text of the verification case. It describes the verification method as a timed test. The formal `#verificationMethod = VerificationMethodKind::test` metadata annotation (§7.24 Table 22) is the spec-defined way to declare the method kind; it is not yet supported in OpenSysML v0.9.0 (toaster#19 / OpenSysML#608)." + "source": "The `doc` block holds the informal text of the verification case. It describes the verification method as a timed test. The formal `#verificationMethod = VerificationMethodKind::test` metadata annotation (§7.24 Table 22) is the spec-defined way to declare the method kind; it is not yet supported in the OpenSysML runtime v0.9.0 ([toaster#19](https://github.com/Open-MBEE/toaster/issues/19) / [OpenSysML#608](https://github.com/Open-MBEE/OpenSysML/issues/608))." }, { "cell_type": "code", @@ -136,7 +136,7 @@ "cell_type": "markdown", "id": "cell-07", "metadata": {}, - "source": "Try the chapter exercise in `exercises/ch03/exercise.ipynb`: declare a `BrewTempTest` verification case for your coffee maker's temperature requirement, with an objective that verifies the requirement usage from notebook 01." + "source": "Try the chapter exercise in [`exercises/ch03/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch03/exercise.ipynb): declare a `BrewTempTest` verification case for your coffee maker's temperature requirement, with an objective that verifies the requirement usage from notebook 01." } ] } diff --git a/chapters/ch03-measures/conclusion.md b/chapters/ch03-measures/conclusion.md index 3bd1e1a..884ec97 100644 --- a/chapters/ch03-measures/conclusion.md +++ b/chapters/ch03-measures/conclusion.md @@ -16,4 +16,4 @@ The chapter answers its engineering question: the model now records and evaluate Chapter 4 asks how the system performs its function step by step. It introduces `action def` for functional decomposition and `item def` for typed flows. -**Exercise:** The [Chapter 3 exercise](../../exercises/ch03/exercise.ipynb) asks you to add a `TemperatureReq` usage to your coffee maker model, record whether it is a measure of effectiveness or a measure of performance in an `asserted_context` framing record, fold a negated satisfaction claim into the `hot` usage only (not `nominal`, whose `brewTemp` is unbound), write an `asserted_solution` record for that claim, and close the requirement's anatomy with a `verification def`. Use the same pattern as `AC-C03`, `timely`/`slow`, and `TimelyToastTest`. +**Exercise:** The [Chapter 3 exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch03/exercise.ipynb) asks you to add a `TemperatureReq` usage to your coffee maker model, record whether it is a measure of effectiveness or a measure of performance in an `asserted_context` framing record, fold a negated satisfaction claim into the `hot` usage only (not `nominal`, whose `brewTemp` is unbound), write an `asserted_solution` record for that claim, and close the requirement's anatomy with a `verification def`. Use the same pattern as `AC-C03`, `timely`/`slow`, and `TimelyToastTest`. diff --git a/chapters/ch03-measures/index.md b/chapters/ch03-measures/index.md index 57272a2..656fdbf 100644 --- a/chapters/ch03-measures/index.md +++ b/chapters/ch03-measures/index.md @@ -19,7 +19,7 @@ Chapter 3 asks: how do we record and check a satisfaction claim against a requir ## Equipment -See [setup](../../docs/setup.md) to provision Python and the OpenSysML binary before running any notebook. Node.js is only needed if you also want to build the rendered book locally, not for running notebooks. +See [setup](../../docs/setup.md) to provision Python and the OpenSysML runtime binary before running any notebook. Node.js is only needed if you also want to build the rendered book locally, not for running notebooks. ## Method @@ -37,4 +37,4 @@ The Python side carries an `asserted_context` ReviewRecord (`AC-C03`) recording ## Experiment -The [chapter exercise](../../exercises/ch03/exercise.ipynb) asks you to add a requirement usage, record an `asserted_context` framing judgment (MoE or MoP), evaluate a negated satisfaction claim folded into a deliberately faulty candidate only, write an `asserted_solution` record for that claim, and close the requirement's anatomy with a `verification def`, to your coffee maker model. Work through it after completing all four notebooks. +The [chapter exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch03/exercise.ipynb) asks you to add a requirement usage, record an `asserted_context` framing judgment (MoE or MoP), evaluate a negated satisfaction claim folded into a deliberately faulty candidate only, write an `asserted_solution` record for that claim, and close the requirement's anatomy with a `verification def`, to your coffee maker model. Work through it after completing all four notebooks. diff --git a/chapters/ch04-functional-decomp/01-action-def-ffbd.ipynb b/chapters/ch04-functional-decomp/01-action-def-ffbd.ipynb index 05dff0e..a21846e 100644 --- a/chapters/ch04-functional-decomp/01-action-def-ffbd.ipynb +++ b/chapters/ch04-functional-decomp/01-action-def-ffbd.ipynb @@ -51,7 +51,7 @@ "metadata": {}, "outputs": [], "source": [ - "# params argument of add_action_def not yet supported, toaster#18 / OpenSysML#605\n", + "# params argument of add_action_def not yet supported, https://github.com/Open-MBEE/toaster/issues/18 / https://github.com/Open-MBEE/OpenSysML/issues/605\n", "# spec: SysML v2 formal/2026-03-02 section 7.15 (ActionDefinition)\n", "IN_PARAMS = \"\"\"\\\n", " in bread : Bread;\n", @@ -202,7 +202,7 @@ "id": "cell-15", "metadata": {}, "source": [ - "The reopened `ToastBread` and the new `ApplyHeat` load without diagnostics. Nesting `ApplyHeat` this way is what makes `ToastBread` an actual decomposition, and it is why `energy` and `duration` are written with an explicit `[0..*]` above rather than left bare. A bare, unstated multiplicity and writing `[0..*]` out explicitly should mean the same thing (SysML v2.0 formal/2026-03-02 7.6.3, 7.6.4: a keyword-less `in`/`out` parameter like these already defaults to `[0..*]`), but OpenSysML v0.9.0 treats them differently: left implicit, any attribute of a `Toaster` usage that reaches `ApplyHeat` through `ToastBread` fails to evaluate; written out, exactly the same model evaluates cleanly (`DEFERRED.md` D-026). Spelling out the multiplicity here states the model's real, spec-default meaning honestly and keeps it fully evaluable, working around a real tool inconsistency without claiming anything new about `energy` or `duration`." + "The reopened `ToastBread` and the new `ApplyHeat` load without diagnostics. Nesting `ApplyHeat` this way is what makes `ToastBread` an actual decomposition, and it is why `energy` and `duration` are written with an explicit `[0..*]` above rather than left bare. A bare, unstated multiplicity and writing `[0..*]` out explicitly should mean the same thing (SysML v2.0 formal/2026-03-02 7.6.3, 7.6.4: a keyword-less `in`/`out` parameter like these already defaults to `[0..*]`), but the OpenSysML runtime v0.9.0 treats them differently: left implicit, any attribute of a `Toaster` usage that reaches `ApplyHeat` through `ToastBread` fails to evaluate; written out, exactly the same model evaluates cleanly ([`DEFERRED.md` D-026](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-026-opensysml-treats-an-implicit-and-an-explicit-but-spec-identical-0-multiplicity-differently-for-an-in-parameter-reachable-through-a-nested-action-step)). Spelling out the multiplicity here states the model's real, spec-default meaning honestly and keeps it fully evaluable, working around a real inconsistency in the OpenSysML runtime without claiming anything new about `energy` or `duration`." ] }, { @@ -281,7 +281,7 @@ "id": "cell-21", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch04/exercise.ipynb`: declare a new action def (not `action def Brew` — Chapter 1 already declares that at package level, so redeclaring it collides) for your coffee maker's `BrewUnit`, with typed `in`/`out` flows and a `first`/`then` sequence nesting its usage inside `Brew`'s own reopened body, mirroring `ApplyHeat` and `ToastBread`." + "Try the chapter exercise in [`exercises/ch04/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch04/exercise.ipynb): declare a new action def (not `action def Brew` — Chapter 1 already declares that at package level, so redeclaring it collides) for your coffee maker's `BrewUnit`, with typed `in`/`out` flows and a `first`/`then` sequence nesting its usage inside `Brew`'s own reopened body, mirroring `ApplyHeat` and `ToastBread`." ] } ], diff --git a/chapters/ch04-functional-decomp/02-heating-refinement.ipynb b/chapters/ch04-functional-decomp/02-heating-refinement.ipynb index 2067c6f..59bc81e 100644 --- a/chapters/ch04-functional-decomp/02-heating-refinement.ipynb +++ b/chapters/ch04-functional-decomp/02-heating-refinement.ipynb @@ -163,7 +163,7 @@ "id": "cell-14", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch04/exercise.ipynb`: add three signal item defs (`BrewStart`, `BrewFinish`, `BrewCancel`) to your coffee maker model, each with a `doc` stating what it denotes and what it is NOT, and confirm each is findable." + "Try the chapter exercise in [`exercises/ch04/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch04/exercise.ipynb): add three signal item defs (`BrewStart`, `BrewFinish`, `BrewCancel`) to your coffee maker model, each with a `doc` stating what it denotes and what it is NOT, and confirm each is findable." ] } ], diff --git a/chapters/ch04-functional-decomp/03-completeness-check.ipynb b/chapters/ch04-functional-decomp/03-completeness-check.ipynb index 6b4c7b6..977e55e 100644 --- a/chapters/ch04-functional-decomp/03-completeness-check.ipynb +++ b/chapters/ch04-functional-decomp/03-completeness-check.ipynb @@ -166,7 +166,7 @@ "id": "2b660112", "metadata": {}, "source": [ - "This fragment is the same text now committed in `models/ch04-cumulative.sysml` onward, alongside the `ReviewRecordRef` definition (carried forward from Chapter 2); loading the cumulative model (the `model.ok` cell above, unchanged) is what makes this a real, checkable tag rather than an assertion the reader has to trust." + "This fragment is the same text now committed in [`models/ch04-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch04-cumulative.sysml) onward, alongside the `ReviewRecordRef` definition (carried forward from Chapter 2); loading the cumulative model (the `model.ok` cell above, unchanged) is what makes this a real, checkable tag rather than an assertion the reader has to trust." ] }, { @@ -605,7 +605,7 @@ "id": "cell-24", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch04/exercise.ipynb`: write an `asserted_inference` record (`AI-C04-EX`) claiming that your new nested action's own flows are accounted for and its balance constraint is real and evaluable — honestly scoped the same way `AI-C04` is scoped for `ApplyHeat`, not that brewing as a whole is functionally decomposed — referencing `AS-C03-EX` in `premises`." + "Try the chapter exercise in [`exercises/ch04/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch04/exercise.ipynb): write an `asserted_inference` record (`AI-C04-EX`) claiming that your new nested action's own flows are accounted for and its balance constraint is real and evaluable — honestly scoped the same way `AI-C04` is scoped for `ApplyHeat`, not that brewing as a whole is functionally decomposed — referencing `AS-C03-EX` in `premises`." ] } ], diff --git a/chapters/ch04-functional-decomp/conclusion.md b/chapters/ch04-functional-decomp/conclusion.md index bd3d3f9..7814624 100644 --- a/chapters/ch04-functional-decomp/conclusion.md +++ b/chapters/ch04-functional-decomp/conclusion.md @@ -6,7 +6,7 @@ title: Conclusion ## What we built -The Chapter 4 model adds `ApplyHeat`, an action definition with typed flows: bread and energy in, toast, delivered energy and loss out. `energy` and `duration` each carry an explicit `[0..*]` multiplicity, the same multiplicity a bare, unstated declaration already defaults to (SysML v2.0 formal/2026-03-02 7.6.3, 7.6.4) but which OpenSysML v0.9.0 only honors when it is written out. An asserted constraint requires `delivered` and `loss` to each be non-negative and their sum bounded by `energy`, without assuming any particular efficiency. `ApplyHeat` is nested inside `ToastBread`, the whole-system function Chapter 1 declared with no body, as its first step: `first start; then action applyHeat : ApplyHeat { in bread = ToastBread::bread; } then done;`. `energy` and `duration` stay unbound; no energy source or control function exists anywhere in the model yet. `Start`, `Finish`, and `Cancel` are item definitions naming the cycle's signals, each carrying a `doc` stating that it names a signal, not the bread or toast material flow. The Python side adds `AI-C04`, an `asserted_inference` ReviewRecord claiming the flows this worked example names are accounted for, with `AS-C03` in its `premises` list. +The Chapter 4 model adds `ApplyHeat`, an action definition with typed flows: bread and energy in, toast, delivered energy and loss out. `energy` and `duration` each carry an explicit `[0..*]` multiplicity, the same multiplicity a bare, unstated declaration already defaults to (SysML v2.0 formal/2026-03-02 7.6.3, 7.6.4) but which the OpenSysML runtime v0.9.0 only honors when it is written out. An asserted constraint requires `delivered` and `loss` to each be non-negative and their sum bounded by `energy`, without assuming any particular efficiency. `ApplyHeat` is nested inside `ToastBread`, the whole-system function Chapter 1 declared with no body, as its first step: `first start; then action applyHeat : ApplyHeat { in bread = ToastBread::bread; } then done;`. `energy` and `duration` stay unbound; no energy source or control function exists anywhere in the model yet. `Start`, `Finish`, and `Cancel` are item definitions naming the cycle's signals, each carrying a `doc` stating that it names a signal, not the bread or toast material flow. The Python side adds `AI-C04`, an `asserted_inference` ReviewRecord claiming the flows this worked example names are accounted for, with `AS-C03` in its `premises` list. ## What this establishes @@ -16,4 +16,4 @@ The chapter answers its engineering question: the toaster now has one functional Chapter 5 asks which logical component performs this function, and how logical components connect. It introduces a named, usage-level `allocate` connecting `ApplyHeat` to the component that performs it, and a named `interface` giving `duration` a connection point between components, without yet binding it to a value. -**Exercise:** The [Chapter 4 exercise](../../exercises/ch04/exercise.ipynb) asks you to define a new action def for your coffee maker's `BrewUnit` (not `action def Brew` — Chapter 1 already declares that at package level), whose usage nests inside `Brew`'s reopened body; name three signal item defs; probe the balance constraint's own three usages; and write an `asserted_inference` record honestly scoped to that one action's own flows, not to whether brewing as a whole is decomposed. Use the same pattern as `ApplyHeat` and `AI-C04`. +**Exercise:** The [Chapter 4 exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch04/exercise.ipynb) asks you to define a new action def for your coffee maker's `BrewUnit` (not `action def Brew` — Chapter 1 already declares that at package level), whose usage nests inside `Brew`'s reopened body; name three signal item defs; probe the balance constraint's own three usages; and write an `asserted_inference` record honestly scoped to that one action's own flows, not to whether brewing as a whole is decomposed. Use the same pattern as `ApplyHeat` and `AI-C04`. diff --git a/chapters/ch04-functional-decomp/index.md b/chapters/ch04-functional-decomp/index.md index 35b1bbf..f21b010 100644 --- a/chapters/ch04-functional-decomp/index.md +++ b/chapters/ch04-functional-decomp/index.md @@ -18,11 +18,11 @@ Chapter 4 asks: how do we describe one functional step and make it an actual ste ## Equipment -See [setup](../../docs/setup.md) to provision Python and the OpenSysML binary before running any notebook. Node.js is only needed if you also want to build the rendered book locally, not for running notebooks. +See [setup](../../docs/setup.md) to provision Python and the OpenSysML runtime binary before running any notebook. Node.js is only needed if you also want to build the rendered book locally, not for running notebooks. ## Method -Notebook 01 adds `ApplyHeat`: bread and energy in, toast, delivered energy and loss out. An asserted constraint requires that delivered energy and loss are each non-negative and that together they cannot exceed the energy supplied, without assuming any particular efficiency. `ApplyHeat` corresponds to "apply thermal energy" in the video's decomposition; the full toaster functional architecture from Part 3 covers approximately 15 verb-noun functions. This tutorial models `ApplyHeat` as one worked example to teach the `action def` construct. The same notebook nests it as an actual step of `ToastBread`, the whole-system function Chapter 1 declared with no body, and binds `ApplyHeat`'s own `bread` input to `ToastBread`'s `bread`, the one flow actually available at that level; `energy` and `duration` stay unbound, since no energy source or control function exists anywhere in the model yet, each written with an explicit `[0..*]` multiplicity, the same multiplicity a bare, unstated declaration already defaults to, so that OpenSysML v0.9.0 keeps the whole model evaluable rather than only accepting it. The same approach applies to the remaining functions. Notebook 02 adds `Start`, `Finish`, and `Cancel`: three item definitions, each carrying a `doc` stating that it names a signal (cycle start, cycle finish, cancel request), not the bread or toast material flow. Notebook 03 introduces the first `asserted_inference` record: a parent claim (the flows this worked example names are accounted for and the model stays evaluable) supported by a child claim (the balance constraint holds for a plausible energy split and fails for both an overdrawn one and a negative-loss one, and `ApplyHeat` is reachable as `ToastBread`'s own step). +Notebook 01 adds `ApplyHeat`: bread and energy in, toast, delivered energy and loss out. An asserted constraint requires that delivered energy and loss are each non-negative and that together they cannot exceed the energy supplied, without assuming any particular efficiency. `ApplyHeat` corresponds to "apply thermal energy" in the video's decomposition; the full toaster functional architecture from Part 3 covers approximately 15 verb-noun functions. This tutorial models `ApplyHeat` as one worked example to teach the `action def` construct. The same notebook nests it as an actual step of `ToastBread`, the whole-system function Chapter 1 declared with no body, and binds `ApplyHeat`'s own `bread` input to `ToastBread`'s `bread`, the one flow actually available at that level; `energy` and `duration` stay unbound, since no energy source or control function exists anywhere in the model yet, each written with an explicit `[0..*]` multiplicity, the same multiplicity a bare, unstated declaration already defaults to, so that the OpenSysML runtime v0.9.0 keeps the whole model evaluable rather than only accepting it. The same approach applies to the remaining functions. Notebook 02 adds `Start`, `Finish`, and `Cancel`: three item definitions, each carrying a `doc` stating that it names a signal (cycle start, cycle finish, cancel request), not the bread or toast material flow. Notebook 03 introduces the first `asserted_inference` record: a parent claim (the flows this worked example names are accounted for and the model stays evaluable) supported by a child claim (the balance constraint holds for a plausible energy split and fails for both an overdrawn one and a negative-loss one, and `ApplyHeat` is reachable as `ToastBread`'s own step). ## Expected result @@ -36,4 +36,4 @@ The Python side carries an `asserted_inference` ReviewRecord (`AI-C04`) with a n ## Experiment -The [chapter exercise](../../exercises/ch04/exercise.ipynb) asks you to define a new action def for your coffee maker's `BrewUnit` (not `action def Brew` — Chapter 1 already declares that at package level), whose usage nests inside `Brew`'s reopened body; name three signal item defs; probe the balance constraint against three usages to confirm it is real and evaluable; and write an honestly scoped `asserted_inference` record about that one nested action. Work through it after completing all three notebooks. +The [chapter exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch04/exercise.ipynb) asks you to define a new action def for your coffee maker's `BrewUnit` (not `action def Brew` — Chapter 1 already declares that at package level), whose usage nests inside `Brew`'s reopened body; name three signal item defs; probe the balance constraint against three usages to confirm it is real and evaluable; and write an honestly scoped `asserted_inference` record about that one nested action. Work through it after completing all three notebooks. diff --git a/chapters/ch05-architecture/01-model-navigation.ipynb b/chapters/ch05-architecture/01-model-navigation.ipynb index 4f1764e..c0fe935 100644 --- a/chapters/ch05-architecture/01-model-navigation.ipynb +++ b/chapters/ch05-architecture/01-model-navigation.ipynb @@ -118,7 +118,7 @@ "id": "cell-09", "metadata": {}, "source": [ - "The qualified names printed above resolve to the same `HeatingSystem` and `ApplyHeat` declared in `models/ch05-cumulative.sysml`, confirmed by the `Symbol` each call returns." + "The qualified names printed above resolve to the same `HeatingSystem` and `ApplyHeat` declared in [`models/ch05-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch05-cumulative.sysml), confirmed by the `Symbol` each call returns." ] }, { @@ -126,7 +126,7 @@ "id": "cell-10", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch05/exercise.ipynb`: use `model.find()` to navigate to `CoffeeDemo::CoffeeFlow` after building the assembly, then confirm `model.find()` returns `None` for an element that does not exist." + "Try the chapter exercise in [`exercises/ch05/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch05/exercise.ipynb): use `model.find()` to navigate to `CoffeeDemo::CoffeeFlow` after building the assembly, then confirm `model.find()` returns `None` for an element that does not exist." ] } ], diff --git a/chapters/ch05-architecture/02-allocate.ipynb b/chapters/ch05-architecture/02-allocate.ipynb index 4765997..e8124ad 100644 --- a/chapters/ch05-architecture/02-allocate.ipynb +++ b/chapters/ch05-architecture/02-allocate.ipynb @@ -53,7 +53,7 @@ "metadata": {}, "outputs": [], "source": [ - "# allocate not yet supported by the Editor API: toaster#13 / OpenSysML#599\n", + "# allocate not yet supported by the Editor API: toaster#13 (https://github.com/Open-MBEE/toaster/issues/13) / OpenSysML#599 (https://github.com/Open-MBEE/OpenSysML/issues/599)\n", "# spec: SysML v2 formal/2026-03-02 §7.15.2 (AllocationUsage)\n", "TOASTER_WITH_ALLOCATION = \"\"\"\\\n", "part def Toaster :> ToastingSystem {\n", @@ -152,7 +152,7 @@ "id": "cell-13", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch05/exercise.ipynb`: allocate your coffee maker's `applyWater` step (`Brew`'s own nested action usage, from Chapter 4) to `brewUnit` (a usage, not `BrewUnit` the definition), following the pattern this notebook builds." + "Try the chapter exercise in [`exercises/ch05/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch05/exercise.ipynb): allocate your coffee maker's `applyWater` step (`Brew`'s own nested action usage, from Chapter 4) to `brewUnit` (a usage, not `BrewUnit` the definition), following the pattern this notebook builds." ] } ], diff --git a/chapters/ch05-architecture/03-interfaces.ipynb b/chapters/ch05-architecture/03-interfaces.ipynb index 266dc94..ab7f732 100644 --- a/chapters/ch05-architecture/03-interfaces.ipynb +++ b/chapters/ch05-architecture/03-interfaces.ipynb @@ -193,7 +193,7 @@ "id": "cell-16", "metadata": {}, "outputs": [], - "source": "from toaster.render import render_toolkit_interconnection\nfrom IPython.display import SVG\n\n# Ch5's own pedagogical point is a conjugated port (durationIn : ~DurationPort); the in-house\n# render_interconnection() collapses port identity to a single edge label, so this cell uses\n# sysml-toolkit's real viz CLI instead, which draws the real port names as their own boxes,\n# not folded into the interface label.\nBINARY = Path.home() / \"Documents/GitHub/sysml-toolkit/target/release/sysmlv2\"\nLIB = Path.home() / \"Documents/GitHub/sysml-toolkit/spec-refs/SysML-v2-Release/sysml.library\"\nPLANTUML_JAR = Path(\"/opt/homebrew/opt/plantuml/libexec/plantuml.jar\")\nJAVA = Path(\"/opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java\")\nassert BINARY.exists(), f\"sysmlv2 binary not found at {BINARY} (see work contract CH05-TOOLKIT-VIZ)\"\n\nout_path = Path(\"../../figures/ch05-interconnection.svg\")\nout_path.parent.mkdir(exist_ok=True)\nrender_toolkit_interconnection(\n Path(\"../../models/ch05-cumulative.sysml\"),\n \"ToasterDemo::Toaster\",\n out_path,\n lib=LIB,\n binary=BINARY,\n plantuml_jar=PLANTUML_JAR,\n java=JAVA,\n)\nprint(out_path.with_suffix(\".puml\").read_text())\nSVG(filename=str(out_path))" + "source": "from toaster.render import render_toolkit_interconnection\nfrom toaster.tools import resolve_java, resolve_library, resolve_plantuml_jar, resolve_sysmlv2\nfrom IPython.display import SVG\n\n# Ch5's own pedagogical point is a conjugated port (durationIn : ~DurationPort); the in-house\n# render_interconnection() collapses port identity to a single edge label, so this cell uses\n# sysml-toolkit's real viz CLI instead, which draws the real port names as their own boxes,\n# not folded into the interface label.\nBINARY = resolve_sysmlv2()\nLIB = resolve_library()\nPLANTUML_JAR = resolve_plantuml_jar()\nJAVA = resolve_java()\n\nout_path = Path(\"../../figures/ch05-interconnection.svg\")\nout_path.parent.mkdir(exist_ok=True)\nrender_toolkit_interconnection(\n Path(\"../../models/ch05-cumulative.sysml\"),\n \"ToasterDemo::Toaster\",\n out_path,\n lib=LIB,\n binary=BINARY,\n plantuml_jar=PLANTUML_JAR,\n java=JAVA,\n)\nprint(out_path.with_suffix(\".puml\").read_text())\nSVG(filename=str(out_path))" }, { "cell_type": "markdown", @@ -212,7 +212,7 @@ "id": "cell-19", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch05/exercise.ipynb`: add a `CoffeeFlow` assembly with a `pump` and a `filterUnit`, each with a matching port joined by a named interface, confirm `port_type_mismatches()` returns an empty list, and render the interconnection diagram." + "Try the chapter exercise in [`exercises/ch05/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch05/exercise.ipynb): add a `CoffeeFlow` assembly with a `pump` and a `filterUnit`, each with a matching port joined by a named interface, confirm `port_type_mismatches()` returns an empty list, and render the interconnection diagram." ] } ], diff --git a/chapters/ch05-architecture/conclusion.md b/chapters/ch05-architecture/conclusion.md index 0050eb2..80c9f11 100644 --- a/chapters/ch05-architecture/conclusion.md +++ b/chapters/ch05-architecture/conclusion.md @@ -10,10 +10,10 @@ The Chapter 5 model adds three constructs to the cumulative model. `model.find() ## What this establishes -The chapter answers its engineering question: the toaster model now allocates a function to the logical component that performs it, and connects two logical components through a real, port-typed interface. The allocation points at two usages reachable from inside `Toaster` itself, `toastBread.applyHeat` and `heating`; `HeatingSystem`'s own `perform` relationship shows it is the same kind of action, allocated and performed by type, not a claim that the two are the same occurrence. The port connection is new structure, not new behavior: it gives the duration signal a place to enter `HeatingSystem`, but binding it to `ApplyHeat::duration` itself is later work, once a control policy exists to produce a value. The staged conformance check for port-type compatibility (`opensysml-query` recipe 5) has a real, non-vacuous pair to compare for the first time in this model: it checks that `durationIn` and `durationOut` declare related types, which they do. It does not check that the conjugation itself is correct, only that the underlying types are equal or one specializes the other. +The chapter answers its engineering question: the toaster model now allocates a function to the logical component that performs it, and connects two logical components through a real, port-typed interface. The allocation points at two usages reachable from inside `Toaster` itself, `toastBread.applyHeat` and `heating`; `HeatingSystem`'s own `perform` relationship shows it is the same kind of action, allocated and performed by type, not a claim that the two are the same occurrence. The port connection is new structure, not new behavior: it gives the duration signal a place to enter `HeatingSystem`, but binding it to `ApplyHeat::duration` itself is later work, once a control policy exists to produce a value. The staged conformance check for port-type compatibility (`port_type_mismatches()`, used in notebook 03) has a real, non-vacuous pair to compare for the first time in this model: it checks that `durationIn` and `durationOut` declare related types, which they do. It does not check that the conjugation itself is correct, only that the underlying types are equal or one specializes the other. ## What comes next Chapter 6 asks what one branch of the recursion shows one level below `HeatingSystem`. It nests a function inside `ApplyHeat`, gives it an abstract logical carrier with its own interface point, records a mechanism selection and a measure framing before specializing it with a concrete realization, checks that realization against a requirement, then records a stopping judgment stating plainly what the branch establishes and what it does not. -**Exercise:** The [Chapter 5 exercise](../../exercises/ch05/exercise.ipynb) asks you to allocate your coffee maker's `applyWater` step to `brewUnit` (both usages, not the `Brew`/`BrewUnit` definitions), add a `CoffeeFlow` assembly with a `pump` and a `filterUnit` joined by a named, port-typed interface, confirm `port_type_mismatches()` returns an empty list, navigate to it with `model.find()` (confirming `None` for a nonexistent element), build the interconnection intent, and confirm the endpoint paths appear correctly in the intent dict. +**Exercise:** The [Chapter 5 exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch05/exercise.ipynb) asks you to allocate your coffee maker's `applyWater` step to `brewUnit` (both usages, not the `Brew`/`BrewUnit` definitions), add a `CoffeeFlow` assembly with a `pump` and a `filterUnit` joined by a named, port-typed interface, confirm `port_type_mismatches()` returns an empty list, navigate to it with `model.find()` (confirming `None` for a nonexistent element), build the interconnection intent, and confirm the endpoint paths appear correctly in the intent dict. diff --git a/chapters/ch05-architecture/index.md b/chapters/ch05-architecture/index.md index 08bdcdc..9743b6d 100644 --- a/chapters/ch05-architecture/index.md +++ b/chapters/ch05-architecture/index.md @@ -20,7 +20,7 @@ After completing this chapter, the cumulative model has a named, usage-level `al ## Equipment -See [docs/setup.md](../../docs/setup.md) for environment setup. No chapter-specific tools are required beyond the base installation. +See [Getting Started](../../docs/setup.md) for environment setup. No chapter-specific tools are required beyond the base installation. ## Method @@ -36,4 +36,4 @@ After running all three notebooks, the cumulative model contains the complete Ch ## Experiment -Try the [Chapter 5 exercise](../../exercises/ch05/exercise.ipynb): allocate your coffee maker's `applyWater` step to `brewUnit` (both usages, not the `Brew`/`BrewUnit` definitions), then add a `CoffeeFlow` assembly with `pump` and `filterUnit` parts joined by a named, port-typed interface, confirm the port types are compatible, and render the interconnection diagram. +Try the [Chapter 5 exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch05/exercise.ipynb): allocate your coffee maker's `applyWater` step to `brewUnit` (both usages, not the `Brew`/`BrewUnit` definitions), then add a `CoffeeFlow` assembly with `pump` and `filterUnit` parts joined by a named, port-typed interface, confirm the port types are compatible, and render the interconnection diagram. diff --git a/chapters/ch06-recursive-decomp/01-subsystem-requirements.ipynb b/chapters/ch06-recursive-decomp/01-subsystem-requirements.ipynb index 09c1d99..0284d2f 100644 --- a/chapters/ch06-recursive-decomp/01-subsystem-requirements.ipynb +++ b/chapters/ch06-recursive-decomp/01-subsystem-requirements.ipynb @@ -413,7 +413,7 @@ "id": "cell-21", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch06/exercise.ipynb`: nest a level-2 function, `MoveWater`, inside `ApplyWater` (mirroring `GenerateHeat` nested inside `ApplyHeat`), give it an abstract logical carrier, `WaterMover` (mirroring `HeatGenerator`: a port, an unbound throughput slot, `perform action moveWater : MoveWater;`), then build `BrewAssembly :> BrewUnit` that composes `part mover : WaterMover;` with a usage-level allocation, the same structural pattern this notebook's own `HeatingAssembly`/`HeatGenerator` composition follows." + "Try the chapter exercise in [`exercises/ch06/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch06/exercise.ipynb): nest a level-2 function, `MoveWater`, inside `ApplyWater` (mirroring `GenerateHeat` nested inside `ApplyHeat`), give it an abstract logical carrier, `WaterMover` (mirroring `HeatGenerator`: a port, an unbound throughput slot, `perform action moveWater : MoveWater;`), then build `BrewAssembly :> BrewUnit` that composes `part mover : WaterMover;` with a usage-level allocation, the same structural pattern this notebook's own `HeatingAssembly`/`HeatGenerator` composition follows." ] } ], diff --git a/chapters/ch06-recursive-decomp/02-second-level.ipynb b/chapters/ch06-recursive-decomp/02-second-level.ipynb index 66ef049..3618018 100644 --- a/chapters/ch06-recursive-decomp/02-second-level.ipynb +++ b/chapters/ch06-recursive-decomp/02-second-level.ipynb @@ -142,7 +142,7 @@ "id": "cell-08", "metadata": {}, "source": [ - "This fragment is the same text now committed in `models/ch06-cumulative.sysml` onward, alongside the `ReviewRecordRef` definition (carried forward since Chapter 2); loading the cumulative model (the `model.ok` cell above, unchanged) is what makes this a real, checkable tag rather than an assertion the reader has to trust. AC-C06 states the claim next: what `heatGenerationReq` is being framed as." + "This fragment is the same text now committed in [`models/ch06-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch06-cumulative.sysml) onward, alongside the `ReviewRecordRef` definition (carried forward since Chapter 2); loading the cumulative model (the `model.ok` cell above, unchanged) is what makes this a real, checkable tag rather than an assertion the reader has to trust. AC-C06 states the claim next: what `heatGenerationReq` is being framed as." ] }, { @@ -501,7 +501,7 @@ "id": "cell-25", "metadata": {}, "source": [ - "This fragment is the same text now committed in `models/ch06-cumulative.sysml` onward, alongside the `ReviewRecordRef` definition and `acC06Tag` (above); loading the cumulative model (the `model.ok` cell above, unchanged) is what makes this a real, checkable tag rather than an assertion the reader has to trust. AS-C06 states the claim next: which mechanism is selected, and over what alternative." + "This fragment is the same text now committed in [`models/ch06-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch06-cumulative.sysml) onward, alongside the `ReviewRecordRef` definition and `acC06Tag` (above); loading the cumulative model (the `model.ok` cell above, unchanged) is what makes this a real, checkable tag rather than an assertion the reader has to trust. AS-C06 states the claim next: which mechanism is selected, and over what alternative." ] }, { @@ -1044,7 +1044,7 @@ "id": "cell-51", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch06/exercise.ipynb`: nest `MoveWater` inside `ApplyWater`, build `WaterMover` as its abstract carrier, and add a `BrewReq` requirement with its subject on `WaterMover` itself (not `BrewUnit`) constraining minimum throughput, then create a lower-throughput candidate that fails it, following the requirement and candidate pattern this notebook builds." + "Try the chapter exercise in [`exercises/ch06/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch06/exercise.ipynb): nest `MoveWater` inside `ApplyWater`, build `WaterMover` as its abstract carrier, and add a `BrewReq` requirement with its subject on `WaterMover` itself (not `BrewUnit`) constraining minimum throughput, then create a lower-throughput candidate that fails it, following the requirement and candidate pattern this notebook builds." ] } ], diff --git a/chapters/ch06-recursive-decomp/03-stopping-judgment.ipynb b/chapters/ch06-recursive-decomp/03-stopping-judgment.ipynb index 91601c9..4a4403b 100644 --- a/chapters/ch06-recursive-decomp/03-stopping-judgment.ipynb +++ b/chapters/ch06-recursive-decomp/03-stopping-judgment.ipynb @@ -329,7 +329,7 @@ "id": "cell-10", "metadata": {}, "source": [ - "This fragment is the same text now committed in `models/ch06-cumulative.sysml` onward, alongside the `ReviewRecordRef` definition (carried forward since Chapter 2) and the `acC06Tag`/`asC06Tag` tags (notebook 02); loading the cumulative model (the `model.ok` cell above, unchanged) is what makes this a real, checkable tag rather than an assertion the reader has to trust." + "This fragment is the same text now committed in [`models/ch06-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch06-cumulative.sysml) onward, alongside the `ReviewRecordRef` definition (carried forward since Chapter 2) and the `acC06Tag`/`asC06Tag` tags (notebook 02); loading the cumulative model (the `model.ok` cell above, unchanged) is what makes this a real, checkable tag rather than an assertion the reader has to trust." ] }, { @@ -675,7 +675,7 @@ "id": "cell-25", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch06/exercise.ipynb`: record the two judgment sites your own `BrewReq` requirement raises (`AC-C06-EX`, a measure-framing judgment; `AS-C06-EX`, a mechanism-selection judgment for `Impeller`), then write an `AI-C06-EX` stopping judgment honestly scoped exactly like this notebook's own `AI-C06` — not a claim that your `BrewUnit` decomposition is complete — with `premises` referencing the real chain this branch rests on: `AC-C06-EX`, `AS-C06-EX`, your Chapter 3 exercise's `AS-C03-EX`, and your Chapter 4 exercise's `AI-C04-EX`." + "Try the chapter exercise in [`exercises/ch06/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch06/exercise.ipynb): record the two judgment sites your own `BrewReq` requirement raises (`AC-C06-EX`, a measure-framing judgment; `AS-C06-EX`, a mechanism-selection judgment for `Impeller`), then write an `AI-C06-EX` stopping judgment honestly scoped exactly like this notebook's own `AI-C06` — not a claim that your `BrewUnit` decomposition is complete — with `premises` referencing the real chain this branch rests on: `AC-C06-EX`, `AS-C06-EX`, your Chapter 3 exercise's `AS-C03-EX`, and your Chapter 4 exercise's `AI-C04-EX`." ] } ], diff --git a/chapters/ch06-recursive-decomp/conclusion.md b/chapters/ch06-recursive-decomp/conclusion.md index 8905765..b194f97 100644 --- a/chapters/ch06-recursive-decomp/conclusion.md +++ b/chapters/ch06-recursive-decomp/conclusion.md @@ -16,4 +16,4 @@ The chapter answers its engineering question for one branch: `GenerateHeat` is a Chapter 7 asks how the model behaves at runtime. It builds `deliveredEnergy` as a calc on `HeatGenerator`, with a bounded `efficiency` slot resolved from the carrier's own bound value, queried through `model.eval` rather than a symbolic binding; gives the toaster's state machine, `Cycle`, a `heating` state whose `do action` invokes `GenerateHeat` and transitions that complete a full run, exhibited by `ToastingSystem` and inherited by `Toaster`; and sweeps `deliveredEnergy`'s own `power` input against `HeatGenerationReq`'s own threshold on `HeatGenerator::power`. -**Exercise:** The [Chapter 6 exercise](../../exercises/ch06/exercise.ipynb) asks you to nest `MoveWater` inside `ApplyWater`, give it an abstract carrier `WaterMover`, build `BrewAssembly :> BrewUnit` composing it with a usage-level allocation, and state a `BrewReq` requirement on `WaterMover` itself for minimum water throughput; decide for yourself, and justify it, what kind of measure that threshold is, and record which mechanism `Impeller` (built only after that selection is argued) represents; and write an honestly scoped `asserted_inference` record stating what the decomposition establishes and does not, with `premises` referencing the real judgment chain this branch rests on. +**Exercise:** The [Chapter 6 exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch06/exercise.ipynb) asks you to nest `MoveWater` inside `ApplyWater`, give it an abstract carrier `WaterMover`, build `BrewAssembly :> BrewUnit` composing it with a usage-level allocation, and state a `BrewReq` requirement on `WaterMover` itself for minimum water throughput; decide for yourself, and justify it, what kind of measure that threshold is, and record which mechanism `Impeller` (built only after that selection is argued) represents; and write an honestly scoped `asserted_inference` record stating what the decomposition establishes and does not, with `premises` referencing the real judgment chain this branch rests on. diff --git a/chapters/ch06-recursive-decomp/index.md b/chapters/ch06-recursive-decomp/index.md index 82cb060..87c8dd9 100644 --- a/chapters/ch06-recursive-decomp/index.md +++ b/chapters/ch06-recursive-decomp/index.md @@ -20,7 +20,7 @@ After completing this chapter, the cumulative model has a real second-level func ## Equipment -See [docs/setup.md](../../docs/setup.md) for environment setup. No chapter-specific tools are required. +See [Getting Started](../../docs/setup.md) for environment setup. No chapter-specific tools are required. ## Method @@ -32,4 +32,4 @@ After running all three notebooks, `perform_relationships(model)` includes `Heat ## Experiment -Try the [Chapter 6 exercise](../../exercises/ch06/exercise.ipynb): nest `MoveWater` inside `ApplyWater`, give it an abstract carrier `WaterMover`, build `BrewAssembly :> BrewUnit` composing it with a usage-level allocation, and state a `BrewReq` requirement on `WaterMover` itself, following the same level-2 function/carrier/allocation/requirement pattern this chapter builds for `GenerateHeat`/`HeatGenerator`; record a measure-framing and a mechanism-selection judgment the requirement raises (`Impeller`, built only after the selection is argued), and write an honestly scoped `asserted_inference` record stating what the decomposition establishes and does not. +Try the [Chapter 6 exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch06/exercise.ipynb): nest `MoveWater` inside `ApplyWater`, give it an abstract carrier `WaterMover`, build `BrewAssembly :> BrewUnit` composing it with a usage-level allocation, and state a `BrewReq` requirement on `WaterMover` itself, following the same level-2 function/carrier/allocation/requirement pattern this chapter builds for `GenerateHeat`/`HeatGenerator`; record a measure-framing and a mechanism-selection judgment the requirement raises (`Impeller`, built only after the selection is argued), and write an honestly scoped `asserted_inference` record stating what the decomposition establishes and does not. diff --git a/chapters/ch07-execution/01-calc-energy.ipynb b/chapters/ch07-execution/01-calc-energy.ipynb index ab0c75d..bbbe4ca 100644 --- a/chapters/ch07-execution/01-calc-energy.ipynb +++ b/chapters/ch07-execution/01-calc-energy.ipynb @@ -298,7 +298,7 @@ "id": "cell-14", "metadata": {}, "source": [ - "The model itself computes 67200 J (OpenSysML prints this as the unsimplified `67200 [MeasurementReferences::one*SI::'kg⋅m²⋅s⁻²']`, dimensionally equivalent to J but not folded back to that symbol or freed of the identity `MeasurementReferences::one` factor -- a display quirk, DEFERRED.md D-033, not a modeling error) from `rated`'s own 800 W rating, read from the model rather than retyped, and this chapter's own assumed 120 s duration, scaled by `rated`'s own 0.7 efficiency: a value the calc reads from `rated`, not one this notebook passes in as a free argument." + "The model itself computes 67200 J (the OpenSysML runtime prints this as the unsimplified `67200 [MeasurementReferences::one*SI::'kg⋅m²⋅s⁻²']`, dimensionally equivalent to J but not folded back to that symbol or freed of the identity `MeasurementReferences::one` factor -- a display quirk, DEFERRED.md [D-033](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-033-opensysmls-eval-does-not-simplify-a-product-against-a-dimensiononeunit-factor-or-fold-an-si-base-unit-expansion-back-into-its-derived-unit-symbol), not a modeling error) from `rated`'s own 800 W rating, read from the model rather than retyped, and this chapter's own assumed 120 s duration, scaled by `rated`'s own 0.7 efficiency: a value the calc reads from `rated`, not one this notebook passes in as a free argument." ] }, { @@ -387,7 +387,7 @@ "id": "cell-18", "metadata": {}, "source": [ - "`efficiencyBounded` fails, witnessed by evaluation against the 1.5 value. The model still *loads* `overEfficient` cleanly (`assert constraint` is not an eager, load-time validator); the bound only does its checking work when a caller actually evaluates it, the same way `HeatGenerationReq`'s own `assert satisfy` claims do. `deliveredEnergy` has no separate `in efficiency` parameter to bypass this with: `overEfficient.deliveredEnergy(800.0 [SI::W], 120.0 [SI::s])` would compute a numerically equivalent 144000 J (again printed as the same unsimplified compound unit, D-033), a real violation of conservation, but only by reading `overEfficient`'s own value, which `efficiencyBounded` already flags. `overEfficient` exists only in this scratch copy, never in the committed model." + "`efficiencyBounded` fails, witnessed by evaluation against the 1.5 value. The model still *loads* `overEfficient` cleanly (`assert constraint` is not an eager, load-time validator); the bound only does its checking work when a caller actually evaluates it, the same way `HeatGenerationReq`'s own `assert satisfy` claims do. `deliveredEnergy` has no separate `in efficiency` parameter to bypass this with: `overEfficient.deliveredEnergy(800.0 [SI::W], 120.0 [SI::s])` would compute a numerically equivalent 144000 J (again printed as the same unsimplified compound unit, [D-033](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-033-opensysmls-eval-does-not-simplify-a-product-against-a-dimensiononeunit-factor-or-fold-an-si-base-unit-expansion-back-into-its-derived-unit-symbol)), a real violation of conservation, but only by reading `overEfficient`'s own value, which `efficiencyBounded` already flags. `overEfficient` exists only in this scratch copy, never in the committed model." ] }, { @@ -403,7 +403,7 @@ "id": "cell-20", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch07/exercise.ipynb`: add a bounded `transferEfficiency` slot and a `deliveredMass` calc to the coffee maker's `WaterMover` carrier, mirroring `efficiency`/`deliveredEnergy` exactly, and query it through `model.eval`." + "Try the chapter exercise in [`exercises/ch07/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch07/exercise.ipynb): add a bounded `transferEfficiency` slot and a `deliveredMass` calc to the coffee maker's `WaterMover` carrier, mirroring `efficiency`/`deliveredEnergy` exactly, and query it through `model.eval`." ] } ], diff --git a/chapters/ch07-execution/02-state-traces.ipynb b/chapters/ch07-execution/02-state-traces.ipynb index 5851d4e..2af719b 100644 --- a/chapters/ch07-execution/02-state-traces.ipynb +++ b/chapters/ch07-execution/02-state-traces.ipynb @@ -100,7 +100,7 @@ "id": "cell-05", "metadata": {}, "source": [ - "`heating`'s `do action` invokes `GenerateHeat`, not the full `ApplyHeat`: `ApplyHeat`'s own `bread` input has no value at this level of decomposition, and only its already-`[0..*]` parameters stay executable when left unbound in OpenSysML v0.9.0 (`DEFERRED.md` D-026, and D-026's own addendum recording this exact case). `GenerateHeat`'s own input is already `[0..*]`, so invoking it directly keeps the state genuinely executable: the invocation is real, confirmed by the tool actually attempting it (an unbound parameter inside `GenerateHeat` raises from inside the state, not silently). `GenerateHeat` itself has no body yet, though: Chapter 6 built it as a typed signature only, so nothing is computed when it runs. The state's own transition table is what changes here, not any quantity: once a state actually carries an action with a computed result and a duration, a trace could yield a derived quantity; that threshold is still not met here." + "`heating`'s `do action` invokes `GenerateHeat`, not the full `ApplyHeat`: `ApplyHeat`'s own `bread` input has no value at this level of decomposition, and only its already-`[0..*]` parameters stay executable when left unbound in the OpenSysML runtime v0.9.0 (`DEFERRED.md` [D-026](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-026-opensysml-treats-an-implicit-and-an-explicit-but-spec-identical-0-multiplicity-differently-for-an-in-parameter-reachable-through-a-nested-action-step), and D-026's own addendum recording this exact case). `GenerateHeat`'s own input is already `[0..*]`, so invoking it directly keeps the state genuinely executable: the invocation is real, confirmed by the runtime actually attempting it (an unbound parameter inside `GenerateHeat` raises from inside the state, not silently). `GenerateHeat` itself has no body yet, though: Chapter 6 built it as a typed signature only, so nothing is computed when it runs. The state's own transition table is what changes here, not any quantity: once a state actually carries an action with a computed result and a duration, a trace could yield a derived quantity; that threshold is still not met here." ] }, { @@ -176,7 +176,7 @@ "id": "cell-09", "metadata": {}, "source": [ - "`accept Start`, `accept Finish` and `accept Cancel` name the events that move the machine between modes. OpenSysML v0.9.0 keeps a transition's trigger only as a string and never resolves it against `Start`, `Finish` or `Cancel`: a typo, or a reference to a name the model never declares, loads without error and simply never fires (`DEFERRED.md` D-023). The tutorial's own guard, `language_gap_findings`, catches what the tool does not; the cell after the negative control below demonstrates it directly." + "`accept Start`, `accept Finish` and `accept Cancel` name the events that move the machine between modes. The OpenSysML runtime v0.9.0 keeps a transition's trigger only as a string and never resolves it against `Start`, `Finish` or `Cancel`: a typo, or a reference to a name the model never declares, loads without error and simply never fires (`DEFERRED.md` [D-023](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-023-opensysml-does-not-resolve-state-machine-transition-trigger-names)). The tutorial's own guard, `language_gap_findings`, catches what the runtime does not; the cell after the negative control below demonstrates it directly." ] }, { @@ -452,7 +452,7 @@ "id": "cell-23", "metadata": {}, "source": [ - "OpenSysML loads the typo cleanly: `Strat` never fires, and nothing in the tool says so. The tutorial's own guard does: `language_gap_findings` flags `Strat` as an unresolved trigger, close enough to the locally-declared `Start` to be a plausible typo (D-023). The tool has a real hole here, and the tutorial supplies the check that closes it: exactly the construct-and-analyze loop this tutorial builds throughout." + "The OpenSysML runtime loads the typo cleanly (the `OpenSysML itself` line printed above is the runtime's verdict): `Strat` never fires, and nothing in the runtime says so; sysml-toolkit v0.9.1 resolves trigger names and warns on a broken reference (D-023). The tutorial's own guard catches it: `language_gap_findings` flags `Strat` as an unresolved trigger, close enough to the locally-declared `Start` to be a plausible typo ([D-023](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-023-opensysml-does-not-resolve-state-machine-transition-trigger-names)). The runtime has a real hole here, and the tutorial supplies the check that closes it: exactly the construct-and-analyze loop this tutorial builds throughout." ] }, { @@ -472,7 +472,7 @@ "id": "cell-25", "metadata": {}, "source": [ - "The same transition drawn with the typo'd trigger: the edge now reads `accept Strat`, visible in the picture the same way it is invisible to OpenSysML's own loader." + "The same transition drawn with the typo'd trigger: the edge now reads `accept Strat`, visible in the picture the same way it is invisible to the OpenSysML runtime's own loader." ] }, { @@ -513,7 +513,7 @@ "id": "cell-27", "metadata": {}, "source": [ - "`ToastingSystem::cycle` is a `stateUsage`, the exhibited occurrence of the `Cycle` state def; `Toaster::cycle` does not resolve by that name, the same way `Toaster::toastBread` does not, since both are inherited members of `ToastingSystem`, not redeclared on `Toaster`. That inheritance is a fact about the model's structure, shown by `model.find` above, not by the trace below: in OpenSysML v0.9.0, `execute_state`'s `performer` argument has no effect on the result (a documented tool gap, D-028); the same trace comes back whether `performer` names `nominal`, a usage that exhibits nothing at all, or is omitted entirely. The trace below runs `Cycle`'s own transition table: `heating` invokes `GenerateHeat`, `ready` follows `Finish`, and the machine returns to `idle` on its own, a real completion of the modeled cycle. No quantity is computed along the way; what changes is which mode the machine is in." + "`ToastingSystem::cycle` is a `stateUsage`, the exhibited occurrence of the `Cycle` state def; `Toaster::cycle` does not resolve by that name, the same way `Toaster::toastBread` does not, since both are inherited members of `ToastingSystem`, not redeclared on `Toaster`. That inheritance is a fact about the model's structure, shown by `model.find` above, not by the trace below: in the OpenSysML runtime v0.9.0, `execute_state`'s `performer` argument has no effect on the result (a documented runtime gap, [D-028](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-028-modelexecute_states-performer-argument-has-no-effect-on-the-result)); the same trace comes back whether `performer` names `nominal`, a usage that exhibits nothing at all, or is omitted entirely. The trace below runs `Cycle`'s own transition table: `heating` invokes `GenerateHeat`, `ready` follows `Finish`, and the machine returns to `idle` on its own, a real completion of the modeled cycle. No quantity is computed along the way; what changes is which mode the machine is in." ] }, { @@ -577,7 +577,7 @@ "id": "cell-32", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch07/exercise.ipynb`: add a `BrewCycle` state machine mirroring `Cycle` exactly — an `idle` entry state, a `brewing` state whose `do action` invokes `moveWater`, two exit states, and completion transitions back to `idle` — and trace it with `execute_state`." + "Try the chapter exercise in [`exercises/ch07/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch07/exercise.ipynb): add a `BrewCycle` state machine mirroring `Cycle` exactly — an `idle` entry state, a `brewing` state whose `do action` invokes `moveWater`, two exit states, and completion transitions back to `idle` — and trace it with `execute_state`." ] } ], diff --git a/chapters/ch07-execution/03-param-sweep.ipynb b/chapters/ch07-execution/03-param-sweep.ipynb index 6efbf63..688ec55 100644 --- a/chapters/ch07-execution/03-param-sweep.ipynb +++ b/chapters/ch07-execution/03-param-sweep.ipynb @@ -269,7 +269,7 @@ "id": "cell-13", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch07/exercise.ipynb`: sweep the coffee maker's `deliveredMass` calc across its free `throughput` argument and mark `BrewReq`'s own threshold, read from the model." + "Try the chapter exercise in [`exercises/ch07/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch07/exercise.ipynb): sweep the coffee maker's `deliveredMass` calc across its free `throughput` argument and mark `BrewReq`'s own threshold, read from the model." ] } ], diff --git a/chapters/ch07-execution/conclusion.md b/chapters/ch07-execution/conclusion.md index a7c84c0..94da287 100644 --- a/chapters/ch07-execution/conclusion.md +++ b/chapters/ch07-execution/conclusion.md @@ -10,7 +10,7 @@ The cumulative model now has `deliveredEnergy`, a calc on `HeatGenerator` with a ## What this establishes -The efficiency bound is a real constraint, not a comment: it holds for `rated`'s own value and fails, witnessed by evaluation, for a value outside it, and there is no way to reach the relation with an efficiency that bypasses this check. `ToastingSystem` now exhibits a real mode machine: `Cycle`'s traces are derived from its own transition table, not entered as a choice. Running `[Start, Finish]` and `[Start, Cancel]` shows the machine actually cycling, including a repeated run that returns to `idle` twice. These traces are specification analysis: they confirm the transition table says what it was meant to say and would catch a mistake in it, not evidence about the toaster's behavior in use. OpenSysML v0.9.0 does not resolve a transition's trigger against the item def it names; the tutorial's own guard, demonstrated directly in notebook 02, catches a typo'd trigger the tool lets through silently (`DEFERRED.md` D-023). +The efficiency bound is a real constraint, not a comment: it holds for `rated`'s own value and fails, witnessed by evaluation, for a value outside it, and there is no way to reach the relation with an efficiency that bypasses this check. `ToastingSystem` now exhibits a real mode machine: `Cycle`'s traces are derived from its own transition table, not entered as a choice. Running `[Start, Finish]` and `[Start, Cancel]` shows the machine actually cycling, including a repeated run that returns to `idle` twice. These traces are specification analysis: they confirm the transition table says what it was meant to say and would catch a mistake in it, not evidence about the toaster's behavior in use. The OpenSysML runtime v0.9.0 does not resolve a transition's trigger against the item def it names; the tutorial's own guard, demonstrated directly in notebook 02, catches a typo'd trigger the runtime lets through silently (`DEFERRED.md` [D-023](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-023-opensysml-does-not-resolve-state-machine-transition-trigger-names)). ## What comes next @@ -18,4 +18,4 @@ Chapter 8 checks the model's own claims against its own values: `verify_satisfac ## Exercise -See `exercises/ch07/exercise.ipynb`: add a bounded `transferEfficiency` slot and `deliveredMass` calc to the coffee maker's `WaterMover`, add a `BrewCycle` state machine, and sweep `deliveredMass`'s `throughput` argument against `BrewReq`'s own threshold, read from the model. +See [`exercises/ch07/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch07/exercise.ipynb): add a bounded `transferEfficiency` slot and `deliveredMass` calc to the coffee maker's `WaterMover`, add a `BrewCycle` state machine, and sweep `deliveredMass`'s `throughput` argument against `BrewReq`'s own threshold, read from the model. diff --git a/chapters/ch07-execution/index.md b/chapters/ch07-execution/index.md index 708123e..85251e8 100644 --- a/chapters/ch07-execution/index.md +++ b/chapters/ch07-execution/index.md @@ -20,7 +20,7 @@ After completing this chapter, the cumulative model has `deliveredEnergy`, a cal ## Equipment -See [docs/setup.md](../../docs/setup.md) for environment setup. Chapter 7 requires `numpy` and `matplotlib` (both in `pyproject.toml`). +See [Getting Started](../../docs/setup.md) for environment setup. Chapter 7 requires `numpy` and `matplotlib` (both in [`pyproject.toml`](https://github.com/Open-MBEE/toaster/blob/main/pyproject.toml)). ## Method @@ -28,8 +28,8 @@ Notebook 01 builds `HeatGenerator` a bounded `efficiency` slot and a `calc deliv ## Expected result -After running all three notebooks, `model.eval("ToasterDemo::rated.deliveredEnergy(ToasterDemo::rated.power, 120.0 [SI::s])")` returns 67200 J (printed by OpenSysML as `67200 [MeasurementReferences::one*SI::'kg⋅m²⋅s⁻²']`, an unsimplified but dimensionally equivalent unit expression rather than the clean `SI::J` symbol — a display quirk, DEFERRED.md D-033); `model.find("ToasterDemo::ToastingSystem::cycle")` returns a `stateUsage`, the usage `ToastingSystem` exhibits and `Toaster` inherits; `model.execute_state("ToasterDemo::Cycle", events=["Start", "Finish"], performer="ToasterDemo::nominal")` returns `states_visited=['idle', 'heating', 'ready', 'idle']`; and the parameter sweep's figure marks `HeatGenerationReq`'s own 600 W threshold, read from the model rather than invented in Python. +After running all three notebooks, `model.eval("ToasterDemo::rated.deliveredEnergy(ToasterDemo::rated.power, 120.0 [SI::s])")` returns 67200 J (printed by the OpenSysML runtime as `67200 [MeasurementReferences::one*SI::'kg⋅m²⋅s⁻²']`, an unsimplified but dimensionally equivalent unit expression rather than the clean `SI::J` symbol — a display quirk, DEFERRED.md [D-033](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-033-opensysmls-eval-does-not-simplify-a-product-against-a-dimensiononeunit-factor-or-fold-an-si-base-unit-expansion-back-into-its-derived-unit-symbol)); `model.find("ToasterDemo::ToastingSystem::cycle")` returns a `stateUsage`, the usage `ToastingSystem` exhibits and `Toaster` inherits; `model.execute_state("ToasterDemo::Cycle", events=["Start", "Finish"], performer="ToasterDemo::nominal")` returns `states_visited=['idle', 'heating', 'ready', 'idle']`; and the parameter sweep's figure marks `HeatGenerationReq`'s own 600 W threshold, read from the model rather than invented in Python. ## Experiment -Try the [Chapter 7 exercise](../../exercises/ch07/exercise.ipynb): add a bounded `transferEfficiency` slot and `deliveredMass` calc to the coffee maker's `WaterMover`, add a `BrewCycle` state machine, and sweep `deliveredMass`'s `throughput` argument against `BrewReq`'s own threshold, read from the model. +Try the [Chapter 7 exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch07/exercise.ipynb): add a bounded `transferEfficiency` slot and `deliveredMass` calc to the coffee maker's `WaterMover`, add a `BrewCycle` state machine, and sweep `deliveredMass`'s `throughput` argument against `BrewReq`'s own threshold, read from the model. diff --git a/chapters/ch08-checking/01-assert-constraint-def.ipynb b/chapters/ch08-checking/01-assert-constraint-def.ipynb index f170e64..61381cb 100644 --- a/chapters/ch08-checking/01-assert-constraint-def.ipynb +++ b/chapters/ch08-checking/01-assert-constraint-def.ipynb @@ -15,7 +15,7 @@ "id": "cell-01", "metadata": {}, "source": [ - "Chapter 7 built `efficiencyBounded` (`0 <= efficiency <= 1`) and `deliveredEnergy` (`power * duration * efficiency`) on `HeatGenerator`, then checked the relation only at `rated`'s own efficiency (0.7). This notebook adds a construct with the same shape as what those two together would imply: delivered energy never exceeds supplied energy, for every value of efficiency in its bound. [Ch8-02](02-violation-witness.ipynb) explains why this is a hand-restated lemma, not a solver-checked reference to `efficiencyBounded` and `deliveredEnergy` themselves (`DEFERRED.md` D-030, D-031). See [Ch7-01 delivered energy](../ch07-execution/01-calc-energy.ipynb) for `efficiencyBounded` and `deliveredEnergy` themselves." + "Chapter 7 built `efficiencyBounded` (`0 <= efficiency <= 1`) and `deliveredEnergy` (`power * duration * efficiency`) on `HeatGenerator`, then checked the relation only at `rated`'s own efficiency (0.7). This notebook adds a construct with the same shape as what those two together would imply: delivered energy never exceeds supplied energy, for every value of efficiency in its bound. [Ch8-02](02-violation-witness.ipynb) explains why this is a hand-restated lemma, not a solver-checked reference to `efficiencyBounded` and `deliveredEnergy` themselves (`DEFERRED.md` [D-030](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-030-two-independently-declared-assert-constraints-are-never-composed-by-verify---solve-whether-sibling-or-inherited), [D-031](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-031-a-chained-calcfunction-invocation-inside-an-assert-constraint-is-not-in-z3s-solvable-fragment)). See [Ch7-01 delivered energy](../ch07-execution/01-calc-energy.ipynb) for `efficiencyBounded` and `deliveredEnergy` themselves." ] }, { @@ -222,7 +222,7 @@ "id": "cell-07", "metadata": {}, "source": [ - "The lemma's antecedent restates what `efficiencyBounded` already guarantees, plus non-negative power and duration; its consequent restates the same arithmetic `deliveredEnergy`'s own definition computes. Both are copied by hand, not referenced: [Ch8-02](02-violation-witness.ipynb) shows directly that neither the model's own bound nor its own calc definition can actually move this lemma's verdict, because this toolchain's solver never reaches through to either one." + "The lemma's antecedent restates what `efficiencyBounded` already guarantees, plus non-negative power and duration; its consequent restates the same arithmetic `deliveredEnergy`'s own definition computes. Both are copied by hand, not referenced: [Ch8-02](02-violation-witness.ipynb) shows directly that neither the model's own bound nor its own calc definition can actually move this lemma's verdict, because sysml-toolkit's solver never reaches through to either one." ] }, { @@ -245,7 +245,7 @@ "id": "cell-09", "metadata": {}, "source": [ - "`ch08-cumulative.sysml` already carries this construct forward: it is not assembled from the fragments above at runtime, it is the chapter's own committed fixture. `model.find()` and `model.query()` below confirm `deliveredEnergyBoundedBySupply` is really part of the loaded model, not only in the strings printed above." + "[`ch08-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch08-cumulative.sysml) already carries this construct forward: it is not assembled from the fragments above at runtime, it is the chapter's own committed fixture. `model.find()` and `model.query()` below confirm `deliveredEnergyBoundedBySupply` is really part of the loaded model, not only in the strings printed above." ] }, { @@ -344,7 +344,7 @@ "id": "cell-14", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch08/exercise.ipynb`: it works through the same `verify_satisfaction()` and stale-detection pattern on its own, separate coffee-maker exercise model." + "Try the chapter exercise in [`exercises/ch08/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch08/exercise.ipynb): it works through the same `verify_satisfaction()` and stale-detection pattern on its own, separate coffee-maker exercise model." ] } ], diff --git a/chapters/ch08-checking/02-violation-witness.ipynb b/chapters/ch08-checking/02-violation-witness.ipynb index 1ff43fe..dadae07 100644 --- a/chapters/ch08-checking/02-violation-witness.ipynb +++ b/chapters/ch08-checking/02-violation-witness.ipynb @@ -14,7 +14,7 @@ "cell_type": "markdown", "id": "cell-01", "metadata": {}, - "source": "Notebook 01 stated `deliveredEnergyBoundedBySupply` and confirmed it is really part of `ch08-cumulative.sysml`. This notebook runs analysis against that construct and, further down, adds the one new model element this chapter's own judgment record needs: a `ReviewRecordRef` tag anchoring `AS-C08` to `deliveredEnergyBoundedBySupply` itself. See [Ch8-01](01-assert-constraint-def.ipynb) for the construct itself." + "source": "Notebook 01 stated `deliveredEnergyBoundedBySupply` and confirmed it is really part of [`ch08-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch08-cumulative.sysml). This notebook runs analysis against that construct and, further down, adds the one new model element this chapter's own judgment record needs: a `ReviewRecordRef` tag anchoring `AS-C08` to `deliveredEnergyBoundedBySupply` itself. See [Ch8-01](01-assert-constraint-def.ipynb) for the construct itself." }, { "cell_type": "code", @@ -193,7 +193,7 @@ "id": "cell-10", "metadata": {}, "source": [ - "Every verdict above is `verify_satisfaction()` evaluating a claim at one fixed set of values, the `run` engine's own kind of answer. `deliveredEnergyBoundedBySupply` asks a different question: does the lemma hold for every value its unbound features could take? `toaster.modelcheck.verify_holds()` wraps `sysml-toolkit`'s real `verify --solve` command (Z3 underneath) to answer exactly that, over a small companion restatement of the construct. Two separate, real limits of this toolchain are why a companion file is used rather than the committed model or the construct's own original elements directly: `toaster.modelcheck`'s own text parser cannot yet read a verdict line for a constraint that is also the subject of an `assert satisfy` declaration, which the committed model has (`DEFERRED.md` D-029); and, independent of that parser gap, this toolchain's Z3 backend never actually composes `efficiencyBounded` and `deliveredEnergy` into this lemma's own check at all, whether by same-scope membership, inheritance, or a chained calc call (`DEFERRED.md` D-030, D-031, both confirmed below). The lemma below is therefore a hand-restated real-arithmetic fact of the same shape as the original relation, not a solver-checked reference to it." + "Every verdict above is `verify_satisfaction()` evaluating a claim at one fixed set of values, the `run` engine's own kind of answer. `deliveredEnergyBoundedBySupply` asks a different question: does the lemma hold for every value its unbound features could take? `toaster.modelcheck.verify_holds()` wraps `sysml-toolkit`'s real `verify --solve` command (Z3 underneath) to answer exactly that, over a small companion restatement of the construct. Two separate, real limits of this toolchain are why a companion file is used rather than the committed model or the construct's own original elements directly: `toaster.modelcheck`'s own text parser cannot yet read a verdict line for a constraint that is also the subject of an `assert satisfy` declaration, which the committed model has (`DEFERRED.md` [D-029](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-029-toastermodelcheckverify_holdss-line-parser-cannot-read-a-verify---solve-verdict-for-an-assert-satisfyassert-not-satisfy-declaration)); and, independent of that parser gap, sysml-toolkit's Z3 backend never actually composes `efficiencyBounded` and `deliveredEnergy` into this lemma's own check at all, whether by same-scope membership, inheritance, or a chained calc call (`DEFERRED.md` [D-030](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-030-two-independently-declared-assert-constraints-are-never-composed-by-verify---solve-whether-sibling-or-inherited), [D-031](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-031-a-chained-calcfunction-invocation-inside-an-assert-constraint-is-not-in-z3s-solvable-fragment), both confirmed below). The lemma below is therefore a hand-restated real-arithmetic fact of the same shape as the original relation, not a solver-checked reference to it." ] }, { @@ -226,12 +226,15 @@ } ], "source": [ + "import os\n", "from pathlib import Path\n", "from toaster import modelcheck as mc\n", + "from toaster.tools import resolve_library, resolve_sysmlv2, resolve_z3, tool_env\n", "\n", - "BINARY = Path.home() / \"Documents/GitHub/sysml-toolkit/target/release/sysmlv2\"\n", - "LIB = Path.home() / \"Documents/GitHub/sysml-toolkit/spec-refs/SysML-v2-Release/sysml.library\"\n", - "assert BINARY.exists(), f\"sysmlv2 binary not found at {BINARY} (see work contract PASS4-008)\"\n", + "BINARY = resolve_sysmlv2()\n", + "LIB = resolve_library()\n", + "Z3 = resolve_z3()\n", + "os.environ[\"PATH\"] = tool_env()[\"PATH\"] # later cells call the CLI without z3=; it finds z3 on PATH\n", "\n", "# A fixed scratch directory and fixed filenames (not tempfile.NamedTemporaryFile's own\n", "# randomized name) keep this notebook's own printed output, including any error message\n", @@ -244,7 +247,8 @@ " p.write_text(content)\n", " return p\n", "\n", - "# Restates deliveredEnergyBoundedBySupply exactly as committed in ch08-cumulative.sysml,\n", + "# Restates deliveredEnergyBoundedBySupply exactly as committed in ch08-cumulative.sysml\n", + "# (https://github.com/Open-MBEE/toaster/blob/main/models/ch08-cumulative.sysml),\n", "# with a minimal HeatGenerator stub, and no assert satisfy declaration (D-029).\n", "COMPANION_POSITIVE = \"\"\"\\\n", "package ConservationCheck {\n", @@ -277,7 +281,7 @@ "\n", "companion_path = _write_companion(\"conservation_check.sysml\", COMPANION_POSITIVE)\n", "\n", - "pos_verdicts = mc.verify_holds(str(companion_path), lib=str(LIB), binary=str(BINARY), solve=True)\n", + "pos_verdicts = mc.verify_holds(str(companion_path), lib=str(LIB), binary=str(BINARY), z3=str(Z3), solve=True)\n", "pos_verdict = pos_verdicts[0]\n", "print(f\"[{pos_verdict.status}] {pos_verdict.element} ({_ascii(pos_verdict.reason)})\")\n", "assert pos_verdict.status == \"satisfied\"\n", @@ -285,7 +289,7 @@ "# the note beside D-029): this checks the reason text actually names z3, not just the\n", "# status, confirming the solver itself resolved this one.\n", "assert \"z3\" in pos_verdict.reason\n", - "assert mc.holds(str(companion_path), lib=str(LIB), binary=str(BINARY), solve=True) is True\n", + "assert mc.holds(str(companion_path), lib=str(LIB), binary=str(BINARY), z3=str(Z3), solve=True) is True\n", "print(\"\\nProved for every value of heatGenCheck.efficiency, heatGenCheck.power and \"\n", " \"heatGenCheckDuration the antecedent admits, not evaluated at one.\")" ] @@ -501,7 +505,7 @@ "id": "cell-19", "metadata": {}, "source": [ - "This fragment is the same text now committed in `models/ch08-cumulative.sysml` onward, alongside the `ReviewRecordRef` definition (carried forward since Chapter 2); loading the cumulative model (the `model.ok` cell above, unchanged) is what makes this a real, checkable tag rather than an assertion the reader has to trust." + "This fragment is the same text now committed in [`models/ch08-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch08-cumulative.sysml) onward, alongside the `ReviewRecordRef` definition (carried forward since Chapter 2); loading the cumulative model (the `model.ok` cell above, unchanged) is what makes this a real, checkable tag rather than an assertion the reader has to trust." ] }, { @@ -829,7 +833,7 @@ "id": "cell-34", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch08/exercise.ipynb`: it works through the same `verify_satisfaction()` and stale-detection pattern on its own, separate coffee-maker exercise model." + "Try the chapter exercise in [`exercises/ch08/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch08/exercise.ipynb): it works through the same `verify_satisfaction()` and stale-detection pattern on its own, separate coffee-maker exercise model." ] } ], diff --git a/chapters/ch08-checking/03-revision-flow.ipynb b/chapters/ch08-checking/03-revision-flow.ipynb index e061177..73d4f57 100644 --- a/chapters/ch08-checking/03-revision-flow.ipynb +++ b/chapters/ch08-checking/03-revision-flow.ipynb @@ -15,7 +15,7 @@ "id": "cell-01", "metadata": {}, "source": [ - "Evidence records are only as good as the model they reference. `check_stale()` compares a `ReviewRecord`'s stored `content_hash` against a model's current source text. This notebook loads the `AS-C08` record [Ch8-02](02-violation-witness.ipynb) persisted with `save_record()`, via `load_record()`, confirms it is current against `ch08-cumulative.sysml`, then loosens the lemma's own bound and shows the record go stale." + "Evidence records are only as good as the model they reference. `check_stale()` compares a `ReviewRecord`'s stored `content_hash` against a model's current source text. This notebook loads the `AS-C08` record [Ch8-02](02-violation-witness.ipynb) persisted with `save_record()`, via `load_record()`, confirms it is current against [`ch08-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch08-cumulative.sysml), then loosens the lemma's own bound and shows the record go stale." ] }, { @@ -97,7 +97,7 @@ "id": "cell-05", "metadata": {}, "source": [ - "`AS-C08` is loaded here from the record notebook 02 persisted with `save_record()`, already hashed against the `ch08-cumulative.sysml` source it was built from, so `check_stale()` has something to compare against." + "`AS-C08` is loaded here from the record notebook 02 persisted with `save_record()`, already hashed against the [`ch08-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch08-cumulative.sysml) source it was built from, so `check_stale()` has something to compare against." ] }, { @@ -191,7 +191,7 @@ "id": "cell-10", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch08/exercise.ipynb`: it works through the same `verify_satisfaction()` and stale-detection pattern on its own, separate coffee-maker exercise model." + "Try the chapter exercise in [`exercises/ch08/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch08/exercise.ipynb): it works through the same `verify_satisfaction()` and stale-detection pattern on its own, separate coffee-maker exercise model." ] } ], diff --git a/chapters/ch08-checking/conclusion.md b/chapters/ch08-checking/conclusion.md index eb4768b..17d77de 100644 --- a/chapters/ch08-checking/conclusion.md +++ b/chapters/ch08-checking/conclusion.md @@ -6,11 +6,11 @@ title: Conclusion ## What we built -`models/ch08-cumulative.sysml` adds one new construct to Chapter 7's content: `deliveredEnergyBoundedBySupply`, an `assert constraint` stating a real-arithmetic lemma of the same shape as `HeatGenerator`'s conservation entailment (`efficiencyBounded` together with `deliveredEnergy`'s own definition would guarantee delivered energy never exceeds supplied energy). `toaster.modelcheck.verify_holds()` proves this lemma `satisfied` for every value of efficiency, power and duration a companion restatement admits, using `sysml-toolkit`'s real `verify --solve` (Z3), reports a fully broken variant of the same shape as `violated`, and reports a merely weakened variant as `undecided`, with a genuine Z3-found witness. A ReviewRecord (`AS-C08`) cites the proof as its evidence and states plainly what it does not establish. +[`models/ch08-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch08-cumulative.sysml) adds one new construct to Chapter 7's content: `deliveredEnergyBoundedBySupply`, an `assert constraint` stating a real-arithmetic lemma of the same shape as `HeatGenerator`'s conservation entailment (`efficiencyBounded` together with `deliveredEnergy`'s own definition would guarantee delivered energy never exceeds supplied energy). `toaster.modelcheck.verify_holds()` proves this lemma `satisfied` for every value of efficiency, power and duration a companion restatement admits, using `sysml-toolkit`'s real `verify --solve` (Z3), reports a fully broken variant of the same shape as `violated`, and reports a merely weakened variant as `undecided`, with a genuine Z3-found witness. A ReviewRecord (`AS-C08`) cites the proof as its evidence and states plainly what it does not establish. ## What this establishes -This is the first chapter that genuinely delivers a model-checked property, not a point evaluation, though the property proved is a hand-restated lemma, not a solver-checked reference to the model's own original elements: this toolchain does not compose two separately declared `assert constraint`s (whether sibling or inherited) and cannot reason through a chained calc invocation, confirmed directly by loosening `efficiencyBounded`'s own bound and by doubling `deliveredEnergy`'s own definition, neither of which moves the lemma's verdict at all (`DEFERRED.md` D-030, D-031). `verify_satisfaction()` (Chapter 3 onward) tells you whether one candidate's own fixed values satisfy a requirement, and stays exactly that kind of check; `verify_holds()` tells you whether a stated lemma holds for every value its unbound features could take, proved or refuted, or genuinely left undecided when it does neither. All three are real, distinguishable outcomes, and this chapter keeps them distinguished throughout: the model's existing `not satisfy timely by slow`, `satisfy heatGenerationReq by rated` and `not satisfy heatGenerationReq by weak` claims are still observed at one point each (`cycleTime` is not yet derived from anything, so the `timely` claims show evaluation mechanics, not a finding about the toaster's actual timing; `HeatGenerator::power` is a chosen physical rating, so the `heatGenerationReq` claims are legitimate feasibility checks of a design choice); `deliveredEnergyBoundedBySupply` is proved for every value its unbound features admit, within the limits stated above. `conformance.report()`'s `satisfaction-claims-evaluated` check, already scheduled from Chapter 3 onward and already passing on ch03 through ch07, now demonstrably passes on ch08's own fixture for the first time too, because the model is language conformant here and carries no false claims. +This is the first chapter that genuinely delivers a model-checked property, not a point evaluation, though the property proved is a hand-restated lemma, not a solver-checked reference to the model's own original elements: sysml-toolkit's `verify --solve` does not compose two separately declared `assert constraint`s (whether sibling or inherited) and cannot reason through a chained calc invocation, confirmed directly by loosening `efficiencyBounded`'s own bound and by doubling `deliveredEnergy`'s own definition, neither of which moves the lemma's verdict at all (`DEFERRED.md` [D-030](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-030-two-independently-declared-assert-constraints-are-never-composed-by-verify---solve-whether-sibling-or-inherited), [D-031](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-031-a-chained-calcfunction-invocation-inside-an-assert-constraint-is-not-in-z3s-solvable-fragment)). `verify_satisfaction()` (Chapter 3 onward) tells you whether one candidate's own fixed values satisfy a requirement, and stays exactly that kind of check; `verify_holds()` tells you whether a stated lemma holds for every value its unbound features could take, proved or refuted, or genuinely left undecided when it does neither. All three are real, distinguishable outcomes, and this chapter keeps them distinguished throughout: the model's existing `not satisfy timely by slow`, `satisfy heatGenerationReq by rated` and `not satisfy heatGenerationReq by weak` claims are still observed at one point each (`cycleTime` is not yet derived from anything, so the `timely` claims show evaluation mechanics, not a finding about the toaster's actual timing; `HeatGenerator::power` is a chosen physical rating, so the `heatGenerationReq` claims are legitimate feasibility checks of a design choice); `deliveredEnergyBoundedBySupply` is proved for every value its unbound features admit, within the limits stated above. `conformance.report()`'s `satisfaction-claims-evaluated` check, already scheduled from Chapter 3 onward and already passing on ch03 through ch07, now demonstrably passes on ch08's own fixture for the first time too, because the model is language conformant here and carries no false claims. ## What comes next @@ -18,4 +18,4 @@ Chapter 9 broadens the analysis again: instead of one proved lemma and a handful ## Exercise -See `exercises/ch08/exercise.ipynb`: it works through the same `verify_satisfaction()` and stale-detection pattern on its own, separate coffee-maker exercise model. +See [`exercises/ch08/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch08/exercise.ipynb): it works through the same `verify_satisfaction()` and stale-detection pattern on its own, separate coffee-maker exercise model. diff --git a/chapters/ch08-checking/index.md b/chapters/ch08-checking/index.md index 84a2ac3..1d50cfd 100644 --- a/chapters/ch08-checking/index.md +++ b/chapters/ch08-checking/index.md @@ -8,7 +8,7 @@ title: Overview This chapter asks a different question from Chapter 3's and Chapter 6's own: not "does the model's own entered value satisfy a threshold" (point evaluation, which those chapters already do), but "does a real-arithmetic lemma hold for every value its unbound features could take" (a genuinely formal, model-checked property). -After completing this chapter, the model has grown by one new construct, `deliveredEnergyBoundedBySupply`, a real SysML `assert constraint` stating a real-arithmetic lemma of the same shape as the conservation entailment that Chapter 7's `efficiencyBounded` and `deliveredEnergy` already imply. It is proved, for every value of efficiency, power and duration a hand-restated companion admits, by a real Z3-backed solver (`sysml-toolkit`'s `verify --solve`, wrapped by `toaster.modelcheck`), not evaluated at one point. It is a hand-restated copy, not a solver-checked reference to `HeatGenerator`'s own `efficiencyBounded` or `deliveredEnergy`: this toolchain does not compose separately declared constraints, and cannot reason through a chained calc invocation (`DEFERRED.md` D-030, D-031). +After completing this chapter, the model has grown by one new construct, `deliveredEnergyBoundedBySupply`, a real SysML `assert constraint` stating a real-arithmetic lemma of the same shape as the conservation entailment that Chapter 7's `efficiencyBounded` and `deliveredEnergy` already imply. It is proved, for every value of efficiency, power and duration a hand-restated companion admits, by a real Z3-backed solver (`sysml-toolkit`'s `verify --solve`, wrapped by `toaster.modelcheck`), not evaluated at one point. It is a hand-restated copy, not a solver-checked reference to `HeatGenerator`'s own `efficiencyBounded` or `deliveredEnergy`: sysml-toolkit's `verify --solve` does not compose separately declared constraints, and cannot reason through a chained calc invocation (`DEFERRED.md` [D-030](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-030-two-independently-declared-assert-constraints-are-never-composed-by-verify---solve-whether-sibling-or-inherited), [D-031](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-031-a-chained-calcfunction-invocation-inside-an-assert-constraint-is-not-in-z3s-solvable-fragment)). ## Ingredients @@ -20,13 +20,13 @@ After completing this chapter, the model has grown by one new construct, `delive ## Equipment -See [docs/setup.md](../../docs/setup.md) for environment setup. This chapter additionally needs a local build of `sysml-toolkit`'s `sysmlv2` CLI and the `z3` binary (see `tests/test_modelcheck.py` for the exact paths this repository's own tests use); without them, `verify_holds()` cannot run. +See [Getting Started](../../docs/setup.md) for environment setup. This chapter additionally needs a local build of `sysml-toolkit`'s `sysmlv2` CLI and the `z3` binary (see [`tests/test_modelcheck.py`](https://github.com/Open-MBEE/toaster/blob/main/tests/test_modelcheck.py) for the exact paths this repository's own tests use); without them, `verify_holds()` cannot run. ## Method -Notebook 01 states the new lemma directly in `models/ch08-cumulative.sysml`. Notebook 02 evaluates the model's existing `assert satisfy` claims with `verify_satisfaction()` (point evaluation, unchanged since Chapter 3 and Chapter 6), proves the lemma with `verify_holds()` (universal, over every value a small companion restatement's unbound features can take), shows a fully broken variant of the same shape reported `violated`, and shows a merely weakened variant reported `undecided`, with `holds()` correctly refusing to collapse that into a clean pass or fail. Notebook 03 shows the resulting judgment record is not static: loosening the lemma's own bound makes the record's stored hash stop matching the model. +Notebook 01 states the new lemma directly in [`models/ch08-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch08-cumulative.sysml). Notebook 02 evaluates the model's existing `assert satisfy` claims with `verify_satisfaction()` (point evaluation, unchanged since Chapter 3 and Chapter 6), proves the lemma with `verify_holds()` (universal, over every value a small companion restatement's unbound features can take), shows a fully broken variant of the same shape reported `violated`, and shows a merely weakened variant reported `undecided`, with `holds()` correctly refusing to collapse that into a clean pass or fail. Notebook 03 shows the resulting judgment record is not static: loosening the lemma's own bound makes the record's stored hash stop matching the model. -Chapter 7's parameter sweep samples 50 specific power values and shows where a threshold is crossed among those samples; it says nothing about values it did not sample. `deliveredEnergyBoundedBySupply`, when genuinely proved, holds for every value in its stated domain at once, not just the ones anyone thought to try. That is the real difference between checking scenarios and model checking a property (AGENTS.md 1.1 item 5): simulation explores; a proof, when it succeeds, covers the whole space it is stated over. +Chapter 7's parameter sweep samples 50 specific power values and shows where a threshold is crossed among those samples; it says nothing about values it did not sample. `deliveredEnergyBoundedBySupply`, when genuinely proved, holds for every value in its stated domain at once, not just the ones anyone thought to try. That is the real difference between checking scenarios and model checking a property: simulation explores; a proof, when it succeeds, covers the whole space it is stated over. ## Expected result @@ -34,4 +34,4 @@ After running all three notebooks: `deliveredEnergyBoundedBySupply` is confirmed ## Experiment -Try the [Chapter 8 exercise](../../exercises/ch08/exercise.ipynb): it works through the same `verify_satisfaction()` and stale-detection pattern on its own, separate coffee-maker exercise model. +Try the [Chapter 8 exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch08/exercise.ipynb): it works through the same `verify_satisfaction()` and stale-detection pattern on its own, separate coffee-maker exercise model. diff --git a/chapters/ch09-coverage-sufficiency/01-requirement-coverage.ipynb b/chapters/ch09-coverage-sufficiency/01-requirement-coverage.ipynb index 0151bab..3901f2b 100644 --- a/chapters/ch09-coverage-sufficiency/01-requirement-coverage.ipynb +++ b/chapters/ch09-coverage-sufficiency/01-requirement-coverage.ipynb @@ -15,7 +15,7 @@ "id": "bc30d0cd", "metadata": {}, "source": [ - "Chapters 3 through 8 declared satisfy claims one at a time, each notebook adding or checking a single candidate against a single requirement. This chapter asks a different question across the whole model at once: for every requirement usage the model declares, has anyone actually claimed a candidate satisfies it, and of which polarity? This notebook adds no new model element: `models/ch08-cumulative.sysml`, the real, current cumulative model Chapter 8 committed, already has everything this query needs, so this chapter queries it directly rather than growing a `models/ch09-cumulative.sysml` file that would carry nothing new (a design choice recorded in this chapter's own [index.md](index.md))." + "Chapters 3 through 8 declared satisfy claims one at a time, each notebook adding or checking a single candidate against a single requirement. This chapter asks a different question across the whole model at once: for every requirement usage the model declares, has anyone actually claimed a candidate satisfies it, and of which polarity? This notebook adds no new model element: [`models/ch08-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch08-cumulative.sysml), the real, current cumulative model Chapter 8 committed, already has everything this query needs, so this chapter queries it directly rather than growing a `models/ch09-cumulative.sysml` file that would carry nothing new (a design choice recorded in this chapter's own [Overview](index.md))." ] }, { @@ -92,7 +92,7 @@ "id": "12e74fee", "metadata": {}, "source": [ - "With the model loaded, the coverage report starts from the same two surfaces every chapter's own satisfy claim already used: every named `RequirementUsage` from `model.query()`, and every `SatisfyRequirementUsage`, named or not, from `get_satisfy_relationships()` (D-001: `model.query()` returns none of these directly). Each raw satisfy element carries `subsets` (the requirement it claims against), `subject` (the candidate, when there is one), `isNegated` (a positive or a negative claim) and `sysx:declaredKeyword` (`\"verify\"` for a verification-case objective)." + "With the model loaded, the coverage report starts from the same two surfaces every chapter's own satisfy claim already used: every named `RequirementUsage` from `model.query()`, and every `SatisfyRequirementUsage`, named or not, from `get_satisfy_relationships()` ([D-001](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-001-ch9-satisfy-coverage-uses-to_api_json-workaround): `model.query()` returns none of these directly). Each raw satisfy element carries `subsets` (the requirement it claims against), `subject` (the candidate, when there is one), `isNegated` (a positive or a negative claim) and `sysx:declaredKeyword` (`\"verify\"` for a verification-case objective)." ] }, { @@ -220,7 +220,7 @@ "id": "7e560605", "metadata": {}, "source": [ - "`heatGenerationReq` is covered on both sides: `rated` was checked and found to satisfy it, `weak` was checked and found not to. `timely` has never had a positive satisfy claim at all. The only claim against it is negative (`slow` does not satisfy it), and the only other reference is `TimelyToastTest`'s own objective, which claims nothing about a subject. This is a real, present gap in the tutorial's own accumulated model, not a scratch example built to fail on purpose: `nominal`, the usage meant to represent the toaster actually meeting its timing requirement, has no `assert satisfy timely by nominal` anywhere in `models/ch08-cumulative.sysml`. This does **not** mean `nominal` fails `timely`: `Toaster::cycleTime` is still a settable attribute, not derived from anything (unchanged since Chapter 2), so no one has ever actually checked whether `nominal` satisfies `timely` in the first place. The gap this query finds is an absence of a claim, not evidence of a failed one." + "`heatGenerationReq` is covered on both sides: `rated` was checked and found to satisfy it, `weak` was checked and found not to. `timely` has never had a positive satisfy claim at all. The only claim against it is negative (`slow` does not satisfy it), and the only other reference is `TimelyToastTest`'s own objective, which claims nothing about a subject. This is a real, present gap in the tutorial's own accumulated model, not a scratch example built to fail on purpose: `nominal`, the usage meant to represent the toaster actually meeting its timing requirement, has no `assert satisfy timely by nominal` anywhere in [`models/ch08-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch08-cumulative.sysml). This does **not** mean `nominal` fails `timely`: `Toaster::cycleTime` is still a settable attribute, not derived from anything (unchanged since Chapter 2), so no one has ever actually checked whether `nominal` satisfies `timely` in the first place. The gap this query finds is an absence of a claim, not evidence of a failed one." ] }, { @@ -228,7 +228,7 @@ "id": "0b765e53", "metadata": {}, "source": [ - "Is this join trustworthy, or could a differently-written join reach a different answer? `src/toaster/query.py` already ships a `requirement_coverage()` helper for exactly this kind of question (named in the `opensysml-query` skill's own cookbook), so the natural check is not to invent a second mechanism but to see whether it agrees. First, though, a genuine wrong way to join the same data, one this repository's own helper actually had until this chapter's own work found and fixed it: a join that counts ANY claim, positive or negative, as coverage." + "Is this join trustworthy, or could a differently-written join reach a different answer? [`src/toaster/query.py`](https://github.com/Open-MBEE/toaster/blob/main/src/toaster/query.py) already ships a `requirement_coverage()` helper for exactly this kind of question, so the natural check is not to invent a second mechanism but to see whether it agrees. First, though, a genuine wrong way to join the same data, one this repository's own helper actually had until this chapter's own work found and fixed it: a join that counts ANY claim, positive or negative, as coverage." ] }, { @@ -274,7 +274,7 @@ "id": "762d34e7", "metadata": {}, "source": [ - "A polarity-blind join reports `timely` as covered, using `slow`'s own FAILING claim as the evidence -- exactly backwards. This was not a hypothetical risk: `src/toaster/query.py`'s `requirement_coverage()` carried this exact bug until this chapter's own round of work found and fixed it, ignoring `isNegated` entirely and reporting `weak`'s failing claim against `heatGenerationReq` as coverage too. The fixed version now splits by polarity the same way this notebook's own join always has, and also excludes `TimelyToastTest`'s own auto-generated, unnamed requirement usage (the objective's own bookkeeping wrapper, not a design requirement) from the requirement list it reports on." + "A polarity-blind join reports `timely` as covered, using `slow`'s own FAILING claim as the evidence -- exactly backwards. This was not a hypothetical risk: [`src/toaster/query.py`](https://github.com/Open-MBEE/toaster/blob/main/src/toaster/query.py)'s `requirement_coverage()` carried this exact bug until this chapter found and fixed it, ignoring `isNegated` entirely and reporting `weak`'s failing claim against `heatGenerationReq` as coverage too. The fixed version now splits by polarity the same way this notebook's own join always has, and also excludes `TimelyToastTest`'s own auto-generated, unnamed requirement usage (the objective's own bookkeeping wrapper, not a design requirement) from the requirement list it reports on." ] }, { @@ -336,7 +336,7 @@ "id": "71f56107", "metadata": {}, "source": [ - "The requirement usages and satisfy relationships declared in `models/ch08-cumulative.sysml`, queried above through two differently-written joins, produced the same coverage gap both times, and a third, deliberately wrong join showed exactly what goes missing when polarity is dropped -- confirming the finding is a property of the model itself, not an artifact of how it was asked." + "The requirement usages and satisfy relationships declared in [`models/ch08-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch08-cumulative.sysml), queried above through two differently-written joins, produced the same coverage gap both times, and a third, deliberately wrong join showed exactly what goes missing when polarity is dropped -- confirming the finding is a property of the model itself, not an artifact of how it was asked." ] }, { @@ -344,7 +344,7 @@ "id": "33486eff", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch09/exercise.ipynb`: it asks you to produce a coverage report over your own coffee-maker model, using the query-and-join pattern this notebook builds." + "Try the chapter exercise in [`exercises/ch09/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch09/exercise.ipynb): it asks you to produce a coverage report over your own coffee-maker model, using the query-and-join pattern this notebook builds." ] } ], diff --git a/chapters/ch09-coverage-sufficiency/02-evidence-completeness.ipynb b/chapters/ch09-coverage-sufficiency/02-evidence-completeness.ipynb index fd03e85..7f8bbd7 100644 --- a/chapters/ch09-coverage-sufficiency/02-evidence-completeness.ipynb +++ b/chapters/ch09-coverage-sufficiency/02-evidence-completeness.ipynb @@ -7,7 +7,7 @@ "source": [ "# evidence sufficiency\n", "\n", - "This notebook introduces Hawkins' sufficiency check (`uv run python -m glossary tutorial sufficiency`), applied to two real `ReviewRecord`s already built earlier in this tutorial; after running it you can tell, for each, whether its own `counterevidence` and `residual_uncertainties` are genuinely substantive and whether its `engineering_conclusion` honestly matches what its own evidence supports." + "This notebook introduces Hawkins' sufficiency check (see the [sufficiency](../../docs/glossary.md#sufficiency) glossary entry), applied to two real `ReviewRecord`s already built earlier in this tutorial; after running it you can tell, for each, whether its own `counterevidence` and `residual_uncertainties` are genuinely substantive and whether its `engineering_conclusion` honestly matches what its own evidence supports." ] }, { @@ -15,7 +15,7 @@ "id": "9b78b2f5", "metadata": {}, "source": [ - "This tutorial has no central registry of every `ReviewRecord` it has ever built: each chapter's notebook constructs its own records as local Python objects, per the construction-zone pattern (`toaster-review-protocol`), with nothing shared beyond the file each one lives in. A search of `src/toaster` for anything resembling a record store or registry (a dataclass, a database, a persisted list) found none. So this notebook does not scan \"every record the tutorial has ever produced\" -- that would need a real registry, which does not exist. Instead it reconstructs two real records verbatim, field for field, from `chapters/ch06-recursive-decomp/02-second-level.ipynb` (`AS-C06`, a mechanism-selection judgment argued from a domain premise) and `chapters/ch08-checking/02-violation-witness.ipynb` (`AS-C08`, grounded in Chapter 8's real Z3 proof), and demonstrates what a sufficiency check on each one actually looks like. What follows demonstrates the mechanics of the check on a small, representative sample, not an exhaustive audit of every judgment record this tutorial has ever produced." + "This tutorial has no central registry of every `ReviewRecord` it has ever built: each chapter's notebook constructs its own records as local Python objects, per the construction-zone pattern, with nothing shared beyond the file each one lives in. A search of [`src/toaster`](https://github.com/Open-MBEE/toaster/tree/main/src/toaster) for anything resembling a record store or registry (a dataclass, a database, a persisted list) found none. So this notebook does not scan \"every record the tutorial has ever produced\" -- that would need a real registry, which does not exist. Instead it reconstructs two real records verbatim, field for field, from [Chapter 6 notebook 02](../ch06-recursive-decomp/02-second-level.ipynb) (`AS-C06`, a mechanism-selection judgment argued from a domain premise) and [Chapter 8 notebook 02](../ch08-checking/02-violation-witness.ipynb) (`AS-C08`, grounded in Chapter 8's real Z3 proof), and demonstrates what a sufficiency check on each one actually looks like. What follows demonstrates the mechanics of the check on a small, representative sample, not an exhaustive audit of every judgment record this tutorial has ever produced." ] }, { @@ -97,7 +97,7 @@ "id": "655df44e", "metadata": {}, "source": [ - "`AS-C06`, loaded below via `load_record()` from the record Chapter 6 notebook 02 persisted with `save_record()`: a mechanism-selection judgment, argued from a domain premise about how a resistive element and a combustion burner each respond to a discrete timing signal. Its `content_hash` is computed against `models/ch06-cumulative.sysml`, the real model it was actually written against, not against the current model this chapter uses -- the same file it was built from, unchanged. Two of its own field strings say \"above\" and \"notebook 01\", pointing at Chapter 6's own earlier cells; they are quoted here exactly as Chapter 6 wrote them, since this is what the record itself actually says, not a rewrite of it." + "`AS-C06`, loaded below via `load_record()` from the record Chapter 6 notebook 02 persisted with `save_record()`: a mechanism-selection judgment, argued from a domain premise about how a resistive element and a combustion burner each respond to a discrete timing signal. Its `content_hash` is computed against [`models/ch06-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch06-cumulative.sysml), the real model it was actually written against, not against the current model this chapter uses -- the same file it was built from, unchanged. Two of its own field strings say \"above\" and \"notebook 01\", pointing at Chapter 6's own earlier cells; they are quoted here exactly as Chapter 6 wrote them, since this is what the record itself actually says, not a rewrite of it." ] }, { @@ -135,7 +135,7 @@ "id": "3e8a56b3", "metadata": {}, "source": [ - "`AS-C08`, loaded the same way, via `load_record()`, from the record Chapter 8 notebook 02 persisted with `save_record()`: the record grounded in the chapter's real Z3 proof of `deliveredEnergyBoundedBySupply`. Its `content_hash` is computed against `models/ch08-cumulative.sysml` -- the same file this chapter's own notebooks already load, since Chapter 8 is the real, current model. Its `evidence_refs` entry is copied from what that notebook's own proof actually printed, not restated." + "`AS-C08`, loaded the same way, via `load_record()`, from the record Chapter 8 notebook 02 persisted with `save_record()`: the record grounded in the chapter's real Z3 proof of `deliveredEnergyBoundedBySupply`. Its `content_hash` is computed against [`models/ch08-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch08-cumulative.sysml) -- the same file this chapter's own notebooks already load, since Chapter 8 is the real, current model. Its `evidence_refs` entry is copied from what that notebook's own proof actually printed, not restated." ] }, { @@ -267,7 +267,7 @@ "id": "0c378f59", "metadata": {}, "source": [ - "Both read as substantive, not placeholder: `AS-C06`'s counterevidence names a specific competing design (a valve-controlled burner) and a specific unmodeled relation (Joule heating), not a generic hedge; `AS-C08`'s counterevidence names three specific, cited toolchain limits (`DEFERRED.md` D-029, D-030, D-031) and the exact edits that were tried and failed to move the lemma's verdict. Where the two records differ most is what `engineering_conclusion` claims. `AS-C06` stays `undetermined` even though the record does make a selection: its own counterevidence admits the selection is argued from a domain premise, not a trade study, so `undetermined` is the honest reading of what the evidence actually supports, not underclaiming. `AS-C08` is `supported`: because its own `claim` field is already narrowed to the hand-restated lemma, not to `HeatGenerator`'s original elements, the thing that actually got a Z3 proof is exactly what the record claims was established." + "Both read as substantive, not placeholder: `AS-C06`'s counterevidence names a specific competing design (a valve-controlled burner) and a specific unmodeled relation (Joule heating), not a generic hedge; `AS-C08`'s counterevidence names three specific, cited toolchain limits (`DEFERRED.md` [D-029](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-029-toastermodelcheckverify_holdss-line-parser-cannot-read-a-verify---solve-verdict-for-an-assert-satisfyassert-not-satisfy-declaration), [D-030](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-030-two-independently-declared-assert-constraints-are-never-composed-by-verify---solve-whether-sibling-or-inherited), [D-031](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-031-a-chained-calcfunction-invocation-inside-an-assert-constraint-is-not-in-z3s-solvable-fragment)) and the exact edits that were tried and failed to move the lemma's verdict. Where the two records differ most is what `engineering_conclusion` claims. `AS-C06` stays `undetermined` even though the record does make a selection: its own counterevidence admits the selection is argued from a domain premise, not a trade study, so `undetermined` is the honest reading of what the evidence actually supports, not underclaiming. `AS-C08` is `supported`: because its own `claim` field is already narrowed to the hand-restated lemma, not to `HeatGenerator`'s original elements, the thing that actually got a Z3 proof is exactly what the record claims was established." ] }, { @@ -357,7 +357,7 @@ "id": "b9643c57", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch09/exercise.ipynb`: it asks you to apply the same sufficiency reading, including the premises argument above, to two of your own already-built judgment records." + "Try the chapter exercise in [`exercises/ch09/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch09/exercise.ipynb): it asks you to apply the same sufficiency reading, including the premises argument above, to two of your own already-built judgment records." ] } ], diff --git a/chapters/ch09-coverage-sufficiency/03-stale-detection.ipynb b/chapters/ch09-coverage-sufficiency/03-stale-detection.ipynb index f130f6e..edb2bc3 100644 --- a/chapters/ch09-coverage-sufficiency/03-stale-detection.ipynb +++ b/chapters/ch09-coverage-sufficiency/03-stale-detection.ipynb @@ -97,7 +97,7 @@ "id": "e833756d", "metadata": {}, "source": [ - "`AS-C06` and `AS-C08`, loaded below via `load_record()`, the exact same records notebook 02 loads (see notebook 02 for the narration behind each field): `AS-C06`'s `content_hash` against `models/ch06-cumulative.sysml`, the model it was actually written against; `AS-C08`'s against `models/ch08-cumulative.sysml`, the real, current model this notebook also loads above." + "`AS-C06` and `AS-C08`, loaded below via `load_record()`, the exact same records notebook 02 loads (see notebook 02 for the narration behind each field): `AS-C06`'s `content_hash` against [`models/ch06-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch06-cumulative.sysml), the model it was actually written against; `AS-C08`'s against [`models/ch08-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch08-cumulative.sysml), the real, current model this notebook also loads above." ] }, { @@ -173,7 +173,7 @@ "id": "92114fda", "metadata": {}, "source": [ - "Checked against `models/ch08-cumulative.sysml` before any edit: `AS-C06` was written against `models/ch06-cumulative.sysml`, and is already stale, but not from mere unrelated bookkeeping. `AS-C08` was written against this exact file, so it is still current." + "Checked against [`models/ch08-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch08-cumulative.sysml) before any edit: `AS-C06` was written against [`models/ch06-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch06-cumulative.sysml), and is already stale, but not from mere unrelated bookkeeping. `AS-C08` was written against this exact file, so it is still current." ] }, { @@ -217,7 +217,7 @@ "id": "283a8248", "metadata": {}, "source": [ - "`AS-C06`'s own `counterevidence` names something specific as still missing: \"Joule heating's own relation ... is still not modeled, so efficiency and response-time comparisons remain out of reach either way.\" A direct diff between `models/ch06-cumulative.sysml` and the current model shows that gap partly overtaken, not exactly closed: Chapter 7 added `efficiency`, `efficiencyBounded` and `deliveredEnergy` to `HeatGenerator`, squarely inside the scope `AS-C06` itself declares (`ToasterDemo::HeatGenerator and its realizations`), so an efficiency comparison can now at least be started. The Joule-heating relation the same counterevidence names, and any response-time comparison, are still not modeled. `AS-C06` is not just formally stale, a hash mismatch; its own stated uncertainty has been substantively overtaken by real, subsequent model growth -- a record that could now, at least in part, actually be re-checked against something that did not previously exist." + "`AS-C06`'s own `counterevidence` names something specific as still missing: \"Joule heating's own relation ... is still not modeled, so efficiency and response-time comparisons remain out of reach either way.\" A direct diff between [`models/ch06-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch06-cumulative.sysml) and the current model shows that gap partly overtaken, not exactly closed: Chapter 7 added `efficiency`, `efficiencyBounded` and `deliveredEnergy` to `HeatGenerator`, squarely inside the scope `AS-C06` itself declares (`ToasterDemo::HeatGenerator and its realizations`), so an efficiency comparison can now at least be started. The Joule-heating relation the same counterevidence names, and any response-time comparison, are still not modeled. `AS-C06` is not just formally stale, a hash mismatch; its own stated uncertainty has been substantively overtaken by real, subsequent model growth -- a record that could now, at least in part, actually be re-checked against something that did not previously exist." ] }, { @@ -337,7 +337,7 @@ "id": "509ec634", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch09/exercise.ipynb`: it asks you to check your own two records for staleness, at scale, the same way this notebook checks `AS-C06` and `AS-C08` together." + "Try the chapter exercise in [`exercises/ch09/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch09/exercise.ipynb): it asks you to check your own two records for staleness, at scale, the same way this notebook checks `AS-C06` and `AS-C08` together." ] } ], diff --git a/chapters/ch09-coverage-sufficiency/conclusion.md b/chapters/ch09-coverage-sufficiency/conclusion.md index 2e72a14..c29bd6e 100644 --- a/chapters/ch09-coverage-sufficiency/conclusion.md +++ b/chapters/ch09-coverage-sufficiency/conclusion.md @@ -6,11 +6,11 @@ title: Conclusion ## What we built -No new model element: every notebook in this chapter queries `models/ch08-cumulative.sysml`, the real, current model Chapter 8 committed, directly. What changed is what can be asked of it: notebook 01 adds a real coverage report joining every requirement usage against every satisfy relationship, and along the way finds and fixes a real polarity-blind bug in the repository's own `requirement_coverage()` helper; notebook 02 applies Hawkins' sufficiency idea to two real, verbatim-reconstructed ReviewRecords (`AS-C06`, `AS-C08`); notebook 03 extends Chapter 8's own `check_stale()` demonstration from one record to two tracked together. +No new model element: every notebook in this chapter queries [`models/ch08-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch08-cumulative.sysml), the real, current model Chapter 8 committed, directly. What changed is what can be asked of it: notebook 01 adds a real coverage report joining every requirement usage against every satisfy relationship, and along the way finds and fixes a real polarity-blind bug in the repository's own `requirement_coverage()` helper; notebook 02 applies Hawkins' sufficiency idea to two real, verbatim-reconstructed ReviewRecords (`AS-C06`, `AS-C08`); notebook 03 extends Chapter 8's own `check_stale()` demonstration from one record to two tracked together. ## What this establishes -The coverage report is not a hypothetical exercise: it finds a real gap already present in this tutorial's own accumulated model. `heatGenerationReq` has been checked against two different candidates, one that meets it and one that does not; `timely` has only ever been checked against a candidate that fails it, plus a verification-case objective that names it without claiming anything about a subject. No one has ever claimed `nominal`, the usage meant to represent the toaster actually meeting its timing requirement, satisfies `timely` -- an absence of a claim, not evidence that it would fail one, since `cycleTime` is still not derived from anything. Finding this gap also surfaced a second, previously-unfixed one: `src/toaster/query.py`'s own `requirement_coverage()` helper, the one the `opensysml-query` skill's own cookbook names for exactly this kind of question, was polarity-blind, counting `slow`'s own failing claim against `timely` as coverage. Fixed here, and reproducible directly against the real model: `requirement_coverage()` now agrees exactly with this chapter's own hand-built join, and a deliberately polarity-blind version, built alongside it, shows precisely what goes missing when polarity is dropped. +The coverage report is not a hypothetical exercise: it finds a real gap already present in this tutorial's own accumulated model. `heatGenerationReq` has been checked against two different candidates, one that meets it and one that does not; `timely` has only ever been checked against a candidate that fails it, plus a verification-case objective that names it without claiming anything about a subject. No one has ever claimed `nominal`, the usage meant to represent the toaster actually meeting its timing requirement, satisfies `timely` -- an absence of a claim, not evidence that it would fail one, since `cycleTime` is still not derived from anything. Finding this gap also surfaced a second, previously-unfixed one: [`src/toaster/query.py`](https://github.com/Open-MBEE/toaster/blob/main/src/toaster/query.py)'s own `requirement_coverage()` helper, written for exactly this kind of question, was polarity-blind, counting `slow`'s own failing claim against `timely` as coverage. Fixed here, and reproducible directly against the real model: `requirement_coverage()` now agrees exactly with this chapter's own hand-built join, and a deliberately polarity-blind version, built alongside it, shows precisely what goes missing when polarity is dropped. Sufficiency, applied to two records rather than asserted about all of them, shows what the check actually demands: not merely a non-empty `counterevidence` field (the mechanized floor `validate_record()` already enforces, and which a placeholder like "None known." would still pass) but a substantive one, and an `engineering_conclusion` that matches what the record's own evidence supports -- `AS-C06` honestly stays `undetermined` because its selection rests on a domain premise, not a trade study; `AS-C08` is `supported` because its own claim was already narrowed to exactly what got proved, and its genuinely empty `premises` field is itself appropriate, not a gap, once the claim's own deductive (proved, not argued) character is read correctly. Staleness, checked across both records at once against the same real edit, shows two different, genuinely real histories, not two flavors of the same bookkeeping fact: `AS-C06`'s own counterevidence named a specific gap ("Joule heating's own relation ... is still not modeled, so efficiency and response-time comparisons remain out of reach") that Chapter 7 has since partly overtaken by adding `HeatGenerator`'s `efficiency`, `efficiencyBounded` and `deliveredEnergy` -- an efficiency comparison can now at least be started, though the Joule relation itself and any response-time comparison are still unmodeled; `AS-C08` goes stale from an edit to a different part of the file (the companion lemma's own bound) than the one its own residual specifically names as a risk (`HeatGenerator`'s `efficiencyBounded`/`deliveredEnergy`), caught only because its `content_hash` is computed over the whole file, a coarse, partial safeguard, not a targeted one. @@ -20,4 +20,4 @@ Chapter 10 builds the full traceability graph this chapter's coverage report onl ## Exercise -See `exercises/ch09/exercise.ipynb`: it asks you to produce a coverage report over your own coffee-maker model from Chapters 1-8 (notebook 01's own join), then apply the same sufficiency reading (notebook 02) and staleness check, at scale (notebook 03), to two of your own already-built judgment records. +See [`exercises/ch09/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch09/exercise.ipynb): it asks you to produce a coverage report over your own coffee-maker model from Chapters 1-8 (notebook 01's own join), then apply the same sufficiency reading (notebook 02) and staleness check, at scale (notebook 03), to two of your own already-built judgment records. diff --git a/chapters/ch09-coverage-sufficiency/index.md b/chapters/ch09-coverage-sufficiency/index.md index adee799..00922b9 100644 --- a/chapters/ch09-coverage-sufficiency/index.md +++ b/chapters/ch09-coverage-sufficiency/index.md @@ -8,7 +8,7 @@ title: Overview This chapter asks whether the model's own requirements have actually been checked, not just declared: for every requirement usage, has any candidate really been claimed to satisfy it, and of what polarity? It also applies Hawkins' sufficiency idea to two real judgment records this tutorial already built, and extends Chapter 8's staleness check from one record to several tracked at once. -This chapter adds no new model element. `models/ch08-cumulative.sysml`, the real, current model Chapter 8 committed, already has everything these notebooks query: two requirement usages (`timely`, `heatGenerationReq`) and four real satisfy relationships. Rather than growing a `models/ch09-cumulative.sysml` that would carry nothing new, every notebook in this chapter queries `models/ch08-cumulative.sysml` directly, and says so. This is a deliberate design choice, not an oversight: the chapter's own coverage-gap finding needs no new element, and the tutorial's own non-goal discipline (Chapter 6 and Chapter 7's own precedent of stating scope honestly) argues against adding one only to keep a file-per-chapter convention. +This chapter adds no new model element. [`models/ch08-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch08-cumulative.sysml), the real, current model Chapter 8 committed, already has everything these notebooks query: two requirement usages (`timely`, `heatGenerationReq`) and four real satisfy relationships. Rather than growing a `models/ch09-cumulative.sysml` that would carry nothing new, every notebook in this chapter queries [`models/ch08-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch08-cumulative.sysml) directly, and says so. This is a deliberate design choice, not an oversight: the chapter's own coverage-gap finding needs no new element, and the tutorial's own non-goal discipline (Chapter 6 and Chapter 7's own precedent of stating scope honestly) argues against adding one only to keep a file-per-chapter convention. ## Ingredients @@ -20,7 +20,7 @@ This chapter adds no new model element. `models/ch08-cumulative.sysml`, the real ## Equipment -See [docs/setup.md](../../docs/setup.md) for environment setup. No additional tooling beyond earlier chapters. +See [Getting Started](../../docs/setup.md) for environment setup. No additional tooling beyond earlier chapters. ## Method @@ -32,4 +32,4 @@ After running all three notebooks: notebook 01's coverage report shows `heatGene ## Experiment -See `exercises/ch09/exercise.ipynb`. +See [`exercises/ch09/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch09/exercise.ipynb). diff --git a/chapters/ch10-traceability-signoff/01-traceability-graph.ipynb b/chapters/ch10-traceability-signoff/01-traceability-graph.ipynb index cbba0de..9ebd248 100644 --- a/chapters/ch10-traceability-signoff/01-traceability-graph.ipynb +++ b/chapters/ch10-traceability-signoff/01-traceability-graph.ipynb @@ -17,7 +17,7 @@ "source": [ "## Traceability, and loading the model\n", "\n", - "SEBoK defines traceability as \"the degree to which a relationship can be established between two or more products of the development process\" (`uv run python -m glossary tutorial traceability`); 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 [index.md](index.md) 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](03-engineering-signoff.ipynb) -- 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](../ch08-checking/03-revision-flow.ipynb) reconstructs a different model state via `source.replace(...)` rather than a second committed file." + "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](../../docs/glossary.md#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`](https://github.com/Open-MBEE/toaster/blob/main/models/ch10-cumulative.sysml) carries [`models/ch08-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch08-cumulative.sysml)'s content forward unchanged, the same design choice Chapter 9 made (see this chapter's own [Overview](index.md) for why, unlike Chapter 9, this chapter still commits its own fixture file). [`models/ch10-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch10-cumulative.sysml) -- the file loaded fresh below, and again by [notebook 03](03-engineering-signoff.ipynb) -- 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](../ch08-checking/03-revision-flow.ipynb) reconstructs a different model state via `source.replace(...)` rather than a second committed file." ] }, { @@ -116,7 +116,7 @@ "source": [ "## Trace the two requirement chains\n", "\n", - "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)." + "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](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-038-opensysmls-api-json-export-has-no-structural-subjectparameter-key-on-requirementdefinition-ch10-reads-each-candidate-features-own-sysxsourcetext-instead))." ] }, { @@ -400,7 +400,7 @@ "source": [ "## A real gap: a proof tied to no requirement\n", "\n", - "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`, `decisions/next-passes.md` item 29, `decisions/log.md` DL-070/DL-071):\n", + "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](../../docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md)):\n", "\n", "- **Satisfy-by-subject**: is the lemma ever named as the SUBJECT of a real `assert satisfy by ;` relationship -- the chapter's own idiom, used above for `rated`/`weak` against `heatGenerationReq`?\n", "- **Direct reference from within a requirement's own body**: does any element owned (at any nesting depth) by a `RequirementDefinition` or `RequirementUsage` (an EXACT type match: a `Concern`/`Viewpoint` owner does not count, even though both are genuine metaclass subtypes of one or the other) have its own `subsets`, `redefines`, `references` or `referent` field pointing at the lemma's id directly?\n", @@ -436,7 +436,7 @@ "\n", "delivered_energy_bounded = idx.by_qn[\"ToasterDemo::deliveredEnergyBoundedBySupply\"]\n", "\n", - "# models/ch10-cumulative.sysml -- the exact file `model` above was loaded from --\n", + "# models/ch10-cumulative.sysml (https://github.com/Open-MBEE/toaster/blob/main/models/ch10-cumulative.sysml) -- the exact file `model` above was loaded from --\n", "# already carries this chapter's own remediation forward (see below): a genuine\n", "# committed fixture, not assembled at runtime. To show honestly what this search\n", "# found BEFORE that remediation, reconstruct that earlier state from `source`\n", @@ -478,7 +478,7 @@ "id": "9d2e5fb8", "metadata": {}, "source": [ - "This check is now genuinely trustworthy (DL-070/DL-071's own narrowing), 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." + "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`](https://github.com/Open-MBEE/toaster/blob/main/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." ] }, { @@ -488,7 +488,7 @@ "source": [ "## Closing the gap\n", "\n", - "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 pilot warning 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?" + "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?" ] }, { @@ -513,10 +513,9 @@ } ], "source": [ - "requirements_lib_path = (\n", - " Path.home() / \"Documents/GitHub/sysml-toolkit/spec-refs/SysML-v2-Release/sysml.library\"\n", - " / \"Systems Library\" / \"Requirements.sysml\"\n", - ")\n", + "from toaster.tools import resolve_library\n", + "\n", + "requirements_lib_path = resolve_library() / \"Systems Library\" / \"Requirements.sysml\"\n", "requirements_lib_text = requirements_lib_path.read_text()\n", "assert \"subject subj : Anything[1]\" in requirements_lib_text, (\n", " \"expected the base requirement def RequirementCheck to still declare \"\n", @@ -531,7 +530,7 @@ "id": "310279c7", "metadata": {}, "source": [ - "`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` -- this branch's own committed file, as reconciled by this contract -- already carries this fix: confirmed directly against the real OMG 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 (AGENTS.md SS1.2)." + "`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`](https://github.com/Open-MBEE/toaster/blob/main/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." ] }, { @@ -652,7 +651,7 @@ "# Three short inline strings, the same way every other negative control in this\n", "# tutorial is built, each loaded as its own one-off model (not the committed\n", "# fixture, which deliberately omits this line) so the real, current model\n", - "# loaded above stays exactly what models/ch10-cumulative.sysml commits.\n", + "# loaded above stays exactly what models/ch10-cumulative.sysml (https://github.com/Open-MBEE/toaster/blob/main/models/ch10-cumulative.sysml) commits.\n", "attempt_models = {}\n", "for label, binding in ENERGY_CONSERVATION_SATISFY_ATTEMPTS.items():\n", " attempt_line = f\"assert satisfy energyConservationReq by {binding};\"\n", @@ -668,7 +667,7 @@ "id": "02281b44", "metadata": {}, "source": [ - "These all load cleanly -- grammatically legal, and OpenSysML 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`." + "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`." ] }, { @@ -712,7 +711,7 @@ "id": "13f89b31", "metadata": {}, "source": [ - "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." + "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`](https://github.com/Open-MBEE/toaster/blob/main/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." ] }, { @@ -720,7 +719,7 @@ "id": "ec23fe5f", "metadata": {}, "source": [ - "`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 Pattern B 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." + "[`models/ch10-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/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." ] }, { @@ -824,7 +823,7 @@ "id": "3ab3b4c5", "metadata": {}, "source": [ - "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." + "This fragment is the same text now committed in [`models/ch10-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/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." ] }, { @@ -933,7 +932,7 @@ "id": "1cdef0c6", "metadata": {}, "source": [ - "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." + "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](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-030-two-independently-declared-assert-constraints-are-never-composed-by-verify---solve-whether-sibling-or-inherited) and [`DEFERRED.md` D-031](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-031-a-chained-calcfunction-invocation-inside-an-assert-constraint-is-not-in-z3s-solvable-fragment)." ] }, { @@ -1024,7 +1023,7 @@ "id": "3a3d9d15", "metadata": {}, "source": [ - "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 `sysmlv2 verify --solve` -- a different tool from `model.verify_satisfaction()`, this tutorial's own Z3-backed solver, used for `deliveredEnergyBoundedBySupply`'s own proof in Chapter 8 -- 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." + "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](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-029-toastermodelcheckverify_holdss-line-parser-cannot-read-a-verify---solve-verdict-for-an-assert-satisfyassert-not-satisfy-declaration)), 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." ] }, { @@ -1050,22 +1049,25 @@ } ], "source": [ + "import os\n", "import subprocess\n", "\n", + "from toaster.tools import resolve_library, resolve_sysmlv2, tool_env\n", + "\n", "# Shelling out directly rather than toaster.modelcheck's own wrapper: that\n", "# wrapper's own line parser cannot read a verdict line for a constraint that is\n", "# also the subject of an assert satisfy/assert not satisfy declaration at all\n", "# (DEFERRED.md D-029) -- this model already has two such declarations\n", "# (timely, heatGenerationReq), so the wrapper would fail before ever reaching\n", "# this chapter's own construct.\n", - "SYSMLV2_BINARY = Path.home() / \"Documents/GitHub/sysml-toolkit/target/release/sysmlv2\"\n", - "SYSMLV2_LIB = Path.home() / \"Documents/GitHub/sysml-toolkit/spec-refs/SysML-v2-Release/sysml.library\"\n", - "assert SYSMLV2_BINARY.exists(), f\"sysmlv2 binary not found at {SYSMLV2_BINARY}\"\n", + "SYSMLV2_BINARY = resolve_sysmlv2()\n", + "SYSMLV2_LIB = resolve_library()\n", + "os.environ[\"PATH\"] = tool_env()[\"PATH\"] # the next cell shells out to the CLI too; it finds z3 on PATH\n", "\n", "solve_result = subprocess.run(\n", " [str(SYSMLV2_BINARY), \"verify\", \"../../models/ch10-cumulative.sysml\",\n", " \"--lib\", str(SYSMLV2_LIB), \"--solve\"],\n", - " capture_output=True, text=True, timeout=30,\n", + " capture_output=True, text=True, timeout=30, env=tool_env(),\n", ")\n", "solve_lines = solve_result.stdout.splitlines()\n", "\n", @@ -1086,7 +1088,7 @@ "id": "0d8d3049", "metadata": {}, "source": [ - "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." + "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`](https://github.com/Open-MBEE/toaster/blob/main/models/ch10-cumulative.sysml) itself, which deliberately omits this line." ] }, { @@ -1286,7 +1288,7 @@ "id": "0f1d3a77", "metadata": {}, "source": [ - "`kind=\"asserted_context\"` is the deliberate choice here: Hawkins' own definition, \"context or assumption is asserted to be appropriate for the argument elements it applies to\" (`toaster-review-protocol`), is 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." + "`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](../../docs/glossary.md#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." ] }, { @@ -1347,7 +1349,7 @@ "id": "4faa9b32", "metadata": {}, "source": [ - "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, AGENTS.md §1.6) 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`." + "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](../../docs/glossary.md#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`." ] }, { @@ -1355,7 +1357,7 @@ "id": "2aedd869", "metadata": {}, "source": [ - "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." + "Try the chapter exercise in [`exercises/ch10/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/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." ] } ], diff --git a/chapters/ch10-traceability-signoff/02-judgment-synthesis.ipynb b/chapters/ch10-traceability-signoff/02-judgment-synthesis.ipynb index 9a7157d..70cab0b 100644 --- a/chapters/ch10-traceability-signoff/02-judgment-synthesis.ipynb +++ b/chapters/ch10-traceability-signoff/02-judgment-synthesis.ipynb @@ -15,7 +15,7 @@ "id": "e7d56322", "metadata": {}, "source": [ - "This tutorial has no central registry of every `ReviewRecord` it has ever built, the same finding Chapter 9 made when it searched for one: each chapter's judgment is authored once, in Python, at the chapter where it is made, per the construction-zone pattern (`toaster-review-protocol`); what Chapter 9 found missing was the other half of that design, a persisted store a later chapter could query -- `toaster.judgment_store` now supplies it, so a record built once is saved with `save_record()` and loaded elsewhere with `load_record()`, never reconstructed. So this notebook does not scan \"every record the tutorial has ever produced\"; instead it loads three real records from that store: `AS-C06` and `AS-C08`, already loaded the same way by Chapter 9's own retrofit, plus one more, `AI-C06` (Chapter 6's own stopping judgment over the level-2 heat-generation branch, an `asserted_inference`, alongside the two `asserted_solution` records Chapter 9 already carried forward). What follows demonstrates the mechanics of a judgment ledger on a small, representative sample, not an exhaustive audit of every judgment record this tutorial has ever produced." + "This tutorial has no central registry of every `ReviewRecord` it has ever built, the same finding Chapter 9 made when it searched for one: each chapter's judgment is authored once, in Python, at the chapter where it is made, per the construction-zone pattern; what Chapter 9 found missing was the other half of that design, a persisted store a later chapter could query -- `toaster.judgment_store` now supplies it, so a record built once is saved with `save_record()` and loaded elsewhere with `load_record()`, never reconstructed. So this notebook does not scan \"every record the tutorial has ever produced\"; instead it loads three real records from that store: `AS-C06` and `AS-C08`, already loaded the same way in Chapter 9, plus one more, `AI-C06` (Chapter 6's own stopping judgment over the level-2 heat-generation branch, an `asserted_inference`, alongside the two `asserted_solution` records Chapter 9 already carried forward). What follows demonstrates the mechanics of a judgment ledger on a small, representative sample, not an exhaustive audit of every judgment record this tutorial has ever produced." ] }, { @@ -99,7 +99,7 @@ "id": "a187ec26", "metadata": {}, "source": [ - "`AS-C06`, loaded below via `load_record()` from the record Chapter 6 notebook 02 persisted with `save_record()`, the same record Chapter 9's own retrofit already carries forward: a mechanism-selection judgment, argued from a domain premise about how a resistive element and a combustion burner each respond to a discrete timing signal. Its `content_hash` was computed against `models/ch06-cumulative.sysml` when that record was built and persisted, the real model it was actually written against." + "`AS-C06`, loaded below via `load_record()` from the record Chapter 6 notebook 02 persisted with `save_record()`, the same record Chapter 9 already carries forward: a mechanism-selection judgment, argued from a domain premise about how a resistive element and a combustion burner each respond to a discrete timing signal. Its `content_hash` was computed against [`models/ch06-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch06-cumulative.sysml) when that record was built and persisted, the real model it was actually written against." ] }, { @@ -135,7 +135,7 @@ "id": "e531af6c", "metadata": {}, "source": [ - "`AS-C08`, loaded the same way, via `load_record()`, from the record Chapter 8 notebook 02 persisted with `save_record()`: the record grounded in that chapter's real Z3 proof of `deliveredEnergyBoundedBySupply`, the same one notebook 01 of this chapter found disconnected from any requirement usage and then tied, by subsetting, to `energyConservationReq`. Its `content_hash` was computed against `models/ch08-cumulative.sysml` when that record was built and persisted, the model it was actually written against." + "`AS-C08`, loaded the same way, via `load_record()`, from the record Chapter 8 notebook 02 persisted with `save_record()`: the record grounded in that chapter's real Z3 proof of `deliveredEnergyBoundedBySupply`, the same one notebook 01 of this chapter found disconnected from any requirement usage and then tied, by subsetting, to `energyConservationReq`. Its `content_hash` was computed against [`models/ch08-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch08-cumulative.sysml) when that record was built and persisted, the model it was actually written against." ] }, { @@ -215,7 +215,7 @@ "id": "6b29f183", "metadata": {}, "source": [ - "All three validate cleanly. The ledger below reports each record's kind (Hawkins' three sites: `asserted_context`, `asserted_inference`, `asserted_solution`), its `disposition` and `record_kind` (SA-7: always `pending`, always `worked_example`, never the forbidden alternatives), and its `engineering_conclusion`, next to what its own `residual_uncertainties` says is NOT yet resolved." + "All three validate cleanly. The ledger below reports each record's kind (Hawkins' three sites: `asserted_context`, `asserted_inference`, `asserted_solution`), its `disposition` and `record_kind` (by this tutorial's standing rule: always `pending`, always `worked_example`, never the forbidden alternatives), and its `engineering_conclusion`, next to what its own `residual_uncertainties` says is NOT yet resolved." ] }, { @@ -359,7 +359,7 @@ "id": "6f503fc9", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch10/exercise.ipynb`: it asks you to build this same traceability graph and judgment ledger over your own coffee-maker model, then synthesize both into one honestly-scoped sign-off record." + "Try the chapter exercise in [`exercises/ch10/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch10/exercise.ipynb): it asks you to build this same traceability graph and judgment ledger over your own coffee-maker model, then synthesize both into one honestly-scoped sign-off record." ] } ], diff --git a/chapters/ch10-traceability-signoff/03-engineering-signoff.ipynb b/chapters/ch10-traceability-signoff/03-engineering-signoff.ipynb index b30fb97..4106de7 100644 --- a/chapters/ch10-traceability-signoff/03-engineering-signoff.ipynb +++ b/chapters/ch10-traceability-signoff/03-engineering-signoff.ipynb @@ -15,7 +15,7 @@ "id": "e797c000", "metadata": {}, "source": [ - "State the distinction this whole notebook rests on before building anything: a completed traceability graph and a judgment ledger are not sign-off itself. Sign-off is a human, accountable act; a design's own engineer decides, given everything the graph and the ledger show, whether to proceed. This notebook can show the inputs to that decision, honestly and within its own stated scope, but it cannot make the decision for anyone, and it does not try to. The record built below stays `disposition = \"pending\"`, the same as every other record this tutorial has ever built (SA-7): this chapter does not get to be the exception." + "State the distinction this whole notebook rests on before building anything: a completed traceability graph and a judgment ledger are not sign-off itself. Sign-off is a human, accountable act; a design's own engineer decides, given everything the graph and the ledger show, whether to proceed. This notebook can show the inputs to that decision, honestly and within its own stated scope, but it cannot make the decision for anyone, and it does not try to. The record built below stays `disposition = \"pending\"`, the same as every other record this tutorial has ever built (a standing rule of this tutorial): this chapter does not get to be the exception." ] }, { @@ -47,7 +47,7 @@ "id": "4b172c3f", "metadata": {}, "source": [ - "Before assembling anything, a real negative control fitting this notebook's own new record: does `validate_record()` reject a draft of it whose own `counterevidence` is empty? `counterevidence` is exactly the field AGENTS.md 1.6 calls load-bearing, and this check is real and mechanized, unlike a different rule discussed just below that no tool enforces." + "Before assembling anything, a real negative control fitting this notebook's own new record: does `validate_record()` reject a draft of it whose own `counterevidence` is empty? `counterevidence` is exactly the field this tutorial treats as load-bearing, and this check is real and mechanized, unlike a different rule discussed just below that no tool enforces." ] }, { @@ -98,7 +98,7 @@ "id": "97ad4325", "metadata": {}, "source": [ - "`validate_record()` correctly rejects this: a real, mechanized floor actually catching something, the same check Chapter 9's own placeholder record exercised. A different rule this tutorial follows just as strictly has no such mechanized floor: AGENTS.md 1.6 forbids ever recording `disposition=\"accepted\"` (SA-7), but reading `src/toaster/evidence.py`'s own `validate_record()` shows what it actually checks: `identifier`, `claim`, `rationale` and `counterevidence` non-empty, `record_kind != \"actual_review\"`, at least one premise for an `asserted_inference`, and `subject_ref` present (resolving in the model and agreeing with any model-side tag, when a model is given) for `asserted_context`/`asserted_solution` or an `asserted_inference` with no premises -- never the `disposition` value itself. That gap is real and worth naming plainly (recorded in `decisions/next-passes.md`), but it is not demonstrated here by constructing the forbidden value: AGENTS.md's own rule against `disposition=\"accepted\"` has no negative-control exception, so this point is made in prose only, never in code. Every record this notebook goes on to build keeps `disposition=\"pending\"`, checked explicitly at the end, not merely asserted." + "`validate_record()` correctly rejects this: a real, mechanized floor actually catching something, the same check Chapter 9's own placeholder record exercised. A different rule this tutorial follows just as strictly has no such mechanized floor: this tutorial forbids ever recording `disposition=\"accepted\"`, but reading [`src/toaster/evidence.py`](https://github.com/Open-MBEE/toaster/blob/main/src/toaster/evidence.py)'s own `validate_record()` shows what it actually checks: `identifier`, `claim`, `rationale` and `counterevidence` non-empty, `record_kind != \"actual_review\"`, at least one premise for an `asserted_inference`, and `subject_ref` present (resolving in the model and agreeing with any model-side tag, when a model is given) for `asserted_context`/`asserted_solution` or an `asserted_inference` with no premises -- never the `disposition` value itself. That gap is real and worth naming plainly, but it is not demonstrated here by constructing the forbidden value: this tutorial's own rule against `disposition=\"accepted\"` has no negative-control exception, so this point is made in prose only, never in code. Every record this notebook goes on to build keeps `disposition=\"pending\"`, checked explicitly at the end, not merely asserted." ] }, { @@ -243,7 +243,7 @@ "id": "a8b4cede", "metadata": {}, "source": [ - "Notebook 01's graph and notebook 02's ledger are now both in hand. The rest of this notebook builds one new record synthesizing them, following the judgment-record construction zone (`toaster-review-protocol`): name each group of fields, narrate what it is for, print it, then assemble." + "Notebook 01's graph and notebook 02's ledger are now both in hand. The rest of this notebook builds one new record synthesizing them, following the judgment-record construction zone: name each group of fields, narrate what it is for, print it, then assemble." ] }, { @@ -519,7 +519,7 @@ "id": "702d22f1", "metadata": {}, "source": [ - "What could be wrong, and what is still open (trustworthiness): named plainly, not folded into a tidier-sounding conclusion." + "What could be wrong, and what is still open (trustworthiness): named plainly, not folded into a tidier-sounding conclusion. The `deliveredEnergyBoundedBySupply` lemma named below is hand-restated, not solver-linked, because of two deferred solver gaps, [`DEFERRED.md` D-030](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-030-two-independently-declared-assert-constraints-are-never-composed-by-verify---solve-whether-sibling-or-inherited) and [`DEFERRED.md` D-031](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-031-a-chained-calcfunction-invocation-inside-an-assert-constraint-is-not-in-z3s-solvable-fragment)." ] }, { @@ -687,7 +687,7 @@ "id": "4d7fa4ac", "metadata": {}, "source": [ - "AGENTS.md's own account of emergence names what a real sign-off actually has to weigh, beyond anything this record states: \"strong emergence (unanticipated; seen only in integration, test or operation) belongs to no layer. It is what sign-off judges.\" This chapter's own synthesis, however honestly built and however carefully bounded, cannot anticipate strong emergence by construction: it is a map of what has already been checked and what has not, not a forecast of what integration or operation might still reveal. It is exactly the kind of artifact a real engineer would weigh against that residual, unanticipated risk before deciding to proceed, not a substitute for weighing it. A traceable, honestly-scoped case makes that judgment easier to exercise well; it does not make the judgment itself unnecessary." + "[AGENTS.md's](https://github.com/Open-MBEE/toaster/blob/main/AGENTS.md) own account of emergence names what a real sign-off actually has to weigh, beyond anything this record states: \"strong emergence (unanticipated; seen only in integration, test or operation) belongs to no layer. It is what sign-off judges.\" This chapter's own synthesis, however honestly built and however carefully bounded, cannot anticipate strong emergence by construction: it is a map of what has already been checked and what has not, not a forecast of what integration or operation might still reveal. It is exactly the kind of artifact a real engineer would weigh against that residual, unanticipated risk before deciding to proceed, not a substitute for weighing it. A traceable, honestly-scoped case makes that judgment easier to exercise well; it does not make the judgment itself unnecessary." ] }, { @@ -703,7 +703,7 @@ "id": "f89d9afb", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch10/exercise.ipynb`: it asks you to build this same traceability graph, judgment ledger and sign-off synthesis over your own coffee-maker model." + "Try the chapter exercise in [`exercises/ch10/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch10/exercise.ipynb): it asks you to build this same traceability graph, judgment ledger and sign-off synthesis over your own coffee-maker model." ] } ], diff --git a/chapters/ch10-traceability-signoff/conclusion.md b/chapters/ch10-traceability-signoff/conclusion.md index 48a1ea8..a7534fe 100644 --- a/chapters/ch10-traceability-signoff/conclusion.md +++ b/chapters/ch10-traceability-signoff/conclusion.md @@ -6,7 +6,7 @@ title: Conclusion ## What we built -One new model element, and only because this chapter's own analysis found the gap it closes: `models/ch10-cumulative.sysml` otherwise carries `models/ch08-cumulative.sysml`'s content forward unchanged, and every notebook in this chapter queries it directly. What changed is what can be asked of it and of the tutorial's own record set: notebook 01 adds a real traceability graph, tracing two of the model's three named requirements from functional intent through allocation and realization to verification evidence, finds that `deliveredEnergyBoundedBySupply` is tied to no requirement usage at all, and closes that gap directly with `EnergyConservationReq`/`energyConservationReq` -- tied by subsetting the lemma from within the requirement's own body, deliberately with no `assert satisfy` line, confirmed by a negative control that shows that idiom fails under `model.verify_satisfaction()` -- then records how that tie should honestly be read in a new judgment record, `AC-C10`; notebook 02 reconstructs three real `ReviewRecord`s (`AS-C06`, `AS-C08`, `AI-C06`) into a ledger and reads what each one's own kind, disposition and residual uncertainty actually says; notebook 03 synthesizes both into one new record, `AI-C10`, and states plainly that the record itself is not sign-off. +One new model element, and only because this chapter's own analysis found the gap it closes: [`models/ch10-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch10-cumulative.sysml) otherwise carries [`models/ch08-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch08-cumulative.sysml)'s content forward unchanged, and every notebook in this chapter queries it directly. What changed is what can be asked of it and of the tutorial's own record set: notebook 01 adds a real traceability graph, tracing two of the model's three named requirements from functional intent through allocation and realization to verification evidence, finds that `deliveredEnergyBoundedBySupply` is tied to no requirement usage at all, and closes that gap directly with `EnergyConservationReq`/`energyConservationReq` -- tied by subsetting the lemma from within the requirement's own body, deliberately with no `assert satisfy` line, confirmed by a negative control that shows that idiom fails under `model.verify_satisfaction()` -- then records how that tie should honestly be read in a new judgment record, `AC-C10`; notebook 02 reconstructs three real `ReviewRecord`s (`AS-C06`, `AS-C08`, `AI-C06`) into a ledger and reads what each one's own kind, disposition and residual uncertainty actually says; notebook 03 synthesizes both into one new record, `AI-C10`, and states plainly that the record itself is not sign-off. ## What this establishes @@ -20,8 +20,8 @@ The synthesis record, `AI-C10`, brings both together honestly, within its own st ## What comes next -This is the tutorial's last chapter. What continues from here is not another chapter but the reader's own accountable engineering: taking the traceable, honestly-scoped case this tutorial teaches how to build, and exercising, on a real design, the judgment this tutorial has shown but never made for them. +This is the tutorial's last chapter. What continues from here is not another chapter but the reader's own accountable engineering: taking the traceable, honestly-scoped case this tutorial teaches how to build, and exercising, on a real design, the judgment this tutorial has shown but never made for them. If you come back to this repository, the contribution it wants is keeping it current to its toolchain and to the SysML v2 specifications, not extending it; the [contributor guide](#what-we-accept) says what that means. ## Exercise -See `exercises/ch10/exercise.ipynb`: it asks you to build this same traceability graph, judgment ledger and sign-off synthesis over your own coffee-maker model. +See [`exercises/ch10/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch10/exercise.ipynb): it asks you to build this same traceability graph, judgment ledger and sign-off synthesis over your own coffee-maker model. diff --git a/chapters/ch10-traceability-signoff/index.md b/chapters/ch10-traceability-signoff/index.md index e738c66..c1e1ddb 100644 --- a/chapters/ch10-traceability-signoff/index.md +++ b/chapters/ch10-traceability-signoff/index.md @@ -8,7 +8,7 @@ title: Overview This chapter builds a real traceability graph over two of the model's three named requirements, synthesizes three of the tutorial's own real judgment records into a ledger, and assembles the real inputs a real sign-off decision would be made from: a bounded, honest synthesis of what is established, what is not, and what residual judgment remains. This chapter does not perform that sign-off itself, and does not claim to: deciding whether to proceed remains a human, accountable act. This is the tutorial's final chapter. -This chapter adds exactly one new named model element, and only because its own traceability analysis 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 deliberate design choice Chapter 9 made (`decisions/pass4-run-009.md`). This is not a departure from that design principle in general: the one exception is warranted precisely because it is not new model content for its own sake, it is the direct result of what this chapter's own traceability graph discovered (notebook 01 finds `deliveredEnergyBoundedBySupply`, Chapter 8's own Z3-proved conservation lemma, tied to no requirement at all, then closes that gap by subsetting it directly from `EnergyConservationReq`'s own required constraint -- deliberately with no `assert satisfy` line; see `docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md` and notebook 01's own `AC-C10` for why). Unlike Chapter 9, this chapter commits its own `models/ch10-cumulative.sysml` file: Chapter 9 left no cumulative fixture of its own, which would have made `scripts/check_construction.py`'s own predecessor-containment check silently no-op between Chapter 8 and Chapter 10 (`decisions/next-passes.md` item 21). This chapter resolves that for real: `check_predecessor_containment()` now falls back to the nearest earlier chapter with a real fixture when the immediate predecessor has none, so Chapter 8's own named elements are actually checked against this chapter's own committed file, not skipped. +This chapter adds exactly one new named model element, and only because its own traceability analysis finds a real gap it is positioned to close directly: otherwise, [`models/ch10-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch10-cumulative.sysml) carries [`models/ch08-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch08-cumulative.sysml)'s content forward unchanged, the same deliberate design choice [Chapter 9](../ch09-coverage-sufficiency/index.md) made. This is not a departure from that design principle in general: the one exception is warranted precisely because it is not new model content for its own sake, it is the direct result of what this chapter's own traceability graph discovered (notebook 01 finds `deliveredEnergyBoundedBySupply`, Chapter 8's own Z3-proved conservation lemma, tied to no requirement at all, then closes that gap by subsetting it directly from `EnergyConservationReq`'s own required constraint -- deliberately with no `assert satisfy` line; see [the case study](../../docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md) and notebook 01's own `AC-C10` for why). Unlike Chapter 9, this chapter commits its own [`models/ch10-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch10-cumulative.sysml) file: Chapter 9 left no cumulative fixture of its own, which would have made [`scripts/check_construction.py`](https://github.com/Open-MBEE/toaster/blob/main/scripts/check_construction.py)'s own predecessor-containment check silently no-op between Chapter 8 and Chapter 10. This chapter resolves that for real: `check_predecessor_containment()` now falls back to the nearest earlier chapter with a real fixture when the immediate predecessor has none, so Chapter 8's own named elements are actually checked against this chapter's own committed file, not skipped. ## Ingredients @@ -20,7 +20,7 @@ This chapter adds exactly one new named model element, and only because its own ## Equipment -See [docs/setup.md](../../docs/setup.md) for environment setup. No additional tooling beyond earlier chapters. +See [Getting Started](../../docs/setup.md) for environment setup. No additional tooling beyond earlier chapters. ## Method @@ -32,4 +32,4 @@ After running all three notebooks: notebook 01's graph shows `heatGenerationReq` ## Experiment -See `exercises/ch10/exercise.ipynb`. +See [`exercises/ch10/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch10/exercise.ipynb). diff --git a/decisions/contribution-policy/final-texts-2.md b/decisions/contribution-policy/final-texts-2.md new file mode 100644 index 0000000..f2a1bc8 --- /dev/null +++ b/decisions/contribution-policy/final-texts-2.md @@ -0,0 +1,67 @@ +# Contribution policy: amendments after the CT-2 review (ACE batch 4, DL-124): authoritative + +Governs over `final-texts.md` where they differ. AGENTS.md §1.12 stays UNCHANGED. All docs edits are to `docs/contributor.md` +unless stated; locate every edit by OLD TEXT (line numbers approximate), exactly one match, else STOP and report. + +## F1 (substantive) +**"Keep current" step 1: replace the whole step with:** +``` +1. Change the version in `pyproject.toml` (Python), `package.json` (Node) or + [`scripts/tool-pins.json`](https://github.com/Open-MBEE/toaster/blob/main/scripts/tool-pins.json) + (the `sysmlv2` binary, Z3 and the PlantUML jar with their sha256 hashes, and the + standard-library commit), then `uv lock` / `npm install` to update the lockfile, or + `uv run python scripts/provision-tools.py` to re-provision `.tools/`. The OpenSysML runtime + binary is pinned separately from the `opensysml` package: change the `version="v0.9.0"` + defaults in `src/toaster/bootstrap.py` (and its `_CLI_SUMS` hashes) and + `src/toaster/connect.py`, and the `opensysml.connect(version=...)` calls in + `scripts/check_conformance.py` and `scripts/check_construction.py`, together. +``` +(Also carries Q3.) **Step 5: replace the whole step with:** +``` +5. Commit the lockfile or the pins alongside the version change; never bump a version without + regenerating and committing what matches it. Update the version strings in + `docs/setup.md`, `docs/reproducibility.md` and `AGENTS.md` §1.2 in the same change, and + re-check chapter prose and `DEFERRED.md` entries that state a version + (`git grep -n 'v0\.9\.[01]' -- chapters DEFERRED.md`): a statement re-probed under the new + release takes the new version; one not re-probed keeps the version it was probed against. +``` +## F2 +- "Keep current" bullet: `updating that entry's status line and the comment cell at the workaround (headings stay: they are linked anchors)` -> `adding a dated status line to that entry (or updating the one it has), updating the comment cell at the workaround, and leaving the heading as it is (headings are linked anchors)` +- step 4 of "Keep current": `retire the workaround, update the entry's status line and the comment cell at the workaround (do not rename the heading)` -> `retire the workaround, add a dated status line to the entry (or update the one it has), update the comment cell at the workaround (do not rename the heading)` +## F3 +- `If you think the tutorial needs something new, open an issue first; only Z decides that, and a glossary term is confirmed only by Z.` -> `If you think the tutorial needs something new, open an issue; only Z decides that, and a glossary term is confirmed only by Z.` (AGENTS.md §1.12 unchanged; default: no exception clause.) +## F4 (three one-word edits) +- `An independent reviewer on a different model checks the statement` -> `An independent reviewer on a different AI model checks the statement` +- `(always on different models, never the same one reviewing its own work)` -> `(always on different AI models, never the same one reviewing its own work)` +- `Get an independent review on a different model than whoever authored the change` -> `Get an independent review on a different AI model than whoever authored the change` +## Q1: no text added (1.3 and 1.11 already govern). Default: not added. +## Q2: insert after the third bullet of "What we accept" ("Not accepted by pull request"), before `## Who is Z`: +``` +The reviewer applies this test. A pull request passes when one "better" line (or, for keeping +current, the currency event) and all three "not worse" lines are evidenced; any "worse" means the +change is not accepted; what the reviewer cannot tell goes to the ACE. + +| Priority | "Worse" means | Verified by, with what evidence | +|---|---|---| +| (1) Spec conformance | Text or model violating a normative clause of one of the three specifications; a spec-anchored construct replaced by a tool idiom; a conformance check or its negative control dropped or weakened | Reviewer: the clause citation, `uv run python scripts/check_conformance.py`, a strict load under the pinned runtime; the OMG SysML v2 Pilot Implementation is the baseline where a clause is ambiguous | +| (2) Didactic clarity | Longer or denser without the learner's task getting harder without it; a second construct or operation in one sub-notebook; a check presented as proof; the pacing rule broken; a figure whose omissions are no longer stated | Reviewer: the pull request's statement, the pacing check, and a simulated-learner checkpoint when the change alters what a learner does or sees beyond wording | +| (3) Tool use | A construct described as working but not run under the pinned version; a new workaround without a `DEFERRED.md` entry and comment cell; a tool demonstration removed; meaning moved from the model into Python | CI (`TOASTER_REQUIRE_TOOLS=1`, `myst build --strict`, `scripts/check-site.py`) and the reviewer re-running the touched notebook; the executed outputs and the `DEFERRED.md` entries touched | +``` +## EXTRA (a): `.github/PULL_REQUEST_TEMPLATE.md`, Protections, first bullet +`- [ ] No new chapter, notebook, exercise, construct or analysis operation, model element, judgment record or glossary term` -> `- [ ] No new chapter, notebook, exercise, construct or analysis operation, model element, judgment record, glossary term or learning outcome` +## EXTRA (b) (CT-6, ACE): `.claude/skills/sysml-diagrams/references/recipes.md` +- ~L92-94 before: `Confirmed directly against real chapter content (\`decisions/diagram-study-real-fixtures.md\`;` / `Ch6's \`ApplyHeat\` action, exit 0, real action-flow notation). No in-house action-flow renderer` / `exists yet. Expected output: the declared actions, initial/final nodes, and successions. Check` + after: `Confirmed against real chapter content (\`decisions/diagram-study-real-fixtures.md\`; Ch4's \`ToastBread\` and Ch6's \`ApplyHeat\` actions, exit 0): the control sequence is drawn, but the CLI's DOT omits the actions' declared typed flows (D-037); the caption must say so. No in-house action-flow renderer exists yet. Expected output: the declared actions, initial/final nodes, and successions, without flow pins. Check` (rest of the paragraph unchanged; keep the file's line wrapping) +- ~L109-111 before: `Confirmed directly against real chapter content (\`decisions/diagram-study-real-fixtures.md\`):` / `100% success across both OpenSysML runtime render forms on Ch7's real \`Cycle\` state machine, and the` / `mutation-control test (retargeting a transition) correctly changes the rendered output. Show` + after: `Run against real chapter content (\`decisions/diagram-study-real-fixtures.md\`): both OpenSysML runtime render forms exit 0 on Ch7's real \`Cycle\` state machine and draw its states and transitions, but the \`do\` activity label omits the performed action's name (D-037); the caption must say so. The mutation-control test (retargeting a transition) correctly changes the rendered output. Show` (rest unchanged) + +## CORRECTION recorded after CT-6 (ACE editor, DL-125 amended): the EXTRA (b) action-flow paragraph, as APPLIED +The batch-4 EXTRA (b) action-flow text cited `decisions/diagram-study-real-fixtures.md` as confirming the Ch4/Ch6 renders; that study (L52) did not rerun the action-flow view and DL-057's probe did. The text actually applied (commit d14bc6a) is: + +Confirmed against real chapter content (Ch4's `ToastBread` and Ch6's `ApplyHeat` actions, exit 0: `figures/ch04-toastbread-flow.svg`, `figures/ch06-applyheat-flow.svg`, DEFERRED.md D-037; the real-fixture study, `decisions/diagram-study-real-fixtures.md`, did not rerun the action-flow view, the DL-057 probe did): the control sequence is drawn, but the CLI's DOT omits the actions' declared typed flows (D-037); the caption must say so. No in-house action-flow renderer exists yet. Expected output: the declared actions, initial/final nodes, and successions, without flow pins. Check (rest of the paragraph unchanged) + +The state-transition paragraph was applied exactly as in EXTRA (b). Follow-up note (not edited; decisions/ is append-only history): `decisions/diagram-survey.md` L185 says "Phase 0 confirms this exact element already renders cleanly on real content" for Ch6's `ApplyHeat` action-flow; the fixture study (L52) and DL-057 (L735) say Phase 0 did not test action-flow. + +## CT-5 addenda (micro-ruling, DL-127) +- docs/contributor.md, "Improve what is here" bullet: `goes to the ACE.` -> `goes to the ACE (the project's triage role, described below).` (first use only; the table lead-in keeps the bare second use; no label or URL). +- README.md ~L52: `decisions/ — ACE decision log` -> `decisions/ — decision log (rulings and escalations)`. diff --git a/decisions/contribution-policy/final-texts-3.md b/decisions/contribution-policy/final-texts-3.md new file mode 100644 index 0000000..9cc54ea --- /dev/null +++ b/decisions/contribution-policy/final-texts-3.md @@ -0,0 +1,34 @@ +# Contribution policy: amendments after the final gate (ACE batch 5, DL-128): authoritative + +Governs over final-texts.md / final-texts-2.md where they differ. All edits are exact-text substitutions: locate by OLD TEXT, +exactly one match, else STOP and report. Files: `docs/contributor.md` and `.github/PULL_REQUEST_TEMPLATE.md` only. No URL is +added or removed (rule g); `AGENTS.md` is unchanged. + +## B1 (docs/contributor.md, "Keep current") +Step 1, LAST sentence: +- before: ... and the `opensysml.connect(version=...)` calls in `scripts/check_conformance.py` and `scripts/check_construction.py`, together. +- after: ... and every `opensysml.connect(version=...)` call in `scripts/check_conformance.py`, `scripts/check_construction.py`, `chapters/`, `exercises/` and `tests/` (`git grep -n 'connect(version=' -- src scripts chapters exercises tests`), together. Code cells, stored outputs and `models/` are protected during editorial passes, not during a bump: the notebooks are re-executed and their outputs regenerate (step 3). `scripts/probes/` and `scripts/diagram_study/` are dated probes and keep the version they probed. + +Step 5, the parenthetical grep and the sentence after it: +- before: re-check chapter prose and `DEFERRED.md` entries that state a version (`git grep -n 'v0\.9\.[01]' -- chapters DEFERRED.md`): a statement re-probed under the new release takes the new version; one not re-probed keeps the version it was probed against. +- after: re-check the prose, tests and `DEFERRED.md` entries that state a version (`git grep -n 'v0\.9\.[01]' -- chapters exercises tests DEFERRED.md`): a test or fixture that pins the version moves with it; a statement re-probed under the new release takes the new version; one not re-probed keeps the version it was probed against. + +## B2 +`docs/contributor.md`, "How a change is built and reviewed" step 3, first line: +- before: 3. Run `uv run python -m glossary lint` before committing prose; run the pacing check in +- after: 3. Before committing prose, check that `uv run python -m glossary lint` reports no hit the base branch does not (it is not a CI gate and exits 1 on pre-existing hits: run `--write-baseline base.json` on the base, then `--baseline base.json` on your branch, which exits 1 only on a new error); run the pacing check in +(the rest of step 3 unchanged; keep the page's wrapping style where the new text lengthens the line.) + +`.github/PULL_REQUEST_TEMPLATE.md`, last Protections bullet: +- before: - [ ] `TOASTER_REQUIRE_TOOLS=1 uv run pytest tests/ glossary/tests/` and `uv run python -m glossary lint` pass locally +- after: - [ ] `TOASTER_REQUIRE_TOOLS=1 uv run pytest tests/ glossary/tests/` passes locally, and `uv run python -m glossary lint` reports no hit the base branch does not (it is not a CI gate; see the contributor guide) + +## n2 (harmonise to "worse on none") +- `docs/contributor.md` "Improve what is here": `State in the pull request which priority improves and the evidence, and for each of the other two why it is not worse;` -> `State in the pull request which priority improves and the evidence, and why the change is worse on none of the three;` +- `.github/PULL_REQUEST_TEMPLATE.md`: `Not worse on each of the others (one line each, with how you checked):` -> `Not worse on any priority (one line each, with how you checked):` + +## n1 (recorded, no edit) +"How a change is built and reviewed" step 2 carries the run instruction for `scripts/check_construction.py --check --chapter=N` +(registered construction zones executed, each TOASTER_INCREMENT and the cumulative fixture loaded; an existing +CONSTRUCTION_NOTEBOOKS entry is edited only if its stubs change; new entries are for new notebooks, which are not accepted), +added in CT-2 after the guard's rule (g) caught the dropped URL (ACE batch-3 follow-up). diff --git a/decisions/contribution-policy/final-texts.md b/decisions/contribution-policy/final-texts.md new file mode 100644 index 0000000..ac3c6ec --- /dev/null +++ b/decisions/contribution-policy/final-texts.md @@ -0,0 +1,195 @@ +# Contribution policy: final texts and rulings (ACE batch 3, DL-122): authoritative + +Source: ACE batch-3 ruling of 2026-10-03 under Z's directive (see DL-122). Inventory: `inventory.md` (CT-1). Where this file and +the inventory differ, THIS FILE governs. Naming convention: DL-116/DL-117/DL-119. + +## Canonical statements +**PS-1.** The contributions we want keep this tutorial current to its toolchain (the OpenSysML runtime, sysml-toolkit and the other pinned tools) and to the OMG SysML v2 specifications; we are not adding new content. + +**PS-2.** Existing content may be refined, clarified or otherwise improved against three priorities: (1) conformance with the SysML v2 specifications (the OMG SysML v2 language, API and Services, and KerML specifications); (2) didactic clarity; (3) effective, demonstrative use of tools from the OpenSysML stack (the OpenSysML runtime and sysml-toolkit). An improvement is accepted only if it is strictly dominant: better on at least one of these and worse on none. + +**PS-3.** New content is a new chapter, notebook, exercise, construct or analysis operation, model element, judgment record or glossary term, or a new learning outcome; none is accepted by pull request. Replacing a recorded workaround with the spec-anchored construct a newer tool release accepts is keeping current, not new content. A trade-off (better on one priority, worse on another) is not an improvement under this policy; propose it in an issue and Z decides. + +**PL-1 (learner-facing, appended to ch10 conclusion.md "What comes next" after "...never made for them.").** If you come back to this repository, the contribution it wants is keeping it current to its toolchain and to the SysML v2 specifications, not extending it; the [contributor guide](#what-we-accept) says what that means. + +(NOTE for the builder: `#what-we-accept` is a label in docs/contributor.md; chapter pages are separate pages, so use the correct cross-page form that MyST resolves in this repo: check how other chapter pages link to docs pages, e.g. `../../docs/contributor.md#what-we-accept`, and verify the built link resolves; report the form used.) + +## AGENTS.md (Z-directed; this change is recorded by DL-122): insert after §1.11 (the paragraph ending before the `---` that follows it), a new section, verbatim: + +``` +## 1.12 What contributions we want + +The contributions we want keep this tutorial current to its toolchain (the OpenSysML runtime, sysml-toolkit and the other pinned tools) and to the OMG SysML v2 specifications; we are not adding new content. Existing content may be refined, clarified or otherwise improved against three priorities: (1) conformance with the SysML v2 specifications (the OMG SysML v2 language, API and Services, and KerML specifications, §1.2); (2) didactic clarity; (3) effective, demonstrative use of tools from the OpenSysML stack (the OpenSysML runtime and sysml-toolkit). An improvement is accepted only if it is strictly dominant: better on at least one of these and worse on none. A trade-off is not an improvement under this rule; it is proposed in an issue and Z decides. + +New content is a new chapter, notebook, exercise, construct or analysis operation, model element, judgment record or glossary term, or a new learning outcome; none is accepted by pull request. Replacing a recorded workaround with the spec-anchored construct a newer tool release accepts is keeping current (§1.9), not new content. Added text or cells count as improvement only where the learner's task gets harder without them (the earn-its-place test, `.claude/skills/ace-protocol/z-principles.md` P4; the pacing rule in `tutorial-style-guide`; SA-8). The reviewer-facing test is in `docs/contributor.md`, and the pull-request template asks for it. +``` +No change to §1.11, Part 2 head, or Part 2 §5. (If the file's section numbering/heading style differs, match it; report.) + +## docs/contributor.md +1. Insert as the NEW FIRST SECTION after the intro paragraph (after line 5), before `## Who is Z`: + +``` +(what-we-accept)= +## What we accept + +The contributions we want keep this tutorial current to its toolchain (the OpenSysML runtime, sysml-toolkit and the other pinned tools) and to the OMG SysML v2 specifications; we are not adding new content. Existing content may be refined, clarified or otherwise improved against three priorities: (1) conformance with the SysML v2 specifications (the OMG SysML v2 language, API and Services, and KerML specifications); (2) didactic clarity; (3) effective, demonstrative use of tools from the OpenSysML stack (the OpenSysML runtime and sysml-toolkit). An improvement is accepted only if it is strictly dominant: better on at least one of these and worse on none. The binding statement is [`AGENTS.md`](https://github.com/Open-MBEE/toaster/blob/main/AGENTS.md) §1.12. + +- **Keep current.** Bump a pin in `pyproject.toml`/`uv.lock`, `package.json`/`package-lock.json` or [`scripts/tool-pins.json`](https://github.com/Open-MBEE/toaster/blob/main/scripts/tool-pins.json) and regenerate the outputs ([below](#keep-current)); retire a workaround whose [`DEFERRED.md`](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md) resolution condition a new release meets, updating that entry's status line and the comment cell at the workaround (headings stay: they are linked anchors); re-check a clause that a new edition of one of the three OMG specifications changed. Reporting drift is a contribution too: open an issue naming the tool and version (or the specification edition), the chapter and cell, and the spec clause. Upstream issues are filed by the project itself, after Z has reviewed the text (`AGENTS.md` §1.9). +- **Improve what is here.** State in the pull request which priority improves and the evidence, and for each of the other two why it is not worse; the pull-request template asks for exactly this. Adding text or a cell counts as improving only if the learner's task gets harder without it, and the pacing rule and the one-construct-per-notebook rule still apply. An independent reviewer on a different model checks the statement; what the reviewer cannot tell goes to the ACE. A trade-off (better on one priority, worse on another) is not an improvement under this policy: open an issue and Z decides. +- **Not accepted by pull request.** A new chapter, notebook, exercise, construct or analysis operation, model element, judgment record or glossary term, or a new learning outcome. Replacing a recorded workaround with the spec-anchored construct a newer tool release accepts is keeping current, not new content. If you think the tutorial needs something new, open an issue first; only Z decides that, and a glossary term is confirmed only by Z. +``` +("Who is Z" follows and names the handle. Because "Z" first appears in this new section, write "mzargham (Z)" at that FIRST "Z" mention on the page per DL-112 (first mention on published pages) and keep the later text.) + +2. C-02 (about lines 48-52, the paragraph starting "If you want to extend a chapter ..."): replace the paragraph with: +``` +If you want to keep a chapter current, clarify a definition, or review didactic content, the harness +tools above are built for exactly that — start at `CLAUDE.md`'s own read order rather than +improvising a workflow from scratch. The sections below cover the maintenance tasks directly; none +of them require running an agent, but all of them follow conventions the harness itself enforces +(the recipe's pacing rule, the layer boundary tests, the review gate), and every change passes the +test in [What we accept](#what-we-accept). +``` +(Locate by text; the old sentence "extend a chapter" must exist exactly once; if the wording around it differs from this description, STOP and report.) + +3. C-04 (about lines 88-98, the section "## Update a dependency and regenerate outputs"): replace heading and steps with: +``` +(keep-current)= +## Keep current: update a dependency or tool pin and regenerate outputs + +1. Change the version in `pyproject.toml` (Python), `package.json` (Node) or + [`scripts/tool-pins.json`](https://github.com/Open-MBEE/toaster/blob/main/scripts/tool-pins.json) + (the `sysmlv2` binary, Z3, the PlantUML jar and the standard-library commit, with their sha256 + hashes), then `uv lock` / `npm install` to update the lockfile, or + `uv run python scripts/provision-tools.py` to re-provision `.tools/`. +2. Run `TOASTER_REQUIRE_TOOLS=1 uv run pytest tests/ glossary/tests/` and + `uv run python scripts/check-tools.py` + ([`check-tools.py` on GitHub](https://github.com/Open-MBEE/toaster/blob/main/scripts/check-tools.py)). +3. Rebuild the local preview (`uv run npx mystmd start --execute`) and spot-check a chapter that + exercises the changed dependency; a version bump in `opensysml` or `sympy` can change + printed output even when no test fails. +4. Re-read the `DEFERRED.md` entries that name the bumped tool. If the new release meets an + entry's resolution condition, retire the workaround, update the entry's status line and the + comment cell at the workaround (do not rename the heading), and recompute the `content_hash` + of any judgment record the model change touches ([below](#change-a-model-element)). A release + that breaks something gets a new entry, not a silently dropped demonstration. +5. Commit the lockfile or the pins alongside the version change; never bump a version without + regenerating and committing what matches it. Update the version strings in + `docs/setup.md`, `docs/reproducibility.md` and `AGENTS.md` §1.2 in the same change. +``` +(Keep any existing anchor/label that other pages link to: the old heading's slug was `update-a-dependency-and-regenerate-outputs`; grep for inbound references; if any exists, keep a `(update-a-dependency-and-regenerate-outputs)=` label too.) + +4. C-05 (about lines 100-113, "## Add a new chapter"): replace with: +``` +## How a change is built and reviewed + +New chapters are not accepted ([What we accept](#what-we-accept)); the rules that governed +building the existing ones govern every change to them: + +1. `toaster-recipe`'s sub-notebook skeleton and `architecture-layers`' boundary tests bind any + model element a change touches; both are binding, not stylistic suggestions. +2. Each cumulative fixture (`models/chNN-cumulative.sysml`) must keep containing everything the + previous chapter's fixture has; see + [`tests/test_predecessor_containment.py`](https://github.com/Open-MBEE/toaster/blob/main/tests/test_predecessor_containment.py) for how that invariant is checked. +3. Run `uv run python -m glossary lint` before committing prose; run the pacing check in + `tutorial-style-guide` (consecutive code cells with no markdown between them) on every + notebook you touched. +4. Get an independent review on a different model than whoever authored the change, per + `decisions/task-states.md`'s merge gate. +``` +(Keep the old heading's slug label if anything links to it; the existing content of that section that is not captured here must be reconciled: read the section first; if it contains steps not covered above that are still correct for CHANGING existing chapters, keep them under the new heading; if it describes adding a chapter only, drop them. Report what you dropped.) + +5. C-06 (after about line 119, the start of the "Change a model element" section): insert one sentence before its numbered list: +``` +A model change is accepted only as keeping current (a workaround retired) or as a strictly +dominant improvement ([What we accept](#what-we-accept)); either way: +``` +C-07 unchanged. + +## README.md +- ~L46: `exercises/ — parallel exercise notebooks (fork and work here)` -> `exercises/ — parallel exercise notebooks (work them in your own fork)` +- ~L57-58: `See [docs/contributor.md](docs/contributor.md) if you want to understand how the tutorial is actually built, tested, and reviewed, or to contribute to it yourself.` -> `See [docs/contributor.md](docs/contributor.md) to understand how the tutorial is built, tested and reviewed, and what contributions it wants: keeping it current to its toolchain (the OpenSysML runtime, sysml-toolkit and the other pinned tools) and to the OMG SysML v2 specifications, not adding new content.` +(Match the file's actual wrapping; locate by text.) + +## docs/setup.md +- Heading `## Fork and exercise` is kept. After the paragraph that says the workspaces are blank (about L183) append: +``` +Your fork is where your exercise work lives; it is not a contribution path. If, while working, you +find the tutorial out of date against a newer release of the OpenSysML runtime, sysml-toolkit or the +OMG specifications, that is the contribution this tutorial wants: see the +[contributor guide](#what-we-accept). +``` +(setup.md and contributor.md are separate pages: use the in-site link form that resolves, e.g. `contributor.md#what-we-accept` or the `contributor` page ref; verify on the built page.) +- N4 (about L149): `**sysml-toolkit** does what the OpenSysML runtime cannot yet: prove that a constraint holds for every` -> `**sysml-toolkit** does what the OpenSysML runtime's Python binding cannot yet: prove that a constraint holds for every` + +## chapters (markdown only) +- `chapters/ch10-traceability-signoff/conclusion.md` "## What comes next": append PL-1 after "...never made for them." +- N5 `chapters/ch07-execution/02-state-traces.ipynb` markdown cell `cell-23`: `The tutorial's own guard does: \`language_gap_findings\` flags` -> `The tutorial's own guard catches it: \`language_gap_findings\` flags` (nothing else changes). +- N6 `chapters/ch03-measures/03-threshold-judgment.ipynb` markdown cell `cell-03` (the cell discussing the file; NOT `968c7318`): append + > Its `doc` comment's note `not yet supported in OpenSysML v0.9.0` predates this tutorial's component naming and refers to the OpenSysML runtime v0.9.0, which does not accept the formal `#verificationMethod` metadata; notebook 04 explains ([toaster#19](https://github.com/Open-MBEE/toaster/issues/19)). + The quoted phrase stays in backticks (the lint rule has ignore_code). Code cells and models are untouched. NOTE: `toaster#19` link text is a protected token: ADDING it is fine. + +## Skills (CT-3, ACE only; DL-122 is the pre-edit record; revert record = commit preceding the first skill edit) +`.claude/skills/tutorial-supporting-pages/SKILL.md`: +- L17: `| \`docs/contributor.md\` | Maintainer guide (4 update scenarios) |` -> `| \`docs/contributor.md\` | Maintainer guide ("What we accept" policy, then 4 maintenance scenarios) |` +- L19-32 template block: add after item 4, inside the fence: `5. Your fork is where your exercise work lives; it is not a contribution path (see the contributor guide).` (Nothing else in the block changes.) +- L48-53 -> +``` +## Contributor guide — 4 required scenarios + +The guide opens with "What we accept" (AGENTS.md §1.12: keep current to the toolchain and the +specifications; strictly dominant improvements; no new content), then: + +1. Keep current: update a dependency or tool pin and regenerate outputs +2. How a change is built and reviewed +3. Change a model element and review stale judgment records +4. Run the full CI pipeline locally +``` +- L59-64: add bullet `- Write a contributor scenario that invites a new chapter, notebook, exercise, model element or glossary term (AGENTS.md §1.12)`. + +## CT-3b (separate ACE session, own pre-edit DL-123): `.claude/skills/sysml-diagrams/SKILL.md` +- L18 cell 3 -> `Confirmed against real chapter content (Ch4's \`ToastBread\`, Ch6's \`ApplyHeat\`): exit 0 and the control sequence is drawn, but the CLI's DOT omits the actions' declared typed flows (D-037); the caption must say so. No in-house action-flow renderer exists yet.` +- L19 cell 3 first sentence -> `The real-fixture study ran this against Ch7's real \`Cycle\` state machine: both OpenSysML runtime render forms exit 0 and draw the states and transitions, but the \`do\` activity label omits the performed action's name (D-037); the caption must say so.` +(Locate by text; the surrounding table cells and fences stay.) + +## CT-4 (.github, lint) +New file `.github/PULL_REQUEST_TEMPLATE.md`: +``` + + +## What this change is (tick one) + +- [ ] Keeps current: pin or dependency bump / workaround retired (DEFERRED entry D-___) / spec-edition re-check / drift fix +- [ ] Improves what is here (fill in the next section) + +## Strictly dominant (improvements only) + +Better on (name it, with evidence): +- [ ] (1) Spec conformance — clause(s): ___ ; check run: ___ +- [ ] (2) Didactic clarity — what gets harder for the learner without this change: ___ ; pacing check: ___ +- [ ] (3) Tool use — construct or operation run under the pinned versions: ___ + +Not worse on each of the others (one line each, with how you checked): +- (1) ___ +- (2) ___ +- (3) ___ + +## Protections + +- [ ] No new chapter, notebook, exercise, construct or analysis operation, model element, judgment record or glossary term +- [ ] `models/`, judgment records, stored outputs and `DEFERRED.md` headings unchanged, or the change says why and recomputes `content_hash` +- [ ] `TOASTER_REQUIRE_TOOLS=1 uv run pytest tests/ glossary/tests/` and `uv run python -m glossary lint` pass locally +``` +`glossary/lint.py`: reject unknown per-rule keys: `unknown = sorted(set(raw) - set(FIELDS) - set(OPTIONAL_BOOL_FIELDS))` -> `LintConfigError(f"rule {name!r}: unknown field(s) {unknown}")` (adapt names to the actual code), a test in `glossary/tests/test_lint.py`, and "unknown rule field" in the README's exit-2 list. Acceptance: `uv run pytest glossary/tests/` passes; lint counts equal to base. + +## N-items ruled (no edit unless listed above) +N1: recorded retroactively (glossary/README.md edit accurate; no revert). N2: no edit. N3, N8: no action. N7 leftovers (stored-output label `OpenSysML itself`, "this toolchain's Z3 backend" in code/outputs, "the pilot itself stays toolchain", D-019 body, D-034 Resolution): left. + +## Reviewer checklist (strictly dominant) +| Priority | "Worse" means | Verified by | Evidence | +|---|---|---|---| +| (1) Conformance with the three OMG specifications | model or notebook text violating a normative clause; a spec-anchored construct replaced by a tool idiom; a conformance check or its negative control dropped or weakened; a staged check's status changed without its stated condition | reviewer (different model); the Pilot Implementation's behavior is the baseline where a clause is ambiguous (AGENTS 1.2) | clause citation; `uv run python scripts/check_conformance.py`; strict load under the pinned runtime; always-on guards still fire on their negative controls | +| (2) Didactic clarity | longer or denser without passing P4's test; a second construct or operation in a sub-notebook (SA-8); a lens named to learners (1.10); a check presented as proof or a disposition "accepted" (1.6, SA-7); pacing rule broken; a figure whose omissions are no longer stated | reviewer; a simulated-learner checkpoint (`user-testing`) when the change alters what a learner does or sees beyond wording | the proposer's P4 statement; pacing-check output; learner report where required; figure recipe and caption | +| (3) Demonstrative use of the OpenSysML stack | a construct described as working but not run under the pinned version (P5); a new workaround without DEFERRED entry, issue draft and comment cell (1.9); a tool demonstration removed; meaning moved from the model into Python (F4) | CI (`TOASTER_REQUIRE_TOOLS=1`, `myst build --strict`, `check-site.py`); reviewer re-runs the touched notebook | executed outputs; DEFERRED entries touched; `check-tools.py` output | + +Decision rule: PASS needs one evidenced "better" (improvements) or an evidenced currency event (keep-current), plus all three evidenced "not worse" lines. Any "worse" -> not accepted (trade-offs go to Z by issue). CANT_TELL or a protected zone touched -> ACE. Ties with no currency event -> churn, declined. diff --git a/decisions/contribution-policy/inventory.md b/decisions/contribution-policy/inventory.md new file mode 100644 index 0000000..398a748 --- /dev/null +++ b/decisions/contribution-policy/inventory.md @@ -0,0 +1,303 @@ +# Contribution-policy inventory (CT-1, read-only) + +Contract CT-1 of the contribution-policy revision queued in `docs/superpowers/plans/2026-10-03-opensysml-terminology-plan.md` +("Queued after OT-8"). Branch `ct/inv`. Requirement (mzargham, 2026-10-03): the contribution sections, in the notebooks and in +the docs, must say that the contributions we want are **keeping the tutorials current to the toolchain**, not adding new content; +existing content may be refined, clarified or improved against the priorities (1) conformance with the SysML v2 specifications +(language, API and Services, KerML), (2) didactic clarity, (3) effective, demonstrative use of tools from the OpenSysML +ecosystem, and an improvement is acceptable if **strictly dominant**. + +## Method + +Read the plan's queued section, DL-116 to DL-121, AGENTS.md Part 1 (1.2, 1.9, 1.11), `docs/contributor.md`, `docs/setup.md`, +`README.md`. Then searched, case-insensitively, `docs/*.md`, `docs/case-studies/*.md`, `README.md`, `myst.yml`, `LICENSE`, +`AGENTS.md`, `CLAUDE.md`, `DEFERRED.md` (header), `.claude/skills/*/SKILL.md` (and `.claude/agents`), `.github/`, every +`chapters/*/index.md` and `conclusion.md`, and the markdown cells of all 42 notebooks under `chapters/` and `exercises/` +(parsed as JSON, markdown cells only, with cell index and id) for: contribut, fork, pull request, PR, issue, extend, add a, new +chapter, new exercise, your own, feedback, improve, suggest, propose, "try ", next steps, what comes next, exercise, good first, +open an, report a. `docs/superpowers/` and `decisions/` are excluded from the surface (history). No file other than this one was +edited. Scripts are in the session scratchpad (`ct1/scan.py`, `ptr.py`, `gen.py`). + +Row IDs: R README, D/S/C docs index/setup/contributor, RF references, RP reproducibility, CS case study, M myst.yml, L LICENSE, +G .github, A AGENTS.md, CL CLAUDE.md, SK skills, DF DEFERRED.md, NB notebook markdown cells, IX chapter index.md, CN +conclusion.md, EX exercise notebook intro cell. NB, IX, CN and EX rows were generated from the files, so their quotes are verbatim. + +### Counts (rows by file group) + +| file group | rows | +|---|---| +| .claude/skills/orchestrator-protocol/SKILL.md | 1 | +| .claude/skills/skill-editor/SKILL.md | 1 | +| .claude/skills/tutorial-supporting-pages/SKILL.md | 4 | +| .github/ | 1 | +| AGENTS.md | 6 | +| CLAUDE.md | 2 | +| DEFERRED.md | 2 | +| LICENSE | 1 | +| README.md | 5 | +| chapters/**/*.ipynb (exercise-pointer & other md cells) | 35 | +| chapters/*/conclusion.md | 20 | +| chapters/*/index.md | 10 | +| docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md | 1 | +| docs/contributor.md | 9 | +| docs/index.md | 1 | +| docs/references.md | 1 | +| docs/reproducibility.md | 1 | +| docs/setup.md | 7 | +| exercises/*/exercise.ipynb | 10 | +| myst.yml | 1 | +| **total rows** | **119** | + +### Notebook finding (verified) + +**No notebook markdown cell, in `chapters/` or `exercises/`, mentions contributing.** Across all 42 notebooks the words +"contribut", "fork", "pull request", "feedback", "propose", "next steps", "good first" and "open an" occur in **zero** markdown cells +(the only "issue" hits are two links in `chapters/ch03-measures/04-verification-case.ipynb` cell index 5, id `e5f6g7h8`, to the +tracker issues `toaster#19` and `OpenSysML#608`; "suggest" is the unrelated "mechanism-suggestive name" in ch06 nb02 cell-39). +`grep -rli contribut chapters exercises` returns nothing, including code cells and outputs. The same holds for chapter +`index.md` and `conclusion.md`: zero hits for "contribut" or "fork". The only text in the notebook surface that invites learner +action is: + +1. **32 exercise-pointer cells** in chapter notebooks, each beginning "Try the chapter exercise in `exercises/chNN/exercise.ipynb`:" + (rows NB-01 to NB-32): ch01 x4, ch02 x3, ch03 x4, ch04 x3, ch05 x3, ch06 x3, ch07 x3, ch08 x3, ch09 x3, ch10 x3. +2. **10 chapter `index.md` lines** pointing at the exercise (IX rows; ch01-ch08 prose, ch09 and ch10 "See ..."). +3. **10 `conclusion.md` exercise paragraphs** (CN `-ex` rows: ch01-ch06 "**Exercise:**", ch07-ch10 an "## Exercise" section) and + **10 "What comes next" sections** (CN rows). ch01-ch06 "What comes next" names the next chapter; ch07-ch09 likewise; **ch10's + says "This is the tutorial's last chapter. What continues from here is not another chapter but the reader's own accountable + engineering"**, the only "what comes next" sentence that points beyond the tutorial and the natural candidate placement for a + single contributor note. +4. **10 exercise notebooks** (EX rows), whose cell 0 uses "Extend your Chapter N coffee maker model": exercise language about + the learner's own model, not a repository contribution. + +So the user's recollection that the notebooks "have a contribution section" is not borne out by the current text: the only +contribution-adjacent text is the exercises. The requirement therefore means an **ADD** in the notebooks (placement for the ACE; +candidates: ch10 `conclusion.md` "What comes next"; the cell-0 intro of `exercises/ch01/exercise.ipynb`; each chapter +`index.md`), not a rewrite. If the "section" the user remembers is somewhere I did not search (a branch other than +`ct/inv`/`contrib`, an unmerged edit), it was not found in this worktree. Notebook edits are markdown-only (terminology-pass +protections); `conclusion.md`/`index.md` are not notebooks and are ordinary markdown. + +### Proposed canonical wording (applied by reference in the table) + +Convention per DL-116/117/119: bare "OpenSysML" is the stack; "the OpenSysML runtime" and "sysml-toolkit" name components; the +Pilot is "the OMG SysML v2 Pilot Implementation" and is not part of OpenSysML. Draft texts (ACE to settle; not applied): + +- **PS-1 (one sentence, for README, setup, index, notebooks, AGENTS).** "The contributions we want are keeping this tutorial + current to the toolchain (the OpenSysML runtime, sysml-toolkit and the other pinned tools) and to the OMG SysML v2 + specifications, not adding new content." +- **PS-2 (the improvement test).** "You may refine, clarify or otherwise improve what is here when the change is strictly + dominant against our priorities: (1) conformance with the SysML v2 specifications (the language, API and Services, and KerML + PDFs); (2) didactic clarity; (3) effective, demonstrative use of tools from the OpenSysML ecosystem. A change is strictly + dominant if it makes at least one of these better and none of them worse." +- **PL-1 (docs/contributor.md, new first section "What we accept").** PS-1, PS-2, then three bullets: *Keep current* (bump a pin + in `scripts/tool-pins.json`/`pyproject.toml`, regenerate outputs, retire a workaround when a DEFERRED entry's condition is met, + re-check a clause that a new edition of the three OMG PDFs changed); *Improve what is here* (the strictly dominant test; say + which priority improves and show the other two do not worsen); *Not accepted* (new chapters, notebooks, exercises, models or + glossary terms; open an issue first). + + +## Row table + +| row | file | locator | quoted text (verbatim) | what it currently invites | conflict with the requirement | proposed treatment | proposed wording | +|---|---|---|---|---|---|---|---| +| R-01 | README.md | L39-40 | See [docs/setup.md](docs/setup.md) for full setup instructions and the fork-and-exercise workflow, including what `uv` and `mystmd` are and why the quick start above uses them. | neutral (points to the fork-and-exercise workflow) | ambiguous ("fork" frames the workflow as a learner exercise; fine if the exercise/contribution distinction is made on setup.md) | KEEP | No change if setup.md keeps the exercise distinction; otherwise "...and the exercise workflow". | +| R-02 | README.md | L45 | chapters/ — worked example notebooks (10 chapters, read-only for exercises) | neutral | none | KEEP | | +| R-03 | README.md | L46 | exercises/ — parallel exercise notebooks (fork and work here) | exercise (learner works in exercises/ in a fork) | ambiguous (reads as invitation to add work in the repo; exercises are private learning) | REWRITE | exercises/ — parallel exercise notebooks (work here in your own copy; exercises are for learning, not for submission) | +| R-04 | README.md | L56-58 | [`AGENTS.md`](AGENTS.md), [`CLAUDE.md`](CLAUDE.md), and [`DEFERRED.md`](DEFERRED.md) at the repo root are not learner material — they're this project's own working contract, for the AI agents and maintainers who build and review the tutorial's content. See [docs/contributor.md](docs/contributor.md) if you want to understand how the tutorial is actually built, tested, and reviewed, or to contribute to it yourself. | add content / contribute (open invitation "to contribute to it yourself") | contradicts (open-ended; does not say what is wanted) | REWRITE | ...See docs/contributor.md if you want to understand how the tutorial is built, tested and reviewed, or to help keep it current. The contributions we want are keeping the tutorial current to the toolchain (the OpenSysML runtime, sysml-toolkit and the other pinned tools) and to the OMG SysML v2 specifications, not adding new content; PS-2 sets the test for improving what is here. | +| R-05 | README.md | (absent) | (no contribution policy sentence) | - | ambiguous (README is the first page a would-be contributor sees) | ADD | Add the PS-1 sentence after the Quick start or in the closing paragraph (see R-04); one sentence plus link to docs/contributor.md#what-we-accept (new anchor). | +| D-01 | docs/index.md | whole page (29 lines) | (no contribution, fork, exercise or feedback text; links only: Setup, Glossary, References, Case studies) | neutral | none | KEEP (or ADD one link) | Optional: add [Contributing](contributor.md) to the footer link row only if the ACE wants a contribution entry point on the landing page; docs/contributor.md is already in the myst.yml toc (L88). | +| S-01 | docs/setup.md | L47-48 | CI builds this book on every pull request and deploys it from `main` to ; see [the contributor guide](#deployment-status). | neutral (points to contributor guide for build/deploy) | none | KEEP | | +| S-02 | docs/setup.md | L74-75 | Building and deploying the GitHub Pages site itself is a maintainer task, not something you need for the tutorial; see [the contributor guide](#deployment-status). | neutral | none | KEEP | | +| S-03 | docs/setup.md | L156-160 ("Revisit this note") | (above), which installs the pinned build from [its GitHub releases page](https://github.com/Open-MBEE/sysml-toolkit/releases). Revisit this note once the maintainers publish the real package: installing it should then replace both this download step and, eventually, the `subprocess` call in [`src/toaster/modelcheck.py`](https://github.com/Open-MBEE/toaster/blob/main/src/toaster/modelcheck.py) | fix / stay current (revisit when upstream publishes the real package; replace download step and subprocess call) | none (already a keep-current hook) | KEEP | Candidate target for the new wording to point at (hook: upstream-change watch). | +| S-04 | docs/setup.md | L164-170 | When a tool does not yet support something a chapter needs, this tutorial says so, uses the next tool that does, and wraps the difference behind a plain Python function so a chapter's own code reads the same either way. [`src/toaster/modelcheck.py`](https://github.com/Open-MBEE/toaster/blob/main/src/toaster/modelcheck.py) is one example: it will call sysml-toolkit's command-line tool under the hood, so Chapter 8's own cells only ever see a Python function call. Each of these wrappers is recorded in [`DEFERRED.md`](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md), with the specific gap it patches and the condition under which the patch comes out: once a published Python package reaches the same capability, the wrapper is replaced with a direct call to it. | neutral / process description (gaps recorded in DEFERRED.md, wrapper replaced when upstream catches up) | none (process hook for keeping current) | KEEP | Candidate target for the new wording to point at. | +| S-05 | docs/setup.md | L172-183 ("## Fork and exercise") | ## Fork and exercise Fork the repository, provision the environment (above), then: 1. Read the worked example: open a chapter notebook in `chapters/` and run every cell. 2. Open the parallel exercise: `ch{N}/exercise.ipynb` in the [`exercises/` directory](https://github.com/Open-MBEE/toaster/tree/main/exercises). 3. The exercise asks you to apply the same construct or operation to a different domain: a coffee maker, built in parallel to the toaster throughout the tutorial. The only tools it needs are the ones the chapter already introduced. The [`exercises/`](https://github.com/Open-MBEE/toaster/tree/main/exercises) notebooks are blank workspaces. They are not pre-executed and not part of the CI pipeline. Work in them directly; do not modify the chapter notebooks while doing an exercise. | exercise (fork, run a chapter, do the coffee-maker exercise in a blank workspace) | ambiguous (names "Fork"; a reader may take a fork as the contribution path; exercises are not contributions) | REWRITE | Keep the three steps. Retitle "Work an exercise" or add one sentence after the opening: "Forking is how you keep your own exercise work; it is not a contribution. The contributions we want are described in the [contributor guide](contributor.md#what-we-accept)." The exercise notebooks stay blank and outside CI (existing sentence retained). | +| S-06 | docs/setup.md | L185-193 ("Keep your model between chapters") | **Keep your model between chapters.** Each exercise's first cell asks you to paste in your own completed model from the previous chapter's exercise — there is no committed solution file to load instead. Save the full `source` string your notebook ends with (for example, to a scratch `.sysml` file in your own fork, or just keep the notebook itself open) before moving to the next chapter's exercise, or you will have nothing to paste in. Chapter 6's own exercise is the one case where you need to keep **two** separate snapshots, not one: the model state right before you add `Impeller` (used by the mechanism-selection judgment, written before the mechanism it selects exists) and the model state right after (used by the stopping judgment, and the one that carries forward into Chapter 7). Chapter 6's own exercise notebook flags exactly where to save each one. | exercise (save your own model between chapters) | none | KEEP | "in your own fork" is acceptable if S-05 states that a fork is a private workspace. | +| S-07 | docs/setup.md | (absent) | (no section on what contributions are wanted) | - | ambiguous | ADD | Under the "Fork and exercise" section add: "Found the tutorial out of date against a newer release of the OpenSysML runtime, sysml-toolkit or the specifications? That is the contribution we want; see the contributor guide." | +| C-01 | docs/contributor.md | L1-5 | This page is for maintainers working on the tutorial itself, not learners working through it. It assumes you can read Python and SysML and that you have the environment from [Getting Started](setup.md) already set up. | neutral (audience statement: maintainers) | none | KEEP | | +| C-02 | docs/contributor.md | L48-53 (end of harness overview) | If you want to extend a chapter, clarify a definition, or review didactic content, the harness tools above are built for exactly that — start at `CLAUDE.md`'s own read order rather than improvising a workflow from scratch. The sections below cover specific maintenance tasks directly; none of them require running an agent, but all of them follow conventions the harness itself enforces (the recipe's pacing rule, the layer boundary tests, the review gate). | add content / extend ("If you want to extend a chapter, clarify a definition, or review didactic content, the harness tools above are built for exactly that") | contradicts (invites extending chapters) | REWRITE | "If you want to keep a chapter current to the toolchain, clarify a definition, or review didactic content, the harness tools above are built for exactly that — start at CLAUDE.md's own read order ..." and point to the new "What we accept" section. | +| C-03 | docs/contributor.md | L55-86 (Deployment status) | CI builds the book on every pull request and deploys it from `main` to . Pull-request builds do not deploy: the `deploy` job runs only for a push to `main`, and only after the `build` job passes. The workflow is [`.github/workflows/ci.yml`](https://github.com/Open-MBEE/toaster/blob/main/.github/workflows/ci.yml). [...] | neutral (CI and release gate) | none | KEEP | The CI gate is one of the process hooks (b below). | +| C-04 | docs/contributor.md | L88-98 ("## Update a dependency and regenerate outputs") | ## Update a dependency and regenerate outputs 1. Change the version in `pyproject.toml` (Python) or `package.json` (Node), then `uv lock` / `npm install` to update the lockfile. 2. Run `uv run pytest tests/ glossary/tests/` and `uv run python scripts/check-tools.py` ([`check-tools.py` on GitHub](https://github.com/Open-MBEE/toaster/blob/main/scripts/check-tools.py)). 3. Rebuild the local preview (`uv run npx mystmd start --execute`) and spot-check a chapter that exercises the changed dependency; a version bump in `opensysml` or `sympy` can change printed output even when no test fails. 4. Commit the lockfile alongside the version change; never bump a version without regenerating and committing the matching lockfile. | fix / stay current (bump pinned version, regenerate lock, spot-check output) | none (this is the central wanted contribution) | KEEP + EXPAND | Promote to the first scenario and extend: also the tool pins (scripts/tool-pins.json via provision-tools.py), re-reading the DEFERRED entries a new release might close, and re-checking the three OMG PDFs for changed clauses. Add a pointer to PS-2 (strictly dominant) for anything beyond a bump. | +| C-05 | docs/contributor.md | L100-114 ("## Add a new chapter") | ## Add a new chapter 1. Follow `toaster-recipe`'s sub-notebook skeleton and `architecture-layers`' boundary tests for every new model element; both are binding, not stylistic suggestions. 2. Add the chapter's cumulative fixture (`models/chNN-cumulative.sysml`), authored to contain everything the previous chapter's fixture has plus the new chapter's own additions; see [`tests/test_predecessor_containment.py`](https://github.com/Open-MBEE/toaster/blob/main/tests/test_predecessor_containment.py) for how that invariant is checked. 3. Register the new notebooks in [`scripts/check_construction.py`](https://github.com/Open-MBEE/toaster/blob/main/scripts/check_construction.py)'s `CONSTRUCTION_NOTEBOOKS` and in `myst.yml`'s table of contents. 4. Run `uv run python -m glossary lint` before committing prose; run the pacing check in `tutorial-style-guide` (consecutive code cells with no markdown between them) on every new notebook. 5. Get an independent review on a different model than whoever authored the chapter, per `decisions/task-states.md`'s merge gate. | add content (new chapter, fixture, registration, review) | contradicts | REWRITE / MOVE | Remove as a scenario. Either (i) delete, or (ii) replace with "We are not adding chapters. A proposed new chapter is out of scope; open an issue first." (needs ACE: see ambiguity A-1). The numbered build-rules (recipe skeleton, cumulative fixture, check_construction registration, pacing check, independent review) should be retained elsewhere as "How a change is built and reviewed" because they apply to any edit. | +| C-06 | docs/contributor.md | L116-127 ("## Change a model element and review stale judgment records") | ## Change a model element and review stale judgment records A `ReviewRecord`'s `content_hash` is computed from the model source it was written against. Changing that source without updating the record leaves it silently stale. 1. Find every `ReviewRecord` whose `model_ref` touches the element you are changing | fix (maintain existing models; refresh stale records) | none to ambiguous (an edit to a model element is allowed if strictly dominant) | KEEP + frame | Keep; add the sentence that a model change must pass PS-2. | +| C-07 | docs/contributor.md | L129-147 ("## Run the full CI pipeline locally") | ## Run the full CI pipeline locally ```sh uv sync --locked npm ci uv run python scripts/provision-tools.py [...] | neutral (local reproduction) | none | KEEP | | +| C-08 | docs/contributor.md | (absent) | (no section stating what contributions are wanted, how an improvement is judged, or where to report toolchain drift / spec gaps) | - | ambiguous | ADD | New first section "What we accept" containing PL-1 (below). | +| C-09 | docs/contributor.md | L24-27 (DEFERRED.md bullet) | where its terms come from, how models are built and queried) and the legacy role roster that used to own each file. It's the harness's own foundational reference, read first by every agent role before it does anything else. - **[`CLAUDE.md`](https://github.com/Open-MBEE/toaster/blob/main/CLAUDE.md)** is the entry point: read order, the glossary CLI, and the skill index below. | report gap (tracks toolchain gaps and the condition under which the workaround comes out) | none (it is the keep-current register) | KEEP + link | Cite from "What we accept": a closed upstream gap is the paradigm contribution. | +| RF-01 | docs/references.md | L69 (OpenSysML runtime paragraph, last sentence) | Gaps between the runtime's current API and the SysML v2 specification are tracked in DEFERRED.md and as issues in this repository and upstream. | report gap | none (hook) | KEEP | | +| RP-01 | docs/reproducibility.md | L6, L55, L67 | [Contributor Guide](contributor.md) for the maintainer-side mechanics this page points at. \| more than one operating system. The [Contributor Guide](#deployment-status) says how \| (the [Contributor Guide](#change-a-model-element)'s section on | neutral (links to contributor guide) | none | KEEP | | +| CS-01 | docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md | whole page | (no contributor-facing text; "exercise mirror" and "exercised" refer to the project's own exercises/ parity and to construct testing) | neutral | none | KEEP | | +| M-01 | myst.yml | L2-9 | title: Toaster; github: https://github.com/Open-MBEE/toaster; license: Apache-2.0; authors: - name: Michael Zargham; exclude: exercises/** | neutral (project metadata; no contributors list or description) | none | KEEP | No project description key exists; none is required. exclude: exercises/** keeps exercises unpublished. | +| L-01 | LICENSE | Apache-2.0 §5 "Submission of Contributions" | Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work ... | neutral (license text) | none | KEEP | Note only: the contribution policy complements, and does not alter, the license. | +| G-01 | .github/ | (directory listing) | Only .github/workflows/ci.yml exists. No PULL_REQUEST_TEMPLATE, ISSUE_TEMPLATE, CONTRIBUTING.md, CODE_OF_CONDUCT or CODEOWNERS file anywhere in the repo. | - | ambiguous (a contributor will look here first and find nothing) | ADD (optional) | PR template with the PS-2 checklist ("Which of the three priorities does this improve; show the other two are not worse") and an issue template "toolchain drift". Requires an ACE decision whether new .github files are in scope (they are outside the notebook/markdown guard). | +| A-01 | AGENTS.md | Part 1 §1.11 (L163) | Alignment passes (changes to this Part 1, the glossary's confirmed definitions, or the ACE skills) are Z-initiated. The ACE triages what needs Z: it rules and logs where Z's frameworks and principles determine the answer (and shows the reasoning), and escalates to Z with a concise request where they do not. Decisions are logged in `decisions/log.md` (§7 below). To reach the ACE, route the question through the orchestrator; if there is no orchestrator in your session, state the question and your recommended default in your report and it will be triaged. Proposals to the glossary (new terms, sources or edges) go to the ACE the same way; only Z confirms. | neutral (internal: who initiates alignment passes; proposals go via ACE) | ambiguous ("Proposals to the glossary (new terms, sources or edges) go to the ACE" invites proposals of new terms) | KEEP / maybe REWRITE (Z-directed) | Possible addition at the end: "Contributions from outside the project team follow docs/contributor.md: keeping the tutorial current to the toolchain, and strictly dominant improvements; new chapters, exercises and models are out of scope." Part 1 edit = Z-directed pass (AGENTS 1.11). | +| A-02 | AGENTS.md | Part 1 §1.9 "Gap-tracking rule" (L147) | **Gap-tracking rule.** Use the spec-anchored construct. If a tool cannot express it, use a bare SysML fragment or custom Python. Every gap gets (a) a `DEFERRED.md` entry, (b) a toaster issue and, where the tool is at fault, an upstream issue, each citing the exact spec section and asking only for what the spec says, and (c) a comment cell wherever the workaround appears. Never work around a gap silently. Nothing is filed on a public repository until Z has reviewed the text. | report gap (DEFERRED entry, toaster issue, upstream issue; nothing public until Z has reviewed) | none (this is the keep-current mechanism); ambiguous only on whether an outside reporter's issue needs Z's review first | KEEP | Cite from contributor guide. | +| A-03 | AGENTS.md | Part 1 §1.1-1.11 | (no sentence states the contribution scope: Part 1 says what the tutorial teaches and how alignment changes, not what outsiders may add) | - | ambiguous | ADD (Z-directed) | One sentence in §1.11 or a new short §1.12 "What contributions we want" carrying PS-1 and PS-2 verbatim, so that Part 1 governs and the docs link to it. | +| A-04 | AGENTS.md | Part 2 L6 and L180 | - **Part 2, Roster and authority**, is the earlier role, file-authority and escalation material. Pass 2 rebuilt the process roles (orchestrator, layer-auditor, builder, reviewer, ace) and Pass 3 rebuilt the simulated-learner role (see the mapping at the head of Part 2); the content-authoring archetypes (A3, A4, A7, A10) remain * ... \| A3 Modeler, A4 Educator, A7 Visualization Assessor and A10 Systems Architect are content-authoring archetypes with no rebuilt role file; they remain the legacy roster below, pending Pass 4 (the didactic content pass). Everything below is the earlier role and file-authority material, kept unchanged except where Part 1 or a rebuil | add content (content-authoring archetypes A3, A4, A7, A10 "pending Pass 4", i.e. the legacy roster authored the chapters) | ambiguous (history of authoring new content; no statement that chapter-adding is closed) | KEEP (note in report) | If ACE wants to close new-chapter authoring, one sentence at Part 2's head: "Pass 4 is complete; the legacy authoring archetypes are not an instruction to add chapters." Part 2 is not Part 1; ordinary edit. | +| A-05 | AGENTS.md | Part 2 §2 file authority matrix (A4 Educator row) | \| A2 Builder \| `src/toaster/` (all 8 modules + `__init__.py`), `tests/`, `.github/`, `pyproject.toml`, `uv.lock`, `package.json`, `package-lock.json`, `myst.yml`, `scripts/`, `.gitignore`, `README.md`, `AGENTS.md`, `CLAUDE.md`, `DEVELOPMENT_PLAN.md` \| Chapter prose cells, `docs/` prose, `models/*.sysml`, `skills/` \| | edit (prose, docs and exercise problem statements) | ambiguous (A4 may edit exercise statements; policy says exercises are not to be added; edits of existing text are fine) | KEEP | | +| A-06 | AGENTS.md | Part 2 §5 "What every agent must never do" (L260-267) | ## 5. What every agent must never do - Never edit files outside your remit. - Never produce `\|\| true` in shell commands. - Never assert `model.ok` without actually loading and checking the model. - Never set `record_kind = "actual_review"` — all records are worked examples (SA-7). - Never invent opensysml API shapes not in the adapter file. - Never re-open SA-1 through SA-9 without A8 logging and ... | neutral | none | KEEP (optional ADD) | Optional bullet: "Never add a chapter, notebook, exercise or model unless the ACE has ruled that the policy test is met." | +| CL-01 | CLAUDE.md | skill list, L26 | - tutorial-glossary — using and extending the glossary knowledge graph | extend (glossary) | ambiguous ("extending the glossary" is a builder task, new terms need Z; reads as add-content) | KEEP (optional REWRITE) | "using and refining the glossary knowledge graph" would match the policy; the skill's own text (Z confirms definitions) is unchanged. Low priority. | +| CL-02 | CLAUDE.md | whole file | (no contribution, fork or issue text) | neutral | none | KEEP | | +| SK-01 | .claude/skills/tutorial-supporting-pages/SKILL.md | L3 (description) and L17 | description: docs/ page inventory, reproducibility statement structure, fork-and-exercise workflow, and contributor guide scenarios. \| `docs/contributor.md` \| Maintainer guide (4 update scenarios) \| | neutral (inventory) | ambiguous ("4 update scenarios" will become a different number) | REWRITE (ACE, skill-editor gate) | Update the count and the scenarios list to match the revised contributor.md. | +| SK-02 | .claude/skills/tutorial-supporting-pages/SKILL.md | L19-34 (fork-and-exercise template) | ## docs/setup.md — fork-and-exercise section ``` ## Fork and exercise Fork the repo, provision the environment (see above), then: 1. Read the worked example: open a chapter notebook in `chapters/` and run all cells. 2. Open the parallel exercise: `exercises/ch{N}/exercise.ipynb`. 3. The exercise asks you to apply the same construct or operation to a different part of the toaster. The only tools you need are the ones introduced up to that chapter. 4. The `exercises/` notebooks are blank workspaces — they are not pre-executed and not part of the CI pipeline. ``` Cells 0–5 of any sub-notebook are the worked example (read-only reference). The exercise lives in `exercises/` as a separate file. Do not modify chapter notebooks while doing exercises. | exercise | ambiguous (same as S-05) | REWRITE (ACE) | Mirror the revised setup.md section. | +| SK-03 | .claude/skills/tutorial-supporting-pages/SKILL.md | L48-53 ("## Contributor guide — 4 required scenarios") | ## Contributor guide — 4 required scenarios 1. Update a dependency and regenerate outputs 2. Add a new chapter 3. Change a model element and review stale judgment records 4. Run the full CI pipeline locally | add content (scenario 2 "Add a new chapter") | contradicts | REWRITE (ACE) | Required scenarios become: update a dependency/tool pin and regenerate outputs; re-check against the specifications; change a model element and review stale records; run the CI pipeline locally; plus the "What we accept" opening section. | +| SK-04 | .claude/skills/tutorial-supporting-pages/SKILL.md | L59-64 ("What A4 must never do") | ## What A4 must never do - Write the reproducibility statement without `{{manifest_field}}` template markers (A2 populates them) - Write contributor instructions that require unavailable tooling - Write setup instructions inside chapter narration (they go in `docs/setup.md`) - Include exercise instructions inside chapter notebooks (exercises live in `exercises/`) | neutral (rule: exercises live in exercises/) | none | KEEP + optional ADD | Optional bullet: "Write a contributor scenario that invites new chapters, exercises or models." | +| SK-05 | .claude/skills/orchestrator-protocol/SKILL.md | L21 (WP-9 row) | \| WP-9 \| A3+A4 \| A5+A6 \| closing page from manifest; contributor guide covers 4 scenarios; https://open-mbee.github.io/toaster/ publicly accessible \| | neutral (historical WBS acceptance: contributor guide covers 4 scenarios) | ambiguous (the "4 scenarios" acceptance becomes stale) | REWRITE (ACE) or KEEP as history | WBS rows are historical acceptance; prefer a dated note over a rewrite. | +| SK-06 | .claude/skills/skill-editor/SKILL.md | L17 (Z-directed alignment pass clause) | **Z-directed alignment pass.** When Z has directed an alignment pass, the DL entry that records Z's direction (with the plan it follows) satisfies the escalate-to-Z gates in Step 2 for the edits it names. ... | neutral (process: how skill edits are authorized) | none | KEEP | Governs the skill edits SK-01..SK-05; no contributor instruction. | +| DF-01 | DEFERRED.md | L1-3 (title and Terminology note) | # Deferred work **Terminology note (2026-10-03, DL-116, DL-117).** OpenSysML (opensysml.org) is the open-source SysML v2 tool stack; this tutorial uses two of its components, the OpenSysML runtime (Go; `Open-MBEE/OpenSysML`; Python package `opensysml`; pinned v0.9.0) and sysml-toolkit (Rust; `sysmlv2` binary; pinned v0.9.1). Entries written before this note use a bare "OpenSysML" (and "opensysml") for **the OpenSysML runtime**; read them that way. Headings are unchanged because their anchors are linked from publish ... | neutral (terminology note only) | ambiguous (the header says nothing about how to contribute gap findings; the rule lives in AGENTS 1.9 and contributor.md) | ADD (optional) | One dated sentence after the terminology note: "Policy note (2026-10-03): this register is how the tutorial stays current to the toolchain. A closed entry is removed or revised when the tool release that fixes it is pinned (scripts/tool-pins.json, pyproject.toml) and the workaround is retired; a new gap is entered with its spec clause (AGENTS.md 1.9)." Header-only edit; headings untouched (anchors are linked). | +| DF-02 | DEFERRED.md | entry template (e.g. D-001 lines 'Resolution:' / 'Upstream issue:' / 'Toaster issue:') | **Resolution:** When upstream fix ships, update `get_satisfy_relationships()` and the opensysml-api skill. **Upstream issue:** Open-MBEE/OpenSysML#590 **Toaster issue:** Open-MBEE/toaster#1 | fix / stay current (each of 39 entries names the condition under which the workaround comes out) | none | KEEP | 39 entries (grep -c '^## D-'); the new wording points here. | +| NB-01 | chapters/ch01-system-purpose/01-abstract-def.ipynb | md cell index 17, id cell-15 | Try the chapter exercise in [`exercises/ch01/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch01/exercise.ipynb): declare item defs for a coffee maker's flows, an action def with a `doc` and typed in/out flows, and an abstract part def that performs it, and verify it loads. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-02 | chapters/ch01-system-purpose/02-part-def.ipynb | md cell index 17, id cell-12 | Try the chapter exercise in [`exercises/ch01/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch01/exercise.ipynb): define `BrewUnit` and `HeatExchanger` as two concrete component types and verify they load. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-03 | chapters/ch01-system-purpose/03-specialization.ipynb | md cell index 11, id cell-10 | Try the chapter exercise in [`exercises/ch01/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch01/exercise.ipynb): declare that `CoffeeMaker` specializes `BrewingSystem` and confirm the specialization records correctly. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-04 | chapters/ch01-system-purpose/04-composition.ipynb | md cell index 19, id cell-14 | Try the chapter exercise in [`exercises/ch01/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch01/exercise.ipynb): compose a `CoffeeMaker` from `BrewUnit` and `HeatExchanger` and verify both parts appear via `parts()`. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-05 | chapters/ch02-requirements/01-requirement-def.ipynb | md cell index 14, id cell-07 | Try the chapter exercise in [`exercises/ch02/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch02/exercise.ipynb): add a new `brewTemp` attribute to `BrewUnit`, declare a `TemperatureReq` that requires `bu.brewTemp <= 369.15 [SI::K]` (96 degrees Celsius), and confirm it loads. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-06 | chapters/ch02-requirements/02-assumptions.ipynb | md cell index 12, id cell-07 | Try the chapter exercise in [`exercises/ch02/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch02/exercise.ipynb): create a `hot` usage of your `BrewUnit` with an overridden `brewTemp` and confirm the override loads. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-07 | chapters/ch02-requirements/03-judgment-context.ipynb | md cell index 26, id cell-19 | Try the chapter exercise in [`exercises/ch02/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch02/exercise.ipynb): write an `asserted_context` record for the `brewTemp` assumption in your coffee maker model. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-08 | chapters/ch03-measures/01-moe-definition.ipynb | md cell index 26, id cell-22 | Try the chapter exercise in [`exercises/ch03/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch03/exercise.ipynb): add `requirement tempCheck : TemperatureReq;` to your coffee maker model, then record whether it is a measure of effectiveness or a measure of performance, following the pattern above. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-09 | chapters/ch03-measures/02-mop-candidate-eval.ipynb | md cell index 11, id cell-11 | Try the chapter exercise in [`exercises/ch03/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch03/exercise.ipynb): fold a negated satisfaction claim into your `hot` usage of `BrewUnit`, following the pattern above; do not assert anything about `nominal`, whose `brewTemp` is unbound. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-10 | chapters/ch03-measures/03-threshold-judgment.ipynb | md cell index 25, id cell-19 | Try the chapter exercise in [`exercises/ch03/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch03/exercise.ipynb): write an `asserted_solution` record for your satisfaction claim. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-11 | chapters/ch03-measures/04-verification-case.ipynb | md cell index 16, id cell-07 | Try the chapter exercise in [`exercises/ch03/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch03/exercise.ipynb): declare a `BrewTempTest` verification case for your coffee maker's temperature requirement, with an objective that verifies the requirement usage from notebook 01. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-12 | chapters/ch04-functional-decomp/01-action-def-ffbd.ipynb | md cell index 24, id cell-21 | Try the chapter exercise in [`exercises/ch04/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch04/exercise.ipynb): declare a new action def (not `action def Brew` — Chapter 1 already declares that at package level, so redeclaring it collides) for your coffee maker's `BrewUnit`, with typed `in`/`out` flows and a `first`/`then` sequence nesting its usage inside `Brew`'s own reopened body, mirroring `ApplyHeat` and `ToastBread`. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-13 | chapters/ch04-functional-decomp/02-heating-refinement.ipynb | md cell index 14, id cell-14 | Try the chapter exercise in [`exercises/ch04/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch04/exercise.ipynb): add three signal item defs (`BrewStart`, `BrewFinish`, `BrewCancel`) to your coffee maker model, each with a `doc` stating what it denotes and what it is NOT, and confirm each is findable. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-14 | chapters/ch04-functional-decomp/03-completeness-check.ipynb | md cell index 30, id cell-24 | Try the chapter exercise in [`exercises/ch04/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch04/exercise.ipynb): write an `asserted_inference` record (`AI-C04-EX`) claiming that your new nested action's own flows are accounted for and its balance constraint is real and evaluable — honestly scoped the same way `AI-C04` is scoped for `ApplyHeat`, not that brewing as a whole is functionally decomposed — referencing `AS-C03-EX` in `premises`. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-15 | chapters/ch05-architecture/01-model-navigation.ipynb | md cell index 11, id cell-10 | Try the chapter exercise in [`exercises/ch05/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch05/exercise.ipynb): use `model.find()` to navigate to `CoffeeDemo::CoffeeFlow` after building the assembly, then confirm `model.find()` returns `None` for an element that does not exist. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-16 | chapters/ch05-architecture/02-allocate.ipynb | md cell index 13, id cell-13 | Try the chapter exercise in [`exercises/ch05/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch05/exercise.ipynb): allocate your coffee maker's `applyWater` step (`Brew`'s own nested action usage, from Chapter 4) to `brewUnit` (a usage, not `BrewUnit` the definition), following the pattern this notebook builds. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-17 | chapters/ch05-architecture/03-interfaces.ipynb | md cell index 19, id cell-19 | Try the chapter exercise in [`exercises/ch05/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch05/exercise.ipynb): add a `CoffeeFlow` assembly with a `pump` and a `filterUnit`, each with a matching port joined by a named interface, confirm `port_type_mismatches()` returns an empty list, and render the interconnection diagram. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-18 | chapters/ch06-recursive-decomp/01-subsystem-requirements.ipynb | md cell index 23, id cell-21 | Try the chapter exercise in [`exercises/ch06/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch06/exercise.ipynb): nest a level-2 function, `MoveWater`, inside `ApplyWater` (mirroring `GenerateHeat` nested inside `ApplyHeat`), give it an abstract logical carrier, `WaterMover` (mirroring `HeatGenerator`: a port, an unbound throughput slot, `perform action moveWater : MoveWater;`), then build `BrewAssembly :> BrewUnit` that composes `part mover : WaterMover;` with a usage-level allocation, the same structural pattern this notebook's own `HeatingAssembly`/`HeatGenerator` composition follows. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-19 | chapters/ch06-recursive-decomp/02-second-level.ipynb | md cell index 51, id cell-51 | Try the chapter exercise in [`exercises/ch06/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch06/exercise.ipynb): nest `MoveWater` inside `ApplyWater`, build `WaterMover` as its abstract carrier, and add a `BrewReq` requirement with its subject on `WaterMover` itself (not `BrewUnit`) constraining minimum throughput, then create a lower-throughput candidate that fails it, following the requirement and candidate pattern this notebook builds. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-20 | chapters/ch06-recursive-decomp/03-stopping-judgment.ipynb | md cell index 30, id cell-25 | Try the chapter exercise in [`exercises/ch06/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch06/exercise.ipynb): record the two judgment sites your own `BrewReq` requirement raises (`AC-C06-EX`, a measure-framing judgment; `AS-C06-EX`, a mechanism-selection judgment for `Impeller`), then write an `AI-C06-EX` stopping judgment honestly scoped exactly like this notebook's own `AI-C06` — not a claim that your `BrewUnit` decomposition is complete — with `premises` referencing the real chain this branch rests on: `AC-C06-EX`, `AS-C06-EX`, your Chapter 3 exercise's `AS-C03-EX`, and your Chapter 4 exercise's `AI-C04-EX`. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-21 | chapters/ch07-execution/01-calc-energy.ipynb | md cell index 20, id cell-20 | Try the chapter exercise in [`exercises/ch07/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch07/exercise.ipynb): add a bounded `transferEfficiency` slot and a `deliveredMass` calc to the coffee maker's `WaterMover` carrier, mirroring `efficiency`/`deliveredEnergy` exactly, and query it through `model.eval`. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-22 | chapters/ch07-execution/02-state-traces.ipynb | md cell index 32, id cell-32 | Try the chapter exercise in [`exercises/ch07/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch07/exercise.ipynb): add a `BrewCycle` state machine mirroring `Cycle` exactly — an `idle` entry state, a `brewing` state whose `do action` invokes `moveWater`, two exit states, and completion transitions back to `idle` — and trace it with `execute_state`. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-23 | chapters/ch07-execution/03-param-sweep.ipynb | md cell index 13, id cell-13 | Try the chapter exercise in [`exercises/ch07/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch07/exercise.ipynb): sweep the coffee maker's `deliveredMass` calc across its free `throughput` argument and mark `BrewReq`'s own threshold, read from the model. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-24 | chapters/ch08-checking/01-assert-constraint-def.ipynb | md cell index 18, id cell-14 | Try the chapter exercise in [`exercises/ch08/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch08/exercise.ipynb): it works through the same `verify_satisfaction()` and stale-detection pattern on its own, separate coffee-maker exercise model. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-25 | chapters/ch08-checking/02-violation-witness.ipynb | md cell index 34, id cell-34 | Try the chapter exercise in [`exercises/ch08/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch08/exercise.ipynb): it works through the same `verify_satisfaction()` and stale-detection pattern on its own, separate coffee-maker exercise model. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-26 | chapters/ch08-checking/03-revision-flow.ipynb | md cell index 10, id cell-10 | Try the chapter exercise in [`exercises/ch08/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch08/exercise.ipynb): it works through the same `verify_satisfaction()` and stale-detection pattern on its own, separate coffee-maker exercise model. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-27 | chapters/ch09-coverage-sufficiency/01-requirement-coverage.ipynb | md cell index 17, id 33486eff | Try the chapter exercise in [`exercises/ch09/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch09/exercise.ipynb): it asks you to produce a coverage report over your own coffee-maker model, using the query-and-join pattern this notebook builds. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-28 | chapters/ch09-coverage-sufficiency/02-evidence-completeness.ipynb | md cell index 19, id b9643c57 | Try the chapter exercise in [`exercises/ch09/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch09/exercise.ipynb): it asks you to apply the same sufficiency reading, including the premises argument above, to two of your own already-built judgment records. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-29 | chapters/ch09-coverage-sufficiency/03-stale-detection.ipynb | md cell index 17, id 509ec634 | Try the chapter exercise in [`exercises/ch09/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch09/exercise.ipynb): it asks you to check your own two records for staleness, at scale, the same way this notebook checks `AS-C06` and `AS-C08` together. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-30 | chapters/ch10-traceability-signoff/01-traceability-graph.ipynb | md cell index 56, id 2aedd869 | Try the chapter exercise in [`exercises/ch10/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/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. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-31 | chapters/ch10-traceability-signoff/02-judgment-synthesis.ipynb | md cell index 19, id 6f503fc9 | Try the chapter exercise in [`exercises/ch10/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch10/exercise.ipynb): it asks you to build this same traceability graph and judgment ledger over your own coffee-maker model, then synthesize both into one honestly-scoped sign-off record. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| NB-32 | chapters/ch10-traceability-signoff/03-engineering-signoff.ipynb | md cell index 30, id f89d9afb | Try the chapter exercise in [`exercises/ch10/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch10/exercise.ipynb): it asks you to build this same traceability graph, judgment ledger and sign-off synthesis over your own coffee-maker model. | exercise (call to action: do the coffee-maker exercise) | ambiguous (learner practice, not a contribution; no sentence says exercises are not submissions) | KEEP | No change to the cell. Candidate: none individually; the distinction is made once on setup.md and in the exercise notebook intro (see NB-X below). | +| IX-ch01 | chapters/ch01-system-purpose/index.md | L44 | The [chapter exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch01/exercise.ipynb) asks you to model a coffee maker using the same constructs. Work through it after completing all four notebooks. | exercise | ambiguous (as NB rows) | KEEP | | +| IX-ch02 | chapters/ch02-requirements/index.md | L43 | The [chapter exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch02/exercise.ipynb) asks you to add a temperature requirement to your coffee maker model and write the first context record for the brew-temperature assumption. | exercise | ambiguous (as NB rows) | KEEP | | +| IX-ch03 | chapters/ch03-measures/index.md | L40 | The [chapter exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch03/exercise.ipynb) asks you to add a requirement usage, record an `asserted_context` framing judgment (MoE or MoP), evaluate a negated satisfaction claim folded into a deliberately faulty candidate only, write an `asserted_solution` record for that claim, and close the requirement's anatomy with a `verification def`, to your coffee maker model. Work through it after completing all four notebooks. | exercise | ambiguous (as NB rows) | KEEP | | +| IX-ch04 | chapters/ch04-functional-decomp/index.md | L39 | The [chapter exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch04/exercise.ipynb) asks you to define a new action def for your coffee maker's `BrewUnit` (not `action def Brew` — Chapter 1 already declares that at package level), whose usage nests inside `Brew`'s reopened body; name three signal item defs; probe the balance constraint against three usages to confirm it is real and evaluable; and write an honestly scoped `asserted_inference` record about that one nested action. Work through it after completing all three notebooks. | exercise | ambiguous (as NB rows) | KEEP | | +| IX-ch05 | chapters/ch05-architecture/index.md | L39 | Try the [Chapter 5 exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch05/exercise.ipynb): allocate your coffee maker's `applyWater` step to `brewUnit` (both usages, not the `Brew`/`BrewUnit` definitions), then add a `CoffeeFlow` assembly with `pump` and `filterUnit` parts joined by a named, port-typed interface, confirm the port types are compatible, and render the interconnection diagram. | exercise | ambiguous (as NB rows) | KEEP | | +| IX-ch06 | chapters/ch06-recursive-decomp/index.md | L35 | Try the [Chapter 6 exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch06/exercise.ipynb): nest `MoveWater` inside `ApplyWater`, give it an abstract carrier `WaterMover`, build `BrewAssembly :> BrewUnit` composing it with a usage-level allocation, and state a `BrewReq` requirement on `WaterMover` itself, following the same level-2 function/carrier/allocation/requirement pattern this chapter builds for `GenerateHeat`/`HeatGenerator`; record a measure-framing and a mechanism-selection judgment the requirement raises (`Impeller`, built only after the selection is argued), and write an honestly scoped `asserted_inference` record stating what the decomposition establishes and does not. | exercise | ambiguous (as NB rows) | KEEP | | +| IX-ch07 | chapters/ch07-execution/index.md | L35 | Try the [Chapter 7 exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch07/exercise.ipynb): add a bounded `transferEfficiency` slot and `deliveredMass` calc to the coffee maker's `WaterMover`, add a `BrewCycle` state machine, and sweep `deliveredMass`'s `throughput` argument against `BrewReq`'s own threshold, read from the model. | exercise | ambiguous (as NB rows) | KEEP | | +| IX-ch08 | chapters/ch08-checking/index.md | L37 | Try the [Chapter 8 exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch08/exercise.ipynb): it works through the same `verify_satisfaction()` and stale-detection pattern on its own, separate coffee-maker exercise model. | exercise | ambiguous (as NB rows) | KEEP | | +| IX-ch09 | chapters/ch09-coverage-sufficiency/index.md | L35 | See [`exercises/ch09/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch09/exercise.ipynb). | exercise | ambiguous (as NB rows) | KEEP | | +| IX-ch10 | chapters/ch10-traceability-signoff/index.md | L35 | See [`exercises/ch10/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch10/exercise.ipynb). | exercise | ambiguous (as NB rows) | KEEP | | +| CN-ch01-ex | chapters/ch01-system-purpose/conclusion.md | L19 | **Exercise:** The [Chapter 1 exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch01/exercise.ipynb) asks you to model a coffee maker using the same constructs. The problem is structurally similar to the toaster but uses a different domain: state the purpose as item defs, a performed action def with a doc, and an abstract part def, add two component types with no content yet, specialize the whole (not the parts) from the concept, and compose it into the top-level system. | exercise | ambiguous (as NB rows) | KEEP | | +| CN-ch01 | chapters/ch01-system-purpose/conclusion.md | ## What comes next | Chapter 2 asks what the toaster must do. It introduces requirements, an attribute override that builds a deliberately faulty fixture, and the first engineering judgment record. The model from Chapter 1 is the starting point. | neutral (names the next chapter; no learner action) | none | KEEP | | +| CN-ch02-ex | chapters/ch02-requirements/conclusion.md | L19 | **Exercise:** The [Chapter 2 exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch02/exercise.ipynb) asks you to add a `TemperatureReq` to your coffee maker model and write an `asserted_context` record for the `brewTemp` assumption. Use the same pattern as `TimelyToast` and `context_record`. | exercise | ambiguous (as NB rows) | KEEP | | +| CN-ch02 | chapters/ch02-requirements/conclusion.md | ## What comes next | Chapter 3 introduces `requirement` usage (applying `TimelyToast` to the model as `timely`) and the `assert satisfy` / `assert not satisfy` idiom, which folds a satisfaction claim into a usage's own context and evaluates it against the model's own values. It also introduces `verification def`, which declares how a requirement will be checked. | neutral (names the next chapter; no learner action) | none | KEEP | | +| CN-ch03-ex | chapters/ch03-measures/conclusion.md | L19 | **Exercise:** The [Chapter 3 exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch03/exercise.ipynb) asks you to add a `TemperatureReq` usage to your coffee maker model, record whether it is a measure of effectiveness or a measure of performance in an `asserted_context` framing record, fold a negated satisfaction claim into the `hot` usage only (not `nominal`, whose `brewTemp` is unbound), write an `asserted_solution` record for that claim, and close the requirement's anatomy with a `verification def`. Use the same pattern as `AC-C03`, `timely`/`slow`, and `TimelyToastTest`. | exercise | ambiguous (as NB rows) | KEEP | | +| CN-ch03 | chapters/ch03-measures/conclusion.md | ## What comes next | Chapter 4 asks how the system performs its function step by step. It introduces `action def` for functional decomposition and `item def` for typed flows. | neutral (names the next chapter; no learner action) | none | KEEP | | +| CN-ch04-ex | chapters/ch04-functional-decomp/conclusion.md | L19 | **Exercise:** The [Chapter 4 exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch04/exercise.ipynb) asks you to define a new action def for your coffee maker's `BrewUnit` (not `action def Brew` — Chapter 1 already declares that at package level), whose usage nests inside `Brew`'s reopened body; name three signal item defs; probe the balance constraint's own three usages; and write an `asserted_inference` record honestly scoped to that one action's own flows, not to whether brewing as a whole is decomposed. Use the same pattern as `ApplyHeat` and `AI-C04`. | exercise | ambiguous (as NB rows) | KEEP | | +| CN-ch04 | chapters/ch04-functional-decomp/conclusion.md | ## What comes next | Chapter 5 asks which logical component performs this function, and how logical components connect. It introduces a named, usage-level `allocate` connecting `ApplyHeat` to the component that performs it, and a named `interface` giving `duration` a connection point between components, without yet binding it to a value. | neutral (names the next chapter; no learner action) | none | KEEP | | +| CN-ch05-ex | chapters/ch05-architecture/conclusion.md | L19 | **Exercise:** The [Chapter 5 exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch05/exercise.ipynb) asks you to allocate your coffee maker's `applyWater` step to `brewUnit` (both usages, not the `Brew`/`BrewUnit` definitions), add a `CoffeeFlow` assembly with a `pump` and a `filterUnit` joined by a named, port-typed interface, confirm `port_type_mismatches()` returns an empty list, navigate to it with `model.find()` (confirming `None` for a nonexistent element), build the interconnection intent, and confirm the endpoint paths appear correctly in the intent dict. | exercise | ambiguous (as NB rows) | KEEP | | +| CN-ch05 | chapters/ch05-architecture/conclusion.md | ## What comes next | Chapter 6 asks what one branch of the recursion shows one level below `HeatingSystem`. It nests a function inside `ApplyHeat`, gives it an abstract logical carrier with its own interface point, records a mechanism selection and a measure framing before specializing it with a concrete realization, checks that realization against a requirement, then records a stopping judgment stating plainly what the branch establishes and what it does not. | neutral (names the next chapter; no learner action) | none | KEEP | | +| CN-ch06-ex | chapters/ch06-recursive-decomp/conclusion.md | L19 | **Exercise:** The [Chapter 6 exercise](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch06/exercise.ipynb) asks you to nest `MoveWater` inside `ApplyWater`, give it an abstract carrier `WaterMover`, build `BrewAssembly :> BrewUnit` composing it with a usage-level allocation, and state a `BrewReq` requirement on `WaterMover` itself for minimum water throughput; decide for yourself, and justify it, what kind of measure that threshold is, and record which mechanism `Impeller` (built only after that selection is argued) represents; and write an honestly scoped `asserted_inference` record stating what the decomposition establishes and does not, with `premises` referencing the real judgment chain this branch rests on. | exercise | ambiguous (as NB rows) | KEEP | | +| CN-ch06 | chapters/ch06-recursive-decomp/conclusion.md | ## What comes next | Chapter 7 asks how the model behaves at runtime. It builds `deliveredEnergy` as a calc on `HeatGenerator`, with a bounded `efficiency` slot resolved from the carrier's own bound value, queried through `model.eval` rather than a symbolic binding; gives the toaster's state machine, `Cycle`, a `heating` state whose `do action` invokes `GenerateHeat` and transitions that complete a full run, exhibited by `ToastingSystem` and inherited by `Toaster`; and sweeps `deliveredEnergy`'s own `power` input against `HeatGenerationReq`'s own threshold on `HeatGenerator::power`. | neutral (names the next chapter; no learner action) | none | KEEP | | +| CN-ch07-ex | chapters/ch07-execution/conclusion.md | L21 | See [`exercises/ch07/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch07/exercise.ipynb): add a bounded `transferEfficiency` slot and `deliveredMass` calc to the coffee maker's `WaterMover`, add a `BrewCycle` state machine, and sweep `deliveredMass`'s `throughput` argument against `BrewReq`'s own threshold, read from the model. | exercise | ambiguous (as NB rows) | KEEP | | +| CN-ch07 | chapters/ch07-execution/conclusion.md | ## What comes next | Chapter 8 checks the model's own claims against its own values: `verify_satisfaction()` on the `assert satisfy` declarations this chapter and its predecessors have built, recorded as judgment records rather than asserted as proofs. | neutral (names the next chapter; no learner action) | none | KEEP | | +| CN-ch08-ex | chapters/ch08-checking/conclusion.md | L21 | See [`exercises/ch08/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch08/exercise.ipynb): it works through the same `verify_satisfaction()` and stale-detection pattern on its own, separate coffee-maker exercise model. | exercise | ambiguous (as NB rows) | KEEP | | +| CN-ch08 | chapters/ch08-checking/conclusion.md | ## What comes next | Chapter 9 broadens the analysis again: instead of one proved lemma and a handful of point-evaluated claims, it queries every requirement declaration and every satisfy relationship in the model to produce a coverage table showing which requirements have been addressed and which have not. | neutral (names the next chapter; no learner action) | none | KEEP | | +| CN-ch09-ex | chapters/ch09-coverage-sufficiency/conclusion.md | L23 | See [`exercises/ch09/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch09/exercise.ipynb): it asks you to produce a coverage report over your own coffee-maker model from Chapters 1-8 (notebook 01's own join), then apply the same sufficiency reading (notebook 02) and staleness check, at scale (notebook 03), to two of your own already-built judgment records. | exercise | ambiguous (as NB rows) | KEEP | | +| CN-ch09 | chapters/ch09-coverage-sufficiency/conclusion.md | ## What comes next | Chapter 10 builds the full traceability graph this chapter's coverage report only samples one join of, and asks what a real sign-off over that graph would actually require. | neutral (names the next chapter; no learner action) | none | KEEP | | +| CN-ch10-ex | chapters/ch10-traceability-signoff/conclusion.md | L27 | See [`exercises/ch10/exercise.ipynb`](https://github.com/Open-MBEE/toaster/blob/main/exercises/ch10/exercise.ipynb): it asks you to build this same traceability graph, judgment ledger and sign-off synthesis over your own coffee-maker model. | exercise | ambiguous (as NB rows) | KEEP | | +| CN-ch10 | chapters/ch10-traceability-signoff/conclusion.md | ## What comes next | This is the tutorial's last chapter. What continues from here is not another chapter but the reader's own accountable engineering: taking the traceable, honestly-scoped case this tutorial teaches how to build, and exercising, on a real design, the judgment this tutorial has shown but never made for them. | learner action beyond the tutorial ("the reader's own accountable engineering"); not a call to add repository content | ambiguous (could be read as 'continue the work'; not a contribution request) | KEEP (candidate placement for one contribution note, see Notebook finding) | After this paragraph the ACE may add: "Contributing back: the contributions we want are keeping this tutorial current to the toolchain, not adding new content; see the contributor guide." | +| EX-ch01 | exercises/ch01/exercise.ipynb | md cell index 0, id cell-00 (title + framing paragraph) | # Chapter 1 Exercise — Coffee Maker Structural Model Model a coffee maker using the same constructs introduced in Chapter 1. Apply each construct in order, building the model cumulatively. | exercise (build the coffee-maker model in a blank workspace) | ambiguous ("Extend your Chapter N-1 coffee maker model" is the learner's own model; the word 'Extend' is exercise language, not a repo contribution) | KEEP | Optionally append to ch01 exercise only: "This exercise is for your own learning; its result is not a contribution to the repository." (cell edit in one place; others unchanged). | +| EX-ch02 | exercises/ch02/exercise.ipynb | md cell index 0, id cell-0 (title + framing paragraph) | # Chapter 2 Exercise — Coffee Maker Requirements and Context Extend your Chapter 1 coffee maker model with a requirement and a context record. | exercise (build the coffee-maker model in a blank workspace) | ambiguous ("Extend your Chapter N-1 coffee maker model" is the learner's own model; the word 'Extend' is exercise language, not a repo contribution) | KEEP | Optionally append to ch01 exercise only: "This exercise is for your own learning; its result is not a contribution to the repository." (cell edit in one place; others unchanged). | +| EX-ch03 | exercises/ch03/exercise.ipynb | md cell index 0, id cell-0 (title + framing paragraph) | # Chapter 3 Exercise — Coffee Maker Measures Extend your Chapter 2 coffee maker model with a requirement usage, a framing judgment, a satisfaction claim, and a verification case that closes the requirement's anatomy. | exercise (build the coffee-maker model in a blank workspace) | ambiguous ("Extend your Chapter N-1 coffee maker model" is the learner's own model; the word 'Extend' is exercise language, not a repo contribution) | KEEP | Optionally append to ch01 exercise only: "This exercise is for your own learning; its result is not a contribution to the repository." (cell edit in one place; others unchanged). | +| EX-ch04 | exercises/ch04/exercise.ipynb | md cell index 0, id cell-0 (title + framing paragraph) | # Chapter 4 Exercise — Coffee Maker Functional Decomposition Add a functional layer and a completeness judgment to your coffee maker model. | exercise (build the coffee-maker model in a blank workspace) | ambiguous ("Extend your Chapter N-1 coffee maker model" is the learner's own model; the word 'Extend' is exercise language, not a repo contribution) | KEEP | Optionally append to ch01 exercise only: "This exercise is for your own learning; its result is not a contribution to the repository." (cell edit in one place; others unchanged). | +| EX-ch05 | exercises/ch05/exercise.ipynb | md cell index 0, id cell-0 (title + framing paragraph) | # Chapter 5 Exercise — Coffee Maker Architecture Extend your Chapter 4 coffee maker model with allocation, a port-typed interface, and an interconnection diagram. | exercise (build the coffee-maker model in a blank workspace) | ambiguous ("Extend your Chapter N-1 coffee maker model" is the learner's own model; the word 'Extend' is exercise language, not a repo contribution) | KEEP | Optionally append to ch01 exercise only: "This exercise is for your own learning; its result is not a contribution to the repository." (cell edit in one place; others unchanged). | +| EX-ch06 | exercises/ch06/exercise.ipynb | md cell index 0, id a550d9b0 (title + framing paragraph) | # Chapter 6 Exercise — Coffee Maker Recursive Decomposition Decompose `BrewUnit`'s own `applyWater` step one level deeper, then record the two judgment sites that decomposition raises and a stopping judgment. | exercise (build the coffee-maker model in a blank workspace) | ambiguous ("Extend your Chapter N-1 coffee maker model" is the learner's own model; the word 'Extend' is exercise language, not a repo contribution) | KEEP | Optionally append to ch01 exercise only: "This exercise is for your own learning; its result is not a contribution to the repository." (cell edit in one place; others unchanged). | +| EX-ch07 | exercises/ch07/exercise.ipynb | md cell index 0, id cell-0 (title + framing paragraph) | # Chapter 7 Exercise — Coffee Maker Execution Query a real calc through the model, trace a real state machine, and sweep a real requirement's own threshold — for the coffee maker instead of the toaster. | exercise (build the coffee-maker model in a blank workspace) | ambiguous ("Extend your Chapter N-1 coffee maker model" is the learner's own model; the word 'Extend' is exercise language, not a repo contribution) | KEEP | Optionally append to ch01 exercise only: "This exercise is for your own learning; its result is not a contribution to the repository." (cell edit in one place; others unchanged). | +| EX-ch08 | exercises/ch08/exercise.ipynb | md cell index 0, id cell-00 (title + framing paragraph) | # Chapter 8 Exercise — Coffee Maker Constraint Checking Prove a hand-restated real-arithmetic lemma for the coffee maker model with Z3, report your model's own real satisfaction claims, and show a `ReviewRecord` go stale when the lemma it depends on changes. | exercise (build the coffee-maker model in a blank workspace) | ambiguous ("Extend your Chapter N-1 coffee maker model" is the learner's own model; the word 'Extend' is exercise language, not a repo contribution) | KEEP | Optionally append to ch01 exercise only: "This exercise is for your own learning; its result is not a contribution to the repository." (cell edit in one place; others unchanged). | +| EX-ch09 | exercises/ch09/exercise.ipynb | md cell index 0, id cell-01 (title + framing paragraph) | # Chapter 9 Exercise: Coffee Maker Coverage and Sufficiency Produce a real coverage report over your own coffee-maker model, apply Hawkins' sufficiency reading to two of your own already-built judgment records, and check both for staleness against one real edit, mirroring `chapters/ch09-coverage-sufficiency/{01-requirement-coverage, 02-evidence-completeness,03-stale-detection}.ipynb` one-for-one, for the coffee-maker domain instead of the toaster's own. | exercise (build the coffee-maker model in a blank workspace) | ambiguous ("Extend your Chapter N-1 coffee maker model" is the learner's own model; the word 'Extend' is exercise language, not a repo contribution) | KEEP | Optionally append to ch01 exercise only: "This exercise is for your own learning; its result is not a contribution to the repository." (cell edit in one place; others unchanged). | +| EX-ch10 | exercises/ch10/exercise.ipynb | md cell index 0, id cell-01 (title + framing paragraph) | # Chapter 10 Exercise: Traceability and Sign-off Build a real traceability graph over your own two coffee-maker requirements, close the one real gap it finds right away (mirroring real notebook 01's own remediation, done in the same place it is found, not later), reconstruct a judgment ledger over three of your own already-built records, and synthesize everything into one honestly-scoped sign-off record -- mirroring `chapters/ch10-traceability-signoff/{01-traceability-graph, 02-judgment-synthesis,03-engineering-signoff}.ipynb` one-for-one, for the coffee-maker domain instead of the toaster's own. | exercise (build the coffee-maker model in a blank workspace) | ambiguous ("Extend your Chapter N-1 coffee maker model" is the learner's own model; the word 'Extend' is exercise language, not a repo contribution) | KEEP | Optionally append to ch01 exercise only: "This exercise is for your own learning; its result is not a contribution to the repository." (cell edit in one place; others unchanged). | +| NB-I1 | chapters/ch03-measures/04-verification-case.ipynb | md cell index 5, id e5f6g7h8 | ...v0.9.0 ([toaster#19](https://github.com/Open-MBEE/toaster/issues/19) / [OpenSysML#608](https://github.com/Open-MBEE/OpenSysML/issues/608)). | report gap (cites open issue numbers) | none (hook: the issue links are the keep-current trail) | KEEP | | +| NB-I2 | chapters/ch05-architecture/01-model-navigation.ipynb | md cell index 1, id cell-01 | Chapter 5 shifts from defining the model to inspecting and extending it. | neutral ("extending the model" is the chapter's own subject) | none | KEEP | | +| NB-I3 | chapters/ch10-traceability-signoff/01-traceability-graph.ipynb | md cell index 1, id f1090e58 | This notebook extends that single join into the full chain ... | neutral | none | KEEP | | + +## (a) Contributor scenarios in `docs/contributor.md` + +| heading | one-line summary | compatible with "keep current"? | +|---|---|---| +| Who is Z | identity of `mzargham` / "Z" in project files | neutral; compatible | +| How this repo is built, tested, and reviewed | tour of AGENTS.md, CLAUDE.md, DEFERRED.md, agents, skills, decisions; ends with the "extend a chapter" sentence (C-02) | compatible except that one sentence | +| Deployment status | CI builds on PR, deploys from `main`, five-check release gate | compatible (hook) | +| Update a dependency and regenerate outputs | change a version, relock, run tests and `check-tools.py`, spot-check, commit lockfile | **the core wanted contribution**; extend it to the tool pins and spec re-check | +| Add a new chapter | recipe skeleton, cumulative fixture, register in `check_construction.py` and `myst.yml`, lint, independent review | **invites new content; contradicts the requirement** (C-05) | +| Change a model element and review stale judgment records | recompute `content_hash`, re-validate, re-read claim/rationale | compatible if the change passes PS-2 (an edit to existing content) | +| Run the full CI pipeline locally | commands mirroring the `build` job | neutral; compatible | + +Missing: a section stating what is wanted, how an improvement is judged, and how to report toolchain drift or a spec gap +(C-08). `docs/setup.md` "Fork and exercise" is a learner scenario, not a contributor one (S-05). + +## (b) Existing process hooks the new wording should point to + +- **`DEFERRED.md`**: register of 39 entries (`## D-NNN`), each with a Workaround, the Resolution condition ("when upstream fix ships + ..."), Upstream issue, Toaster issue and Status; AGENTS.md 1.9 "Gap-tracking rule" requires (a) a DEFERRED entry, (b) a toaster + issue and, where the tool is at fault, an upstream issue, (c) a comment cell at the workaround; "Nothing is filed on a public + repository until Z has reviewed the text". Headings are anchors linked from published pages (do not rename). +- **`scripts/tool-pins.json`** (pinned `sysmlv2` v0.9.1, Z3 z3-5.1.0, PlantUML, standard library commit, with sha256 per + platform) installed by `scripts/provision-tools.py`; **`pyproject.toml`/`uv.lock`** (runtime `opensysml` 0.9.0); `package.json` + and `package-lock.json`; `.nvmrc`. +- **`scripts/check-tools.py`**: reports each resolved tool and version, downloads the runtime binary, exits non-zero naming the + provisioning command; CI step "Check tool versions". Env vars `SYSMLV2_BINARY`, `SYSMLV2_LIB_DIR`, `PLANTUML_JAR`, `JAVA`, + `Z3`; `TOASTER_REQUIRE_TOOLS=1` turns a missing tool from skip into failure. +- **CI** `.github/workflows/ci.yml`: provisions pinned tools (cache keyed on `tool-pins.json`), runs pytest with + `TOASTER_REQUIRE_TOOLS=1`, builds with `myst build --html --execute --strict`, then `scripts/check-site.py` (five checks, + baseline in `scripts/site-baseline.json`). +- **Spec-conformance guards**: `scripts/check_conformance.py`, `scripts/check_construction.py`, `glossary check` and + `glossary lint` (including the six terminology rules), `tests/test_skill_snippets.py`. +- **Hawkins-record staleness** (`content_hash`) and the reproducibility page's pin list (`docs/reproducibility.md` L8-30). +- **Existing wording hooks**: `docs/setup.md` L156-170 ("Revisit this note once the maintainers publish the real package"; + "...replaced with a direct call"), `docs/references.md` L69, the Resolution lines of DEFERRED. + +## (c) AMBIGUOUS items needing the ACE + +1. **Is adding anything ever allowed?** A chapter, notebook, exercise, diagram, figure, model element, judgment record or glossary + term. Options: never; only when it is the minimal addition needed to restore currency (for example a new cell showing a + construct a new release now accepts, replacing a workaround); or via issue-first approval. The queued text says "not adding + new content" but PS-2 allows "otherwise improve", which can include a new diagram or a clarifying cell. +2. **Is a pure addition ever "strictly dominant"?** Adding a cell cannot lower conformance, but may lower didactic clarity (pacing, + length) and, per the style guide, adds review burden. Does additive-but-clarifying count as improving existing content? +3. **How is "strictly dominant" judged, by whom, with what evidence?** Proposed: the PR states which priority improves, with + evidence (spec clause number, a rerun, a simulated-learner report), and for each of the other two why it does not worsen; + the independent reviewer (different model) and the ACE rule; ties and trade-offs go to Z. Needs a binary or an ordered test: + the priorities are listed in rank order; is a gain in (1) allowed to cost (3)? The requirement says no (strict dominance). +4. **Do upstream-gap bug reports count?** A report that a tool still does not follow a spec clause (a DEFERRED entry) is + currently the AGENTS 1.9 path and needs Z's review before public filing; a report that a new release fixes one is a + keep-current contribution. Do outside reporters file issues directly, or only via a PR to DEFERRED.md? +5. **Exercises.** Learner exercises are "exercise-as-call-to-action", not contributions. Is a one-sentence disclaimer needed in + setup.md and in the ch01 exercise intro ("not a contribution to the repository")? Are new exercises ever welcome? +6. **Fixing existing exercises/chapters against a newer tool**: allowed under PS-2/keep-current; but exercises are excluded from + CI and from the published site, so currency of the exercises depends on manual checks. Does a `tests/` check for exercise + currency count as new content? +7. **Where does the notebook contribution note go?** None exists today. Candidates: ch10 `conclusion.md` "What comes next"; a + one-line note in each chapter `index.md`; the intro cell of each exercise notebook; none (docs only). Notebook cells + are guarded (markdown-only, protected zones, output equality); a note in 10 `index.md`s is cheaper than 32 pointer cells. +8. **Component naming in the policy text**: PS-1 names "the OpenSysML runtime, sysml-toolkit" explicitly; should it say "tools + from the OpenSysML ecosystem" (the requirement's phrase) in priority (3) and keep "the OpenSysML stack" distinct from the + Pilot (DL-119)? Priority (1) uses "the three OMG PDFs" which sits with the Pilot on the specs side. +9. **`.github` templates and CONTRIBUTING.md**: none exist; adding them is new files outside the markdown guard. In or out of + this contract? +10. **README tagline "using SysML v2 and OpenSysML"**: bare OpenSysML is correct under the convention (the stack); no change + needed, listed for completeness. +11. **Dated records**: `decisions/` and `docs/superpowers/` contain older "extend"/"add a chapter" text; treated as history, no + edits proposed. + +## (d) Z-directed (AGENTS.md Part 1) versus ordinary docs + +Needs a Z-directed pass (AGENTS 1.11; Part 1 governs; the orchestrator reads the final text itself, as with OT-5): +A-01 (1.11 sentence), A-03 (new sentence or section stating the contribution scope). Z has stated the requirement +(2026-10-03), so DL-116-style "Z's direction" records can authorise the edit; still Part 1 and must be logged (a DL entry). +Skills (ACE-edited under the skill-editor Z-directed clause; pre-edit DL entry; revert record): SK-01 to SK-05, CL-01. +Ordinary doc edits (A4/ A2 scope, ordinary review): R-01 to R-05, D-01, S-05 to S-07, C-02, C-04, C-05, C-06, C-08, C-09, +README and setup wording, `docs/contributor.md` restructure. Header note in `DEFERRED.md` (DF-01): a top-of-file note was +already added by OT-5 under DL-116; another dated note of the same kind is the same class (ACE decides). Chapter markdown +(IX, CN): ordinary markdown, but `chapters/**` is inside the terminology-pass protected set; treat the same way (guard +checker, reviewer on a different model). Notebook markdown cells (NB, EX): markdown-only edits with the guard checker; cell +`source` only; no output changes. No change proposed to `LICENSE`, `myst.yml`, `docs/case-studies/`, +`docs/reproducibility.md` or `docs/references.md` beyond the optional links already named. diff --git a/decisions/log.md b/decisions/log.md index be1cdbc..c83404e 100644 --- a/decisions/log.md +++ b/decisions/log.md @@ -1538,3 +1538,190 @@ Reasoning: DL-108's condition (run `verify-sources` against the registered files Determined: yes. Extension: no. Provenance: DL-104, DL-105..108, DL-109; commit messages on branch novice-test-bprime. Chapter text may now link the three terms (not done in this change). + +## DL-111 | 2026-10-03 | PAGES-PUBLISHING-PHASE-A | Phase A survey for publishing the book to GitHub Pages: three of four discovery contracts merged; the real-runner run is pending + +Path: Orchestrator-run (builder/reviewer pipeline per decisions/work-contract-template.md); judgment items listed for Z and the ACE, none ruled here +Decision: PA-1 (clean-checkout reproduction), PA-3 (release-binary output equivalence) and PA-4 (dangling-reference inventory) merged to local main with independent reviews; PA-2 (diagnostic workflow for ubuntu-latest) is written, reviewed and amended three times (pinned PlantUML jar, honest failure summary, page-JSON figure count, uv run build, leak scan) but not run. Findings and consequences for Phase B are in `decisions/pages-publishing-survey.md`. The main results: no notebook executes unless the project venv is first on PATH; `myst build` exits 0 with notebook errors so CI needs `--strict`; a clean-HOME build fails exactly three notebooks (Ch5-03, Ch8-02, Ch10-01) on hard-coded toolkit paths; figures are in page JSON, not the DOM (18 in a fully working build); the built site publishes local paths and, via linked files, exercises ch01-ch08 and DEFERRED.md; the toolkit v0.9.1 release binary reproduces every toolkit-dependent output on macOS arm64 (Linux not yet measured); 365 learner-visible references to internal artifacts are classified (LINK 164, REWORD 58, KEEP 143). +Principles applied: P5 (measure before fixing; record what was not measured); P4 (Phase A changes no learner-facing file). +Reasoning: review findings changed the work materially: the first diagnostic workflow would have blamed the book for a failure caused by apt's 2020 PlantUML and would have reported a green build over cell errors and a figure count near 1 instead of 18; the first PA-4 table double-counted 40 `models/` paths and missed `chapters/` paths. Corrections were made before merge. +Determined: yes for the measurements; underdetermined for the reference policy items A1-A10 (Z/ACE) and for the Linux behavior (PA-2). +Extension: no. +Provenance: decisions/pages-publishing/a1-clean-checkout.md, a3-output-equivalence.md, a4-dangling-references.md; decisions/pages-publishing-survey.md; docs/superpowers/specs/2026-10-03-pages-publishing-design.md; docs/superpowers/plans/2026-10-03-pages-publishing-phase-a-plan.md; branch pub/diagnose. + +## DL-112 | 2026-10-03 | IDENTITY | "Z" is the contributor identity mzargham; public pages name the handle; the mapping is recorded in the repo + +Path: mzargham decided directly (chat, 2026-10-03), answering PA-4's open item A3 (should the handle "Z" appear on the public site) +Decision: "Z" refers to the contributor identity `mzargham` (Michael Zargham, GitHub user `mzargham`, the project's author and chief engineer; the only contributor so far). Where the repo or published site mentions "Z" to readers, it says `mzargham` (e.g. "mzargham (Z)") at first mention rather than a bare "Z", or defines the mapping once on the page. The mapping is recorded durably in `AGENTS.md` Part 1 and `docs/contributor.md` (Phase B, PUB-5/PUB-6). If other contributors join, they are named by their own handles; "Z" keeps meaning `mzargham` and is never reused for another person. Internal files (`decisions/`, skills, agents) keep using "Z" as before; only the published pages change, and only to add the handle. +Principles applied: P5 (record who a role-name refers to so history stays readable); P6 (the owner decides how their identity appears publicly). +Reasoning: none beyond the owner's direct ruling; recorded here so the A3 default in `decisions/pages-publishing-survey.md` is superseded. +Determined: yes. +Extension: no. +Provenance: `decisions/pages-publishing/a4-dangling-references.md` A3; `decisions/pages-publishing-survey.md` section 4; git author identity `Michael Zargham ` on all commits; `myst.yml` project author "Michael Zargham". + +## DL-113 | 2026-10-03 | PAGES-PUBLISHING-PHASE-A | PA-2 complete: the unmodified book builds on ubuntu-latest and reproduces the stored outputs once Z3 is provisioned; Phase A done + +Path: Orchestrator-run; Z authorized the push and the run +Decision: Two diagnostic runs on `ubuntu-latest` (branch `pub/diagnose`, kept on the remote). Run 1 failed Ch8-02 and Ch10-01 only because `sysmlv2 verify --solve` needs a separate `z3` executable. Run 2, with Z3 5.1.0 (pinned sha256), built all 59 pages with zero cell errors, 18 figures and outputs identical to the stored macOS outputs for the toolkit-dependent notebooks. About 95 s per job. Findings: `decisions/pages-publishing/a2-real-runner.md`; survey updated. Phase A is complete; Phase B waits only on the reference-policy decisions for Z/ACE (survey section 4). +Principles applied: P5 (measure before fixing; a green job over cell errors is not a pass, so errors and figures are counted separately). +Reasoning: the first run's two failures were attributable to a missing tool rather than the book, and the pinned-tool second run isolates that cause; the figure count alone (18 in both runs) would have hidden the failures. +Determined: yes. +Extension: no. +Provenance: runs 37146440620 and 37146757647; commits 7498a97, 48b8bb7 on pub/diagnose; decisions/pages-publishing/a2-real-runner.md; decisions/pages-publishing-survey.md. + +## DL-114 | 2026-10-03 | PAGES-PUBLISHING-PHASE-B-PLAN | Phase B plan written: nine builder contracts plus an ACE gate, on one integration branch + +Path: Orchestrator-run; mzargham approved the reference-policy defaults (A1, A2/A4, A9, A10) in chat +Decision: `docs/superpowers/plans/2026-10-03-pages-publishing-phase-b-plan.md` specifies Phase B as full contracts on one integration branch `pages-publishing` with one PR at the end: PUB-1 tool resolver (`toaster.tools`), PUB-2 notebooks use it, PUB-3 tests/scripts without local paths plus a guard test, PUB-4 pinned provisioning into a gitignored `.tools/` (sysml-toolkit v0.9.1, Z3 5.1.0, PlantUML 1.2026.8, SysML library at a pinned commit), PUB-5 the release gate `scripts/check-site.py` (cell errors, figure baseline 18, host-path leaks, no published exercises/DEFERRED.md, internal links under BASE_URL), PUB-6A-D reference cleanup by chapter group from the 365-row PA-4 table, PUB-7 docs/README/identity (including the "Z is mzargham" statement in AGENTS.md Part 1 and docs/contributor.md), PUB-8 CI build + gate + Pages deploy from main only, PUB-9 first deploy with mzargham enabling Pages, and an ACE gate for the A7 rows (default: leave unchanged). Merge order and dependencies are in Task 0 and each contract's State line. +Principles applied: P5 (the gate measures errors and figures separately because run 1 had all 18 figures and two failing notebooks); P4 (rows that would change models or persisted records are held for the ACE rather than done in passing). +Reasoning: Phase A showed the work divides cleanly: tooling (PUB-1..5), content (PUB-6, PUB-7), pipeline (PUB-8, PUB-9). Content edits are split by chapter group so each reviewer can sample rows meaningfully; the A7 rows are gated because editing ReviewRecord strings or SysML doc comments would change `models/*.sysml` and make `AS-C08.json` stale. +Determined: yes for the plan; the ACE gate decides whether one extra contract (PUB-6E) is needed. +Extension: no. +Provenance: decisions/pages-publishing-survey.md; decisions/pages-publishing/a1..a4; DL-111, DL-112, DL-113; docs/superpowers/specs/2026-10-03-pages-publishing-design.md. + +## DL-115 | 2026-10-03 | PAGES-PUBLISHING-PHASE-B | A7 gate: record strings, doc comments and captured tool output stay as written; the adjacent links are completed in markdown; one misattributed Hawkins quotation is fixed + +Path: Handled by ACE +Decision: All 22 A7 rows (69, 85, 86, 148, 149, 159, 164-167, 224, 235, 236, 242-247, 249, 278, 279) and row 256 stay unchanged in their code cells, SysML doc comments, ReviewRecord strings, persisted records and stored outputs; the KEEP default covers the REWORD-tagged record strings too (AGENTS.md SS1, the ch04 index.md string, the case-study path strings). The second half of the A7 default is still owed: PUB-6E adds adjacent markdown links for D-038, D-030 and D-031 in ch10 nb01 and for D-030 and D-031 in ch10 nb03 (markdown cells only; blast zone excludes code cells, models/ and decisions/judgment-records/). PUB-6E also corrects ch10 nb01 cell 0f1d3a77, which quotes the tutorial's paraphrase as "Hawkins' own definition". Row 218 stays as is. The "not yet supported" code comments on the closed OpenSysML issues stay (they describe pinned v0.9.0); DEFERRED.md gets a non-blocking follow-up recording the closures as unverified against the pin. No further judgment is needed from Z before the first deploy. +Principles applied: P1, P5, P4, F4, F5, SA-7; AGENTS.md Part 1 SS1.6 (load-bearing record fields) and SS1.9 (comment at the workaround); Z-13; the reference policy approved in DL-114. +Reasoning: (1) the ids in doc and code comments are the SS1.9(c) gap-tracking comments, so P5 keeps them; three sit in models/*.sysml whose hash AS-C08 pins, so an edit would change the staleness outputs ch08 nb03 and ch09 nb03 teach with, for no learner benefit (P4). (2) Record strings are evidence (F4, P1, SS1.6); they change only through re-run and re-persist, and the REWORD-tagged strings are accurate locators the site already resolves (AGENTS.md linked per A2; case study linked on the same page), so P4 yields no benefit against a re-run chain that includes AI-C04.json and its three downstream readers. (3) Row 256 is captured tool output quoted as evidence; editing it is fabrication (P1, P5). (4) The approved policy links every deferred-gap reference; on the ch10 pages D-038, D-030 and D-031 have no link, and the A7 default's own text places it in adjacent markdown; completing that is applying the approved policy, not a new call. (5) Row 257's quotation marks attribute refinement wording to Hawkins; F5 and Z-13 forbid that; the glossary edge gives the verbatim words. (6) The closed-issue comments describe the pinned toolchain; a closed issue is not a run (P5); docs/reproducibility.md states the pin policy; the register update is durable recording (Z-20), not a content change. +Determined: yes. +Extension: yes, P1/F4 applied to presentation edits of persisted judgment-record text: record text is changed only through the loop (edit source, re-run, re-persist), never patched in a JSON file or stored output, and only when the change earns its place (P4). Z may want to skim this. +Provenance: decisions/pages-publishing/a4-dangling-references.md (A7 paragraph, "Classification rules applied"); decisions/pages-publishing-survey.md section 4 items 2-4; DL-112, DL-113, DL-114; AGENTS.md Part 1 SS1.6, SS1.9; docs/reproducibility.md; `uv run python -m glossary lookup "asserted context"` (Hawkins Sec. 3.2 p. 9); git diff main..pages-publishing -- models decisions/judgment-records is empty; sha256(models/ch08-cumulative.sysml) equals AS-C08 content_hash 35b4bff8; AI-C04.json is read by ch06 nb03, ch10 nb02 and ch10 nb03. Orchestrator note: the ACE found that "records are never reconstructed or silently reworded" is not literal AGENTS.md text; the ruling rests on SS1.6 and SA-7. + +## DL-116 | 2026-10-03 | OPENSYSML-TERMINOLOGY | "OpenSysML" is the stack; components are the OpenSysML runtime and sysml-toolkit; protected tokens, binding-text wording, glossary stays out, edit scope and guard ruled; Pilot membership escalated with default out + +Path: Handled by ACE (items 1, 3-7); item 2 escalated to Z, default out, non-blocking +Decision: Z's direction of 2026-10-03 (`decisions/opensysml-terminology/website-review.md`) is applied as a Z-initiated alignment pass (AGENTS.md 1.11; skill-editor "Z-directed alignment pass"). (1) Convention: bare "OpenSysML" names the stack only; components are "the OpenSysML runtime" (Go; Open-MBEE/OpenSysML; `opensysml` v0.9.0) and "sysml-toolkit" (Rust; `sysmlv2` v0.9.1; "the OpenSysML toolkit" only as a first-mention gloss); every component-specific claim names the component; versions attach to component names; contrasts name both components; first mention per published page is the full component name; the stack is defined on docs/setup.md, docs/references.md and docs/reproducibility.md; the Pilot is always named as the OMG SysML v2 Pilot Implementation. (2) Pilot in or out of "OpenSysML": escalated, default out; the definition sentence is written to hold under either answer. (3) Protected, never edited: code identifiers, imports and env vars, package/skill/directory/file names and skill `name:` frontmatter, URLs and repo/issue refs, models/*.sysml, decisions/judgment-records, figures, stored outputs and the code-cell strings producing them (DL-115), DEFERRED.md headings, decisions/log.md and all dated evidence under decisions/ (reading rule: before DL-116 a bare "OpenSysML" in this repository means the runtime), quoted tool output. DEFERRED.md gets one dated note at the top; bodies may gain "runtime" only in the sentence stating the gap. Skills are edited by the ACE only, one logical change, one session, after the content contracts close; this entry is the pre-edit record and the revert record is the commit preceding the first skill edit; `opensysml-api`/`opensysml-query` names and snippets stay; `opensysml-query` description and H1 say "the OpenSysML runtime v0.9.0". (4) AGENTS.md 1.2 toolchain paragraph, 1.7 import bullet, 1.9 surface 1 and fuel-port example, and the CLAUDE.md sources line are rewritten (wording in the ACE report, quoted in the plan); this entry satisfies the escalate-to-Z gates for exactly those edits plus the 14 skills' prose. (5) No glossary term for OpenSysML or its components. (6) Edited: learner-facing markdown (chapters, exercises, docs, README), binding docs, skill prose, DEFERRED.md note; left: code, tests, src/scripts comments, code cells and outputs (clarify in adjacent markdown), docs/superpowers, decisions/, models/. Guard: lint rules in glossary/lint_rules.toml (learner scope, error) for "OpenSysML (or|and|nor|vs|versus) sysml-toolkit", "sysml-toolkit (or|and) OpenSysML", "neither OpenSysML", "not OpenSysML", "OpenSysML (alone|itself|cannot|can't|does not|doesn't|only)", "OpenSysML v0.9"; every contract's acceptance criteria include a reviewer diff check that protected-token counts, models/, judgment records, figures, stored outputs and DEFERRED headings are unchanged. (7) Truth check: each bare-subject capability claim is classified against its DEFERRED entry as toolkit-differs (false under the umbrella: AGENTS 1.7/D-017; ch07 nb02 cells 9, 22, 23, 25/D-023; docs/setup.md 147; DEFERRED headings D-017, D-019, D-023, D-034, D-036), toolkit-same (D-014, D-020, D-032) or toolkit-unprobed (ch10 nb01 cell 30; case study lines 104, 124; ch07 nb01 cell 14 and ch07 index.md/D-033); all three classes are rewritten to name the runtime, the first class first. +Principles applied: P5 and "probe before asserting"; P4 and Z-18 (fewest edits: keep the project name sysml-toolkit, no per-page definition, a reading note instead of ~300 record edits); F5 and Z-12 (toolchain is not a source, so no glossary term); P1/F4 as applied in DL-115; P6 (licensing: Pilot membership goes to Z); AGENTS.md 1.2, 1.7, 1.9, 1.11; skill-editor Steps 1-4 and the Z-directed pass clause; glossary/README.md line 74; DL-112 (first-mention precedent). +Reasoning: (1) opensysml.org uses the name at two levels and Z adopts the umbrella level, so a bare name no longer identifies the subject of a capability claim; P5 requires the probed component to be named, and the version pins (v0.9.0 runtime, v0.9.1 toolkit) are properties of components. (2) "sysml-toolkit" is the component's own name, already used in the repo, issue refs and DEFERRED, so keeping it leaves those mentions valid and confines the pass to runtime mentions. (3) The protected list follows DL-115 and the published-anchor dependency on DEFERRED headings; dated records are history and gain a reading rule, not edits. (4) The skill-editor clause makes the DL entry recording Z's direction the gate for the edits it names. (5) glossary/README.md line 74 and AGENTS.md 1.2 exclude a term without a canonical source. (6) The learner linter already exists with data-driven rules; one rule set is the small, durable guard. (7) Under the umbrella reading a claim "OpenSysML cannot X" is false exactly where DEFERRED records sysml-toolkit doing X. +Determined: yes for items 1, 3-7; no for item 2 (Z's "permissively licensed" may be definitional or descriptive; licensing goes to Z under P6). +Extension: no (DL-115's extension is applied as a prior decision). +Provenance: Z's direction 2026-10-03; decisions/opensysml-terminology/website-review.md; z-model Z-12, Z-18, Z-20; AGENTS.md 1.2, 1.7, 1.9, 1.11; CLAUDE.md sources paragraph; skill-editor Step 1; glossary/README.md line 74; glossary/lint.py and lint_rules.toml; myst.yml; DEFERRED.md D-014, D-017, D-019, D-023, D-033, D-034, D-036; DL-112, DL-115. + Brief to Z (item 2): Does "OpenSysML" in this repository's prose include the OMG SysML v2 Pilot Implementation (EPL-2.0)? A. Out (OpenSysML = the permissively licensed stack: runtime, sysml-toolkit, Flexo MMS; the Pilot is named separately as the OMG reference implementation and conformance baseline). B. In (follow opensysml.org's four-project stack, Pilot as its stated licensing exception). ACE recommendation: A. + Z's decision: [pending; the pass proceeds under A] + Z's rationale: [pending] + +## DL-117 | 2026-10-03 | OPENSYSML-TERMINOLOGY | Inventory triage: AMBIGUOUS rows ruled, DEFINE texts fixed, FALSE/UNPROBED wording policy, DEFERRED note and D-035 correction authorized + +Path: Handled by ACE +Decision: (1) DL-116 (3) clarified: in a DEFERRED body the editable "sentence stating the gap" is any sentence reporting a tool's observed behavior; Workaround, Resolution, Upstream-issue, Toaster-issue and Status lines are left as dated record under the top note (B-057, B-014, B-048, B-060, B-064, B-067, B-068, B-071 become KEEP-BODY). (2) SA-6's text is not rewritten; the ACE appends a parenthetical recording Z's DL-046 decision (runtime `check` evaluate-only; sysml-toolkit `verify --solve` via `toaster.modelcheck`, D-025). (3) z-model Z-12 is not reworded by anyone; the ACE adds a bracketed dated reading note. (4) Case study L129: "no diagnostic of the OpenSysML runtime or the pilot computes"; A10 to A14 name sysml-toolkit (its `verify --solve` / Z3 backend) wherever D-030/D-031 behavior is attributed. (5) UNPROBED rows name the runtime and add no sentence about sysml-toolkit; FALSE-UNDER-STACK rows name the runtime, and a sysml-toolkit contrast appears at most once per page, at the sentence whose point is the hole (ch07 nb02 cell-23; dropped from cells 9 and 25 and from the ch07 conclusion); AGENTS 1.7 keeps a one-clause D-017 contrast; AGENTS 1.9 fuel-port example names both components (D-014 probed both). (6) Final texts fixed for AGENTS.md 1.2 (carries the convention because the OT-7 lint messages cite it), 1.7, 1.9 (two sites), CLAUDE.md sources line, docs/references.md OpenSysML section, docs/setup.md and docs/reproducibility.md definition paragraphs, glossary/sources/notes/reading-notes.md:35 (assigned to OT-5); every definition sentence holds whether or not the Pilot is inside the stack, and says the Pilot is not one of the two tools the chapters run. (7) DEFERRED.md top note text fixed, listing D-017, D-019, D-023, D-034, D-035, D-036 and the runtime-tracker reading of `OpenSysML#NNN`. (8) D-035 line 878's factual error (citing D-032 and D-033 as one-of-three cases) is corrected as a dated inline correction in its own commit in OT-5. (9) DL-116 (4)'s "14 skills" reads as all 15 skill directories. (10) OT-7 must scope the new lint rules to markdown cells and .md files: protected code-cell strings in ch07 nb02 cell-22 and ch03 nb04 a1b2c3d4 match two of the patterns. All final texts: decisions/opensysml-terminology/final-texts.md. +Principles applied: P5; P4 and Z-18; P6 and the SA quick reference (SA rules reopened only by Z; recording Z's own prior decision is a clarification); ace-protocol skill-modification authority; DL-116 (1), (3), (6), (7); DL-046 and D-025 as Z's decision; the register's own dated-correction practice (D-032 line 849, D-034 line 867). +Reasoning: (1) The top note makes every pre-DL-116 bare name in a field line unambiguous, so editing field lines adds only churn; gap statements are what readers and anchors quote. (2) DL-046 shows Z already decided what SA-6 allows for holds questions; appending that record changes no rule. (3) z-model.md is a record of statements, so it takes a reading note. (4) D-030/D-031 are sysml-toolkit probes and the runtime has its own unprobed engines, so P5 forces the component name; "no diagnostic in this toolchain" over-reaches to the unprobed toolkit. (5) Naming the runtime is what makes a toolkit-differs claim true; a contrast beyond that is added content, admitted once per page where it helps. (6) AGENTS.md 1.2 is where the lint sends people, so the convention's truth rule belongs there in one sentence. (7) and (8) follow P5 and the register's practice; the correction is not terminology, so it is authorized explicitly and committed separately. (10) Protected strings cannot be edited (DL-115), so the guard must not scan them. +Determined: yes, for every item. +Extension: yes, one small one: the DL-116 (3) record-gets-a-reading-note treatment applied to z-model.md (a dated record outside decisions/). Z may want to skim this. +Provenance: DL-116; DL-046 and D-024/D-025; DEFERRED.md D-014, D-017, D-023, D-030, D-031, D-032, D-033, D-034, D-035, D-036; decisions/log.md:876; decisions/opensysml-terminology/website-review.md, inventory-a.md, inventory-b.md, final-texts.md; AGENTS.md 1.2, 1.7, 1.9, 1.11; skill-editor "Z-directed alignment pass"; z-model Z-12, Z-18; z-principles P4, P5, P6. + +## DL-118 | 2026-10-03 | OPENSYSML-TERMINOLOGY | Batch 2: B-056 reverted and DEFERRED top note amended; unlisted docs and chapter sentences ruled; lint guard specified (ignore_code, docs/superpowers excluded) + +Path: Handled by ACE +Decision: (1) B-056 is KEEP-BODY like B-057: the D-032 Status-line sentence is restored byte-identical to main; its DL-117 listing among "gap-statement edits" was an oversight. The DEFERRED top note's last sentence becomes: "Where an entry records a different result for sysml-toolkit (D-017, D-019, D-023, D-024, D-034, D-035, D-036), a bare "OpenSysML" in the heading is the runtime and the body states what sysml-toolkit v0.9.1 does." (2) Case study L69 "No tool diagnostic and neither review process caught Approach A's own defect"; L189 "checked against the OpenSysML runtime"; L201 "the runtime's own point-evaluation engine". docs/references.md sysml-toolkit paragraph names Chapter 5 (`sysmlv2 viz`) and Chapters 8 and 10 (`verify --solve`). docs/reproducibility.md runtime-binary bullet: "a different version of the runtime" plus one sentence on chapters 5, 8 and 10 handing model text to sysml-toolkit's `sysmlv2`. docs/setup.md: "**sysml-toolkit** does what the OpenSysML runtime cannot yet:". (3) ch07 nb02 cell-05 "the runtime actually attempting it", cell-27 "a documented runtime gap"; ch08 nb02 cell-10 "sysml-toolkit's Z3 backend never actually composes"; ch10 nb01 cell 3a3d9d15 names sysml-toolkit and the runtime's `model.verify_satisfaction()` correctly; cell 9d918eda gives the Pilot's full name on first mention; cell 310279c7 "the real pilot"; exercises/ch08 cell-16's sentence-initial "sysml-toolkit's" stands. (4) OT-7: the lint already ignores code cells and outputs; it gains an optional per-rule boolean `ignore_code` that masks fenced blocks and inline code spans (line numbers preserved), the six new rules set it; `docs/superpowers/` is excluded from scanning by prefix; DEFERRED.md and exercises/ are outside the lint's scope; acceptance is new rules at 0 and every other rule's count equal to base outside docs/superpowers. The lint is not a CI gate. (5) No further false or ambiguous reader-visible sentence found on the merged branch; the regex guard cannot catch generic-noun ambiguity or false counts, which stay with reviewers. All edits go to one contract, OT-3C; the DEFERRED items are applied on term/binding before merge. Exact texts: decisions/opensysml-terminology/final-texts-2.md. +Principles applied: P5; P4 and Z-18; P6; DL-116 (1), (3), (6), (7); DL-117 (1), (4), (5), (10); DL-028. +Reasoning: (1) DL-117 (1) states the rule for field lines and its reason; keeping the B-056 edit would falsify the top note's own "Status lines are unchanged" and leave a misattached "itself"; D-024's heading records a different sysml-toolkit result, so the list must include it. (2) A component-specific claim names the component and the first mention per page is the full name; the case study records no runtime probe of Approach A, so "Neither tool" is replaced by the page's own "no tool diagnostic" (P5). The references and reproducibility sentences were false by omission (Chapter 5's `viz`, Chapter 10's direct `verify --solve`; a second pinned binary reads model text). The setup count claim is contradicted by D-017 and D-023; dropping the count adds nothing. (3) D-030/D-031 behavior must name sysml-toolkit (DL-117 (4)); the ch10 appositive misattaches the solver and the page never names sysml-toolkit or the Pilot in full. (4) DL-116 (3) protects the stored output that cell-23 quotes in backticks, so the guard must not read code spans; DL-116 (6) leaves docs/superpowers unedited, so an error-severity rule over it could never be satisfied; per-rule masking keeps existing rules' behavior identical. +Determined: yes. +Extension: yes, two small ones for Z to skim: DL-117 (10)'s scope rule extended from code cells to inline code spans and fences (per-rule `ignore_code`), and DL-116 (6)'s "docs/superpowers left" applied to the lint's scanning scope (prefix exclusion). +Provenance: DL-116, DL-117, DL-028; decisions/opensysml-terminology/final-texts.md, final-texts-2.md, inventory-b.md rows B-056/B-057; DEFERRED.md D-017, D-019, D-020, D-023, D-024, D-030, D-031, D-032, D-034, D-035, D-036; glossary/lint.py, lint_rules.toml, glossary/tests/test_lint.py; myst.yml; decisions/pass4-phase0-close.md:20; chapters ch05 nb03 and ch10 nb01 code cells; src/toaster/render.py. + +## DL-119 | 2026-10-03 | OPENSYSML-TERMINOLOGY | Z decides the Pilot Implementation is outside "OpenSysML"; the contribution-policy contract starts autonomously; the validation push is authorized after it + +Path: Z's decision (answers DL-116 item 2; no ACE ruling needed) +Decision: mzargham (Z) ruled, in chat 2026-10-03: the OMG SysML v2 Pilot Implementation is lumped with the OMG published specifications that OpenSysML builds on; it is not part of "OpenSysML". This is option A of the DL-116 brief. The convention already written (AGENTS.md 1.2 and the other definition sites: the Pilot is "the OMG SysML v2 Pilot Implementation", the conformance baseline, never counted among the tools) holds as written; no text changes. Z also directed: (a) the queued contribution-policy contract starts autonomously once the terminology pass has cleared; (b) the orchestrator may push `pages-publishing` for the CI validation run after the contribution-policy contract, and may remediate errors the run shows; this authorizes the branch push and the validation runs only, not opening the pull request, enabling Pages, or merging (those remain Z's). +Principles applied: P6 (licensing/category question goes to Z; Z ruled); Z-12 (the toolchain is cited only to flag spec gaps; the Pilot sits with the specs side); DL-116 item 2. +Reasoning: Z's classification matches the written default and the definition sentences, which say the Pilot is the conformance baseline and not one of the two tools the chapters run, so nothing in the pass depends on revisiting wording. +Determined: yes (Z). +Extension: no. +Provenance: DL-116 (item 2 brief, "Z's decision: [pending]" now answered here), DL-117, DL-118; decisions/opensysml-terminology/website-review.md; AGENTS.md 1.2. + Z's decision: A (Pilot out of OpenSysML; lumped with the OMG published specs OpenSysML builds on) + Z's rationale: the Pilot Implementation is the OMG-side reference, grouped with the published specs, while OpenSysML is the permissively licensed tool stack built on them. + +## DL-120 | 2026-10-03 | OPENSYSML-TERMINOLOGY | OT-6: the ACE applies the terminology convention to the skills' prose (term/skills 758bdb3; revert record f491403) + +Path: Handled by ACE +Decision: The 15 skills' prose is edited in one commit under DL-116 (3)/(4) and DL-117 (9), as the Z-directed alignment pass. B-082 (SA-6 parenthetical) and B-084 (Z-12 bracketed reading note) are applied verbatim from final-texts.md; 22 further inventory-b rows are applied: the opensysml-query description and H1 ("the OpenSysML runtime v0.9.0"), its recipe-5 G4 sentence naming both components (D-014 SAME), the sysml-diagrams and recipes `-render` CLI sentences naming the runtime (D-037 UNPROBED, no toolkit clause), the D-004 gap sentence, tutorial-glossary, tutorial-style-guide (tracker wording) and tutorial-supporting-pages. The Pilot is "the OMG SysML v2 Pilot Implementation" on first mention and "the pilot" after, in sysml-diagrams, its recipes and the ace-protocol licensing bullet. The orchestrator lifted the no-heading-rename constraint for B-090, B-125, B-126 and B-128 after an inbound-anchor check found no reference; those four headings take the inventory wording. B-129 and B-131 are kept: lowercase `opensysml` is the runtime's package name (AGENTS.md 1.2), so the version already attaches to a component name, it is the form the plan preserves in the opensysml-api description, and a heading change is structural (skill-editor Step 3). opensysml-query line 177 is corrected to the recorded facts (the binding is toolchain and not a chapter dependency: no chapter imports it, AGENTS.md 1.9; Chapters 5, 8 and 10 use sysml-toolkit only through the `sysmlv2` binary, `viz` in Chapter 5 and `verify --solve` in Chapters 8 and 10, DL-118 B3/B4). 55 KEEP rows untouched; no fence, non-markdown skill file, skill directory, `name:` frontmatter or opensysml-api description changed. The Opus reviewer's FAIL on two lines (line 177 wording, "pilot EPL-2.0") was fixed in the amended commit. Pre-edit record DL-116; revert record f491403467188d946e4f4bde767dec2678426084. +Follow-up (not in this diff): sysml-diagrams/SKILL.md L18-19 "real action-flow notation" and "100% success" overstate the runtime's `-render` CLI against D-037 (flow pins and the performed action's name in the "do" state are dropped); a separate skill correction should cite D-037. +Principles applied: P5 (each capability claim classified against its DEFERRED entry: D-014 SAME, D-004 and D-037 UNPROBED; the line-177 correction states only recorded facts); P4 and Z-18 (30 lines over 10 files; no optional clauses; no toolkit sentence added to unprobed claims); P6 and the SA quick reference (SA-6 and Z-12 wording unchanged, record appended); skill-editor Steps 1-4 and the Z-directed alignment clause; ace-protocol skill-modification authority; DL-116 (1), (3), (4); DL-117 (2), (3), (5), (9); DL-118 B3/B4; DL-119 (Pilot outside the stack). +Reasoning: A bare "OpenSysML" no longer identifies the subject of a runtime claim, so every runtime-specific sentence names the runtime and versions attach to it; the one SAME claim (G4) names both components because D-014 records both results; UNPROBED claims name the runtime only. SA-6 and Z-12 are records and take an appended parenthetical and a bracketed note, not rewording. Heading wording is a structural change under skill-editor Step 3, so it was applied only once the contract author lifted the constraint and an anchor check showed no inbound reference; the lowercase package-name headings already satisfy the convention. The Pilot rule in DL-116 (1) is not limited to learner pages, so it applies to skill prose as DL-118 C5 applied it to ch10. Line 177's original "not a chapter dependency" is true of the binding (AGENTS.md 1.9; no import anywhere), so the correction keeps it and adds only which binary subcommand each chapter uses. +Determined: yes. +Extension: no (DL-117's reading-note treatment of z-model.md applied as a prior decision). +Provenance: DL-116, DL-117, DL-118, DL-119; decisions/opensysml-terminology/final-texts.md, final-texts-2.md, inventory-b.md rows B-082..B-160; DEFERRED.md D-004, D-014, D-017, D-037; AGENTS.md 1.2, 1.7, 1.9; src/toaster/render.py, modelcheck.py, tools.py and chapters/ch10 notebooks 01, 03; plan Task 6; guard run (base f491403, head 758bdb3: PASS); tests/test_skill_snippets.py 4 passed; full suite 1847 passed; glossary check ok; six-pattern scan of skills 0 hits; Opus review of OT-6. + +## DL-121 | 2026-10-03 | OPENSYSML-TERMINOLOGY | Whole-branch gate passed; terminology merged into pages-publishing + +Path: Orchestrator-run; reviewer gate (Opus) passed with no blockers +Decision: The OpenSysML terminology revision (DL-115..DL-120; contracts OT-1a, OT-1b, OT-2, OT-3A, OT-3B, OT-3C, OT-4, OT-5, OT-6, OT-7, OT-8) is merged into `pages-publishing` (merge commit 795d0f6). Gate evidence: both trees build under `myst build --html --execute --strict` with the pinned tools; `scripts/check-site.py` passes all five checks on both (18 figures, no leaks, no published exercise/DEFERRED files, links resolve); executed outputs identical across 59 content files and 255 outputs; judgment records byte-identical and AS-C08 content_hash equals the sha256 of models/ch08-cumulative.sysml; visible page text differs only on the 20 edited sources and every differing line maps to an inventory row or final text (0 orphans); lint equal to base outside docs/superpowers with the six new rules at 0; 1882 passed, 0 skipped with TOASTER_REQUIRE_TOOLS=1; the pinned guard passes against the base with the two visible glossary exemptions. Non-blocking notes N1-N8 are in decisions/opensysml-terminology/gate-notes.md and go to the next ACE round. +Principles applied: P5 (the gate measures output equality and unauthorized change rather than trusting the builders); P4. +Reasoning: Every contract passed an independent review on a different model, and the gate re-derived the guarantees (protected zones, outputs, hashes, orphans) from scratch rather than from the contracts' own reports. +Determined: yes. +Extension: no. +Provenance: decisions/opensysml-terminology/{website-review,inventory-a,inventory-b,final-texts,final-texts-2,gate-notes}.md; DL-115..DL-120; plan docs/superpowers/plans/2026-10-03-opensysml-terminology-plan.md; the OT-8 gate report. + +## DL-122 | 2026-10-03 | CONTRIBUTION-POLICY | Z's directive applied: keep current, no new content, strictly dominant improvements; AGENTS.md 1.12 and the docs/skills texts fixed; N1-N8 carry-over ruled + +Path: Handled by ACE, under Z's direction of 2026-10-03 (a Z-initiated alignment pass, AGENTS.md 1.11; this entry is the gate record for AGENTS.md 1.12 and the tutorial-supporting-pages edits under the skill-editor Z-directed clause, and the pre-edit record for CT-3; revert record: the commit preceding the first skill edit) +Decision: (1) Two heads: keeping current (pin or dependency bump with regenerated outputs; a recorded workaround retired for the spec-anchored construct a new release accepts, with DEFERRED status line and comment cell updated and headings kept; a spec-edition re-check; a drift report by toaster issue) and strictly dominant improvement (better on one of spec conformance, didactic clarity, demonstrative OpenSysML tool use; worse on none). New content (chapter, notebook, exercise, construct or operation, model element, judgment record, glossary term, learning outcome) is not accepted by pull request; issue first, Z decides. (2) Added text or cells count as improvement only where P4's test is met; presumption against. (3) Judged by the PR statement (template), an independent reviewer on a different model, the ACE on CANT_TELL or protected zones; a trade-off is not accepted (Z by issue); a no-op is churn. (4) AGENTS 1.9's Z-review gate governs the project's own filings; an outside reporter's toaster issue is the intake. (5) Exercises are learning; one disclaimer in docs/setup.md; no exercise or pointer cell changes. (6) Infrastructure that keeps existing content current is in scope and is not content. (7) The notebook-level statement is one sentence in ch10 conclusion.md "What comes next" (no index.md, pointer or exercise cells). (8) Naming per DL-116: PS-1 says "its toolchain (the OpenSysML runtime, sysml-toolkit and the other pinned tools)"; priority (3) says "tools from the OpenSysML stack (the OpenSysML runtime and sysml-toolkit)"; Z's "ecosystem" is read as the stack; no spec editions or Pilot in the policy sentence. (9) One new .github/PULL_REQUEST_TEMPLATE.md carrying the checklist; no CONTRIBUTING.md, no issue template. Final texts (PS-1, PS-2, PS-3, PL-1, AGENTS.md 1.12, docs/contributor.md, README.md, docs/setup.md, tutorial-supporting-pages, the reviewer checklist) are in decisions/contribution-policy/final-texts.md. Carry-over: N1 recorded retroactively (glossary/README.md edit accurate; no revert); N2 no edit; N4 "the OpenSysML runtime's Python binding cannot yet"; N5 "The tutorial's own guard catches it:"; N6 one backticked sentence in ch03 nb03 cell-03; N7 fix the lint's silent acceptance of unknown rule fields (CT-4) and the sysml-diagrams D-037 overstatement (CT-3b, separate session, DL-123); other N7 residuals left as protected or dated record; N3, N8 no action. Contracts: CT-2 (docs/README/AGENTS/chapter markdown and two notebook markdown cells), CT-3 and CT-3b (ACE, skills), CT-4 (.github, lint). +Principles applied: Z's directive 2026-10-03 (policy content); P4 and Z-18 (placement, additions, no CONTRIBUTING.md, N2); P5 (keep-current hooks, N1, N4, N6, N7); P1 and P2 (the dominance statement is a justified, evidenced, per-case judgment, not a formula); P6 (trade-offs and new content go to Z); F4 and F6; SA-7, SA-8, AGENTS.md 1.3, 1.6, 1.9, 1.10, 1.11; DL-116 (1), (3), DL-117 (6), DL-119; skill-editor Steps 1-4 and the Z-directed clause; ace-protocol handle-case "second exercises: No". +Reasoning: Z fixed the policy's substance, so the open questions are scope, placement and procedure. "Content" is what the tutorial teaches and covers (SA-8's units, chapters, models, records, terms); a workaround retired for the spec construct adds no coverage and was always meant to come out (1.9), so it is currency. P4 makes an unearned addition worse on didactic clarity by definition, so a pure addition is rarely dominant and never when it is new content. P1/P2 make the test a recorded judgment checked by an independent reviewer; P6 keeps trade-offs with Z, so a non-dominant change is declined rather than escalated. The notebooks contain no contribution text (inventory, verified), so Z's "in the notebooks" is an addition, and Z-18 puts one sentence where the chapter text already looks past the tutorial. 1.9's gate protects what the project files in its own name; a reporter's issue on our tracker is how a gap reaches us. The PR template is the mechanism (3) relies on; a second policy page does not earn its place. N4: D-024/D-025 record the binding, not the runtime, as unable to pose a holds question. N7: fail-closed lint validation is the design the README promises; the D-037 skill rows describe as working what D-037 records as dropped content. +Determined: yes, for every item. +Extension: yes, three small ones for Z to skim: (a) AGENTS 1.9's "nothing is filed on a public repository until Z has reviewed" read as governing the project's own filings, not an outside reporter's toaster issue; (b) "content" in Z's directive read as tutorial material, not tests/scripts/CI; (c) "keep current" extended from the toolchain to the pinned spec editions (AGENTS 1.2 pins editions), so priority (1) is reachable under the keep-current head. Also for Z's confirmation, not an extension: that one sentence in ch10 conclusion.md satisfies "in the notebooks". +Provenance: Z's directive (docs/superpowers/plans/2026-10-03-opensysml-terminology-plan.md, queued section; decisions/contribution-policy/inventory.md); AGENTS.md 1.2, 1.3, 1.6, 1.9, 1.10, 1.11; z-model Z-7, Z-12, Z-17, Z-18, Z-20; DL-112, DL-116..DL-121; decisions/opensysml-terminology/gate-notes.md N1-N8; DEFERRED.md D-023, D-024, D-025, D-037; glossary/lint.py and README (commit 949826e); decisions/contribution-policy/final-texts.md. + +## DL-123 | 2026-10-03 | SKILL-EDIT | PENDING: sysml-diagrams skill corrected against D-037 (CT-3b, ACE, separate session) + +Path: Handled by ACE (P5 correction within the ACE's unilateral skill authority; not a Z-directed edit) +Decision (intent, PENDING): in .claude/skills/sysml-diagrams/SKILL.md replace the table cell at L18 (cell 3) and the first sentence of the cell at L19 (cell 3) with the D-037-accurate wording in decisions/contribution-policy/final-texts.md section CT-3b. One logical change, one session; no fence, no other line. +Revert record: the commit preceding the first edit of this session; `git show :.claude/skills/sysml-diagrams/SKILL.md` restores the file. Current text of the two cells is captured in the session report. +Principles applied: P5 (a skill must not describe as working what the register records as dropping content); skill-editor Steps 1-4. +Status: PENDING until the edit lands; the ACE or orchestrator sets COMPLETE with the commit. + +## DL-124 | 2026-10-03 | CONTRIBUTION-POLICY | Reviewer findings in the governed text ruled: runtime-binary pin and version re-probe rule added to "Keep current"; DEFERRED status wording, "AI model", "open an issue", library-commit fix; reviewer table placed in docs/contributor.md; recipes.md D-037 correction queued + +Path: Handled by ACE (amendments under the DL-122 pass; two items with a policy flavor ruled with Z's default stated) +Decision: (F1) "Keep current" step 1 names the OpenSysML runtime binary pin (src/toaster/bootstrap.py version defaults and _CLI_SUMS, src/toaster/connect.py, the opensysml.connect(version=...) calls in scripts/check_conformance.py and scripts/check_construction.py) as separate from the opensysml package pin; step 5 adds the version re-check of chapters and DEFERRED.md with the rule that a re-probed statement takes the new version and an un-probed one keeps the version it was probed against. (F2) "status line" becomes "add a dated status line (or update the one it has)" at both sites. (F3) "open an issue first" loses "first"; AGENTS.md 1.12 gains no exception: what Z directs after an issue is a Z-initiated scope change under 1.11, not a pull-request path (Z may add an explicit exception; default none). (F4) "different model" becomes "different AI model" at three sites. (Q1) Not restated: 1.3 and 1.11 already govern Part 1, confirmed definitions and the ACE skills; a one-sentence form is recorded in the ACE report if Z wants it explicit. (Q2) The reviewer-facing test is a compact three-row table in docs/contributor.md after the "What we accept" bullets, with the pass rule; reviewer.md unchanged. (Q3) "the sysmlv2 binary, Z3 and the PlantUML jar with their sha256 hashes, and the standard-library commit". Extras: PR template Protections bullet gains "or learning outcome"; sysml-diagrams references/recipes.md L92-94 and L109-111 take the D-037 wording (CT-6). Exact texts: decisions/contribution-policy/final-texts-2.md. Contracts: CT-5 (builder: docs/contributor.md, .github template), CT-6 (ACE: sysml-diagrams/references/recipes.md). +Principles applied: P5 (an incomplete bump instruction and an overstated render claim both describe as done what is not); DL-116 (1) and (3); P4 and Z-18 (no duplication of 1.3/1.11; smallest table; one-word edits); P6 (F3 and Q1 defaults stated for Z); DL-117 (dated-correction practice in DEFERRED.md); DL-122. +Reasoning: The reviewer's line claims were verified by grep (bootstrap.py, connect.py, check_conformance.py, check_construction.py; 1 of 39 DEFERRED entries has a Status line; the library pin is a commit with no hash). "None is accepted by pull request" is a statement about unsolicited pull requests; Z deciding to add content is Z changing scope, which 1.11 already provides for, so dropping "first" removes the implication without a new clause. A human reviewer reads the published page, which is why 1.12 says the test is there; the table is the smallest form that makes "worse" checkable per priority. +Determined: yes. +Extension: no (DL-122's extensions applied as prior decisions). +Provenance: the CT-2/CT-3 reviewer report; src/toaster/bootstrap.py, connect.py; scripts/check_conformance.py, check_construction.py; pyproject.toml; DEFERRED.md top note; .claude/agents/reviewer.md; recipes.md; D-037; AGENTS.md 1.3, 1.11, 1.12; DL-116, DL-117, DL-122. + +## DL-125 | 2026-10-03 | SKILL-EDIT | PENDING: sysml-diagrams/references/recipes.md corrected against D-037 (CT-6, ACE, separate session) + +Path: Handled by ACE (P5 correction within the ACE's unilateral skill authority; not a Z-directed edit) +Decision (intent, PENDING): in .claude/skills/sysml-diagrams/references/recipes.md replace the two paragraphs at ~L92-94 (action-flow) and ~L109-111 (state-transition) with the D-037-accurate wording in decisions/contribution-policy/final-texts-2.md section EXTRA (b). One logical change, one session; no fence, no other line. +Revert record: the commit preceding the first edit of this session; `git show :.claude/skills/sysml-diagrams/references/recipes.md` restores the file. +Principles applied: P5; skill-editor Steps 1-4. +Status: PENDING until the edit lands; the ACE or orchestrator records COMPLETE with the commit. + +## DL-126 | 2026-10-03 | SKILL-EDIT | COMPLETE: CT-3 and CT-3b skill edits (DL-122, DL-123) + +Path: Orchestrator-run; edits by the ACE +Decision: CT-3 (tutorial-supporting-pages: inventory row, template item 5, contributor-guide scenarios 1-2 renamed with the "What we accept" lead, new never-do bullet; commit 02d8e9c, merged daf06b1) and CT-3b (sysml-diagrams SKILL.md L18-19 corrected against D-037; commit 4f775d5, merged) are complete. Revert records: cfb3672 for both. DL-123 is COMPLETE. The pinned guard reports exactly one authorized fenced-block change (rule (e), tutorial-supporting-pages template item 5), all other rules PASS. +Provenance: DL-122, DL-123; guard output; tests/test_skill_snippets.py 4 passed. + +## DL-127 | 2026-10-03 | CONTRIBUTION-POLICY | COMPLETE: CT-5 and CT-6 applied; DL-125 closed with a citation correction; ACE gloss and README row fixed + +Path: Orchestrator-run; edits by a builder (CT-5) and the ACE as editor (CT-6); texts ruled by the ACE (DL-124 and a micro-ruling) +Decision: CT-5 (commits 243bc9e, e972bf7; merged) applied the DL-124 amendments to docs/contributor.md (runtime-binary pin and version re-check in "Keep current", dated-status-line wording, "open an issue", "AI model" x3, the reviewer-facing table, the library-commit fix) and the PR template ("or learning outcome"), plus the micro-ruling: a one-time gloss "(the project's triage role, described below)" at the first use of "the ACE" on the page and README row "decisions/ - decision log (rulings and escalations)". CT-6 (commit d14bc6a; merged) corrected both paragraphs of .claude/skills/sysml-diagrams/references/recipes.md against D-037; the ACE editor stopped before editing because the batch-4 action-flow text cited decisions/diagram-study-real-fixtures.md, which says (L52) it did not rerun the action-flow view; the orchestrator widened the mandate to a citation fix; the applied wording is recorded verbatim in decisions/contribution-policy/final-texts-2.md. DL-125 is COMPLETE (revert record d0c2645). decisions/diagram-survey.md L185 carries the same unsupported claim; it is dated evidence and is not edited (follow-up note only). +Principles applied: P5 (an unsupported citation is not evidence; the editor stopped rather than applying known-inaccurate text); P4 and Z-18 (one parenthetical, no new label or URL); skill-editor Steps 1-4; DL-122, DL-124. +Reasoning: The independent reviewer and the ACE editor each caught an inaccuracy that the authoring pass missed (a missing pin site; a citation that did not support the claim); both were corrected at the source with the evidence recorded. +Determined: yes. +Extension: no. +Provenance: decisions/contribution-policy/final-texts.md, final-texts-2.md; DL-122..DL-126; DEFERRED.md D-037; decisions/diagram-study-real-fixtures.md L52; decisions/log.md DL-057; guard runs PASS; full suite with provisioned tools 1884 passed, 0 skipped. + +## DL-128 | 2026-10-03 | CONTRIBUTION-POLICY | Final gate: the runtime bump instruction covers the 42 notebook and test connect calls; the lint ask becomes "no new hits against the base"; check_construction step recorded; "worse on none" harmonised + +Path: Handled by ACE (amendments under the DL-122 pass; no Part 1 text touched) +Decision: (B1) docs/contributor.md "Keep current" step 1 names every opensysml.connect(version=...) call in src/, scripts/, chapters/ (32 notebooks), exercises/ (10) and tests/ as moving with the pin, with the grep that finds them, states that code cells, stored outputs and models/ are protected during editorial passes and not during a bump (outputs regenerate on re-execution), and that scripts/probes/ and scripts/diagram_study/ are dated probes that keep the version they probed; step 5's grep extends to exercises/ and tests/ with "a test or fixture that pins the version moves with it". (B2) The lint is asked for as "reports no hit the base branch does not" (write-baseline on base, baseline on the branch), at the only two sites that mention it (contributor.md step 3; PR template last Protections bullet); no baseline file is committed; README, setup, AGENTS.md 1.12 and the reviewer table contain no lint sentence. (n1) Recorded: "How a change is built and reviewed" step 2 carries the run instruction for scripts/check_construction.py --check --chapter=N (text in decisions/contribution-policy/final-texts-3.md), added in CT-2 after the guard's rule (g) caught the dropped URL. (n2) "worse on none" at contributor.md and "Not worse on any priority" in the template, matching AGENTS.md 1.12. Contract CT-7 (builder): docs/contributor.md, .github/PULL_REQUEST_TEMPLATE.md. Exact texts: decisions/contribution-policy/final-texts-3.md. +Principles applied: P5 (a bump instruction that leaves notebooks and tests on the old binary, and a checkbox a clean checkout cannot tick, both describe as done what is not); DL-116 (1) and (3); P4 and Z-18 (clauses added to existing steps, no new step or file; the gate's own "lint equal to base" criterion reused); DL-122, DL-124, DL-127. +Reasoning: Verified counts: 42 notebooks with the escaped connect(version="v0.9.0") call, 47 v0.9.x lines in tests/, 66 in chapters plus DEFERRED.md, lint exit 1 with 79 hits on a clean checkout and no CI wiring (decisions/pass4-phase0-close.md L20). Only prose a contributor can act on keeps the policy honest, so the instruction names where the pin lives and what is deliberately left alone; the lint criterion that every gate in this pass applied is the one the page now states, through the lint's own baseline flags rather than a committed file. +Determined: yes. +Extension: no. +Provenance: the final gate report (B1, B2, n1, n2); git grep counts on contrib 517b72b; glossary/lint.py baseline functions and glossary/README.md "Lint"; decisions/pass4-phase0-close.md L20; docs/contributor.md; .github/PULL_REQUEST_TEMPLATE.md; AGENTS.md 1.12; DL-116, DL-122, DL-124, DL-127. + +## DL-129 | 2026-10-03 | CONTRIBUTION-POLICY | Contribution-policy pass complete and merged into pages-publishing; gate evidence + +Path: Orchestrator-run; two independent gates (Opus) and an ACE-ruled amendment round +Decision: The contribution-policy pass (DL-122..DL-128; contracts CT-1 inventory, CT-2, CT-3, CT-3b, CT-4, CT-5, CT-6, CT-7) is merged into `pages-publishing` (merge commit 32d947e). The final whole-branch gate found two inaccuracies in ACE-authored text (an incomplete runtime-pin list for a version bump; a lint checkbox a clean checkout cannot satisfy); both were ruled by the ACE (DL-128), applied in CT-7 and verified against the approved text byte-wise and by running the claims (connect-call grep: 42 notebooks; lint baseline exit codes). Everything else the gate checked passed: guard shape (rule (e) flags only the one authorized template line in tutorial-supporting-pages; visible waivers: the PR template, glossary/lint.py, glossary/tests/test_lint.py), executed strict builds of both trees with provisioned tools, check-site 5/5 with 18 figures, executed outputs identical (one nondeterministic gRPC stderr line present only in the base build), judgment records byte-identical and AS-C08 hash intact, built-page text differing only on contributor, setup, ch10 conclusion, ch07 state-traces and ch03 threshold-judgment with every line mapped to an approved text, lint equal to base with the six OpenSysML rules at 0, 1884 passed with 0 skipped under TOASTER_REQUIRE_TOOLS=1. Z's confirmations requested (DL-122): that one sentence in the ch10 conclusion satisfies "in the notebooks"; the three small extensions (AGENTS 1.9's Z-review gate governs the project's own filings; "content" means tutorial material; "keep current" covers the pinned spec editions); the defaults that AGENTS.md 1.12 carries no pull-request exception and does not restate that Part 1/glossary/ACE skills change only by Z-initiated passes. +Principles applied: P5 (the gates measure rather than trust; two inaccuracies were caught by independent review); P6 (policy-flavored defaults flagged for Z). +Determined: yes. +Extension: no. +Provenance: DL-122..DL-128; decisions/contribution-policy/{inventory,final-texts,final-texts-2,final-texts-3}.md; the two gate reports. diff --git a/decisions/opensysml-terminology/final-texts-2.md b/decisions/opensysml-terminology/final-texts-2.md new file mode 100644 index 0000000..0b71e75 --- /dev/null +++ b/decisions/opensysml-terminology/final-texts-2.md @@ -0,0 +1,26 @@ +# Final texts and rulings, batch 2 (ACE, DL-118): authoritative over final-texts.md and the inventories + +Source: ACE batch 2 ruling of 2026-10-03, recorded as DL-118. Where this file and `final-texts.md` differ, THIS FILE governs. + +## A. DEFERRED.md (on branch term/binding, before it merges) +- **A1. B-056 is REVERTED** (KEEP-BODY, like B-057). Restore the D-032 `**Status: GUARDED.**` sentence byte-identical to `main` + (`pages-publishing:DEFERRED.md`): `The underlying OpenSysML/sysml-toolkit acceptance-without-diagnostic gap itself remains open upstream — this entry documents the gap and its guard, not a fix to either tool.` +- **A2. Top note last sentence** becomes: `Where an entry records a different result for sysml-toolkit (D-017, D-019, D-023, D-024, D-034, D-035, D-036), a bare "OpenSysML" in the heading is the runtime and the body states what sysml-toolkit v0.9.1 does.` (Rest of the note unchanged.) + +## B. docs (OT-3C) +- **B1. Case study L69-71:** `No tool diagnostic and neither review process caught Approach A's own defect; it took re-reading §7.21.1 directly, later, to name it.` +- **B2. Case study L189:** `deserves to be checked against the OpenSysML runtime, not just read off the spec text.` **L201:** `tested directly against \`model.verify_satisfaction()\` — the runtime's own point-evaluation engine,` (existing em-dash left as is). +- **B3. docs/references.md sysml-toolkit paragraph (amends final-texts.md):** `The Rust toolkit (\`sysmlv2\` binary, pinned v0.9.1), used in Chapter 5 to draw the interconnection diagram (\`sysmlv2 viz\`, laid out by PlantUML), in Chapters 8 and 10 for \`sysmlv2 verify --solve\` (through \`toaster.modelcheck\` in Chapter 8; called directly in Chapter 10), and for the cross-checks recorded in DEFERRED.md.` +- **B4. docs/reproducibility.md L20-22:** replace `Every model-loading call in every notebook goes through this one pinned binary; there's no code path that reaches a different version.` with `Every model-loading call in every notebook goes through this one pinned binary; there's no code path that reaches a different version of the runtime. Chapters 5, 8 and 10 also hand model text to sysml-toolkit's \`sysmlv2\`, pinned by the next item.` +- **B5. docs/setup.md (R112):** `**sysml-toolkit** does what the OpenSysML runtime cannot yet: prove that a constraint holds for every value of an unbound quantity, not just check it against one fixed value, using the Z3 solver.` (The following sentence "Chapter 8 uses it directly ..." stays.) + +## C. chapters (OT-3C; markdown cells only) +- **C1.** ch07 nb02 `cell-05`: `confirmed by the tool actually attempting it` -> `confirmed by the runtime actually attempting it`. +- **C2.** ch07 nb02 `cell-27`: `(a documented tool gap, [D-028]` -> `(a documented runtime gap, [D-028]`. +- **C3.** ch08 nb02 `cell-10`: `this toolchain's Z3 backend never actually composes` -> `sysml-toolkit's Z3 backend never actually composes`. ("Two separate, real limits of this toolchain" stays; cells 14 and 16 "this toolchain" stay.) +- **C4.** ch10 nb01 `3a3d9d15`: replace `what \`sysmlv2 verify --solve\` -- a different tool from \`model.verify_satisfaction()\`, this tutorial's own Z3-backed solver, used for \`deliveredEnergyBoundedBySupply\`'s own proof in Chapter 8 -- actually reports` with `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` (ASCII `--` as the cell uses). +- **C5.** ch10 nb01 `9d918eda`: `no pilot warning ever fired against it` -> `no warning from the OMG SysML v2 Pilot Implementation (the pilot) ever fired against it`. ch10 nb01 `310279c7`: `the real OMG pilot` -> `the real pilot`. +- **C6.** exercises/ch08 `cell-16`: no change (sentence-initial lowercase `sysml-toolkit's` stands). + +## D. Lint (OT-7) specification +- `glossary/lint.py` already reads only markdown cells of `chapters/**/*.ipynb`, `chapters/**/*.md`, `docs/**/*.md` (minus `docs/glossary.md`); code cells and outputs are never read. Add: (1) optional per-rule boolean `ignore_code` (default false; accepted only as bool, else exit 2) that masks fenced blocks and inline code spans with equal-length spaces (newlines preserved) before matching; the six new rules set it. (2) prefix exclusion of `docs/superpowers/` in `_units()`. (3) Six `[[rule]]` tables, `severity = "error"`, `scope = "learner"`, `why` citing AGENTS.md 1.2 and DL-116: `OpenSysML\s+(or|and|nor|vs\.?|versus)\s+sysml-toolkit`; `sysml-toolkit\s+(or|and)\s+OpenSysML`; `neither\s+OpenSysML`; `not\s+OpenSysML`; `OpenSysML\s+(alone|itself|cannot|can't|does\s+not|doesn't|only)`; `OpenSysML\s+v0\.9`. (4) Acceptance: the six rules show 0 on the merged branch after OT-3C; every other rule's count equals base outside `docs/superpowers` (record before/after per-rule counts). The lint is not a CI gate. DEFERRED.md and exercises/ are outside the lint's scope. diff --git a/decisions/opensysml-terminology/final-texts.md b/decisions/opensysml-terminology/final-texts.md new file mode 100644 index 0000000..4058348 --- /dev/null +++ b/decisions/opensysml-terminology/final-texts.md @@ -0,0 +1,97 @@ +# Final texts and rulings (ACE, DL-117): authoritative over the inventories + +Where `inventory-a.md` / `inventory-b.md` proposed wording that differs from this file, THIS FILE governs. Source: ACE batch +ruling of 2026-10-03 (recorded as DL-117 in `decisions/log.md`). Normative convention: DL-116 and +`docs/superpowers/plans/2026-10-03-opensysml-terminology-plan.md`. + +## Clarification of DL-116 (3) +In a DEFERRED.md body, "the sentence stating the gap" means any sentence that reports a tool's observed behavior (what +loads, what is rejected, what is printed), wherever it sits in the entry. The field lines **Workaround**, **Resolution**, +**Upstream issue**, **Toaster issue** and **Status** are dated record and are LEFT AS WRITTEN (class KEEP-BODY): B-014, +B-048, B-057, B-060, B-064, B-067, B-068, B-071. Headings are never edited. + +## Binding-surface rulings (inventory-b) +- **B-057** (DEFERRED.md:852): leave, KEEP-BODY. +- **B-082** (`.claude/skills/ace-protocol/SKILL.md:63`, SA-6): done by the ACE in OT-6. SA words are not rewritten; append a + parenthetical. Final line: + `| SA-6 | Bounded model checking: opensysml \`check\` engine only (the runtime's engine; for "holds" questions Z accepted sysml-toolkit's \`sysmlv2 verify --solve\` wrapped by \`toaster.modelcheck\`: DL-046, D-025) |` +- **B-084** (`.claude/skills/ace-protocol/z-model.md:21`, Z-12): done by the ACE in OT-6. Nobody rewrites Z's words; add a + bracketed dated reading note. Final line: + `- Z-12. OpenSysML and other implementations are toolchain, cited only to flag spec gaps. Tall's three worlds and the optimization/control lens are builder-facing and never named in learner content. [Reading note, 2026-10-03, DL-116/DL-117: said when this repository used "OpenSysML" for the runtime; it now names the stack whose components the tutorial uses are the OpenSysML runtime and sysml-toolkit. The position holds under both readings.]` +- **Gap-statement edits stand** (B-038, B-045, B-047, B-055, B-056, B-063 all three replacements, B-066 first + replacement, B-070 both replacements). **B-066's second replacement is subsumed** by the D-035 correction below. +- **B-079** (`glossary/sources/notes/reading-notes.md:35`) goes to OT-5: `- The OpenSysML runtime and sysml-toolkit (components of the OpenSysML stack) and the Pilot Implementation are toolchain, cited only to flag spec gaps. They define no terms.` (`uv run python -m glossary check` must stay green.) +- **Skills:** 15 directories, not 14; OT-6 covers every skill's prose. + +## DEFINE sites (exact text) + +**AGENTS.md line 43 (1.2):** +> **Toolchain, not sources.** OpenSysML (opensysml.org) is the open-source SysML v2 tool stack; this tutorial uses two of its components and names them by role, **the OpenSysML runtime** (Go; `Open-MBEE/OpenSysML`; Python package `opensysml`; pinned v0.9.0) and **sysml-toolkit** (Rust; `sysmlv2` binary; pinned v0.9.1). A bare "OpenSysML" is a statement about the stack; a claim that is true of, or was probed against, one component names that component, and a version number attaches to a component name. The OMG SysML v2 Pilot Implementation (EPL-2.0) is the conformance baseline and is always named as such; it is not one of the two tools the chapters run. All of these execute the specs. They are cited only to flag a spec gap (§1.9), never to define a term. + +**AGENTS.md line 127 (1.7 bullet):** +> - The OpenSysML runtime v0.9.0 does not resolve `import` across separately loaded sources (sysml-toolkit v0.9.1 does, D-017). A notebook therefore assembles the SysML text from the imported modules plus its explicit increment into one source and loads that (gap G7, §1.9). + +**AGENTS.md line 139 (1.9 surface 1):** `1. \`model.query()\` in the OpenSysML runtime: the API Query (select, where, scope, inverse; no traversal). It sees named elements only. Name your allocations, connections and flows and it sees those too.` (line 143 unchanged) + +**AGENTS.md line 145 (fuel-port parenthesis):** `(the OpenSysML runtime v0.9.0 and sysml-toolkit v0.9.1 both accept a power port connected to a fuel port; the KerML 1.1 spec searched has no validation constraint for it)` + +**CLAUDE.md lines 19-20 (tail of the sources paragraph):** `The OpenSysML runtime and sysml-toolkit, two components of the OpenSysML stack (opensysml.org; AGENTS.md 1.2), are toolchain, cited only to flag spec gaps.` + +**docs/references.md "OpenSysML" section (heading unchanged):** +> ## OpenSysML +> +> OpenSysML ([opensysml.org](https://opensysml.org/)) is the open-source SysML v2 tool stack. This tutorial uses two of its components and names them by role: the OpenSysML runtime (Go; repository `Open-MBEE/OpenSysML`; Python package `opensysml`; pinned v0.9.0) and sysml-toolkit (Rust; `sysmlv2` binary; pinned v0.9.1). The OMG SysML v2 Pilot Implementation (EPL-2.0) is the conformance baseline and is always named as such. +> +> The OpenSysML runtime: Open-MBEE/OpenSysML. +> +> The OpenSysML runtime's Python package (`opensysml==0.9.0`), used to load, validate, evaluate, and query SysML v2 models in this tutorial. All model loading uses `conn.load_from_content(content, strict=False)`. Gaps between the runtime's current API and the SysML v2 specification are tracked in [DEFERRED.md](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md) and as issues in this repository and upstream. +> +> sysml-toolkit: Open-MBEE/sysml-toolkit. +> +> The Rust toolkit (`sysmlv2` binary, pinned v0.9.1), used in Chapter 8 for `sysmlv2 verify --solve` through `toaster.modelcheck`, and for the cross-checks recorded in DEFERRED.md. + +(The builder preserves any existing link/anchor/text in that section that this block does not change, and keeps the +`Open-MBEE/OpenSysML` and `Open-MBEE/sysml-toolkit` URLs byte-identical.) + +**D1, docs/setup.md, new paragraph inserted before line 136 (existing text continues unchanged):** +> OpenSysML ([opensysml.org](https://opensysml.org/)) is the open-source SysML v2 tool stack. This tutorial uses two of its components and names them by role: the OpenSysML runtime (Go; repository `Open-MBEE/OpenSysML`; Python package `opensysml`; pinned v0.9.0) and sysml-toolkit (Rust; `sysmlv2` binary; pinned v0.9.1). The OMG SysML v2 Pilot Implementation (EPL-2.0) is the conformance baseline and is always named as such; it is not one of the two tools the chapters run. + +**D2, docs/reproducibility.md, new paragraph after line 12:** +> OpenSysML ([opensysml.org](https://opensysml.org/)) is the open-source SysML v2 tool stack. This tutorial pins two of its components: the OpenSysML runtime (Go; `Open-MBEE/OpenSysML`; Python package `opensysml`; v0.9.0) and sysml-toolkit (Rust; `sysmlv2` binary; v0.9.1, pinned with Z3 and the standard library in `scripts/tool-pins.json`). The OMG SysML v2 Pilot Implementation (EPL-2.0) is the conformance baseline. + +## DEFERRED.md +**Top note** (insert after `# Deferred work`, before `## D-001`): +> **Terminology note (2026-10-03, DL-116, DL-117).** OpenSysML (opensysml.org) is the open-source SysML v2 tool stack; this tutorial uses two of its components, the OpenSysML runtime (Go; `Open-MBEE/OpenSysML`; Python package `opensysml`; pinned v0.9.0) and sysml-toolkit (Rust; `sysmlv2` binary; pinned v0.9.1). Entries written before this note use a bare "OpenSysML" (and "opensysml") for **the OpenSysML runtime**; read them that way. Headings are unchanged because their anchors are linked from published pages, and Workaround, Resolution, Upstream-issue and Status lines are unchanged because they are dated record; `OpenSysML#NNN` references are issues on the runtime's tracker. Where an entry records a different result for sysml-toolkit (D-017, D-019, D-023, D-034, D-035, D-036), the heading describes the runtime's behavior only and the body states what sysml-toolkit v0.9.1 does. + +**D-035 line 878 factual correction** (SEPARATE COMMIT in OT-5; explicitly authorized; replaces the sentence from "This is the same three-way disagreement shape" to "not less."): +> This is the same disagreement shape as D-034 (one pinned tool disagrees with the other two on a real construct's validity), but in the OPPOSITE direction: there, the OpenSysML runtime was the outlier tolerating something the other two correctly rejected; here, sysml-toolkit alone is the outlier, MORE permissive than the other two, not less. **Corrected 2026-10-03 (DL-117):** this sentence originally also cited D-032 and D-033 as one-of-three cases; D-032 records sysml-toolkit accepting the construct too, and D-033 is a runtime display defect with no three-way comparison. + +(B-066's first replacement, "confirmed correct against the OpenSysML runtime v0.9.0", stands.) + +## Learner-surface rulings (inventory-a) +- **UNPROBED rows (R032, R037, R042, R044, R060, R065, R075, R091, R096, R097, A03; D-028/R? as RUNTIME):** rewrite to name the + runtime; add NOTHING about sysml-toolkit and no "not probed" sentence in learner text. **R097 final:** + `**Tool support.** Neither the OpenSysML runtime nor the pilot flags` (no parenthesis). +- **R125** (exercises/ch07 `cell-0` L48-49): accept `which the OpenSysML runtime loads silently but` and A04 `and the OMG SysML v2 Pilot Implementation both flag`. Residual (note in the OT-3B report, do not block): decisions/log.md:876 does not record the pilot flagging `done` specifically. +- **A09** (case study L129-130): `That is a semantic property no diagnostic of the OpenSysML runtime or the pilot computes.` (A06 at L60 must give the Pilot its full name first; apply in file order.) +- **A10-A14:** name sysml-toolkit where D-030/D-031 behavior is attributed: + - A10 (ch08 nb01 `cell-07`): `because sysml-toolkit's solver never reaches through to either one` + - A11 (ch08 conclusion L13): `sysml-toolkit's \`verify --solve\` does not compose two separately declared \`assert constraint\`s (whether sibling or inherited) and cannot reason through a chained calc invocation` + - A12 (ch08 index L11): `sysml-toolkit's \`verify --solve\` does not compose separately declared constraints, and cannot reason through a chained calc invocation` + - A13 (exercises/ch08 `cell-14`): `D-030 says sysml-toolkit's Z3 backend does not compose two separately` + - A14 (exercises/ch08 `cell-16`): `sysml-toolkit's Z3 backend never composes two separately declared \`assert` + "This toolchain" stays only in sentences that are genuinely generic (the inventory's "reviewed, no change" list stands). +- **FALSE-UNDER-STACK finals** (a sysml-toolkit contrast appears at most once per page, at the sentence whose point is the hole): + - **R066** (ch07 nb02 `cell-09`): `The OpenSysML runtime v0.9.0 keeps a transition's trigger only as a string and never resolves it against \`Start\`, \`Finish\` or \`Cancel\`: a typo, or a reference to a name the model never declares, loads without error and simply never fires`. Same cell, new sub-row: `catches what the tool does not` → `catches what the runtime does not`. + - **R069** (`cell-23`): `The OpenSysML runtime loads the typo cleanly (the \`OpenSysML itself\` line printed above is the runtime's verdict): \`Strat\` never fires, and nothing in the runtime says so; sysml-toolkit v0.9.1 resolves trigger names and warns on a broken reference (D-023).` A02: `The runtime has a real hole here`. (The stored output line is `OpenSysML itself: typo_model.ok=True`; the wording must match it.) + - **R070** (`cell-25`, caption): `invisible to the OpenSysML runtime's own loader.` (drop the parenthesis) + - **R074** (ch07 conclusion L13): `The OpenSysML runtime v0.9.0 does not resolve a transition's trigger against the item def it names;` (drop "where sysml-toolkit v0.9.1 does"). A05: `the runtime lets through silently`. + - **R112** (docs/setup.md L147): `**sysml-toolkit** does one thing the OpenSysML runtime cannot yet:` + - **B-002** (AGENTS.md 1.7): as the 1.7 text above. +- The inventory's other proposed wording stands where this file is silent. Where a row's wording is silent AND the convention + does not settle it, STOP and report. + +## Notes for later contracts +- **OT-7 (lint):** `glossary/lint.py` must be checked for what it scans; the new rules must apply to markdown cells and `.md` files + only (protected code-cell strings in ch07 nb02 `cell-22` and ch03 nb04 `a1b2c3d4` would match `OpenSysML itself` and `OpenSysML v0.9`). +- **OT-6 (ACE edits skills):** all 15 skills' prose; B-082 and B-084 finals above; record the pre-edit revert SHA. diff --git a/decisions/opensysml-terminology/gate-notes.md b/decisions/opensysml-terminology/gate-notes.md new file mode 100644 index 0000000..28d0b10 --- /dev/null +++ b/decisions/opensysml-terminology/gate-notes.md @@ -0,0 +1,15 @@ +# OT-8 gate notes (2026-10-03): non-blocking items carried forward + +Gate verdict: PASS, no blockers (Opus reviewer; executed `--strict` builds of both trees, output equality 59 content files / +255 outputs, 0 orphan page-text differences, protected zones and judgment records byte-identical, AS-C08 hash intact, +lint equal to base outside `docs/superpowers/`, 1882 passed with 0 skipped under `TOASTER_REQUIRE_TOOLS=1`). + +Open items for the ACE (to be rulings in the next ACE round; none block publication): +- **N1.** `glossary/README.md` (lint documentation) was edited by OT-7 outside its stated blast zone (`lint.py` is implied by DL-118 D). The edit is accurate; record retroactively. +- **N2.** The Pilot's exclusion ("not one of the two tools the chapters run") is explicit on `docs/setup.md` only; `docs/references.md` and `docs/reproducibility.md` follow `final-texts.md` verbatim and omit it, though DL-117 (6) says every definition sentence carries it. Decide whether to add the clause there. +- **N3.** Plan Task 8 says to log the merge as DL-117; that number is taken (this entry is DL-121). +- **N4.** `docs/setup.md`: "sysml-toolkit does what the OpenSysML runtime cannot yet: prove ..." is broader than D-024/D-025, which record that the runtime's *Python binding* is evaluate-only while the runtime has its own `check`/`smt` engines. Decide whether to say "the runtime's Python binding". +- **N5.** ch07 nb02 cell-23: "The tutorial's own guard does:" now follows the inserted sysml-toolkit clause, so "does" refers back past it. Cosmetic. +- **N6.** The ch03 threshold-judgment page shows the doc comment "not yet supported in OpenSysML v0.9.0" from `models/ch03-cumulative.sysml` (protected). Nothing on that page clarifies it (verification-case does, via R032). Consider an adjacent markdown clarification. +- **N7.** Residual phrasing left on purpose: the ch07 stored-output label `OpenSysML itself`; "the pilot itself stays toolchain" (ch10 nb01 310279c7); "this toolchain's Z3 backend" in code cells/outputs and "nothing in this toolchain checks" in markdown/outputs; "The tool rejects `perform ToastBread;`" (D-019 body); the sysml-diagrams overstatement of D-037 ("real action-flow notation", "100% success"); the KEEP-BODY Resolution line "upstream fix in OpenSysML alone" (D-034). Unknown rule fields in `lint_rules.toml` are silently ignored (follow-up). +- **N8.** Two benign "Kernel: dead" lines appear in the head executed build log (base has none); all outputs identical, treated as shutdown noise. diff --git a/decisions/opensysml-terminology/inventory-a.md b/decisions/opensysml-terminology/inventory-a.md new file mode 100644 index 0000000..761930e --- /dev/null +++ b/decisions/opensysml-terminology/inventory-a.md @@ -0,0 +1,289 @@ +# OT-1a inventory: OpenSysML terminology, learner-facing surface + +Contract OT-1a (read-only), 2026-10-03, branch `term/inv-a`, base `terminology` 1649785. Authority: DL-116 and the normative convention in `docs/superpowers/plans/2026-10-03-opensysml-terminology-plan.md`. Nothing but this file was written. + +## Method + +- Surface: `chapters/**` (markdown cells of the 32 chapter notebooks, code cells for KEEP-ID listing, plus every `index.md` and `conclusion.md`), `exercises/**` (10 notebooks), `docs/*.md`, `docs/case-studies/*.md`, `README.md`. Excluded: `docs/superpowers/**` and everything under `decisions/` (protected; read only as evidence). +- Unit: one line of a cell's `source` (notebooks, JSON read, cell `id` plus 1-based line within the cell) or one file line (markdown). Raw JSON file lines are not the unit, because some notebooks store a whole code cell in one JSON string. Stored cell outputs are not in the surface; the one output containing the name is noted at R067-R068. +- Selection: every unit matching `/opensysml/i`, extracted by script (`ot1a/ext.py` in the scratch dir) and then read in full. A second pass searched the surface for sentences that conflate or contrast the runtime and sysml-toolkit without the word (`the tool`, `the library`, `toolchain`, `the pilot`, `OMG pilot`, `sysmlv2`, `toolkit`); those are the adjacent rows A01-A14 and D1-D2 (outside the machine check, listed separately). +- Capability claims were classified against DEFERRED.md bodies (not only headings). Entries read: D-004 (second, line 172), D-014, D-017, D-018, D-019, D-020, D-023, D-024, D-025, D-026, D-028, D-030, D-031, D-032, D-033, D-034, D-035, D-036, D-037, D-038, plus `decisions/log.md:876` for one claim with no DEFERRED entry. +- Every quote is verbatim, was checked to occur exactly once in its cell (notebooks) or file (markdown) for every non-KEEP-ID row and for the adjacent rows, and sits on the stated line. KEEP-ID code rows quote the line truncated to 110 characters; their locator (cell id and line) identifies them. +- Rules cited in replacements: [C1] a claim true of, or probed against, one component names that component; [C2] a version number attaches to a component name; [C3] first mention on a published page uses the full component name (then `the runtime` may follow); [C4] a contrast names both components; [C5] the stack is defined once each on setup, references and reproducibility with the opensysml.org link; [C6] the Pilot is named in full on first mention (`the OMG SysML v2 Pilot Implementation`, then `the pilot`) and is never counted among the two tools. Replacements assume the convention paragraph of the plan, not a Pilot-membership answer (default OUT). +- Replacement format: the quote is replaced by the replacement text exactly (a substring substitution); where a replacement starts with a capital the quote's preceding text ends a sentence. + +## Machine check + +Lines in the surface matching /opensysml/i: **132**. Primary rows: **132**. Additional rows on a line already having a row (listed duplicates): **1** (R-id marked `+dup` in the locator). Total rows: **133**. Check: lines 132 = rows 133 - listed duplicates 1. Adjacent rows A01-A14, D1-D2 are outside this count because their lines do not match /opensysml/i. + +Independent cross-check: markdown files only, `git grep -ci opensysml` over the surface's `.md` files gives 24 matching lines (README 1, docs/index 2, docs/setup 4, docs/references 3, docs/reproducibility 3, docs/contributor 2, case study 2, chapters index/conclusion 7); notebooks by JSON cell-source scan: 96 code lines + 12 markdown lines = 108; 24 + 108 = 132. + +## Counts by class (primary rows, one per matching line) + +| class | rows | +|---|---| +| KEEP-ID | 100 | +| KEEP-STACK | 3 | +| RUNTIME | 10 | +| TOOLKIT | 0 | +| BOTH | 2 | +| FALSE-UNDER-STACK | 6 | +| SAME | 0 | +| UNPROBED | 10 | +| DEFINE | 1 | +| AMBIGUOUS | 0 | +| **total** | **132** | + +Adjacent rows (outside the machine check): AMBIGUOUS 6, DEFINE 2, FALSE-UNDER-STACK 2, RUNTIME 5, UNPROBED 1, total 16. + +Notes on the classes: no row is `TOOLKIT` (no learner-facing sentence is about sysml-toolkit alone and wrong) and no row is `SAME` (the toolkit-same entries D-014, D-020, D-032 are not cited anywhere in the surface, which also contains no sentence quoting them). `FALSE-UNDER-STACK` and `UNPROBED` rows are the capability claims; their DEFERRED entry id and the line that shows what sysml-toolkit does (or that nothing was probed) are in the row note. + +## Counts by file + +Columns: matching lines | classes of the primary rows. + +| file | lines | classes | +|---|---|---| +| README.md | 1 | KEEP-STACK 1 | +| chapters/ch01-system-purpose/01-abstract-def.ipynb | 3 | KEEP-ID 3 | +| chapters/ch01-system-purpose/02-part-def.ipynb | 2 | KEEP-ID 2 | +| chapters/ch01-system-purpose/03-specialization.ipynb | 2 | KEEP-ID 2 | +| chapters/ch01-system-purpose/04-composition.ipynb | 2 | KEEP-ID 2 | +| chapters/ch01-system-purpose/index.md | 1 | RUNTIME 1 | +| chapters/ch02-requirements/01-requirement-def.ipynb | 3 | KEEP-ID 3 | +| chapters/ch02-requirements/02-assumptions.ipynb | 3 | KEEP-ID 3 | +| chapters/ch02-requirements/03-judgment-context.ipynb | 2 | KEEP-ID 2 | +| chapters/ch03-measures/01-moe-definition.ipynb | 2 | KEEP-ID 2 | +| chapters/ch03-measures/02-mop-candidate-eval.ipynb | 3 | KEEP-ID 3 | +| chapters/ch03-measures/03-threshold-judgment.ipynb | 2 | KEEP-ID 2 | +| chapters/ch03-measures/04-verification-case.ipynb | 6 | KEEP-ID 5, UNPROBED 1 | +| chapters/ch03-measures/index.md | 1 | RUNTIME 1 | +| chapters/ch04-functional-decomp/01-action-def-ffbd.ipynb | 4 | KEEP-ID 3, UNPROBED 1 | +| chapters/ch04-functional-decomp/02-heating-refinement.ipynb | 2 | KEEP-ID 2 | +| chapters/ch04-functional-decomp/03-completeness-check.ipynb | 2 | KEEP-ID 2 | +| chapters/ch04-functional-decomp/conclusion.md | 1 | UNPROBED 1 | +| chapters/ch04-functional-decomp/index.md | 2 | RUNTIME 1, UNPROBED 1 | +| chapters/ch05-architecture/01-model-navigation.ipynb | 2 | KEEP-ID 2 | +| chapters/ch05-architecture/02-allocate.ipynb | 3 | KEEP-ID 3 | +| chapters/ch05-architecture/03-interfaces.ipynb | 2 | KEEP-ID 2 | +| chapters/ch06-recursive-decomp/01-subsystem-requirements.ipynb | 2 | KEEP-ID 2 | +| chapters/ch06-recursive-decomp/02-second-level.ipynb | 2 | KEEP-ID 2 | +| chapters/ch06-recursive-decomp/03-stopping-judgment.ipynb | 2 | KEEP-ID 2 | +| chapters/ch07-execution/01-calc-energy.ipynb | 5 | KEEP-ID 4, UNPROBED 1 | +| chapters/ch07-execution/02-state-traces.ipynb | 9 | FALSE-UNDER-STACK 3, KEEP-ID 4, RUNTIME 1, UNPROBED 1 | +| chapters/ch07-execution/03-param-sweep.ipynb | 2 | KEEP-ID 2 | +| chapters/ch07-execution/conclusion.md | 1 | FALSE-UNDER-STACK 1 | +| chapters/ch07-execution/index.md | 1 | UNPROBED 1 | +| chapters/ch08-checking/01-assert-constraint-def.ipynb | 2 | KEEP-ID 2 | +| chapters/ch08-checking/02-violation-witness.ipynb | 2 | KEEP-ID 2 | +| chapters/ch08-checking/03-revision-flow.ipynb | 2 | KEEP-ID 2 | +| chapters/ch09-coverage-sufficiency/01-requirement-coverage.ipynb | 2 | KEEP-ID 2 | +| chapters/ch09-coverage-sufficiency/02-evidence-completeness.ipynb | 2 | KEEP-ID 2 | +| chapters/ch09-coverage-sufficiency/03-stale-detection.ipynb | 2 | KEEP-ID 2 | +| chapters/ch10-traceability-signoff/01-traceability-graph.ipynb | 4 | KEEP-ID 3, UNPROBED 1 | +| chapters/ch10-traceability-signoff/02-judgment-synthesis.ipynb | 2 | KEEP-ID 2 | +| chapters/ch10-traceability-signoff/03-engineering-signoff.ipynb | 2 | KEEP-ID 2 | +| docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md | 2 | UNPROBED 2 | +| docs/contributor.md | 2 | BOTH 1, KEEP-ID 1 | +| docs/index.md | 2 | KEEP-STACK 2 | +| docs/references.md | 3 | DEFINE 1, KEEP-ID 1, RUNTIME 1 | +| docs/reproducibility.md | 3 | BOTH 1, RUNTIME 2 | +| docs/setup.md | 4 | FALSE-UNDER-STACK 1, RUNTIME 3 | +| exercises/ch01/exercise.ipynb | 2 | KEEP-ID 2 | +| exercises/ch02/exercise.ipynb | 2 | KEEP-ID 2 | +| exercises/ch03/exercise.ipynb | 2 | KEEP-ID 2 | +| exercises/ch04/exercise.ipynb | 2 | KEEP-ID 2 | +| exercises/ch05/exercise.ipynb | 2 | KEEP-ID 2 | +| exercises/ch06/exercise.ipynb | 2 | KEEP-ID 2 | +| exercises/ch07/exercise.ipynb | 3 | FALSE-UNDER-STACK 1, KEEP-ID 2 | +| exercises/ch08/exercise.ipynb | 2 | KEEP-ID 2 | +| exercises/ch09/exercise.ipynb | 2 | KEEP-ID 2 | +| exercises/ch10/exercise.ipynb | 2 | KEEP-ID 2 | + +## Truth-class summary (capability claims) + +FALSE-UNDER-STACK (false once OpenSysML includes sysml-toolkit; DEFERRED entry shows the toolkit differs): R066, R069, R070, R074, R112, R125 (6 primary rows: D-023 for the ch07 notebook 02 and ch07 conclusion rows, D-024/D-025 for docs/setup.md, decisions/log.md:876 for the exercises/ch07 row). Adjacent rows A02 and A05 (D-023) are also FALSE-UNDER-STACK. + +UNPROBED (the entry names only the runtime; sysml-toolkit not run): R032, R037, R042, R044, R060, R065, R075, R091, R096, R097 (10 rows; D-004 second entry, D-026, D-033, and the two case-study lines / ch10 cell 02281b44 with no DEFERRED entry). Adjacent row A03 (D-038) is also UNPROBED. + +SAME: none in this surface. (D-014, D-020, D-032 are the toolkit-same entries; none is cited by a learner-facing OpenSysML sentence.) + +## Row table + +| row | file | locator | quoted sentence | class | proposed replacement wording | +|---|---|---|---|---|---| +| R001 | README.md | L3 | using SysML v2 and OpenSysML. | KEEP-STACK | (no change) **NOTE:** The tutorial uses SysML v2 with two components of the OpenSysML tool stack; the sentence is about the stack. Optional: link https://opensysml.org/ (not required by the convention). | +| R002 | chapters/ch01-system-purpose/01-abstract-def.ipynb | `cell-02` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R003 | chapters/ch01-system-purpose/01-abstract-def.ipynb | `cell-02` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R004 | chapters/ch01-system-purpose/01-abstract-def.ipynb | `cell-06` line 3 | # abstract modifier: the Editor API does not yet author it (https://github.com/Open-MBEE/toaster/issues/9 / ht | KEEP-ID | (no change) Code comment carrying a repo/issue reference (`Open-MBEE/OpenSysML#NNN`, protected token and code-cell string). | +| R005 | chapters/ch01-system-purpose/02-part-def.ipynb | `cell-02` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R006 | chapters/ch01-system-purpose/02-part-def.ipynb | `cell-02` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R007 | chapters/ch01-system-purpose/03-specialization.ipynb | `cell-02` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R008 | chapters/ch01-system-purpose/03-specialization.ipynb | `cell-02` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R009 | chapters/ch01-system-purpose/04-composition.ipynb | `cell-02` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R010 | chapters/ch01-system-purpose/04-composition.ipynb | `cell-02` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R011 | chapters/ch01-system-purpose/index.md | L22 | provision Python and the OpenSysML binary | RUNTIME | provision Python and the OpenSysML runtime binary **NOTE:** setup.md L27 states the downloaded binary is the one the `opensysml` Python package connects to, i.e. the runtime. [C1][C3] - first mention on the page. | +| R012 | chapters/ch02-requirements/01-requirement-def.ipynb | `cell-02` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R013 | chapters/ch02-requirements/01-requirement-def.ipynb | `cell-02` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R014 | chapters/ch02-requirements/01-requirement-def.ipynb | `976fc6bc` line 2 | # require constraint: the Editor API does not yet author it (https://github.com/Open-MBEE/toaster/issues/11 / | KEEP-ID | (no change) Code comment carrying a repo/issue reference (`Open-MBEE/OpenSysML#NNN`, protected token and code-cell string). | +| R015 | chapters/ch02-requirements/02-assumptions.ipynb | `cell-02` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R016 | chapters/ch02-requirements/02-assumptions.ipynb | `cell-02` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R017 | chapters/ch02-requirements/02-assumptions.ipynb | `fa97b3a7` line 2 | # attribute :>> redefinition: the Editor API does not yet author it (https://github.com/Open-MBEE/toaster/issu | KEEP-ID | (no change) Code comment carrying a repo/issue reference (`Open-MBEE/OpenSysML#NNN`, protected token and code-cell string). | +| R018 | chapters/ch02-requirements/03-judgment-context.ipynb | `cell-02` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R019 | chapters/ch02-requirements/03-judgment-context.ipynb | `cell-02` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R020 | chapters/ch03-measures/01-moe-definition.ipynb | `cell-02` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R021 | chapters/ch03-measures/01-moe-definition.ipynb | `cell-02` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R022 | chapters/ch03-measures/02-mop-candidate-eval.ipynb | `cell-02` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R023 | chapters/ch03-measures/02-mop-candidate-eval.ipynb | `cell-02` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R024 | chapters/ch03-measures/02-mop-candidate-eval.ipynb | `cell-02` line 8 | # assert satisfy not yet supported by the Editor API - https://github.com/Open-MBEE/toaster/issues/12 / https: | KEEP-ID | (no change) Code comment carrying a repo/issue reference (`Open-MBEE/OpenSysML#NNN`, protected token and code-cell string). | +| R025 | chapters/ch03-measures/03-threshold-judgment.ipynb | `cell-02` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R026 | chapters/ch03-measures/03-threshold-judgment.ipynb | `cell-02` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R027 | chapters/ch03-measures/04-verification-case.ipynb | `cell-02` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R028 | chapters/ch03-measures/04-verification-case.ipynb | `cell-02` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R029 | chapters/ch03-measures/04-verification-case.ipynb | `a1b2c3d4` line 3 | # Note: #verificationMethod = VerificationMethodKind::test metadata not yet supported (https://github.com/Open | KEEP-ID | (no change) Code comment carrying a repo/issue reference (`Open-MBEE/OpenSysML#NNN`, protected token and code-cell string). | +| R030 | chapters/ch03-measures/04-verification-case.ipynb | `a1b2c3d4` line 10 | * Note: formal #verificationMethod metadata not yet supported in OpenSysML v0.9.0; | KEEP-ID | (no change) Code-cell string (protected, DL-115). | +| R031 | chapters/ch03-measures/04-verification-case.ipynb | `a1b2c3d4` line 11 | * tracked at toaster#19 / OpenSysML#608. | KEEP-ID | (no change) Code comment carrying a repo/issue reference (`Open-MBEE/OpenSysML#NNN`, protected token and code-cell string). | +| R032 | chapters/ch03-measures/04-verification-case.ipynb | `e5f6g7h8` line 1 | it is not yet supported in OpenSysML v0.9.0 ( | UNPROBED | it is not yet supported in the OpenSysML runtime v0.9.0 ( **NOTE:** D-004 second entry (DEFERRED.md:172-189) records only the runtime ('In OpenSysML v0.9.0 this raises "expected a body member"'); no sysml-toolkit probe. [C1][C2][C3]. The same cell's `OpenSysML#608` link text is KEEP-ID. This cell also carries the clarification for code cell `a1b2c3d4` lines 3, 10, 11 (comment/`doc` text 'not yet supported in OpenSysML v0.9.0', KEEP-ID). | +| R033 | chapters/ch03-measures/index.md | L22 | provision Python and the OpenSysML binary | RUNTIME | provision Python and the OpenSysML runtime binary **NOTE:** setup.md L27 states the downloaded binary is the one the `opensysml` Python package connects to, i.e. the runtime. [C1][C3] - first mention on the page. | +| R034 | chapters/ch04-functional-decomp/01-action-def-ffbd.ipynb | `cell-02` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R035 | chapters/ch04-functional-decomp/01-action-def-ffbd.ipynb | `cell-02` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R036 | chapters/ch04-functional-decomp/01-action-def-ffbd.ipynb | `cell-04` line 1 | # params argument of add_action_def not yet supported, https://github.com/Open-MBEE/toaster/issues/18 / https: | KEEP-ID | (no change) Code comment carrying a repo/issue reference (`Open-MBEE/OpenSysML#NNN`, protected token and code-cell string). | +| R037 | chapters/ch04-functional-decomp/01-action-def-ffbd.ipynb | `cell-15` line 1 | but OpenSysML v0.9.0 treats them differently: | UNPROBED | but the OpenSysML runtime v0.9.0 treats them differently: **NOTE:** D-026 (DEFERRED.md:342-546) has no sysml-toolkit probe ('the tool accepts both (`model.ok == True`)' is `model.ok`, the runtime API). [C1][C2][C3]. URL anchor `d-026-opensysml-treats...` on the same line is KEEP-ID. | +| R038 | chapters/ch04-functional-decomp/02-heating-refinement.ipynb | `cell-02` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R039 | chapters/ch04-functional-decomp/02-heating-refinement.ipynb | `cell-02` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R040 | chapters/ch04-functional-decomp/03-completeness-check.ipynb | `cell-02` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R041 | chapters/ch04-functional-decomp/03-completeness-check.ipynb | `cell-02` line 7 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R042 | chapters/ch04-functional-decomp/conclusion.md | L9 | but which OpenSysML v0.9.0 only honors when it is written out | UNPROBED | but which the OpenSysML runtime v0.9.0 only honors when it is written out **NOTE:** D-026 (no toolkit probe). [C1][C2][C3]. First mention on this page. | +| R043 | chapters/ch04-functional-decomp/index.md | L21 | provision Python and the OpenSysML binary | RUNTIME | provision Python and the OpenSysML runtime binary **NOTE:** setup.md L27 states the downloaded binary is the one the `opensysml` Python package connects to, i.e. the runtime. [C1][C3] - first mention on the page. | +| R044 | chapters/ch04-functional-decomp/index.md | L25 | so that OpenSysML v0.9.0 keeps the whole model evaluable | UNPROBED | so that the OpenSysML runtime v0.9.0 keeps the whole model evaluable **NOTE:** D-026 (no toolkit probe). [C1][C2]. Full component name kept (line 21 of the same page already introduced it; 'the runtime v0.9.0' would also satisfy the convention). | +| R045 | chapters/ch05-architecture/01-model-navigation.ipynb | `cell-02` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R046 | chapters/ch05-architecture/01-model-navigation.ipynb | `cell-02` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R047 | chapters/ch05-architecture/02-allocate.ipynb | `cell-02` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R048 | chapters/ch05-architecture/02-allocate.ipynb | `cell-02` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R049 | chapters/ch05-architecture/02-allocate.ipynb | `cell-04` line 1 | # allocate not yet supported by the Editor API: toaster#13 (https://github.com/Open-MBEE/toaster/issues/13) / | KEEP-ID | (no change) Code comment carrying a repo/issue reference (`Open-MBEE/OpenSysML#NNN`, protected token and code-cell string). | +| R050 | chapters/ch05-architecture/03-interfaces.ipynb | `cell-02` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R051 | chapters/ch05-architecture/03-interfaces.ipynb | `cell-02` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R052 | chapters/ch06-recursive-decomp/01-subsystem-requirements.ipynb | `cell-02` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R053 | chapters/ch06-recursive-decomp/01-subsystem-requirements.ipynb | `cell-02` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R054 | chapters/ch06-recursive-decomp/02-second-level.ipynb | `cell-02` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R055 | chapters/ch06-recursive-decomp/02-second-level.ipynb | `cell-02` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R056 | chapters/ch06-recursive-decomp/03-stopping-judgment.ipynb | `cell-02` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R057 | chapters/ch06-recursive-decomp/03-stopping-judgment.ipynb | `cell-02` line 7 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R058 | chapters/ch07-execution/01-calc-energy.ipynb | `cell-02` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R059 | chapters/ch07-execution/01-calc-energy.ipynb | `cell-02` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R060 | chapters/ch07-execution/01-calc-energy.ipynb | `cell-14` line 1 | (OpenSysML prints this as the unsimplified | UNPROBED | (the OpenSysML runtime prints this as the unsimplified **NOTE:** D-033 (DEFERRED.md:856-864): found by ad hoc `model.eval()` calls; sysml-toolkit has no probe. [C1][C3]. URL anchor `d-033-opensysmls-eval...` on the line is KEEP-ID. | +| R061 | chapters/ch07-execution/01-calc-energy.ipynb | `cell-15` line 6 | # opensysml's own verdict string uses an em-dash; the printed form here uses a | KEEP-ID | (no change) Code-cell string (protected, DL-115). | +| R062 | chapters/ch07-execution/01-calc-energy.ipynb | `cell-18` line 1 | d-033-opensysmls-eval-does-not-simplify | KEEP-ID | (no change) **NOTE:** Only occurrence on the line is the DEFERRED.md anchor inside a URL. The sentence's 'printed as the same unsimplified compound unit' needs no edit (the page's first mention is cell-14). | +| R063 | chapters/ch07-execution/02-state-traces.ipynb | `cell-02` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R064 | chapters/ch07-execution/02-state-traces.ipynb | `cell-02` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R065 | chapters/ch07-execution/02-state-traces.ipynb | `cell-05` line 1 | stay executable when left unbound in OpenSysML v0.9.0 ( | UNPROBED | stay executable when left unbound in the OpenSysML runtime v0.9.0 ( **NOTE:** D-026 (no toolkit probe). [C1][C2][C3] - this is the page's first prose mention, so the full component name. URL anchor on the line is KEEP-ID. | +| R066 | chapters/ch07-execution/02-state-traces.ipynb | `cell-09` line 1 | OpenSysML v0.9.0 keeps a transition's trigger only as a string and never resolves it against `Start`, `Finish` or `Cancel`: a typo, or a reference to a name the model never declares, loads without error and simply never fires | FALSE-UNDER-STACK | The OpenSysML runtime keeps a transition's trigger only as a string and never resolves it against `Start`, `Finish` or `Cancel`: in the runtime v0.9.0 a typo, or a reference to a name the model never declares, loads without error and simply never fires, where sysml-toolkit v0.9.1 resolves these names and warns on a broken reference **NOTE:** D-023 (DEFERRED.md:298 'sysml-toolkit v0.9.1 does resolve these names and warns on broken references'). Under the stack reading 'OpenSysML ... never resolves' is false. [C1][C2][C4]. Quote begins mid-sentence: the preceding text is 'modes. ' so the replacement starts with a capital 'The'. URL anchor on the line is KEEP-ID. (Builders: if a shorter fix is preferred, 'the OpenSysML runtime v0.9.0 keeps ... never fires, where sysml-toolkit v0.9.1 warns' is equivalent.) | +| R067 | chapters/ch07-execution/02-state-traces.ipynb | `cell-22` line 3 | OpenSysML itself accepts the typo'd trigger with no diagnostic | KEEP-ID | (no change in the code cell) **NOTE:** Code-cell assertion message (DL-115 protected). Wrong under the stack reading (the string says the whole stack accepts the typo; sysml-toolkit v0.9.1 warns, D-023). Clarification lives in markdown cell-23 (row for cell-23). | +| R068 | chapters/ch07-execution/02-state-traces.ipynb | `cell-22` line 4 | OpenSysML itself: typo_model.ok= | KEEP-ID | (no change in the code cell) **NOTE:** Code-cell print; the string is also stored in the cell's stdout output ('OpenSysML itself: typo_model.ok=True', the only stored output in the surface containing the name). Protected; clarified in markdown cell-23. | +| R069 | chapters/ch07-execution/02-state-traces.ipynb | `cell-23` line 1 | OpenSysML loads the typo cleanly: `Strat` never fires, and nothing in the tool says so. | FALSE-UNDER-STACK | The OpenSysML runtime loads the typo cleanly (the `OpenSysML itself` line printed above is the runtime's verdict): `Strat` never fires, and nothing in the runtime says so, where sysml-toolkit v0.9.1 warns on the broken reference. **NOTE:** D-023 (DEFERRED.md:298 'sysml-toolkit v0.9.1 does resolve these names and warns on broken references'). [C1][C2][C4]. This sentence is the markdown clarification for the code-cell strings in cell-22 (rows above). The same cell's 'The tool has a real hole here' is adjacent row A2. | +| R070 | chapters/ch07-execution/02-state-traces.ipynb | `cell-25` line 1 | invisible to OpenSysML's own loader. | FALSE-UNDER-STACK | invisible to the OpenSysML runtime's own loader (sysml-toolkit v0.9.1 would warn on it). **NOTE:** D-023 (DEFERRED.md:298 'sysml-toolkit v0.9.1 does resolve these names and warns on broken references'). [C1][C4]. Cell 25 is the figure caption cell for the typo'd trigger. If the ACE prefers a shorter edit, drop the parenthesis: 'invisible to the OpenSysML runtime's own loader.' | +| R071 | chapters/ch07-execution/02-state-traces.ipynb | `cell-27` line 1 | in OpenSysML v0.9.0, `execute_state`'s `performer` argument has no effect on the result | RUNTIME | in the OpenSysML runtime v0.9.0, `execute_state`'s `performer` argument has no effect on the result **NOTE:** D-028 (DEFERRED.md:600-637): an argument of the runtime's Python `Model.execute_state`; the entry has no sysml-toolkit probe, and sysml-toolkit's Python `Session` is a different API, so this is an API-surface claim, not a language-capability claim. [C1][C2]. | +| R072 | chapters/ch07-execution/03-param-sweep.ipynb | `cell-02` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R073 | chapters/ch07-execution/03-param-sweep.ipynb | `cell-02` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R074 | chapters/ch07-execution/conclusion.md | L13 | OpenSysML v0.9.0 does not resolve a transition's trigger against the item def it names; | FALSE-UNDER-STACK | The OpenSysML runtime v0.9.0 does not resolve a transition's trigger against the item def it names, where sysml-toolkit v0.9.1 does; **NOTE:** D-023 (DEFERRED.md:298 'sysml-toolkit v0.9.1 does resolve these names and warns on broken references'). [C1][C2][C4]. Quote starts after 'in use. '. URL anchor on the line is KEEP-ID. The same sentence's 'the tool lets through silently' is adjacent row A5. | +| R075 | chapters/ch07-execution/index.md | L31 | (printed by OpenSysML as | UNPROBED | (printed by the OpenSysML runtime as **NOTE:** D-033 (no toolkit probe; `model.eval` is the runtime's API). [C1][C3]. URL anchor on the line is KEEP-ID. | +| R076 | chapters/ch08-checking/01-assert-constraint-def.ipynb | `cell-02` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R077 | chapters/ch08-checking/01-assert-constraint-def.ipynb | `cell-02` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R078 | chapters/ch08-checking/02-violation-witness.ipynb | `cell-02` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R079 | chapters/ch08-checking/02-violation-witness.ipynb | `cell-02` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R080 | chapters/ch08-checking/03-revision-flow.ipynb | `cell-02` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R081 | chapters/ch08-checking/03-revision-flow.ipynb | `cell-02` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R082 | chapters/ch09-coverage-sufficiency/01-requirement-coverage.ipynb | `5de63c72` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R083 | chapters/ch09-coverage-sufficiency/01-requirement-coverage.ipynb | `5de63c72` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R084 | chapters/ch09-coverage-sufficiency/02-evidence-completeness.ipynb | `419ecbee` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R085 | chapters/ch09-coverage-sufficiency/02-evidence-completeness.ipynb | `419ecbee` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R086 | chapters/ch09-coverage-sufficiency/03-stale-detection.ipynb | `dc06bb34` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R087 | chapters/ch09-coverage-sufficiency/03-stale-detection.ipynb | `dc06bb34` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R088 | chapters/ch10-traceability-signoff/01-traceability-graph.ipynb | `b8bb18ce` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R089 | chapters/ch10-traceability-signoff/01-traceability-graph.ipynb | `b8bb18ce` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R090 | chapters/ch10-traceability-signoff/01-traceability-graph.ipynb | `ed18ec1d` line 3 | d-038-opensysmls-api-json-export | KEEP-ID | (no change) **NOTE:** Only occurrence on the line is the DEFERRED.md anchor inside a URL. The surrounding sentence 'a gap in the API-JSON export' is adjacent row A3 (D-038, UNPROBED). | +| R091 | chapters/ch10-traceability-signoff/01-traceability-graph.ipynb | `02281b44` line 1 | and OpenSysML raises no diagnostic against any of them | UNPROBED | and the OpenSysML runtime raises no diagnostic against any of them **NOTE:** No DEFERRED entry; DL-116 item 7 and the case study (rows below) list this as toolkit-unprobed (no sysml-toolkit run on the subject-less `assert satisfy` fixtures is recorded). [C1][C3]. This is the page's first prose mention of the component (the code cells' `import opensysml` are KEEP-ID). | +| R092 | chapters/ch10-traceability-signoff/02-judgment-synthesis.ipynb | `407bfd17` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R093 | chapters/ch10-traceability-signoff/02-judgment-synthesis.ipynb | `407bfd17` line 6 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R094 | chapters/ch10-traceability-signoff/03-engineering-signoff.ipynb | `bc09af84` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R095 | chapters/ch10-traceability-signoff/03-engineering-signoff.ipynb | `bc09af84` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R096 | docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md | L104 | and loads cleanly under OpenSysML. | UNPROBED | and loads cleanly under the OpenSysML runtime. **NOTE:** DL-116 item 7: toolkit-unprobed (no sysml-toolkit run on this line recorded; the file mentions sysml-toolkit only for `verify --solve`, line 24). [C1][C3]. This is the file's first mention of OpenSysML. | +| R097 | docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md | L124 | **Tool support.** Neither OpenSysML nor the OMG pilot flags | UNPROBED | **Tool support.** Neither the OpenSysML runtime nor the pilot flags **NOTE:** DL-116 item 7: toolkit-unprobed. [C1][C6]. Wording assumes the Pilot is first named in full at line 60 (adjacent row A6); the sentence does not claim anything about sysml-toolkit. Alternative if the ACE wants the claim to cover the stack: add '(sysml-toolkit was not probed on this line)' after 'pilot'. | +| R098 | docs/contributor.md | L28 | in the toolchain (OpenSysML, sysml-toolkit) | BOTH | in the toolchain (the OpenSysML runtime, sysml-toolkit) **NOTE:** Lists the runtime and sysml-toolkit as parallel items, which only works if the first names the runtime; under the stack reading 'OpenSysML, sysml-toolkit' lists the whole and a part. [C1][C4]. | +| R099 | docs/contributor.md | L95 | a version bump in `opensysml` or `sympy` | KEEP-ID | (no change) **NOTE:** Package name `opensysml` (protected token). | +| R100 | docs/index.md | L3 | using SysML v2 and OpenSysML. | KEEP-STACK | (no change) **NOTE:** As README.md L3. | +| R101 | docs/index.md | L11 | Execute model analyses using OpenSysML and interpret the results | KEEP-STACK | (no change) **NOTE:** The analyses run on the runtime (chapters 1-10) and, for Chapter 8, sysml-toolkit, so a statement about the stack is true. Setup page defines the stack and components. | +| R102 | docs/references.md | L63 | ## OpenSysML | DEFINE | ## OpenSysML

OpenSysML ([opensysml.org](https://opensysml.org/)) is the open-source SysML v2 tool stack. This tutorial uses two of its components and names them by role: the OpenSysML runtime (Go; repository `Open-MBEE/OpenSysML`; Python package `opensysml`; pinned v0.9.0) and sysml-toolkit (Rust; `sysmlv2` binary; pinned v0.9.1). The OMG SysML v2 Pilot Implementation (EPL-2.0) is the conformance baseline and is always named as such. **NOTE:** Heading text stays 'OpenSysML' (the anchor #opensysml stays valid). Definition site per plan: stack defined once each on setup.md, references.md, reproducibility.md. Wording applies the convention paragraph verbatim in substance and holds whichever way the Pilot-membership question is answered. [C5]. Rows below give the two component entries. | +| R103 | docs/references.md | L65 | Open-MBEE/OpenSysML. | KEEP-ID | (no change to the repo name or URL; prefix a label, see replacement) **NOTE:** Repo name and URL are protected. Proposed: put 'The OpenSysML runtime: ' in front of the unchanged repo line, and add a parallel line after the runtime paragraph: 'sysml-toolkit: Open-MBEE/sysml-toolkit. ' followed by 'The Rust toolkit (`sysmlv2` binary, pinned v0.9.1) used for `sysmlv2 verify --solve` in Chapter 8 and for the cross-checks recorded in DEFERRED.md (D-014, D-017, D-019, D-023).' | +| R104 | docs/references.md | L67 | The Python library (`opensysml==0.9.0`) used to load, validate, evaluate, and query SysML v2 models in this tutorial. | RUNTIME | The OpenSysML runtime's Python package (`opensysml==0.9.0`), used to load, validate, evaluate, and query SysML v2 models in this tutorial. **NOTE:** Line matches /opensysml/ only through the package token (kept). The sentence is the runtime's description. [C1][C2]. | +| R105 | docs/references.md | L67 +dup | Gaps between the library's current API and the SysML v2 specification | RUNTIME | Gaps between the runtime's current API and the SysML v2 specification **NOTE:** Second sentence on the same line (row suffix b: not counted as a separate line in the machine check). Also covers sysml-toolkit gaps in DEFERRED.md, so the sentence 'are tracked in DEFERRED.md' is still true of both; optional extension: 'Gaps in either component and the SysML v2 specification'. [C1][C4]. | +| R106 | docs/reproducibility.md | L11 | the OpenSysML binary, | RUNTIME | the OpenSysML runtime binary, **NOTE:** Pinned runtime binary (line 17 says `v0.9.0`). [C1][C3] - first mention on the page. DEFINE row D2 (adjacent table) adds the stack definition on this page. | +| R107 | docs/reproducibility.md | L17 | **The OpenSysML binary** is pinned by version string (`v0.9.0` as of this tutorial) | RUNTIME | **The OpenSysML runtime binary** is pinned by version string (`v0.9.0` as of this tutorial) **NOTE:** Version attaches to the component named in the same bold phrase. [C1][C2]. | +| R108 | docs/reproducibility.md | L90 | Where OpenSysML or sysml-toolkit | BOTH | Where the OpenSysML runtime or sysml-toolkit **NOTE:** Both-tools contrast; the banned bare form 'OpenSysML or sysml-toolkit' is exactly what this row removes. [C1][C4]. | +| R109 | docs/setup.md | L27 | the OpenSysML binary this tutorial's Python package connects to | RUNTIME | the OpenSysML runtime binary this tutorial's Python package connects to **NOTE:** [C1][C3] - first mention on the page; the `opensysml` package connects to the runtime. DEFINE row D1 (adjacent table) adds the stack definition on this page. | +| R110 | docs/setup.md | L140 | **OpenSysML** (`opensysml`, installed automatically by | RUNTIME | **The OpenSysML runtime** (`opensysml`, installed automatically by **NOTE:** 'is the primary tool: it loads, validates, queries, and evaluates every model' is a runtime statement (D-024/D-025: toolkit's Python Session has no such role in chapters 1-7). [C1]. | +| R111 | docs/setup.md | L142 | also provisions a second OpenSysML binary, the | RUNTIME | also provisions a second OpenSysML runtime binary, the **NOTE:** The render-capable CLI is the runtime's `-render` CLI (D-037: 'OpenSysML's own `-render` CLI'). [C1]. | +| R112 | docs/setup.md | L147 | **sysml-toolkit** does one thing OpenSysML cannot yet: | FALSE-UNDER-STACK | **sysml-toolkit** does one thing the OpenSysML runtime cannot yet: **NOTE:** Self-contradictory under the stack reading (sysml-toolkit IS part of OpenSysML). Evidence: D-024 (DEFERRED.md:325 '(sysml-toolkit can)', retracted claim that no tool could; runtime's Python binding is evaluate-only) and D-025 (DEFERRED.md:333 'only the Rust CLI (`sysmlv2 verify --solve`) proves a constraint holds for all values'). [C1][C4]. | +| R113 | exercises/ch01/exercise.ipynb | `cell-01` line 1 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R114 | exercises/ch01/exercise.ipynb | `cell-01` line 3 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R115 | exercises/ch02/exercise.ipynb | `cell-1` line 1 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R116 | exercises/ch02/exercise.ipynb | `cell-1` line 4 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R117 | exercises/ch03/exercise.ipynb | `cell-1` line 1 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R118 | exercises/ch03/exercise.ipynb | `cell-1` line 4 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R119 | exercises/ch04/exercise.ipynb | `cell-1` line 1 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R120 | exercises/ch04/exercise.ipynb | `cell-1` line 4 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R121 | exercises/ch05/exercise.ipynb | `cell-1` line 1 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R122 | exercises/ch05/exercise.ipynb | `cell-1` line 4 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R123 | exercises/ch06/exercise.ipynb | `62d48965` line 1 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R124 | exercises/ch06/exercise.ipynb | `62d48965` line 4 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R125 | exercises/ch07/exercise.ipynb | `cell-0` line 48 | which OpenSysML loads silently but | FALSE-UNDER-STACK | which the OpenSysML runtime loads silently but **NOTE:** The next line says 'sysml-toolkit and the pilot both flag' it, so the sentence is a contrast whose first term is false under the stack reading. Evidence: decisions/log.md:876 ('not by OpenSysML (which loads it silently) but by sysml-toolkit ... `validateNamespaceDistinguishibility`'); no DEFERRED entry. [C1][C4][C3]. The Pilot naming on line 49 is adjacent row A4. | +| R126 | exercises/ch07/exercise.ipynb | `cell-1` line 1 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R127 | exercises/ch07/exercise.ipynb | `cell-1` line 4 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R128 | exercises/ch08/exercise.ipynb | `cell-01` line 1 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R129 | exercises/ch08/exercise.ipynb | `cell-01` line 4 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R130 | exercises/ch09/exercise.ipynb | `cell-03` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R131 | exercises/ch09/exercise.ipynb | `cell-03` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | +| R132 | exercises/ch10/exercise.ipynb | `cell-03` line 2 | import opensysml | KEEP-ID | (no change) `import opensysml` (protected token). | +| R133 | exercises/ch10/exercise.ipynb | `cell-03` line 5 | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) `opensysml.connect(version="v0.9.0")` (protected: API call and version literal; the page's markdown names the runtime). | + +## Adjacent rows (no `opensysml` on the line; outside the machine check) + +A = conflating or naming fixes found by the second pass; D = stack definition insertions (class DEFINE). Same columns; the last column carries replacement and note. + +| row | file | locator | quoted sentence | class | proposed replacement wording and note | +|---|---|---|---|---|---| +| A01 | chapters/ch04-functional-decomp/01-action-def-ffbd.ipynb | `cell-15` line 1 | working around a real tool inconsistency | RUNTIME | working around a real inconsistency in the OpenSysML runtime **NOTE:** 'the tool' here is the runtime (D-026). Same cell as row for L1 (cell-15), so 'the OpenSysML runtime' is used again rather than 'the runtime' only if the ACE wants each sentence self-contained; 'the runtime' is also acceptable. | +| A02 | chapters/ch07-execution/02-state-traces.ipynb | `cell-23` line 1 | The tool has a real hole here | FALSE-UNDER-STACK | The runtime has a real hole here **NOTE:** D-023: false for sysml-toolkit v0.9.1, which warns. Same cell as the cell-23 row. | +| A03 | chapters/ch10-traceability-signoff/01-traceability-graph.ipynb | `ed18ec1d` line 3 | a gap in the API-JSON export, which has no structural | UNPROBED | a gap in the OpenSysML runtime's API-JSON export, which has no structural **NOTE:** D-038 (DEFERRED.md:908-915): 'The API-JSON export OpenSysML v0.9.0 produces' - runtime only, no toolkit probe. Page's first prose mention is this cell (ed18ec1d precedes 02281b44), so the FULL name is used here and the 02281b44 row can then use 'the OpenSysML runtime' (still fine) or 'the runtime'. | +| A04 | exercises/ch07/exercise.ipynb | `cell-0` line 49 | and the pilot both flag | RUNTIME | and the OMG SysML v2 Pilot Implementation both flag **NOTE:** Pilot named in full on first mention (convention: 'the OMG SysML v2 Pilot Implementation (then "the pilot")'); first mention on this page. Not a runtime/toolkit claim; class is naming only. Counted with the L48 row's contrast. | +| A05 | chapters/ch07-execution/conclusion.md | L13 | the tool lets through silently | FALSE-UNDER-STACK | the runtime lets through silently **NOTE:** D-023: sysml-toolkit v0.9.1 does not let it through silently. Same sentence as the L13 row. | +| A06 | docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md | L60 | so the OMG pilot had no live binding | RUNTIME | so the OMG SysML v2 Pilot Implementation (the pilot) had no live binding **NOTE:** First mention of the Pilot in this file (convention: full name, then 'the pilot'). Naming only. | +| A07 | docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md | L277 | The OMG pilot *does* catch | RUNTIME | The pilot *does* catch **NOTE:** After first mention, 'the pilot'. Naming only. (Lines 61, 65, 69, 125, 278, 283 already say 'the pilot' or 'pilot'.) | +| A08 | exercises/ch10/exercise.ipynb | `a2487aff` line 19 | real OMG pilot did flag directly | RUNTIME | real OMG SysML v2 Pilot Implementation did flag directly **NOTE:** First mention of the Pilot on this page. Naming only. | +| A09 | docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md | L129 | That is a semantic | AMBIGUOUS | (line 129-130: 'no diagnostic in this toolchain computes') **NOTE:** Claim 'no diagnostic in this toolchain computes' covers the runtime, the pilot and sysml-toolkit, but sysml-toolkit was not probed on this line (DL-116 item 7). Options: (a) keep 'this toolchain' (no bare OpenSysML; true as a statement about what was probed); (b) 'no diagnostic of the OpenSysML runtime or the pilot computes'; (c) (a) plus '(sysml-toolkit was not probed on this line)'. Recommend (c). | +| A10 | chapters/ch08-checking/01-assert-constraint-def.ipynb | `cell-07` line 1 | because this toolchain's solver never reaches through to either one | AMBIGUOUS | because sysml-toolkit's solver never reaches through to either one **NOTE:** D-030/D-031 are probes of sysml-toolkit's `verify --solve` (Z3), so a component-naming reader would write sysml-toolkit. 'toolchain' is not the bare OpenSysML name, so the convention does not force a change. Options: (a) leave 'this toolchain' (b) 'sysml-toolkit' as shown. Recommend (b) where the sentence cites D-030/D-031, to keep the claim attached to the probed component. Not a lint-rule hit. | +| A11 | chapters/ch08-checking/conclusion.md | L13 | this toolchain does not compose two separately declared `assert constraint`s | AMBIGUOUS | sysml-toolkit does not compose two separately declared `assert constraint`s **NOTE:** D-030/D-031 are probes of sysml-toolkit's `verify --solve` (Z3), so a component-naming reader would write sysml-toolkit. 'toolchain' is not the bare OpenSysML name, so the convention does not force a change. Options: (a) leave 'this toolchain' (b) 'sysml-toolkit' as shown. Recommend (b) where the sentence cites D-030/D-031, to keep the claim attached to the probed component. Not a lint-rule hit. | +| A12 | chapters/ch08-checking/index.md | L11 | this toolchain does not compose separately declared constraints | AMBIGUOUS | sysml-toolkit does not compose separately declared constraints **NOTE:** D-030/D-031 are probes of sysml-toolkit's `verify --solve` (Z3), so a component-naming reader would write sysml-toolkit. 'toolchain' is not the bare OpenSysML name, so the convention does not force a change. Options: (a) leave 'this toolchain' (b) 'sysml-toolkit' as shown. Recommend (b) where the sentence cites D-030/D-031, to keep the claim attached to the probed component. Not a lint-rule hit. | +| A13 | exercises/ch08/exercise.ipynb | `cell-14` line 1 | D-030 says this toolchain's Z3 backend does not compose two separately | AMBIGUOUS | D-030 says sysml-toolkit's Z3 backend does not compose two separately **NOTE:** D-030/D-031 are probes of sysml-toolkit's `verify --solve` (Z3), so a component-naming reader would write sysml-toolkit. 'toolchain' is not the bare OpenSysML name, so the convention does not force a change. Options: (a) leave 'this toolchain' (b) 'sysml-toolkit' as shown. Recommend (b) where the sentence cites D-030/D-031, to keep the claim attached to the probed component. Not a lint-rule hit. | +| A14 | exercises/ch08/exercise.ipynb | `cell-16` line 5 | toolchain's Z3 backend never composes two separately declared `assert | AMBIGUOUS | sysml-toolkit's Z3 backend never composes two separately declared `assert **NOTE:** D-030/D-031 are probes of sysml-toolkit's `verify --solve` (Z3), so a component-naming reader would write sysml-toolkit. 'toolchain' is not the bare OpenSysML name, so the convention does not force a change. Options: (a) leave 'this toolchain' (b) 'sysml-toolkit' as shown. Recommend (b) where the sentence cites D-030/D-031, to keep the claim attached to the probed component. Not a lint-rule hit. | +| D1 | docs/setup.md | L136 | This tutorial models a system in SysML v2 and runs that model with Python. Two tools do that | DEFINE | OpenSysML ([opensysml.org](https://opensysml.org/)) is the open-source SysML v2 tool stack. This tutorial uses two of its components and names them by role: the OpenSysML runtime (Go; repository `Open-MBEE/OpenSysML`; Python package `opensysml`; pinned v0.9.0) and sysml-toolkit (Rust; `sysmlv2` binary; pinned v0.9.1). The OMG SysML v2 Pilot Implementation (EPL-2.0) is the conformance baseline and is always named as such. It is not one of the two tools the tutorial runs.

Then the existing text continues unchanged ('This tutorial models a system ... Two tools do that work ...'). Insert as a new paragraph immediately before line 136. **NOTE:** Definition site (setup page). The last sentence ('not one of the two tools the tutorial runs') is true whichever way the ACE/Z rules on Pilot membership in 'OpenSysML' (the Pilot is not run by the tutorial), and keeps the Pilot out of 'both tools'. [C5][C6] | +| D2 | docs/reproducibility.md | L12 | building the rendered book) the Node toolchain. | DEFINE | Insert after the sentence ending on line 12 a new short paragraph: 'OpenSysML ([opensysml.org](https://opensysml.org/)) is the open-source SysML v2 tool stack. This tutorial pins two of its components: the OpenSysML runtime (Go; `Open-MBEE/OpenSysML`; Python package `opensysml`; v0.9.0) and sysml-toolkit (Rust; `sysmlv2` binary; v0.9.1). The OMG SysML v2 Pilot Implementation (EPL-2.0) is the conformance baseline.' **NOTE:** Definition site (reproducibility page): the page pins versions, so each version attaches to a component here. Line 8-12 says 'four things exactly: ... the OpenSysML binary' (row for L11 renames it). [C2][C5][C6] | + +## AMBIGUOUS list (for the ACE) + +- A09 (docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md L129): options in the row note. +- A10 (chapters/ch08-checking/01-assert-constraint-def.ipynb `cell-07` line 1): options in the row note. +- A11 (chapters/ch08-checking/conclusion.md L13): options in the row note. +- A12 (chapters/ch08-checking/index.md L11): options in the row note. +- A13 (exercises/ch08/exercise.ipynb `cell-14` line 1): options in the row note. +- A14 (exercises/ch08/exercise.ipynb `cell-16` line 5): options in the row note. + +No row among the 132 matching lines is AMBIGUOUS. + +## Reviewed, no change proposed + +- `exercises/ch05/exercise.ipynb` `cell-0` line 10: 'filter ... some tools silently tolerate' (D-034: one tool, the runtime) names no tool; leave. D-034 body reads 'OpenSysML alone', which is DEFERRED text, protected. +- `chapters/ch09-coverage-sufficiency/02-evidence-completeness.ipynb` `0c378f59` and `exercises/ch09/exercise.ipynb` `cell-23`, `cell-27` say 'toolchain limits' (D-029 is the tutorial's wrapper parser; D-030/D-031 are sysml-toolkit): generic, not a bare OpenSysML use; leave. +- `chapters/ch08-checking/02-violation-witness.ipynb` `cell-16` 'nothing in this toolchain checks that the restated copy stays in sync' and `cell-10`, `3a3d9d15` (ch10) already name `sysml-toolkit`/`sysmlv2` correctly; leave. +- `docs/setup.md` lines 81-165 and `docs/reproducibility.md` lines 21-24 already name sysml-toolkit by its own name; leave. +- Other index/conclusion pages that say 'the tools' (ch05, ch08 index) without the name: no row. diff --git a/decisions/opensysml-terminology/inventory-b.md b/decisions/opensysml-terminology/inventory-b.md new file mode 100644 index 0000000..be9562d --- /dev/null +++ b/decisions/opensysml-terminology/inventory-b.md @@ -0,0 +1,339 @@ +# Inventory B: the binding surface (CONTRACT OT-1b) + +Read-only inventory for the OpenSysML terminology revision. Base commit `1649785` on branch `term/inv-b` (before this file). Convention and classes: `docs/superpowers/plans/2026-10-03-opensysml-terminology-plan.md` (normative convention, protected zones, OT-1b, OT-6); DL-116 (`decisions/log.md` line 1592); `decisions/opensysml-terminology/website-review.md`. No file other than this one was edited. + +## Method + +1. `grep -i opensysml` per file over the scoped surface; every matching **line** is one row (one row can carry several phrases on the same line; the replacement column then lists each, or the note does). +2. Each line was read in context and classified. A line whose only matches are protected tokens (package `opensysml`, `import opensysml`, `opensysml.` calls, `opensysml-api`/`opensysml-query`, `Open-MBEE/OpenSysML`, `OpenSysML#NNN`, `OPENSYSML_*`, URLs, file names such as `adapters/opensysml.py`) is auto-classified KEEP-ID (script: strip those tokens, test whether `opensysml` remains). Every line that retains a bare prose use was classified by hand. +3. Capability claims were classified against the DEFERRED entry that holds the probe results: FALSE-UNDER-STACK (sysml-toolkit v0.9.1 differs, so the bare-subject sentence is false if OpenSysML includes the toolkit), SAME (toolkit behaves the same), UNPROBED (the entry has no toolkit result). The class column carries the primary class, then `;` and the truth class. +4. The ACE paragraph for AGENTS.md 1.2/1.7/1.9 and the CLAUDE.md sources line is **not in the repository** (DL-116 Decision (4) says "wording in the ACE report, quoted in the plan"; the plan quotes only the normative convention). Those DEFINE rows are composed from the normative convention and marked "ACE wording to confirm". +5. Extra DEFERRED/skill sentences that conflate the runtime with sysml-toolkit without the token "OpenSysML" are listed in "Related bare-tool sentences" below; they are outside the line-count check. + +Class vocabulary used (plan classes plus one): KEEP-ID, KEEP-STACK, RUNTIME, TOOLKIT, BOTH, DEFINE, AMBIGUOUS, and **KEEP-BODY** (new sub-class of KEEP: a DEFERRED body sentence outside the gap sentence, where DL-116 (3) permits "runtime" insertions only in the gap sentence; the dated top note carries the reading rule; an optional edit is given in the note). + +## Machine check (lines matching /opensysml/i = rows) + +Command per file: `grep -ci opensysml FILE`. Result at base `1649785`: every file below has lines == rows, 0 duplicates listed, and the set of files with matches under `AGENTS.md CLAUDE.md DEFERRED.md .claude glossary` equals the set below (asserted by the generator). + +| file | lines matching | rows | of which in code fences | +|---|---|---|---| +| `AGENTS.md` | 6 | 6 | 0 | +| `CLAUDE.md` | 3 | 3 | 0 | +| `DEFERRED.md` | 69 | 69 | 0 | +| `glossary/sources/notes/reading-notes.md` | 1 | 1 | 0 | +| `.claude/agents/layer-auditor.md` | 1 | 1 | 0 | +| `.claude/agents/orchestrator.md` | 1 | 1 | 0 | +| `.claude/skills/ace-protocol/SKILL.md` | 2 | 2 | 0 | +| `.claude/skills/ace-protocol/z-model.md` | 1 | 1 | 0 | +| `.claude/skills/architecture-layers/SKILL.md` | 3 | 3 | 0 | +| `.claude/skills/opensysml-api/SKILL.md` | 20 | 20 | 7 | +| `.claude/skills/opensysml-query/SKILL.md` | 6 | 6 | 2 | +| `.claude/skills/orchestrator-protocol/SKILL.md` | 1 | 1 | 0 | +| `.claude/skills/skill-editor/SKILL.md` | 1 | 1 | 0 | +| `.claude/skills/sysml-diagrams/SKILL.md` | 7 | 7 | 0 | +| `.claude/skills/sysml-diagrams/references/recipes.md` | 6 | 6 | 0 | +| `.claude/skills/sysml-v2-toaster-model/SKILL.md` | 24 | 24 | 0 | +| `.claude/skills/toaster-recipe/SKILL.md` | 4 | 4 | 4 | +| `.claude/skills/tutorial-glossary/SKILL.md` | 1 | 1 | 0 | +| `.claude/skills/tutorial-style-guide/SKILL.md` | 2 | 2 | 1 | +| `.claude/skills/tutorial-supporting-pages/SKILL.md` | 1 | 1 | 0 | +| **total** | **160** | **160** | **14** | + +Count-only groups (not rowed; all KEEP, at base `1649785`, `grep -rIli` files / `grep -rIhi` lines): + +| group | files | lines | class | +|---|---|---|---| +| `decisions/**` (dated evidence, append-only; DL-116 reading rule) | 93 | 451 | KEEP | +| `docs/superpowers/**` (plans/specs; protected) | 14 | 160 | KEEP | +| `src/**` (protected, incl. comments) | 8 | 43 | KEEP | +| `scripts/**` (protected, incl. comments) | 9 | 42 | KEEP | +| `tests/**` (protected) | 18 | 86 | KEEP | + +(`decisions/**` includes the plan-adjacent `decisions/opensysml-terminology/website-review.md`; adding this inventory file changes the `decisions/**` counts by +1 file; and `docs/superpowers/plans/2026-10-03-opensysml-terminology-plan.md` carries 23 lines of the 160.) `glossary/**` has exactly one match, rowed above (`glossary/sources/notes/reading-notes.md:35`); `glossary/README.md`, `docs/glossary.md` and `glossary/definitions/*.ttl` have **0** mentions of OpenSysML (the plan's expectation of ttl mentions did not materialise: nothing to KEEP or fix). `.claude/agents/*.md`: 2 matches, both `opensysml-query` (KEEP-ID). + +## Class totals (primary class) + +| class | rows | +|---|---| +| KEEP-ID | 94 | +| RUNTIME | 50 | +| DEFINE | 5 | +| KEEP-BODY | 5 | +| AMBIGUOUS | 3 | +| BOTH | 2 | +| KEEP-STACK | 1 | +| **total** | **160** | + +Truth classes (capability claims, any primary class): FALSE-UNDER-STACK 16, SAME 11, UNPROBED 27. + +### Per-file primary-class counts + +| file | AMBIGUOUS | BOTH | DEFINE | KEEP-BODY | KEEP-ID | KEEP-STACK | RUNTIME | +|---|---|---|---|---|---|---|---| +| `AGENTS.md` | 0 | 0 | 4 | 0 | 2 | 0 | 0 | +| `CLAUDE.md` | 0 | 0 | 1 | 0 | 2 | 0 | 0 | +| `DEFERRED.md` | 1 | 1 | 0 | 5 | 35 | 0 | 27 | +| `glossary/sources/notes/reading-notes.md` | 0 | 0 | 0 | 0 | 0 | 0 | 1 | +| `.claude/agents/layer-auditor.md` | 0 | 0 | 0 | 0 | 1 | 0 | 0 | +| `.claude/agents/orchestrator.md` | 0 | 0 | 0 | 0 | 1 | 0 | 0 | +| `.claude/skills/ace-protocol/SKILL.md` | 1 | 0 | 0 | 0 | 0 | 0 | 1 | +| `.claude/skills/ace-protocol/z-model.md` | 1 | 0 | 0 | 0 | 0 | 0 | 0 | +| `.claude/skills/architecture-layers/SKILL.md` | 0 | 0 | 0 | 0 | 3 | 0 | 0 | +| `.claude/skills/opensysml-api/SKILL.md` | 0 | 0 | 0 | 0 | 19 | 0 | 1 | +| `.claude/skills/opensysml-query/SKILL.md` | 0 | 1 | 0 | 0 | 3 | 0 | 2 | +| `.claude/skills/orchestrator-protocol/SKILL.md` | 0 | 0 | 0 | 0 | 1 | 0 | 0 | +| `.claude/skills/skill-editor/SKILL.md` | 0 | 0 | 0 | 0 | 1 | 0 | 0 | +| `.claude/skills/sysml-diagrams/SKILL.md` | 0 | 0 | 0 | 0 | 0 | 0 | 7 | +| `.claude/skills/sysml-diagrams/references/recipes.md` | 0 | 0 | 0 | 0 | 0 | 0 | 6 | +| `.claude/skills/sysml-v2-toaster-model/SKILL.md` | 0 | 0 | 0 | 0 | 21 | 0 | 3 | +| `.claude/skills/toaster-recipe/SKILL.md` | 0 | 0 | 0 | 0 | 4 | 0 | 0 | +| `.claude/skills/tutorial-glossary/SKILL.md` | 0 | 0 | 0 | 0 | 0 | 0 | 1 | +| `.claude/skills/tutorial-style-guide/SKILL.md` | 0 | 0 | 0 | 0 | 1 | 0 | 1 | +| `.claude/skills/tutorial-supporting-pages/SKILL.md` | 0 | 0 | 0 | 0 | 0 | 1 | 0 | + +## Skills: prose, fences, frontmatter, executed snippets + +There are **15** skill directories (the plan and CLAUDE.md say 14; `ace-protocol`, `architecture-layers`, `myst-publication`, `opensysml-api`, `opensysml-query`, `orchestrator-protocol`, `skill-editor`, `sysml-diagrams`, `sysml-v2-toaster-model`, `toaster-recipe`, `toaster-review-protocol`, `tutorial-glossary`, `tutorial-style-guide`, `tutorial-supporting-pages`, `user-testing`). Reference files: `ace-protocol/z-model.md`, `z-principles.md`, `architecture-layers/example-layers.sysml`, `sysml-diagrams/references/recipes.md`. + +| file | total lines | lines inside code fences (incl. fence lines) | prose lines | OpenSysML lines | in fences | in prose | +|---|---|---|---|---|---|---| +| `.claude/skills/ace-protocol/SKILL.md` | 159 | 25 | 134 | 2 | 0 | 2 | +| `.claude/skills/ace-protocol/z-model.md` | 39 | 0 | 39 | 1 | 0 | 1 | +| `.claude/skills/ace-protocol/z-principles.md` | 62 | 0 | 62 | 0 | 0 | 0 | +| `.claude/skills/architecture-layers/SKILL.md` | 96 | 0 | 96 | 3 | 0 | 3 | +| `.claude/skills/architecture-layers/example-layers.sysml` | 45 | 0 | 45 | 0 | 0 | 0 | +| `.claude/skills/myst-publication/SKILL.md` | 75 | 25 | 50 | 0 | 0 | 0 | +| `.claude/skills/opensysml-api/SKILL.md` | 155 | 76 | 79 | 20 | 7 | 13 | +| `.claude/skills/opensysml-query/SKILL.md` | 182 | 111 | 71 | 6 | 2 | 4 | +| `.claude/skills/orchestrator-protocol/SKILL.md` | 72 | 0 | 72 | 1 | 0 | 1 | +| `.claude/skills/skill-editor/SKILL.md` | 65 | 0 | 65 | 1 | 0 | 1 | +| `.claude/skills/sysml-diagrams/SKILL.md` | 79 | 0 | 79 | 7 | 0 | 7 | +| `.claude/skills/sysml-diagrams/references/recipes.md` | 167 | 52 | 115 | 6 | 0 | 6 | +| `.claude/skills/sysml-v2-toaster-model/SKILL.md` | 192 | 29 | 163 | 24 | 0 | 24 | +| `.claude/skills/toaster-recipe/SKILL.md` | 229 | 55 | 174 | 4 | 4 | 0 | +| `.claude/skills/toaster-review-protocol/SKILL.md` | 193 | 90 | 103 | 0 | 0 | 0 | +| `.claude/skills/tutorial-glossary/SKILL.md` | 61 | 8 | 53 | 1 | 0 | 1 | +| `.claude/skills/tutorial-style-guide/SKILL.md` | 121 | 11 | 110 | 2 | 1 | 1 | +| `.claude/skills/tutorial-supporting-pages/SKILL.md` | 65 | 12 | 53 | 1 | 0 | 1 | +| `.claude/skills/user-testing/SKILL.md` | 160 | 28 | 132 | 0 | 0 | 0 | + +(Fence counts include the ``` delimiter lines.) + +### Frontmatter `name:` / `description:` lines (line 2 / line 3 of each SKILL.md) + +`name:` values equal the directory names and must not change (plan: protected; checker rule f). Lines carrying OpenSysML: `opensysml-api` line 2 (`name: opensysml-api`, KEEP-ID) and line 3 (`description: opensysml v0.9.0 interface — ...`, KEEP-ID by plan OT-6); `opensysml-query` line 2 (`name:`, KEEP-ID) and line 3 (`description: ... with OpenSysML v0.9.0 ...`, RUNTIME, DL-116 item 3 says it becomes "the OpenSysML runtime v0.9.0"). The other 13 skills' `name:`/`description:` lines carry no match. Note `opensysml-query` line 3 ends "Snippets are executed by tests/test_skill_snippets.py."; that clause stays. + +### Snippets executed by `tests/test_skill_snippets.py` (skill edits cannot touch these) + +1. `.claude/skills/architecture-layers/example-layers.sysml` (whole file, 45 lines, loaded by `test_architecture_layers_example_loads` and `test_opensysml_query_perform_recipe_on_layers_example`). Not a SKILL.md; it has no OpenSysML mention. +2. Every ```` ```python ```` block in `.claude/skills/opensysml-query/SKILL.md` (`python_blocks()` matches a fence line equal to ```` ```python ````), executed in order by `test_opensysml_query_recipes_run_against_ch08`. There are 6 blocks at lines **22-45** (setup; block 0, includes `import opensysml` line 26 and `opensysml.connect(version="v0.9.0")` line 28), **49-55** (block 1), **61-82** (block 2), **88-107** (block 3), **113-126** (block 4, the perform recipe, re-run by `test_opensysml_query_perform_recipe_on_layers_example`), **134-157** (block 5, port-type recipe; used by `test_port_type_conformance_recipe_catches_mismatch_and_accepts_specialization`, blocks 0-2, 3 (split at `flows = `), 5 (split at `assert port_type_mismatches()`)). Everything between those line ranges (including prose lines 129-133 and the paragraph at line 132) is editable prose; the fences themselves and the code inside them are not. +3. No other skill file has code executed by that test. Other fences (opensysml-api lines 11-15, 28, 125, 132; toaster-recipe 55-87; tutorial-style-guide 89; sysml-diagrams/references/recipes.md) are documentation snippets; they are still protected by the plan (checker rule e compares all fenced blocks), but only items 1 and 2 are executed. + +## DEFERRED.md: headings and the proposed dated note + +DEFERRED.md has 69 matching lines: **13 heading lines (KEEP-ID, byte-identical)** — D-017 (232), D-019 (260), D-020 (269), D-023 (296), D-024 (325), D-026 (342), D-032 (837), D-033 (856), D-034 (865), D-035 (876), D-036 (885), D-037 (894), D-038 (908) — and 56 body lines. Note that headings `## D-004` appears twice in the file (lines 42 and 172); neither carries OpenSysML. Headings are not edited; their GitHub anchors are linked from published pages. + +Proposed text of the ONE dated terminology note for the top of DEFERRED.md (insert after the `# Deferred work` heading, before `## D-001`; no heading is touched): + +> **Terminology note (2026-10-03, DL-116).** OpenSysML (opensysml.org) is the open-source SysML v2 tool stack; this tutorial uses two of its components, the OpenSysML runtime (Go; `Open-MBEE/OpenSysML`; Python package `opensysml`; pinned v0.9.0) and sysml-toolkit (Rust; `sysmlv2`; pinned v0.9.1). Entries written before this note use a bare "OpenSysML" (and "opensysml") to mean **the OpenSysML runtime**; read them that way. Where an entry records a different result for sysml-toolkit, the entry's own text governs: for D-017, D-019, D-023, D-034, D-035 and D-036 the heading names the runtime's behavior only, and the body states what sysml-toolkit v0.9.1 does. Headings are unchanged because their anchors are linked from published pages. + +This wording is composed from DL-116 and the convention; the ACE should confirm. It deliberately lists D-035 (heading says "OpenSysML and the pilot both reject it", but sysml-toolkit warns) in addition to the five entries DL-116 item 7 names; D-037 and D-038 are UNPROBED (runtime-specific CLI/export) and need no listing. + +## Truth-class findings + +**FALSE-UNDER-STACK** (bare-subject runtime claim contradicted by the toolkit result in the cited entry): +- `AGENTS.md:127` (DEFINE): D-017 line 236: "sysml-toolkit v0 +- `DEFERRED.md:232` (KEEP-ID): Heading text is false under the umbrella: line 236 "sysml-toolkit v0 +- `DEFERRED.md:260` (KEEP-ID): Line 262: "sysml-toolkit v0 +- `DEFERRED.md:262` (RUNTIME): Gap sentence; the following sentence already names sysml-toolkit v0 +- `DEFERRED.md:296` (KEEP-ID): Line 298: "sysml-toolkit v0 +- `DEFERRED.md:323` (RUNTIME): "OpenSysML itself" is a banned pattern; D-023 line 298 shows sysml-toolkit v0 +- `DEFERRED.md:865` (KEEP-ID): Line 867: "sysml-toolkit v0 +- `DEFERRED.md:867` (RUNTIME): Same line also: "only OpenSysML silently tolerates it" -> "only the OpenSysML runtime silently tolerates it"; "OpenSysML alone is the outlier" -> "the OpenSysML runtime is the outlier" (drops the bann +- `DEFERRED.md:872` (RUNTIME): Banned pattern "OpenSysML alone"; edit warranted even though a Resolution line +- `DEFERRED.md:876` (KEEP-ID): Heading says OpenSysML rejects it; line 878: "sysml-toolkit v0 +- `DEFERRED.md:878` (RUNTIME): Same line also: "there, OpenSysML alone was the outlier tolerating something the other two correctly rejected" -> "there, the OpenSysML runtime was the outlier +- `DEFERRED.md:880` (RUNTIME): Workaround line; the sentence is exactly the one contrasting with toolkit behavior, so the bare name would now include the toolkit +- `DEFERRED.md:881` (RUNTIME): Resolution line, but it asserts a behavior the toolkit lacks (D-035 line 878) +- `DEFERRED.md:885` (KEEP-ID): Line 887: "sysml-toolkit v0 +- `DEFERRED.md:887` (RUNTIME): Same line also: "likely OpenSysML being too permissive" -> "likely the OpenSysML runtime being too permissive" +- `DEFERRED.md:890` (RUNTIME): Resolution line; the contrast "OpenSysML (not sysml-toolkit + +**AMBIGUOUS** (needs the ACE; options in the row table): `DEFERRED.md:852`, `.claude/skills/ace-protocol/SKILL.md:63`, `.claude/skills/ace-protocol/z-model.md:21`. Also pending Z (not a row): whether the OMG SysML v2 Pilot Implementation is inside "OpenSysML" (DL-116 item 2, default out; the AGENTS.md 1.2 replacement holds under either answer). + +Items DL-116 item 7 did not list, found here: D-035 heading (FALSE-UNDER-STACK), D-037 and D-038 headings/bodies (UNPROBED), D-026/D-027 bodies (UNPROBED), D-003/D-004 (UNPROBED). D-024 and D-020/D-032 are SAME or already contrast both tools. + +## Related bare-tool sentences (no "OpenSysML" token; outside the count) + +These say "the tool" or similar about a runtime-only or both-tools behavior; the convention ("a claim true of one component names that component") applies if a later pass widens scope: +- `.claude/skills/architecture-layers/SKILL.md:47` "Mismatched port types are **not diagnosed** by the tool (gap G4, ...)" and `:71` "The tool will not diagnose it." (D-014: SAME; both tools accept it). +- `DEFERRED.md` D-019 body (line 263): "The tool rejects `perform ToastBread;` naming a definition (G2)" (runtime behavior; sentence already inside the D-019 gap text). +- `AGENTS.md:147` Gap-tracking rule "where the tool is at fault" (generic; no change). + +## Row table + +Columns: row id; file; locator (`line` of the file; `F` = inside a code fence); quoted text (verbatim from that line; multi-line quote joined with ⏎); class (`primary; truth`); proposed replacement (with the convention rule it applies in the note column, merged into the replacement cell as "Note:"). + +| row | file | locator | quoted text (verbatim) | class | proposed replacement | +|---|---|---|---|---|---| +| B-001 | `AGENTS.md` | 43 | **Toolchain, not sources.** OpenSysML, sysml-toolkit, the Pilot Implementation and the like execute the specs. | DEFINE | **Toolchain, not sources.** OpenSysML (opensysml.org), the open-source SysML v2 tool stack, is toolchain: this tutorial uses two of its components, named by role, **the OpenSysML runtime** (Go; repo `Open-MBEE/OpenSysML`; Python package `opensysml`; pinned v0.9.0) and **sysml-toolkit** (Rust; `sysmlv2` binary; pinned v0.9.1). The OMG SysML v2 Pilot Implementation (EPL-2.0) is the conformance baseline and is always named as such. They execute the specs. Note: ACE wording to confirm: the exact DL-116 ACE paragraph is not in the repo (only the Decision (4) summary and the plan's normative convention). This replacement is composed from the convention; the sentence is written so it holds whether the Pilot is in or out of OpenSysML (DL-116 item 2, pending Z). Rest of the paragraph ("They are cited only to flag a spec gap (§1.9), never to define a term.") unchanged. Part 1 governs: orchestrator reads final text before merge. | +| B-002 | `AGENTS.md` | 127 | OpenSysML v0.9.0 does not resolve `import` across separately loaded sources. | DEFINE; FALSE-UNDER-STACK | The OpenSysML runtime v0.9.0 does not resolve `import` across separately loaded sources (sysml-toolkit v0.9.1 does, D-017). Note: D-017 line 236: "sysml-toolkit v0.9.1 resolves the same imports across files (`sysmlv2 check base.sysml chapter.sysml`, `Session.from_files`)". Convention: contrasts name both components. ACE wording to confirm (1.7 bullet wording not in repo). The rest of the bullet (assemble by concatenation, gap G7) unchanged. | +| B-003 | `AGENTS.md` | 137 | chapter notebook (recipes and limits are in the `opensysml-query` skill): | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-004 | `AGENTS.md` | 139 | 1. `model.query()` in OpenSysML: the API Query | DEFINE | 1. `model.query()` in the OpenSysML runtime: the API Query Note: Convention: a claim about one component's API names it. `model.query()` is the runtime's Python API; the sysml-toolkit binding is the fourth surface (AGENTS.md line 143, no change). ACE wording to confirm. | +| B-005 | `AGENTS.md` | 145 | (OpenSysML v0.9.0 accepts a power port connected to a fuel port; the KerML 1.1 spec searched has no validation constraint for it) | DEFINE; SAME | (the OpenSysML runtime v0.9.0 and sysml-toolkit v0.9.1 both accept a power port connected to a fuel port; the KerML 1.1 spec searched has no validation constraint for it) Note: D-014 lines 192-194: runtime accepts with ok=True; "sysml-toolkit v0.9.1 `check` and `lint` (default rules) accept it too". Truth class SAME. Convention: versions attach to component names; DL-116 item 7 default is runtime-only wording ("the OpenSysML runtime v0.9.0 accepts"); the both-components form is offered because D-014 probed both. ACE wording to confirm which. | +| B-006 | `AGENTS.md` | 266 | Never invent opensysml API shapes not in the adapter file. | KEEP-ID | (no change) Note: Lowercase `opensysml` is the Python package name (protected token class: package names). | +| B-007 | `CLAUDE.md` | 19 | policy only), Douglas (story and the toaster example). OpenSysML and sysml-toolkit ⏎ are toolchain, cited only to flag spec gaps. | DEFINE | policy only), Douglas (story and the toaster example). The OpenSysML runtime and sysml-toolkit (components of OpenSysML, opensysml.org) ⏎ are toolchain, cited only to flag spec gaps. Note: Spans CLAUDE.md lines 19-20 (match is on line 19). ACE wording to confirm (the "CLAUDE.md sources line" wording is not in the repo). Alternative if the stack is to be named: "OpenSysML (its runtime and sysml-toolkit) and the Pilot Implementation are toolchain". | +| B-008 | `CLAUDE.md` | 26 | - opensysml-query — the three query surfaces, tested recipes, | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-009 | `CLAUDE.md` | 28 | - opensysml-api — opensysml v0.9.0 interface (A2, A5) | KEEP-ID | (no change) Note: Skill list entry: skill name and its frontmatter description (unchanged per DL-116/plan OT-6: description uses the package name). Optional: none. | +| B-010 | `DEFERRED.md` | 5 | urn `SatisfyRequirementUsage` elements (Open-MBEE/OpenSysML#TBD). | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-011 | `DEFERRED.md` | 12 | ips, update `get_satisfy_relationships()` and the opensysml-api skill. | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-012 | `DEFERRED.md` | 13 | **Upstream issue:** Open-MBEE/OpenSysML#590 | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-013 | `DEFERRED.md` | 29 | `ISQ::DimensionOneValue` is not defined in opensysml v0.9.0 (Open-MBEE/OpenSysML#TBD). | RUNTIME; UNPROBED | `ISQ::DimensionOneValue` is not defined in the OpenSysML runtime v0.9.0 (`opensysml`; Open-MBEE/OpenSysML#TBD). Note: Gap sentence. D-003 has no sysml-toolkit result, so UNPROBED. Version attaches to the component name (convention). | +| B-014 | `DEFERRED.md` | 35 | **Resolution:** When `ISQ::DimensionOneValue` and `SI::one` ship in opensysml: | RUNTIME; UNPROBED | **Resolution:** When `ISQ::DimensionOneValue` and `SI::one` ship in the OpenSysML runtime: Note: Resolution line restating the gap; optional. If the Resolution rule (gap sentence only) is applied strictly, class becomes KEEP-BODY. | +| B-015 | `DEFERRED.md` | 39 | **Upstream issue:** Open-MBEE/OpenSysML#594 | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-016 | `DEFERRED.md` | 50 | **Upstream issue:** Open-MBEE/OpenSysML#595 | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-017 | `DEFERRED.md` | 62 | **Upstream issue:** Open-MBEE/OpenSysML#596 | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-018 | `DEFERRED.md` | 73 | **Upstream issue:** Open-MBEE/OpenSysML#597 | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-019 | `DEFERRED.md` | 85 | **Upstream issue:** Open-MBEE/OpenSysML#598 | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-020 | `DEFERRED.md` | 97 | **Upstream issue:** Open-MBEE/OpenSysML#599 | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-021 | `DEFERRED.md` | 111 | **Upstream issue:** Open-MBEE/OpenSysML#601 | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-022 | `DEFERRED.md` | 123 | **Upstream issue:** Open-MBEE/OpenSysML#602 | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-023 | `DEFERRED.md` | 139 | **Upstream issue:** Open-MBEE/OpenSysML#603 | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-024 | `DEFERRED.md` | 154 | **Upstream issue:** Open-MBEE/OpenSysML#604 | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-025 | `DEFERRED.md` | 169 | **Upstream issue:** Open-MBEE/OpenSysML#605 | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-026 | `DEFERRED.md` | 179 | In OpenSysML v0.9.0 this raises "expected a body member" and `ok=False`. | RUNTIME; UNPROBED | In the OpenSysML runtime v0.9.0 this raises "expected a body member" and `ok=False`. Note: D-004 (VerificationMethodKind): no toolkit result in the entry. Gap sentence. | +| B-027 | `DEFERRED.md` | 187 | **Upstream issue:** Open-MBEE/OpenSysML#608 | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-028 | `DEFERRED.md` | 192 | OpenSysML v0.9.0 accepts `connect outlet.o to torch.fuelIn` | RUNTIME; SAME | The OpenSysML runtime v0.9.0 accepts `connect outlet.o to torch.fuelIn` Note: D-014 line 193: "sysml-toolkit v0.9.1 `check` and `lint` (default rules) accept it too". Minimal insertion of "The ... runtime" in the gap sentence; the next sentence already names the toolkit. | +| B-029 | `DEFERRED.md` | 199 | ort_type_mismatches(model)` (tested; recipe 5 in `opensysml-query`), applied from the | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-030 | `DEFERRED.md` | 207 | Probed 2026-09-26 (OpenSysML v0.9.0): named `allocation`, `connection` and `flow` are visible to `model.query()`; | RUNTIME; UNPROBED | Probed 2026-09-26 (the OpenSysML runtime v0.9.0): named `allocation`, `connection` and `flow` are visible to `model.query()`; Note: D-015 is about the runtime's `model.query()` API; no toolkit comparison in the entry. | +| B-031 | `DEFERRED.md` | 214 | Related: OpenSysML#590 (closed 2026-09-26). A maintainer said anonymous elemen | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-032 | `DEFERRED.md` | 218 | **Upstream issue:** filed 2026-09-27, [OpenSysML#643](https://github.com/Open-MBEE/OpenSysML/issues/643) | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-033 | `DEFERRED.md` | 229 | **Upstream issue:** filed 2026-09-27, [OpenSysML#644](https://github.com/Open-MBEE/OpenSysML/issues/644) | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-034 | `DEFERRED.md` | 232 | ## D-017: `import` across separately loaded sources does not resolve in OpenSysML (gap G7) | KEEP-ID; FALSE-UNDER-STACK | (heading protected; byte-identical) Note: Heading text is false under the umbrella: line 236 "sysml-toolkit v0.9.1 resolves the same imports across files". Reading rule in the dated top note must cover it. DL-116 item 7 lists D-017. | +| B-035 | `DEFERRED.md` | 244 | Re-test when OpenSysML changes. | KEEP-BODY | (no change) Note: Resolution sentence. Option if edited: "Re-test when the OpenSysML runtime changes." | +| B-036 | `DEFERRED.md` | 245 | **Upstream issue:** filed 2026-09-27, [OpenSysML#645](https://github.com/Open-MBEE/OpenSysML/issues/645) (fi | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-037 | `DEFERRED.md` | 260 | ## D-019: OpenSysML accepts an allocate between definitions (language conformance hole) | KEEP-ID; FALSE-UNDER-STACK | (heading protected; byte-identical) Note: Line 262: "sysml-toolkit v0.9.1 with the standard library rejects it". DL-116 item 7 lists D-019. | +| B-038 | `DEFERRED.md` | 262 | OpenSysML v0.9.0 loads `allocate ApplyHeat to HeatingSystem;` | RUNTIME; FALSE-UNDER-STACK | The OpenSysML runtime v0.9.0 loads `allocate ApplyHeat to HeatingSystem;` Note: Gap sentence; the following sentence already names sysml-toolkit v0.9.1 rejecting it. | +| B-039 | `DEFERRED.md` | 265 | **Resolution:** upstream fix in OpenSysML; re-test with `scripts/probes`. | KEEP-BODY | (no change) Note: Resolution/non-gap sentence; DL-116 (3) permits "runtime" only in the sentence stating the gap; the dated top note carries the reading rule. Option if edited: "upstream fix in the OpenSysML runtime". | +| B-040 | `DEFERRED.md` | 266 | **Upstream issue:** filed 2026-09-27, [OpenSysML#646](https://github.com/Open-MBEE/OpenSysML/issues/646) (ci | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-041 | `DEFERRED.md` | 269 | ## D-020: Neither OpenSysML nor sysml-toolkit reports a part usage typed only by an item definition | KEEP-ID; SAME | (heading protected; byte-identical) Note: Line 271: runtime ok=True and "passes sysml-toolkit v0.9.1 `check --lib`". Heading uses the banned pattern "Neither OpenSysML nor" but is protected; true as stated only if OpenSysML is read as the runtime (reading rule). DL-116 item 7: D-020 is toolkit-same. | +| B-042 | `DEFERRED.md` | 271 | loads with `ok=True` in OpenSysML v0.9.0 and passes sysml-toolkit v0.9.1 `check --lib` | RUNTIME; SAME | loads with `ok=True` in the OpenSysML runtime v0.9.0 and passes sysml-toolkit v0.9.1 `check --lib` Note: Gap sentence; contrast already names both tools. | +| B-043 | `DEFERRED.md` | 275 | **Upstream issue:** filed 2026-09-27, [OpenSysML#647](https://github.com/Open-MBEE/OpenSysML/issues/647) and | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-044 | `DEFERRED.md` | 296 | ## D-023: OpenSysML does not resolve state-machine transition trigger names | KEEP-ID; FALSE-UNDER-STACK | (heading protected; byte-identical) Note: Line 298: "sysml-toolkit v0.9.1 does resolve these names and warns on broken references". DL-116 item 7 lists D-023. | +| B-045 | `DEFERRED.md` | 323 | loads with `ok=True` (OpenSysML itself does not catch it) | RUNTIME; FALSE-UNDER-STACK | loads with `ok=True` (the OpenSysML runtime does not catch it) Note: "OpenSysML itself" is a banned pattern; D-023 line 298 shows sysml-toolkit v0.9.1 resolves trigger names. Same-entry note, gap sentence. | +| B-046 | `DEFERRED.md` | 325 | ## D-024: RETRACTED — OpenSysML v0.9.0's Python binding cannot ask a "holds" question (sysml-toolkit can) | KEEP-ID; SAME | (heading protected; byte-identical) Note: Heading already contrasts the runtime's binding with sysml-toolkit; true as stated under the convention (bare name = runtime in this entry). | +| B-047 | `DEFERRED.md` | 327 | it checked only OpenSysML. `sysmlv2 verify --solve` | RUNTIME; SAME | it checked only the OpenSysML runtime. `sysmlv2 verify --solve` Note: Same line also has "OpenSysML's own gap (its Python binding is evaluate-only) still stands as a fact" -> "the OpenSysML runtime's own gap (its Python binding is evaluate-only)". Truth: line 327 itself says sysml-toolkit does `verify --solve`; the original claim "no tool in the toolchain" was the retracted false-under-stack-style error. Two replacements, one row. | +| B-048 | `DEFERRED.md` | 336 | (b) OpenSysML's Python binding gains a way to pose a holds/outcomes question to its own `check`/`smt` engines (D-024's original ask, still true as a fact about OpenSysML even though it is no longer blocking) | RUNTIME; SAME | (b) the OpenSysML runtime's Python binding gains a way to pose a holds/outcomes question to its own `check`/`smt` engines (D-024's original ask, still true as a fact about the OpenSysML runtime even though it is no longer blocking) Note: Resolution line, but it restates a capability gap and (a) in the same sentence names sysml-toolkit; the bare name would now read as including the toolkit. Optional; KEEP-BODY if the strict rule is applied. | +| B-049 | `DEFERRED.md` | 342 | ## D-026: OpenSysML treats an implicit and an explicit-but-spec-identical `[0..*]` multiplicity differently for an `in` parameter reachable through a nested action step | KEEP-ID; UNPROBED | (heading protected; byte-identical) Note: No sysml-toolkit result anywhere in D-026 (lines 342-546, grep toolkit: none). UNPROBED. | +| B-050 | `DEFERRED.md` | 346 | OpenSysML v0.9.0 can evaluate the model. | RUNTIME; UNPROBED | The OpenSysML runtime v0.9.0 can evaluate the model. Note: Line starts "OpenSysML v0.9.0 can evaluate the model." Gap sentence. | +| B-051 | `DEFERRED.md` | 408 | **The mechanism this evidence actually supports:** OpenSysML v0.9.0 gives a | RUNTIME; UNPROBED | **The mechanism this evidence actually supports:** The OpenSysML runtime v0.9.0 gives a Note: Wrapped sentence (continues on 409). Replace "OpenSysML v0.9.0 gives a" with "the OpenSysML runtime v0.9.0 gives a" (lower-case after the bold lead-in). | +| B-052 | `DEFERRED.md` | 581 | since two OpenSysML surfaces (load, and the API-JSON conversion this | RUNTIME; UNPROBED | since two OpenSysML runtime surfaces (load, and the API-JSON conversion this Note: D-027 (lines 547-599): load and API-JSON conversion are runtime surfaces; no toolkit result. | +| B-053 | `DEFERRED.md` | 837 | ## D-032: OpenSysML accepts an allocate connector end that reaches into another type's nested feature by qualified name, with no featuring context to make it accessible (GUARDED) | KEEP-ID; SAME | (heading protected; byte-identical) Note: Line 839: "sysml-toolkit v0.9.1 accepts it too". DL-116 item 7: toolkit-same. | +| B-054 | `DEFERRED.md` | 839 | OpenSysML v0.9.0 loads `allocate to ::;` | RUNTIME; SAME | The OpenSysML runtime v0.9.0 loads `allocate to ::;` Note: Gap sentence; same sentence continues "sysml-toolkit v0.9.1 accepts it too". | +| B-055 | `DEFERRED.md` | 845 | OpenSysML accepted it with zero findings, the pilot rejected it. | RUNTIME; UNPROBED | the OpenSysML runtime accepted it with zero findings, the pilot rejected it. Note: Probe of a specific fixture against the runtime and the pilot only; no toolkit result for this fixture. Lower-case "the" mid-sentence after the semicolon. | +| B-056 | `DEFERRED.md` | 849 | The underlying OpenSysML/sysml-toolkit acceptance-without-diagnostic gap itself remains open upstream | BOTH; SAME | The underlying acceptance-without-diagnostic gap in the OpenSysML runtime and sysml-toolkit itself remains open upstream Note: D-032 line 839 shows both accept. "OpenSysML/sysml-toolkit" reads as parent/child under the new meaning. Convention: contrasts/conjunctions name both components. Edit sits in a status sentence, not the heading. | +| B-057 | `DEFERRED.md` | 852 | **Resolution:** upstream fix in both OpenSysML and sysml-toolkit; re-test with `scripts/probes`. | AMBIGUOUS | Option A (leave, KEEP-BODY): the top note's reading rule says a bare name before DL-116 means the runtime. Option B: "**Resolution:** upstream fix in both the OpenSysML runtime and sysml-toolkit; re-test with `scripts/probes`." Note: Banned pattern "OpenSysML and sysml-toolkit" in a Resolution line (outside the gap sentence, so outside DL-116 (3) permission). Needs the ACE. | +| B-058 | `DEFERRED.md` | 856 | ## D-033: OpenSysML's `eval()` does not simplify a product against a `DimensionOneUnit` factor, or fold an SI base-unit expansion back into its derived-unit symbol | KEEP-ID; UNPROBED | (heading protected; byte-identical) Note: No sysml-toolkit comparison in D-033 (lines 856-863). `eval()` is a runtime Python API. UNPROBED (DL-116 item 7 agrees). | +| B-059 | `DEFERRED.md` | 858 | OpenSysML DOES normally resolve a `kg⋅m²⋅s⁻²` base-unit product | RUNTIME; UNPROBED | The OpenSysML runtime DOES normally resolve a `kg⋅m²⋅s⁻²` base-unit product Note: Gap sentence. Quote is the start of the sentence inside a long line (line 858). | +| B-060 | `DEFERRED.md` | 860 | with a note that OpenSysML's own printed output shows an unsimplified unit expression | RUNTIME; UNPROBED | with a note that the OpenSysML runtime's own printed output shows an unsimplified unit expression Note: Workaround line describing what chapters/ch07 index.md and nb01 cells 14, 17, 18 say; those learner-surface markdown cells are covered by OT-1a/OT-3B and must carry the same wording. | +| B-061 | `DEFERRED.md` | 861 | **Resolution:** upstream fix in OpenSysML's `eval()`/`Quantity`/`Unit` display logic | KEEP-BODY | (no change) Note: Resolution/non-gap sentence; DL-116 (3) permits "runtime" only in the sentence stating the gap; the dated top note carries the reading rule. Names the runtime's own `eval()` API, so the bare name is unambiguous; option: "the OpenSysML runtime's". | +| B-062 | `DEFERRED.md` | 865 | ## D-034: `filter` is a reserved word in the real SysML v2 grammar; OpenSysML alone accepts it as a bare feature name with no diagnostic | KEEP-ID; FALSE-UNDER-STACK | (heading protected; byte-identical) Note: Line 867: "sysml-toolkit v0.9.1 correctly REJECTS bare `filter` too ... only OpenSysML silently tolerates it". "OpenSysML alone" is false under the umbrella. DL-116 item 7 lists D-034. | +| B-063 | `DEFERRED.md` | 867 | loads with `model.ok == True` and zero diagnostics in OpenSysML v0.9.0. | RUNTIME; FALSE-UNDER-STACK | loads with `model.ok == True` and zero diagnostics in the OpenSysML runtime v0.9.0. Note: Same line also: "only OpenSysML silently tolerates it" -> "only the OpenSysML runtime silently tolerates it"; "OpenSysML alone is the outlier" -> "the OpenSysML runtime is the outlier" (drops the banned "alone"). Three replacements, one row. | +| B-064 | `DEFERRED.md` | 872 | **Resolution:** upstream fix in OpenSysML alone (diagnose a reserved-word-as-identifier collision at parse time, matching sysml-toolkit's and the pilot's own behavior) | RUNTIME; FALSE-UNDER-STACK | **Resolution:** upstream fix in the OpenSysML runtime (diagnose a reserved-word-as-identifier collision at parse time, matching sysml-toolkit's and the pilot's own behavior) Note: Banned pattern "OpenSysML alone"; edit warranted even though a Resolution line. Truth: D-034 line 867 shows toolkit rejects. | +| B-065 | `DEFERRED.md` | 876 | ## D-035: sysml-toolkit reports an `assert satisfy`/`assert not satisfy` naming an undeclared requirement as a warning, not an error; OpenSysML and the pilot both reject it outright | KEEP-ID; FALSE-UNDER-STACK | (heading protected; byte-identical) Note: Heading says OpenSysML rejects it; line 878: "sysml-toolkit v0.9.1 ... reports `warning: unresolved reference 'missingReq'` and exits 0". False if OpenSysML includes the toolkit. NOT listed in DL-116 item 7 (omission to flag). | +| B-066 | `DEFERRED.md` | 878 | confirmed correct against OpenSysML v0.9.0 (`bad.ok == False`) | RUNTIME; FALSE-UNDER-STACK | confirmed correct against the OpenSysML runtime v0.9.0 (`bad.ok == False`) Note: Same line also: "there, OpenSysML alone was the outlier tolerating something the other two correctly rejected" -> "there, the OpenSysML runtime was the outlier ...". Caution: that sentence cites D-032 as an "OpenSysML alone" case but D-032 line 839 says sysml-toolkit accepts too (pre-existing inconsistency in the entry; do not fix, flag). | +| B-067 | `DEFERRED.md` | 880 | assert against OpenSysML's behavior only, matching the pattern real Chapter 9 notebook 01 already uses (`bad.ok == False`) | RUNTIME; FALSE-UNDER-STACK | assert against the OpenSysML runtime's behavior only, matching the pattern real Chapter 9 notebook 01 already uses (`bad.ok == False`) Note: Workaround line; the sentence is exactly the one contrasting with toolkit behavior, so the bare name would now include the toolkit. | +| B-068 | `DEFERRED.md` | 881 | matching OpenSysML's and the pilot's own behavior) | RUNTIME; FALSE-UNDER-STACK | matching the OpenSysML runtime's and the pilot's own behavior) Note: Resolution line, but it asserts a behavior the toolkit lacks (D-035 line 878). | +| B-069 | `DEFERRED.md` | 885 | ## D-036: the `connector` keyword (a KerML-only construct) is accepted in a `.sysml` file by OpenSysML; sysml-toolkit and the pilot both correctly reject it there | KEEP-ID; FALSE-UNDER-STACK | (heading protected; byte-identical) Note: Line 887: "sysml-toolkit v0.9.1 rejects every one at parse time". DL-116 item 7 lists D-036. | +| B-070 | `DEFERRED.md` | 887 | OpenSysML v0.9.0 loads each cleanly (`model.ok == True`, no diagnostics) | RUNTIME; FALSE-UNDER-STACK | The OpenSysML runtime v0.9.0 loads each cleanly (`model.ok == True`, no diagnostics) Note: Same line also: "likely OpenSysML being too permissive" -> "likely the OpenSysML runtime being too permissive". | +| B-071 | `DEFERRED.md` | 890 | file an upstream issue against OpenSysML (not sysml-toolkit/the pilot) | RUNTIME; FALSE-UNDER-STACK | file an upstream issue against the OpenSysML runtime (`Open-MBEE/OpenSysML`; not sysml-toolkit or the pilot) Note: Resolution line; the contrast "OpenSysML (not sysml-toolkit...)" would be false under the umbrella. Never counts the pilot among the stack tools. | +| B-072 | `DEFERRED.md` | 894 | ## D-037: OpenSysML's own `-render` CLI drops real content from both action-flow and state diagrams | KEEP-ID; UNPROBED | (heading protected; byte-identical) Note: Runtime-specific CLI (`opensysml -render`); no sysml-toolkit `viz` comparison in D-037 (D-018 covers `sysmlv2 viz` separately). UNPROBED; not in DL-116 item 7 (omission to flag). | +| B-073 | `DEFERRED.md` | 896 | both shell out to OpenSysML's own `-render '#action:...' -render-form dot` / `-render '#state:...' -render-form dot` CLI | RUNTIME; UNPROBED | both shell out to the OpenSysML runtime's own `-render '#action:...' -render-form dot` / `-render '#state:...' -render-form dot` CLI Note: Gap sentence. | +| B-074 | `DEFERRED.md` | 901 | (OpenSysML's `-render` CLI) | RUNTIME; UNPROBED | (the OpenSysML runtime's `-render` CLI) Note: Gap sentence. | +| B-075 | `DEFERRED.md` | 904 | either an upstream fix in OpenSysML's own `-render` CLI | KEEP-BODY | (no change) Note: Resolution/non-gap sentence; DL-116 (3) permits "runtime" only in the sentence stating the gap; the dated top note carries the reading rule. Names the runtime's own CLI; option: "the OpenSysML runtime's own `-render` CLI". | +| B-076 | `DEFERRED.md` | 908 | ## D-038: OpenSysML's API-JSON export has no structural `subjectParameter` key on `RequirementDefinition`; Ch10 reads each candidate feature's own `sysx:sourceText` instead | KEEP-ID; UNPROBED | (heading protected; byte-identical) Note: Runtime API-JSON export; no toolkit result in D-038. UNPROBED; not in DL-116 item 7 (omission to flag). | +| B-077 | `DEFERRED.md` | 910 | The API-JSON export OpenSysML v0.9.0 produces carries no `subjectParameter` key | RUNTIME; UNPROBED | The API-JSON export the OpenSysML runtime v0.9.0 produces carries no `subjectParameter` key Note: Gap sentence. | +| B-078 | `DEFERRED.md` | 913 | **Resolution:** upstream fix in OpenSysML's API-JSON export | KEEP-BODY | (no change) Note: Resolution/non-gap sentence; DL-116 (3) permits "runtime" only in the sentence stating the gap; the dated top note carries the reading rule. Names the runtime's own export; option: "the OpenSysML runtime's API-JSON export". | +| B-079 | `glossary/sources/notes/reading-notes.md` | 35 | - OpenSysML, sysml-toolkit and the Pilot Implementation are toolchain, cited only to flag spec gaps. They define no terms. | RUNTIME | - The OpenSysML runtime, sysml-toolkit (both components of OpenSysML) and the Pilot Implementation are toolchain, cited only to flag spec gaps. They define no terms. Note: Under the umbrella the original list names the parent and a child in parallel. Not a glossary term and not a .ttl; `uv run python -m glossary check` must still pass. Mirrors AGENTS.md line 43 wording; ACE wording to confirm. | +| B-080 | `.claude/agents/layer-auditor.md` | 12 | ery the model with the recipes in `.claude/skills/opensysml-query/SKILL.md` or `toaster.query`, and read known files by | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-081 | `.claude/agents/orchestrator.md` | 36 | the glossary CLI, `model.query`, the recipes in `opensysml-query`, and direct reads of known files and ranges. | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-082 | `.claude/skills/ace-protocol/SKILL.md` | 63 | \| SA-6 \| Bounded model checking: opensysml `check` engine only \| | AMBIGUOUS | Option A: "\| SA-6 \| Bounded model checking: the OpenSysML runtime's `check` engine only \|". Option B: leave (dated scope decision SA-6) and add a note that `sysmlv2 verify --solve` (sysml-toolkit) is used via toaster.modelcheck per D-024/D-025. Note: SA-6 is a dated scope decision that predates D-024 (sysml-toolkit v0.9.1 `verify --solve` via Z3 now in use). Rewriting changes what a scope decision says. ACE (author of the skill) should rule. | +| B-083 | `.claude/skills/ace-protocol/SKILL.md` | 132 | - Required opensysml capability missing from v0.9.0 with no workable simplification | RUNTIME | - Required OpenSysML runtime capability missing from v0.9.0 with no workable simplification Note: Escalation trigger; version attaches to the runtime. Optional: "and no sysml-toolkit alternative". | +| B-084 | `.claude/skills/ace-protocol/z-model.md` | 21 | - Z-12. OpenSysML and other implementations are toolchain, cited only to flag spec gaps. | AMBIGUOUS | Option A: "- Z-12. The OpenSysML runtime, sysml-toolkit and the Pilot Implementation are toolchain, cited only to flag spec gaps." Option B: leave as Z's recorded position (z-model records Z's positions; Z-12 is cited by DL-116 provenance) and rely on the reading rule. Note: z-model entries are Z's positions with ids cited in DL-116 ("z-model Z-12"). Editing the wording of a numbered Z position is the ACE's/Z's call. Skill prose, ACE-edited in OT-6. | +| B-085 | `.claude/skills/architecture-layers/SKILL.md` | 47 | rmance check (AGENTS.md 1.9) and use recipe 5 in `opensysml-query`, with a negative control. \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-086 | `.claude/skills/architecture-layers/SKILL.md` | 71 | el port)? Apply the port-type conformance check (`opensysml-query` recipe 5) from the point the connection is declared | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-087 | `.claude/skills/architecture-layers/SKILL.md` | 95 | \| Tool behavior \| `decisions/probes.md`, `opensysml-query` \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-088 | `.claude/skills/opensysml-api/SKILL.md` | 2 | name: opensysml-api | KEEP-ID | (no change) Note: Frontmatter `name:` line; protected. | +| B-089 | `.claude/skills/opensysml-api/SKILL.md` | 3 | description: opensysml v0.9.0 interface — correct method names, return shapes, limitations, and the D-001 encapsulation rule. | KEEP-ID | (no change) Note: Plan OT-6: "`opensysml-api` description stays (\"opensysml v0.9.0 interface\" uses the package name)". Frontmatter line. | +| B-090 | `.claude/skills/opensysml-api/SKILL.md` | 6 | # OpenSysML v0.9.0 API | RUNTIME | # The OpenSysML runtime v0.9.0 API Note: H1 (not frontmatter). Version attaches to a component name. Not covered by DL-116 explicit wording; derived from the convention. | +| B-091 | `.claude/skills/opensysml-api/SKILL.md` | 11 (F) | import opensysml | KEEP-ID | (no change) Note: Inside a code fence. | +| B-092 | `.claude/skills/opensysml-api/SKILL.md` | 12 (F) | import opensysml.binary | KEEP-ID | (no change) Note: Inside a code fence. | +| B-093 | `.claude/skills/opensysml-api/SKILL.md` | 14 (F) | opensysml.binary.ensure_binary(version="v0.9.0") | KEEP-ID | (no change) Note: Inside a code fence. | +| B-094 | `.claude/skills/opensysml-api/SKILL.md` | 15 (F) | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) Note: Inside a code fence. | +| B-095 | `.claude/skills/opensysml-api/SKILL.md` | 28 (F) | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) Note: Inside a code fence. | +| B-096 | `.claude/skills/opensysml-api/SKILL.md` | 75 | \| `abstract part def` \| toaster#9 / OpenSysML#595 \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-097 | `.claude/skills/opensysml-api/SKILL.md` | 76 | \| `attribute :>>` redefinition \| toaster#10 / OpenSysML#596 \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-098 | `.claude/skills/opensysml-api/SKILL.md` | 77 | \| `require constraint { ... }` \| toaster#11 / OpenSysML#597 \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-099 | `.claude/skills/opensysml-api/SKILL.md` | 78 | \| `assert satisfy R by P` \| toaster#12 / OpenSysML#598 \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-100 | `.claude/skills/opensysml-api/SKILL.md` | 79 | \| `allocate X to Y` \| toaster#13 / OpenSysML#599 \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-101 | `.claude/skills/opensysml-api/SKILL.md` | 80 | \| `flow X.port to Y.port` \| toaster#14 / OpenSysML#TBD \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-102 | `.claude/skills/opensysml-api/SKILL.md` | 81 | usage` (sub-state) + `transition` \| toaster#15 / OpenSysML#TBD \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-103 | `.claude/skills/opensysml-api/SKILL.md` | 111 | ted in Pass 1, see `tests/test_query.py` and the `opensysml-query` skill). | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-104 | `.claude/skills/opensysml-api/SKILL.md` | 125 (F) | satisfy, and metadata are invisible here; see the opensysml-query skill. | KEEP-ID | (no change) Note: Inside a code fence. | +| B-105 | `.claude/skills/opensysml-api/SKILL.md` | 132 (F) | helpers in src/toaster/query.py or the recipes in opensysml-query | KEEP-ID | (no change) Note: Inside a code fence. | +| B-106 | `.claude/skills/opensysml-api/SKILL.md` | 152 | `OPENSYSML_VERSION` (preferred), `OPENSYSML_GRPC_VERSION` (upstream fa | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-107 | `.claude/skills/opensysml-api/SKILL.md` | 154 | **Reference:** `sysmlv2-testing/adapters/opensysml.py` — authoritative; probed 2026-09-25 against v0.9.0. | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-108 | `.claude/skills/opensysml-query/SKILL.md` | 2 | name: opensysml-query | KEEP-ID | (no change) Note: Frontmatter `name:` line; protected. | +| B-109 | `.claude/skills/opensysml-query/SKILL.md` | 3 | description: Tested cookbook for interrogating a loaded SysML v2 model with OpenSysML v0.9.0 | RUNTIME | description: Tested cookbook for interrogating a loaded SysML v2 model with the OpenSysML runtime v0.9.0 Note: DL-116 item 3: "description and H1 say \"the OpenSysML runtime v0.9.0\"". Frontmatter `description:` line (editable; `name:` line 2 is not). | +| B-110 | `.claude/skills/opensysml-query/SKILL.md` | 6 | # Querying a model (OpenSysML v0.9.0) | RUNTIME | # Querying a model (the OpenSysML runtime v0.9.0) Note: H1; DL-116 item 3. | +| B-111 | `.claude/skills/opensysml-query/SKILL.md` | 26 (F) | import opensysml | KEEP-ID | (no change) Note: Inside an executed ```python block (tests/test_skill_snippets.py). | +| B-112 | `.claude/skills/opensysml-query/SKILL.md` | 28 (F) | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) Note: Inside an executed ```python block (tests/test_skill_snippets.py). | +| B-113 | `.claude/skills/opensysml-query/SKILL.md` | 132 | OpenSysML accepts a connection between ports of unrelated types with no diagnostic (gap G4 | BOTH; SAME | The OpenSysML runtime v0.9.0 and sysml-toolkit v0.9.1 both accept a connection between ports of unrelated types with no diagnostic (gap G4 Note: D-014 lines 192-194 (toolkit `check`/`lint` accept it too). Prose paragraph between code fences (lines 113-126 and 134-157 are executed blocks; line 132 is outside them). | +| B-114 | `.claude/skills/orchestrator-protocol/SKILL.md` | 37 | - Required construct unavailable in opensysml==0.9.0 | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-115 | `.claude/skills/skill-editor/SKILL.md` | 53 | - Incorrect API shape or return type in `opensysml-api` | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-116 | `.claude/skills/sysml-diagrams/SKILL.md` | 16 | rendering a single root via OpenSysML's `#tree:` form only shows that root's own direct features | RUNTIME; UNPROBED | rendering a single root via the OpenSysML runtime's `#tree:` form only shows that root's own direct features Note: Runtime `-render` CLI. Table cell. | +| B-117 | `.claude/skills/sysml-diagrams/SKILL.md` | 18 | \| OpenSysML CLI, `-render #action:element -render-form dot` → Graphviz SVG \| | RUNTIME; UNPROBED | \| OpenSysML runtime CLI, `-render #action:element -render-form dot` → Graphviz SVG \| Note: Table cell. | +| B-118 | `.claude/skills/sysml-diagrams/SKILL.md` | 19 | \| OpenSysML CLI, `-render #state:element -render-form dot` → Graphviz SVG \| | RUNTIME; UNPROBED | \| OpenSysML runtime CLI, `-render #state:element -render-form dot` → Graphviz SVG \| Note: Same line also: "100% success across both OpenSysML render forms" -> "both OpenSysML runtime render forms". | +| B-119 | `.claude/skills/sysml-diagrams/SKILL.md` | 20 | \| OpenSysML sequence query → DOT → Graphviz SVG \| | RUNTIME; UNPROBED | \| OpenSysML runtime sequence query → DOT → Graphviz SVG \| Note: Same line also has the command `opensysml -render-form dot` inside backticks: KEEP-ID (executable name). | +| B-120 | `.claude/skills/sysml-diagrams/SKILL.md` | 56 | - **OpenSysML CLI (`-render-form dot`)** — action flow and state views | RUNTIME; UNPROBED | - **OpenSysML runtime CLI (`-render-form dot`)** — action flow and state views | +| B-121 | `.claude/skills/sysml-diagrams/SKILL.md` | 62 | found opensysml had no native DOT or render-form CLI | RUNTIME; UNPROBED | found the OpenSysML runtime had no native DOT or render-form CLI Note: Same line also: "the earlier finding about OpenSysML's CLI itself no longer holds" -> "the earlier finding about the OpenSysML runtime's CLI no longer holds" (drops "itself"). | +| B-122 | `.claude/skills/sysml-diagrams/SKILL.md` | 64 | For action flow and state: OpenSysML's own `-render` CLI, not a custom Python renderer | RUNTIME; UNPROBED | For action flow and state: the OpenSysML runtime's own `-render` CLI, not a custom Python renderer | +| B-123 | `.claude/skills/sysml-diagrams/references/recipes.md` | 9 | is the pinned OpenSysML CLI; `model.sysml` is the chapter-generated snapshot. | RUNTIME | is the pinned OpenSysML runtime CLI; `model.sysml` is the chapter-generated snapshot. Note: Prose (line 9 of the file; not in a code fence). | +| B-124 | `.claude/skills/sysml-diagrams/references/recipes.md` | 74 | the way OpenSysML's own interconnection export does | RUNTIME | the way the OpenSysML runtime's own interconnection export does | +| B-125 | `.claude/skills/sysml-diagrams/references/recipes.md` | 83 | ## Action flow — OpenSysML | RUNTIME | ## Action flow — OpenSysML runtime Note: Heading in a reference file; no anchors inbound known (not a protected heading). | +| B-126 | `.claude/skills/sysml-diagrams/references/recipes.md` | 100 | ## State transition — OpenSysML | RUNTIME | ## State transition — OpenSysML runtime | +| B-127 | `.claude/skills/sysml-diagrams/references/recipes.md` | 110 | 100% success across both OpenSysML render forms | RUNTIME | 100% success across both OpenSysML runtime render forms | +| B-128 | `.claude/skills/sysml-diagrams/references/recipes.md` | 119 | ## Sequence — OpenSysML and Mermaid | RUNTIME | ## Sequence — OpenSysML runtime and Mermaid | +| B-129 | `.claude/skills/sysml-v2-toaster-model/SKILL.md` | 8 | ## Confirmed construct subset (opensysml v0.9.0) | RUNTIME; UNPROBED | ## Confirmed construct subset (the OpenSysML runtime, `opensysml` v0.9.0) Note: "Confirmed" = probed against the runtime only; no toolkit result for the subset. | +| B-130 | `.claude/skills/sysml-v2-toaster-model/SKILL.md` | 29 | This metadata construct does not parse in OpenSysML v0.9.0 | RUNTIME; UNPROBED | This metadata construct does not parse in the OpenSysML runtime v0.9.0 Note: D-004 (VerificationMethodKind) has no toolkit result. Token `OpenSysML#608` on the same line: KEEP-ID. | +| B-131 | `.claude/skills/sysml-v2-toaster-model/SKILL.md` | 78 | ## ISQ/SI unit typing — confirmed in opensysml v0.9.0 | RUNTIME; UNPROBED | ## ISQ/SI unit typing — confirmed in the OpenSysML runtime v0.9.0 | +| B-132 | `.claude/skills/sysml-v2-toaster-model/SKILL.md` | 133 | and opensysml edit.py. All five constructs parse correctly | KEEP-ID | (no change) Note: "opensysml edit.py" names a file in the Python package (file/package name). | +| B-133 | `.claude/skills/sysml-v2-toaster-model/SKILL.md` | 138 | \| D-004 \| `abstract part def` \| Ch1 \| OpenSysML#595 \| `conn.load_from_content()` \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-134 | `.claude/skills/sysml-v2-toaster-model/SKILL.md` | 139 | \| D-005 \| `attribute :>>` redefinition \| Ch2 \| OpenSysML#596 \| `conn.load_from_content()` \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-135 | `.claude/skills/sysml-v2-toaster-model/SKILL.md` | 140 | \| D-006 \| `require constraint { ... }` \| Ch2 \| OpenSysML#597 \| `conn.load_from_content()` \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-136 | `.claude/skills/sysml-v2-toaster-model/SKILL.md` | 141 | \| D-007 \| `assert satisfy R by P` \| Ch3 \| OpenSysML#598 \| `conn.load_from_content()` \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-137 | `.claude/skills/sysml-v2-toaster-model/SKILL.md` | 142 | \| D-008 \| `allocate X to Y` \| Ch5 \| OpenSysML#599 \| `conn.load_from_content()` \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-138 | `.claude/skills/sysml-v2-toaster-model/SKILL.md` | 143 | \| D-009 \| `flow X.port to Y.port` \| Ch5 \| OpenSysML#601 \| `conn.load_from_content()` \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-139 | `.claude/skills/sysml-v2-toaster-model/SKILL.md` | 144 | -010 \| `state` + sub-states + transitions \| Ch7 \| OpenSysML#602 \| `conn.load_from_content()` \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-140 | `.claude/skills/sysml-v2-toaster-model/SKILL.md` | 145 | 1 \| `attribute` with `default =` modifier \| Ch1 \| OpenSysML#603 \| `conn.load_from_content()` \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-141 | `.claude/skills/sysml-v2-toaster-model/SKILL.md` | 146 | \| `calc def` body (inputs + return expr) \| Ch3 \| OpenSysML#604 \| `conn.load_from_content()` \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-142 | `.claude/skills/sysml-v2-toaster-model/SKILL.md` | 147 | \| `action def` body (params + sequencing) \| Ch4 \| OpenSysML#605 \| `conn.load_from_content()` \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-143 | `.claude/skills/sysml-v2-toaster-model/SKILL.md` | 173 | \| `abstract part def` \| Ch1/nb01 \| toaster#9 / OpenSysML#595 \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-144 | `.claude/skills/sysml-v2-toaster-model/SKILL.md` | 174 | ute` (with `default =`) \| Ch1/nb02 \| toaster#16 / OpenSysML#603 \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-145 | `.claude/skills/sysml-v2-toaster-model/SKILL.md` | 177 | + `require constraint` \| Ch2/nb01 \| toaster#11 / OpenSysML#597 \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-146 | `.claude/skills/sysml-v2-toaster-model/SKILL.md` | 178 | attribute :>>` override \| Ch2/nb02 \| toaster#10 / OpenSysML#596 \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-147 | `.claude/skills/sysml-v2-toaster-model/SKILL.md` | 179 | sage + `assert satisfy` \| Ch3/nb01 \| toaster#12 / OpenSysML#598 \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-148 | `.claude/skills/sysml-v2-toaster-model/SKILL.md` | 180 | body (inputs + return) \| Ch3/nb02 \| toaster#17 / OpenSysML#604 \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-149 | `.claude/skills/sysml-v2-toaster-model/SKILL.md` | 181 | `action def` with body \| Ch4/nb01 \| toaster#18 / OpenSysML#605 \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-150 | `.claude/skills/sysml-v2-toaster-model/SKILL.md` | 183 | \| `allocate` \| Ch5/nb02 \| toaster#13 / OpenSysML#599 \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-151 | `.claude/skills/sysml-v2-toaster-model/SKILL.md` | 184 | \| `flow` \| Ch5/nb03 \| toaster#14 / OpenSysML#601 \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-152 | `.claude/skills/sysml-v2-toaster-model/SKILL.md` | 185 | b-states + transitions) \| Ch7/nb02 \| toaster#15 / OpenSysML#602 \| | KEEP-ID | (no change) Note: Protected token (package/skill name, repo or issue reference, URL, or env var). | +| B-153 | `.claude/skills/toaster-recipe/SKILL.md` | 55 (F) | import opensysml | KEEP-ID | (no change) Note: Inside a code fence. | +| B-154 | `.claude/skills/toaster-recipe/SKILL.md` | 58 (F) | conn = opensysml.connect(version="v0.9.0") | KEEP-ID | (no change) Note: Inside a code fence. | +| B-155 | `.claude/skills/toaster-recipe/SKILL.md` | 60 (F) | abstract modifier not yet supported — toaster#9 / OpenSysML#595 | KEEP-ID | (no change) Note: Inside a code fence. | +| B-156 | `.claude/skills/toaster-recipe/SKILL.md` | 87 (F) | fault = modifier not yet supported — toaster#16 / OpenSysML#603 | KEEP-ID | (no change) Note: Inside a code fence. | +| B-157 | `.claude/skills/tutorial-glossary/SKILL.md` | 43 | Implementations (OpenSysML, sysml-toolkit) are toolchain, not sources. | RUNTIME | Implementations (the OpenSysML runtime, sysml-toolkit, the Pilot Implementation) are toolchain, not sources. Note: Parent and child listed in parallel under the umbrella reading; mirrors AGENTS.md 1.2. | +| B-158 | `.claude/skills/tutorial-style-guide/SKILL.md` | 86 | add a comment citing the toaster issue + OpenSysML issue + spec section | RUNTIME | add a comment citing the toaster issue + the tracker issue of the component at fault (the runtime's `Open-MBEE/OpenSysML#NNN` or `Open-MBEE/sysml-toolkit#N`) + spec section Note: Convention: the runtime's tracker is `Open-MBEE/OpenSysML#NNN`; "OpenSysML issue" now ambiguous. Line 89 (in a code fence) is KEEP-ID. | +| B-159 | `.claude/skills/tutorial-style-guide/SKILL.md` | 89 (F) | abstract modifier not yet supported — toaster#9 / OpenSysML#595 | KEEP-ID | (no change) Note: Inside a code fence. | +| B-160 | `.claude/skills/tutorial-supporting-pages/SKILL.md` | 15 | \| `docs/references.md` \| Citations: Brian Douglas video, Hawkins 2011, opensysml, mystmd \| | KEEP-STACK | \| `docs/references.md` \| Citations: Brian Douglas video, Hawkins 2011, OpenSysML (the stack definition and links), mystmd \| Note: Lower-case "opensysml" here names the references section, which per the plan becomes the stack definition (OT-4). Optional capitalisation/clarification. | diff --git a/decisions/opensysml-terminology/website-review.md b/decisions/opensysml-terminology/website-review.md new file mode 100644 index 0000000..d293199 --- /dev/null +++ b/decisions/opensysml-terminology/website-review.md @@ -0,0 +1,44 @@ +# opensysml.org review (2026-10-03) + +Read by the orchestrator in the in-app browser: the home page, Downloads and Documentation. Paraphrased; no page text is +copied beyond short names. Requested by mzargham (Z) as the basis for an "OpenSysML" terminology revision. + +## What the site says OpenSysML is + +- The home page calls the whole thing a **stack** ("OpenSysML isn't a single product"): four independently maintained + projects built against the same open interfaces. + 1. **OpenSysML**, labelled "the runtime": a complete SysML v2 and KerML implementation in Go (language server, REPL, an + execution runtime that instantiates parts, evaluates constraints and runs action and state behavior), with Python, Node, + Java, Rust, Julia and MATLAB clients for its service, and a Go API. Repo `Open-MBEE/OpenSysML`. Apache-2.0. + 2. **sysml-toolkit**, labelled "the tool kit": a Rust workspace for parsing, formatting, linting, JSON/CBOR interchange, + constraint solving and span-preserving model transformation, plus a language server and Python and WebAssembly + bindings, built for programmatic pipelines and CI. Repo `Open-MBEE/sysml-toolkit`. Apache-2.0. + 3. **SysML v2 Pilot Implementation**, labelled "the reference": the OMG Systems Modeling Community's implementation + (Java, Xtext, PlantUML visualization, Jupyter kernel). The other two track its grammar and standard library as their + conformance baseline. EPL-2.0. + 4. **Flexo MMS**, labelled "the model store": Kotlin microservices that version model data (RDF, SPARQL 1.1) with a + SysML v2 API layer. Apache-2.0. +- The hero line counts "three independently built implementations": a Go runtime, a Rust toolchain and OMG's reference build. +- Licensing statement: the stack is dual-licensed (Apache-2.0 for code, CC BY 4.0 for wiki text and diagrams) with one + stated exception, the OMG reference implementation under EPL-2.0. +- Documentation sections: Guide, Manual (document generation), Reference, Internals, Project. +- Downloads (runtime): release bundles named `opensysml-` contain the `sysml` CLI and `sysml-lsp`; the + `sysml-grpc` service is separate; packages exist on PyPI (`opensysml`), npm (`@openmbee/opensysml`), crates.io + (`opensysml`) and as the Go module `github.com/Open-MBEE/OpenSysML`. + +## The naming ambiguity the toaster repo must handle + +The site uses "OpenSysML" at two levels: the **umbrella** (the stack) and the **runtime project** (repo, Python package, +CLI bundle, version numbers). This repository has used "OpenSysML" for the runtime only (Python package `opensysml`, +version v0.9.0, issues `Open-MBEE/OpenSysML#NNN`), and "sysml-toolkit" for the Rust toolchain (`sysmlv2` binary, +v0.9.1) as a separate thing. Under the new reading, a bare "OpenSysML" no longer says which tool a sentence is about, so +statements of the form "OpenSysML cannot X" change truth conditions (true of the runtime, possibly false of the toolkit). + +## Direction from mzargham (Z), 2026-10-03 + +"The tools provided as part of the sysml toolkit are also considered part of OpenSysML, not just the Go runtime. OpenSysML +is the broader term for the permissively licensed tools related to SysML v2." + +Open point: the site's own licensing statement keeps the Pilot Implementation (EPL-2.0) inside the stack as an exception, +while Z's wording says "permissively licensed". Whether the Pilot counts as part of OpenSysML in this repository's prose is +not settled by the site and is for the ACE or Z. diff --git a/decisions/pages-publishing-survey.md b/decisions/pages-publishing-survey.md new file mode 100644 index 0000000..f5a5eb6 --- /dev/null +++ b/decisions/pages-publishing-survey.md @@ -0,0 +1,61 @@ +# Pages publishing: Phase A survey + +**Date:** 2026-10-03 +**Inputs:** `decisions/pages-publishing/a1-clean-checkout.md` (PA-1), `a3-output-equivalence.md` (PA-3), +`a4-dangling-references.md` (PA-4); PA-2 (real `ubuntu-latest` runs) is complete: `decisions/pages-publishing/a2-real-runner.md`. +Every finding below was reproduced by an independent reviewer on a different model, except where noted. + +## 1. What breaks on a clean machine (PA-1) + +| # | Finding | Evidence | +|---|---|---| +| F1 | **No notebook executes unless the project venv is first on PATH.** MyST starts the first `jupyter` on PATH (here Anaconda's, broken). `docs/setup.md` says only `npx mystmd start --execute`. With the literal sequence: 32 errors, 1 figure. | PA-1 literal run; reviewer reproduced (rc=0 despite 32 errors). | +| F2 | **`myst build` exits 0 with notebook errors;** only `--strict` exits 1. A CI job without `--strict` cannot fail. | rc=0 / rc=1 measured by builder and reviewer. | +| F3 | With venv on PATH and a clean HOME, **exactly 3 notebooks fail**, all on missing toolkit paths: Ch5-03 cell-16 (`sysmlv2 binary not found`), Ch8-02 cell-11 (same), Ch10-01 cell `4d859570` (`FileNotFoundError ... sysml.library/Systems Library/Requirements.sysml`). Allowing errors to continue shows 11 failing cells (cascading NameErrors in Ch8-02 and Ch10-01). | Both reproduced exactly. | +| F4 | **Figures are not in the page DOM.** SVG figures live in the page JSON and are drawn client-side. Count `image/*` outputs in `_build/site/content/*.json`: 17 in the clean build (Ch5-03 missing), 18 in a fully working build. | Reviewer re-derived 17 and 18. | +| F5 | **The built site leaks local paths:** `Documents/GitHub` in 12 files (68 occurrences), `/opt/homebrew` in 6 files, plus the build host's home directory in failed-cell tracebacks (reviewer: 6 files). `/Users/` appears nowhere. The paths come from notebook source, so they publish even when execution succeeds. | Both reproduced counts. | +| F6 | **Files are published that should not be:** MyST copies any linked non-page file into `/build/`. Exercises ch01-ch08 (8 of 10 notebooks) and `DEFERRED.md` are published as raw downloads because chapter pages link to them; ch08's exercise and `DEFERRED.md` carry the author's `Documents/GitHub` path. `project.exclude: exercises/**` only stops them being parsed as pages. | Reviewer md5-matched the 8 exercises. | +| F7 | Internal links under `BASE_URL=/toaster`: 2305 checked, 0 broken (a wrong-base control reports thousands broken, so the check can fail). | Both. | +| F8 | Tooling drift and unpinned inputs: MyST downloads the book theme from `refs/heads/main.zip` (unpinned) at build time; `ci.yml` pins uv `0.5.x` but this machine ran 0.9.18 and Python 3.14.7; node here is 23.7 vs `.nvmrc` 22; `scripts/check-tools.py` checks only Graphviz and downloads OpenSysML (nothing about `sysmlv2`, Java, PlantUML). | PA-1 observations. | +| F9 | Hard-coded local paths also exist in `tests/` (`test_render_toolkit_interconnection.py`, `test_modelcheck.py`, `test_ch08_conservation_property.py`), `scripts/diagram_study/provision_check.py`, `exercises/ch08` cell-09, and `src/toaster/bootstrap.py` (cache dir, benign). | Reviewer re-grepped. | + +Notebook cells that name a local path (the full list for the fix): Ch5-03 cell-16 (`Path.home()` x2, `/opt/homebrew` plantuml jar and java), Ch8-02 cell-11, Ch10-01 `4d859570` (index 25) and `3850dee3` (index 46), `exercises/ch08` cell-09. The plan's "Ch8-02 cells 11/13/15, Ch10-01 cells 25/46/48" is the list of *failing* cells; cells 13, 15 and 48 use variables set earlier. + +## 2. Toolchain provisioning facts (PA-2 prep, PA-3) + +- sysml-toolkit v0.9.1 release assets: `sysmlv2-0.9.1-x86_64-unknown-linux-gnu.tar.gz` sha256 + `76b4a1e4f159bdf72bde8857bf25e60f59b4ffe4c7c48eeb8c419d856f4ff570` (from the release `SHA256SUMS`); + `sysmlv2-0.9.1-aarch64-apple-darwin.tar.gz` sha256 `ad0204041c95ce9817d398420e5057132a1378d53a812172cbd207cc40100c4a`. + Tarball layout `sysmlv2-0.9.1-/{sysmlv2,README.md,LICENSE}`. The Linux binary links only libc, libm, libgcc_s (no Z3 library needed). +- `sysml.library`: `Systems-Modeling/SysML-v2-Release` commit `de1070ae8e79c21532b8004fc663d47b35d0e9fa`, top-level `sysml.library`; the sparse-clone commands in the workflow were run by a reviewer and fetch it (117 files, 1.4 MB `.git`). +- Z3: `sysmlv2 verify --solve` shells out to a separate `z3` (no libz3 link); Linux needs `z3-5.1.0-x64-glibc-2.39.zip`, sha256 `f47be8d27d3230e823bf1eeede2fe0abaca55bb78d0b59974370e6689a92284a` (matches Z's local Z3 5.1.0). Without it Ch8-02 and Ch10-01 fail on the runner. +- PlantUML: Ubuntu's apt `plantuml` (1.2020.2) **rejects** the committed `figures/ch05-interconnection.puml` (a `port` inside a `rectangle`); the upstream jar `plantuml-1.2026.8.jar` (sha256 `5e1ecfa8ecd32c90b03bbf3b1eb6f020943f98ab0fcf4032be31a0002ee2c462`, matching GitHub's published digest) renders it. apt `plantuml` pulls Java 21, not 17. +- **Output equivalence (PA-3, macOS arm64 only):** the release v0.9.1 binary reproduces every output of Ch5-03, Ch8-01/02/03 and Ch10-01 (cells 25, 46, 48) versus both Z's local build (`0.9.1-1-gaf839f0`) and the stored outputs: zero verdict-changing differences, Ch5 figure byte-identical, same Z3 witness. Cosmetic only: stdout stream chunk boundaries vary run to run, so any CI comparison of raw outputs must join adjacent same-stream chunks first. **Linux is covered by PA-2:** the same outputs reproduce exactly on ubuntu-latest with the release binary, Z3 5.1.0 and PlantUML 1.2026.8. + +## 3. Dangling references (PA-4) + +365 rows across 58 of 59 pages: LINK 164, REWORD 58, KEEP 143. By cause: +- 55 `exercises/` references (excluded from the site; the markdown-linked ones currently publish as raw downloads). Fix: link to GitHub, which also stops publishing `exercises/ch08` and `DEFERRED.md`. +- 43 `models/` and 34 `DEFERRED.md` entry references, 9 issue numbers, 23 bare repo file names: mostly LINK to `https://github.com/Open-MBEE/toaster/blob/main/` (42 distinct paths, all verified to exist on `main`; DEFERRED anchors verified against GitHub's slug rule). +- Process jargon, `AGENTS.md`/`.claude/`/`decisions/`/`SA-n`/`DL-nnn`/work-contract ids/commit hashes: REWORD (58) or KEEP where the page is about the project (contributor guide). +- Reclassification rules the orchestrator set: `models/` paths on executable lines and D-nnn ids inside SysML doc comments and `ReviewRecord` strings are KEEP (editing them would change `models/*.sysml`, AS-C08's `content_hash`, and persisted records; AS-C08.json would go stale until ch08 nb02 is re-run); add links in adjacent markdown instead. + +## 4. Decisions needed (Z, or the ACE where it is judgment) + +1. **A1** `docs/contributor.md` names harness files (`AGENTS.md`, `CLAUDE.md`, `.claude/`, `decisions/`): default KEEP with links on first mention. +2. **A2/A4** pages that cite `AGENTS.md` and `decisions/log.md` DL entries as sources of substantive text (references.md, the case study, one Ch10 quoted sentence): default LINK to GitHub; DL-nnn in chapter text REWORD. +3. **A3** the handle "Z" and process narration ("this session", "unmerged worktree") in the case study: **ruled by mzargham (DL-112)**: keep "Z", but make the identity explicit. "Z" means the contributor identity `mzargham` (Michael Zargham, GitHub `mzargham`); public pages name `mzargham` ("mzargham (Z)") at first mention instead of a bare "Z", the identity is recorded in `AGENTS.md` Part 1 and `docs/contributor.md`, and any later contributor is named by their own handle ("Z" never changes meaning). The case study stays a record of the episode; "approved by Z" in references.md becomes "approved by mzargham (Z)". +4. **A7** whether the D-nnn-in-record-strings default (leave unchanged, link adjacent) also covers the other record strings that name internal files (`AGENTS.md SS1`, the ch04 `index.md` string, case-study path strings): ACE to rule. +5. **A10** `reproducibility.md` cites `decisions/pass4-run-*.md` (a glob): default link the `decisions/` directory; alternative drop the pointer. +6. The published `docs/setup.md`, `docs/contributor.md` and `docs/reproducibility.md` currently say deployment is off; they must change when the site is published. +7. Which exercises, if any, should be published (default: none; link to GitHub). +8. Spec Q1 (CI time): answered by PA-2: about 95 s per job on a cold ubuntu-latest runner (build 24-27 s), no caching. Acceptable by the spec's default. + +## 5. Consequences for Phase B + +- PUB-1/PUB-2/PUB-3 stand as outlined (tool resolver reading `SYSMLV2_BINARY`, `SYSMLV2_LIB_DIR`, `PLANTUML_JAR`, `JAVA`; remove the hard-coded paths in the cells above and `exercises/ch08`; provision the pinned toolkit, library and PlantUML jar). Add `tests/` and `scripts/diagram_study/` to the path-removal scope (F9). +- PUB-4 (CI) changes: build with `uv run --frozen npx myst build --html --execute --strict` (F1, F2); the figure check counts `image/*` outputs in `_build/site/content/*.json` against a recorded baseline (not DOM images), plus a zero-error check (a notebook that halts after a figure still shows its figure); the leak scan must cover `$HOME`/`/home/runner`/`/opt/homebrew`/`Documents/GitHub`/`/Users/`; pin uv, Python and the Node version in CI; decide whether to pin the MyST book theme (F8). +- New contract: retarget links to `exercises/` and `DEFERRED.md` to GitHub (F6) and document `uv run` for local preview in `docs/setup.md` (F1). +- PUB-5 edits are driven by the 365-row table, minimal diffs, no model or persisted-record changes (section 3). +- PUB-3/PUB-4 must also provision Z3 5.1.0 (pinned sha256) and put it on PATH; the resolver gains `Z3` (env `Z3`, then PATH). PUB-4's check is: zero cell errors (`--strict`) AND image-output count equal to the recorded baseline (18). +- Phase B waits only for the decisions above (Z/ACE); PA-2 is complete. diff --git a/decisions/pages-publishing/a1-clean-checkout.md b/decisions/pages-publishing/a1-clean-checkout.md new file mode 100644 index 0000000..deca0b8 --- /dev/null +++ b/decisions/pages-publishing/a1-clean-checkout.md @@ -0,0 +1,287 @@ +# A1: clean-checkout reproduction of the Pages build + +Contract PA-1, 2026-10-03. Builder: claude-sonnet-5-5. Investigation only: nothing was fixed. +Every number below comes from a real run on this Mac (Darwin 25.5.0, arm64) on 2026-10-03; scratch dirs were `mktemp -d` +directories (volatile; paths shown as `$T...`). No tracked file was edited. Downloads went to the scratch dirs only. + +## 0. What "clean" can and cannot mean on this machine (fidelity limits) + +- macOS, not `ubuntu-latest`. The absolute paths `/opt/homebrew/opt/plantuml/...` and `/opt/homebrew/opt/openjdk/...` + exist on this Mac, so a clean `HOME` hides them. They would fail on a Linux runner; that is not observable here. +- `HOME=$T/home` was applied to `uv sync`, `check-tools.py` and the MyST build, exactly as the contract says. `npm ci` + ran with the real `HOME` (the contract's command has no `HOME=` on it), so the npm cache was warm. +- `PATH` was not scrubbed in the literal run (see 2.1): the Mac `PATH` has Homebrew, Anaconda, `dot`, `plantuml`. +- Toolchain seen: uv 0.9.18 (CI pins `0.5.x`), node v23.7.0 (`.nvmrc` says 22), npm 10.9.2, mystmd 1.11.0, + `uv sync` picked CPython 3.14.7 (`requires-python >= 3.12`), Graphviz 16.0.0. +- The sequence was run on a `git clone --no-local` of `/Users/z/Documents/GitHub/toaster` at `main` = `e6e928b`. + +## 1. Premises: hard-coded locations (grep before the run) + +Method: JSON-aware scan of every code cell of every `.ipynb` under `chapters/` and `exercises/` for +`Path.home()`, `/opt/homebrew`, `Documents/GitHub` (plus a plain grep of the `.md` files and a check of stored outputs). +Totals: `Path.home()` 9, `/opt/homebrew` 2, `Documents/GitHub` 9 (line-level occurrences). Stored outputs: none. +Markdown files under `chapters/`, `exercises/`: none. + +| notebook | cell id (index) | line | text | +|---|---|---|---| +| chapters/ch05-architecture/03-interfaces.ipynb | cell-16 (16) | 8 | `BINARY = Path.home() / "Documents/GitHub/sysml-toolkit/target/release/sysmlv2"` | +| same | cell-16 | 9 | `LIB = Path.home() / "Documents/GitHub/sysml-toolkit/spec-refs/SysML-v2-Release/sysml.library"` | +| same | cell-16 | 10 | `PLANTUML_JAR = Path("/opt/homebrew/opt/plantuml/libexec/plantuml.jar")` | +| same | cell-16 | 11 | `JAVA = Path("/opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java")` | +| chapters/ch08-checking/02-violation-witness.ipynb | cell-11 (11) | 4 | `BINARY = Path.home() / "Documents/GitHub/sysml-toolkit/target/release/sysmlv2"` | +| same | cell-11 | 5 | `LIB = Path.home() / "Documents/GitHub/sysml-toolkit/spec-refs/SysML-v2-Release/sysml.library"` | +| chapters/ch10-traceability-signoff/01-traceability-graph.ipynb | 4d859570 (25) | 2 | `Path.home() / "Documents/GitHub/sysml-toolkit/spec-refs/SysML-v2-Release/sysml.library"` | +| same | 3850dee3 (46) | 9 | `SYSMLV2_BINARY = Path.home() / "Documents/GitHub/sysml-toolkit/target/release/sysmlv2"` | +| same | 3850dee3 | 10 | `SYSMLV2_LIB = Path.home() / "Documents/GitHub/sysml-toolkit/spec-refs/SysML-v2-Release/sysml.library"` | +| exercises/ch08/exercise.ipynb | cell-09 (9) | 4 | `BINARY = Path.home() / "Documents/GitHub/sysml-toolkit/target/release/sysmlv2"` | +| same | cell-09 | 5 | `LIB = Path.home() / "Documents/GitHub/sysml-toolkit/spec-refs/SysML-v2-Release/sysml.library"` | + +(Each `Path.home()` line is also a `Documents/GitHub` line, hence 9 + 2 = 11 rows, 9 distinct `Path.home()` lines.) + +Premise discrepancies against the plan/spec text: +- The plan lists "Ch8-02 cells 11/13/15" and "Ch10-01 cells 25/46/48". Those are cells that FAIL on a clean machine. + Only Ch8-02 `cell-11` and Ch10-01 `4d859570` / `3850dee3` NAME a path. Ch8-02 `cell-13`/`cell-15` and Ch10-01 + `9cc5f8de` (index 48) use the variables `BINARY`/`SYSMLV2_BINARY` set earlier, so a grep for the three strings + does not find them. A fix keyed on the grep list alone misses nothing today, but the failing set is larger than the naming set. +- Outside `chapters/` and `exercises/` (flagged, not fixed): `tests/test_render_toolkit_interconnection.py`, + `tests/test_modelcheck.py`, `tests/test_ch08_conservation_property.py` hard-code the same `Path.home()` / + `/opt/homebrew` locations; `scripts/diagram_study/provision_check.py:53` hard-codes `~/Documents/GitHub/sysml-toolkit`; + `src/toaster/bootstrap.py:50` uses `Path.home() / ".opensysml" / "bin"` (legitimate cache, but it is why `HOME` matters). +- `scripts/check-tools.py` verifies only Graphviz `dot` and downloads the OpenSysML binaries. It checks nothing about + `sysmlv2`, its library, Java or PlantUML, so a green `check-tools` says nothing about the notebooks that need them. + +## 2. The contract sequence, run once, verbatim + +### 2.1 Flag check + +``` +$ npx myst build --help (excerpt) + --execute Execute Notebooks (default: false) + --html Build static HTML site content (default: false) + --strict Summarize build warnings and exit non-zero on any errors. (default: false) +``` +`--html --execute` is valid as written. No equivalent substitution was needed. + +### 2.2 Commands and per-step wall time (a) + +``` +T=$(mktemp -d); git clone --no-local /Users/z/Documents/GitHub/toaster $T/repo # clone 1 s +mkdir $T/home; cd $T/repo +env HOME=$T/home uv sync --locked # uv-sync 5 s (rc 0) +env HOME=$T/home uv run python scripts/check-tools.py # check-tools 7 s (rc 0) +npm ci # npm-ci 1 s (rc 0) +env HOME=$T/home BASE_URL=/toaster npx myst build --html --execute 2>&1 | tee $T/build.log # build 15 s ("Built 59 pages ... in 6.3 s") +``` +Outputs (quoted): +``` +Using CPython 3.14.7 interpreter at: /opt/homebrew/opt/python@3.14/bin/python3.14 +Creating virtual environment at: .venv +Resolved 143 packages in 1ms +Prepared 141 packages in 3.44s +Installed 141 packages in 426ms +--- +dot: dot - graphviz version 16.0.0 (20260814.1018) +All tools verified. +--- +added 1 package, and audited 2 packages in 625ms +found 0 vulnerabilities +``` +The pipeline's exit status is `tee`'s (0), not MyST's. MyST itself, run without `tee` on a build that has errors: +`npx myst build --html --execute` exits 0; with `--strict` it exits 1 ("Site has 3 errors and 39 warnings, stopping build."). +So the contract's command (and any CI step written the same way) cannot fail the job on a notebook error. + +### 2.3 RESULT OF THE LITERAL RUN: nothing executes (finding F1, blocking) + +The literal sequence never puts the project virtualenv on `PATH`. MyST starts a Jupyter server with whatever +`jupyter` is first on `PATH`, and the notebooks' kernelspec is the generic `python3` (all 42 notebooks: +`"kernelspec": {"name": "python3"}`). Result: +``` +🚀 Starting new Jupyter server +🪐 Jupyter server did not start +Unable to instantiate connection to Jupyter Server +⛔️ chapters/ch04-functional-decomp/02-heating-refinement.ipynb Could not load Jupyter session manager to run executable nodes +... (32 such lines, one per executable notebook) +``` +On this Mac the first `jupyter` is Anaconda's, which is broken (`jupyter lab --version` ends in +`ImportError: cannot import name 'run_sync_in_worker_thread' from 'anyio'`). A run with every directory that +contains a `jupyter` removed from `PATH` (variant A2) fails the same way, with the clearer text +`Unable to instantiate connection to Jupyter Server Error: not found: python`. +Warning/error counts for this run: 32 errors, 98 warnings (breakdown in 2.5). Figures in the built site: 1 (see 2.6). + +`docs/setup.md` documents `npm install` then `npx mystmd start --execute` with no mention of activating `.venv` or +using `uv run`. A contributor following it on a clean machine meets this failure. +CI has the same gap: the step list in `ci.yml` has no MyST step yet, and a naive `npx myst build --html --execute` +after `uv sync` has no `jupyter` on `PATH`. + +## 3. Variant B: the same sequence with `.venv/bin` first on `PATH` (the realistic CI shape) + +Same clean `HOME`, same clone method, one change: `PATH=$T/repo/.venv/bin:$PATH` for the MyST step. +Timing: clone 1 s, uv-sync 4 s, check-tools 8 s, npm-ci 0 s, build 46 s ("Built 59 pages for project in 38 s"). +The Jupyter server starts (`🪐 Jupyter server started`), 32 notebooks execute, 59 pages are built. + +### 3.1 (b) Failing notebooks and the first error line of each failing cell + +As built by MyST (it halts a notebook at its first error, so only one cell per notebook is visible): + +| notebook | cell id | first error line | +|---|---|---| +| chapters/ch05-architecture/03-interfaces.ipynb | cell-16 | `AssertionError: sysmlv2 binary not found at $T/home/Documents/GitHub/sysml-toolkit/target/release/sysmlv2 (see work contract CH05-TOOLKIT-VIZ)` | +| chapters/ch08-checking/02-violation-witness.ipynb | cell-11 | `AssertionError: sysmlv2 binary not found at $T/home/Documents/GitHub/sysml-toolkit/target/release/sysmlv2 (see work contract PASS4-008)` | +| chapters/ch10-traceability-signoff/01-traceability-graph.ipynb | 4d859570 | `FileNotFoundError: [Errno 2] No such file or directory: '$T/home/Documents/GitHub/sysml-toolkit/spec-refs/SysML-v2-Release/sysml.library/Systems Library/Requirements.sysml'` | + +MyST also printed `Kernel "python3" did not become ready (attempt 1/3); restarting it` once during this run. +Of the 32 chapter notebooks, 29 execute with no error; 3 fail. (The other 10 `.ipynb` files are exercises, excluded from the book.) + +Complete failing-cell list, found by executing every notebook with `nbclient` (`allow_errors=True`, clean `HOME`, +`.venv/bin` first) in the same clone, because MyST's halt hides later cells. Cells marked (root) fail for the environment +reason; cells marked (cascade) fail only because an earlier cell in the notebook did not define a name: + +``` +chapters/ch05-architecture/03-interfaces.ipynb + cell-16 (root) AssertionError: sysmlv2 binary not found at .../home/Documents/GitHub/sysml-toolkit/target/release/sysmlv2 (see work contract CH05-TOOLKIT-VIZ) +chapters/ch08-checking/02-violation-witness.ipynb + cell-11 (root) AssertionError: sysmlv2 binary not found at ... (see work contract PASS4-008) + cell-13 (cascade) NameError: name '_write_companion' is not defined + cell-15 (cascade) NameError: name '_write_companion' is not defined + cell-28 (cascade) NameError: name 'pos_verdict' is not defined + cell-32 (cascade) NameError: name 'evidence_refs' is not defined +chapters/ch10-traceability-signoff/01-traceability-graph.ipynb + 4d859570 (root) FileNotFoundError: [Errno 2] No such file or directory: '.../home/Documents/GitHub/sysml-toolkit/spec-refs/SysML-v2-Release/sysml.library/Systems Library/Requirements.sysml' + 3850dee3 (root) AssertionError: sysmlv2 binary not found at .../target/release/sysmlv2 + 9cc5f8de (root) FileNotFoundError: [Errno 2] No such file or directory: '.../target/release/sysmlv2' + cc09043c (cascade) NameError: name 'energy_conservation_lines' is not defined + e14ede2b (cascade) NameError: name 'ac_c10_evidence_refs' is not defined +``` +Compare with the plan's "Ch8-02 cells 11/13/15" and "Ch10-01 cells 25/46/48": 13 and 15 are cascade NameErrors, not path failures. + +`exercises/` (excluded from the book, so not built by MyST): every exercise notebook except ch01, ch02, ch04 raises errors both +in the clean environment and in Z's environment (they are fill-in-the-blank exercises, e.g. `exercises/ch03` cell-2 +`ExecutionError: evaluation failed: unresolved reference: CoffeeDemo::hot`). Total failing cells across chapters + exercises: +63 clean vs 52 baseline (`nbrun_B.log` vs `nbrun_C2.log`). The only clean-attributable differences are the three chapter +notebooks above (11 cells) and `exercises/ch08` `cell-09`, whose error text changes from `ModelCheckError: ... sysmlv2 verify ...` +(baseline: the binary exists, the check itself errors) to `AssertionError: sysmlv2 binary not found ...` (clean). + +### 3.2 (c) Warnings by type (identical across all variants except the error line) + +``` + 59 WARN myst.yml Extension inferred for table of contents entry (every toc entry lacks its extension) + 39 WARN Duplicate identifier in project (cell ids "cell-00".."cell-NN", "cell-06b", ... reused across notebooks) +total WARN: 98 total ERROR: 3 +``` +(`--strict` counts "3 errors and 39 warnings"; the 59 toc warnings are printed earlier and are not in that count.) +Literal run: 98 warnings, 32 errors, all `Could not load Jupyter session manager to run executable nodes`. +Errors in variant B: 3, all `An exception occurred during code execution, halting further execution`. + +### 3.3 (d) Figures in the built `_build/html` + +Figure cells in the source (code cells that end in `SVG(...)` or `plt`, counted from the notebooks): 18, on 15 pages. +Static facts about the built HTML: SVG figures are NOT in the page DOM. They live in the page's embedded +`window.__remixContext` JSON (`"image/svg+xml":{"content":"`/inline `` in `
` therefore finds only the one PNG. Counts below are therefore given three ways. + +Per page with a figure, variant B (clean HOME, venv on PATH), `figs.py` output: +``` +page dom_img dom_svg page_json_image_outputs html_embedded_image_outputs +action-def-ffbd 0 0 1 1 +assert-constraint-def 0 0 1 1 +completeness-check 0 0 1 1 +composition 0 0 1 1 +judgment-context 0 0 1 1 +judgment-synthesis 0 0 1 1 +model-navigation 0 0 1 1 +param-sweep 1 0 1 1 (matplotlib PNG, stored output) +part-def 0 0 1 1 +state-traces 0 0 2 2 +stopping-judgment 0 0 2 2 +subsystem-requirements 0 0 2 2 +threshold-judgment 0 0 1 1 +traceability-graph 0 0 1 1 +TOTAL 1 0 17 17 (pages=59) +``` +Page with a figure cell that produced no figure: `interfaces` (Ch5-03, `cell-16`, fails on the missing `sysmlv2` binary). +So the clean build has 17 figures; the baseline (variant C2) has 18, the same list plus `interfaces`. +`ch10 traceability-graph` shows its figure (cell `47344a8b`) because that cell runs before the failing cell `4d859570`; +cells after the failure never execute, so a notebook can show a partial page without any visible gap. +Literal run (no Jupyter): 1 figure total, the `param-sweep` PNG, which is the only stored output in the repo's chapters. + +### 3.4 (e) Published local paths: `grep -rIl` of the built tree + +Variant B, `$T/repo/_build/html` (22 + 68 occurrences): +``` +== grep -rIl '/Users/' . (0 files) +== grep -rIl '/opt/homebrew' . (6 files, 22 occurrences) +build/03-interfaces-.ipynb +interfaces.json +interfaces/index.html +myst.search.json +traceability-graph.json (a stored traceback line: /opt/homebrew/Cellar/python@3.14/3.14.7/.../pathlib/__init__.py) +traceability-graph/index.html (same traceback) +== grep -rIl 'Documents/GitHub' . (12 files, 68 occurrences) +build/01-traceability-grap-.ipynb +build/02-violation-witness-.ipynb +build/03-interfaces-.ipynb +build/DEFERRED-.md +build/exercise-.ipynb (exercises/ch08/exercise.ipynb, copied into the site because a chapter page links to it) +interfaces.json +interfaces/index.html +myst.search.json +traceability-graph.json +traceability-graph/index.html +violation-witness.json +violation-witness/index.html +``` +(In B the "occurrences" are `grep -o` match counts; `grep -c` counts matching lines, which is lower because JSON and HTML +put many matches on one line.) Baseline C2 (Z's environment, no errors): `/Users/` 0 files; `/opt/homebrew` 4 files +(10 occurrences; matching lines per file: `build/03-interfaces.ipynb` 1, `interfaces.json` 1, `interfaces/index.html` 3, +`myst.search.json` 1); `Documents/GitHub` 12 files (38 occurrences; matching lines per file: `build/01-traceability-grap.ipynb` 3, +`build/02-violation-witness.ipynb` 2, `build/03-interfaces.ipynb` 1, `build/DEFERRED.md` 1, `build/exercise.ipynb` 2, +`interfaces.json` 1, `interfaces/index.html` 3, `myst.search.json` 1, `traceability-graph.json` 1, +`traceability-graph/index.html` 4, `violation-witness.json` 1, `violation-witness/index.html` 3). +The literal run (A) shows the same 4 + 12 files. +Takeaways: (1) the paths are published from the notebook SOURCE, whether or not execution succeeds, so replacing them is needed +for the spec's "no built page contains ..." check; (2) the site also publishes `DEFERRED.md` (line 340 names +`~/Documents/GitHub/sysml-toolkit/target/release/sysmlv2`) and `exercises/ch08/exercise.ipynb` as downloadable files +under `/toaster/build/`; (3) failed runs additionally leak interpreter paths (`/opt/homebrew/Cellar/...`) in stored tracebacks; +(4) no `/Users/` string appears anywhere in any variant. + +### 3.5 (f) Internal link check under `BASE_URL=/toaster` + +Script: `links.py` (HTML parser over all 59 pages; every ``, `
`, `' + 'emt' + f'f' + ) + (site / "ch01" / "index.html").write_text(f'home') + (site / "build" / "app.css").write_text("body{}") + (site / "build" / "app.js").write_text("1") + + +@pytest.fixture +def site(tmp_path: Path) -> Path: + s = tmp_path / "html" + write_site(s) + return s + + +# check_log +def test_log_good(): + assert cs.check_log("building\nall done\n") == [] + + +@pytest.mark.parametrize("marker", cs.LOG_MARKERS) +def test_log_fails_on_each_marker(marker): + out = cs.check_log(f"ok\n{marker}: boom\n") + assert len(out) == 1 and "line 2" in out[0] + + +def test_log_strips_ansi(): + assert cs.check_log("\x1b[31mAn exception occurred\x1b[0m during code execution\n") + assert cs.check_log("An exception occ\x1b[1murred during code execution\n") + + +# check_figures +def test_figures_good(tmp_path): + write_content(tmp_path / "c", {"a": 10, "b": 8, "empty": 0}) + assert cs.check_figures(tmp_path / "c", 18) == [] + + +def test_figures_17_vs_18(tmp_path): + write_content(tmp_path / "c", {"a": 10, "b": 7}) + out = cs.check_figures(tmp_path / "c", 18) + assert len(out) == 1 and "17" in out[0] and "18" in out[0] and "a.json=10" in out[0] + + +def test_figures_nested_and_non_image_not_counted(tmp_path): + c = tmp_path / "c" + c.mkdir() + (c / "p.json").write_text(json.dumps({"a": [{"b": {"jupyter_data": {"data": {"image/png": "x", "image/svg+xml": "y"}}}}]})) + assert cs.check_figures(c, 1) == [] + (c / "q.json").write_text(json.dumps({"jupyter_data": {"data": {"text/html": "x"}}})) + assert cs.check_figures(c, 1) == [] + + +def test_figures_missing_or_empty_dir(tmp_path): + assert cs.check_figures(tmp_path / "nope", 18) + (tmp_path / "e").mkdir() + assert cs.check_figures(tmp_path / "e", 18) + + +def test_figures_unreadable_json(tmp_path): + write_content(tmp_path / "c", {"a": 18}) + (tmp_path / "c" / "bad.json").write_text("{not json") + assert any("bad.json" in f for f in cs.check_figures(tmp_path / "c", 18)) + + +def test_baseline_file(): + assert json.loads(BASELINE.read_text()) == {"figures": 18} + + +# check_leaks +def test_leaks_good(site): + assert cs.check_leaks(site) == [] + + +@pytest.mark.parametrize("s", ["/opt/homebrew", "/home/runner", "Documents/GitHub", "/Users/", "/var/folders"]) +def test_leaks_each_default(site, s): + (site / "ch01" / "leak.js").write_text(f"x = '{s}/bin'") + out = cs.check_leaks(site) + assert len(out) == 1 and "ch01/leak.js" in out[0] and s in out[0] + + +def test_leaks_extra_and_skip_binary(site): + (site / "a.txt").write_text("secret-home-xyz") + assert cs.check_leaks(site) == [] + assert cs.check_leaks(site, ["secret-home-xyz"]) + (site / "a.bin").write_bytes(b"\0\1/opt/homebrew") + assert [f for f in cs.check_leaks(site) if "a.bin" in f] == [] + + +# check_published_files +def test_published_good(site): + assert cs.check_published_files(site) == [] + + +@pytest.mark.parametrize("name", ["exercise-xyz.ipynb", "exercise01.md", "DEFERRED.md"]) +def test_published_fails(site, name): + (site / "build" / name).write_text("x") + assert cs.check_published_files(site) == [f"build/{name}"] + + +def test_published_nested_and_outside_build_ignored(site): + (site / "build" / "sub").mkdir() + (site / "build" / "sub" / "exercise-a.ipynb").write_text("x") + (site / "exercise-page.html").write_text("x") + assert cs.check_published_files(site) == ["build/sub/exercise-a.ipynb"] + + +# check_links +def test_links_good(site): + assert cs.check_links(site, BASE) == [] + assert cs.check_links(site, BASE + "/") == [] + + +def test_links_broken(site): + (site / "ch01" / "index.html").write_text(f'x') + out = cs.check_links(site, BASE) + assert len(out) == 2 and all("ch01/index.html" in f for f in out) + + +def test_links_wrong_base(site): + (site / "index.html").write_text('xyz') + out = cs.check_links(site, BASE) + assert len(out) == 3 and all("does not begin with base" in f for f in out) + + +def test_links_dir_without_index_is_broken(site): + (site / "empty").mkdir() + (site / "index.html").write_text(f'x') + assert len(cs.check_links(site, BASE)) == 1 + + +def test_links_base_root_only(site): + (site / "index.html").write_text(f'xy') + assert cs.check_links(site, BASE) == [] + + +# missing / empty inputs must fail +SITE_CHECKS = { + "leaks": lambda d: cs.check_leaks(d), + "published": lambda d: cs.check_published_files(d), + "links": lambda d: cs.check_links(d, BASE), +} + + +@pytest.mark.parametrize("name", list(SITE_CHECKS)) +def test_site_checks_fail_on_missing_dir(tmp_path, name): + out = SITE_CHECKS[name](tmp_path / "nonexistent") + assert len(out) == 1 and "nonexistent" in out[0] + + +@pytest.mark.parametrize("name", list(SITE_CHECKS)) +def test_site_checks_fail_on_empty_dir(tmp_path, name): + (tmp_path / "empty").mkdir() + out = SITE_CHECKS[name](tmp_path / "empty") + assert len(out) == 1 and "no index.html" in out[0] and "empty" in out[0] + + +@pytest.mark.parametrize("name", list(SITE_CHECKS)) +def test_site_checks_fail_on_file_not_dir(tmp_path, name): + (tmp_path / "f").write_text("x") + assert SITE_CHECKS[name](tmp_path / "f") + + +def test_site_dir_without_root_index_fails(tmp_path): + (tmp_path / "ch01").mkdir() + (tmp_path / "ch01" / "index.html").write_text("x") + assert all(SITE_CHECKS[n](tmp_path) for n in SITE_CHECKS) + + +@pytest.mark.parametrize("text", ["", " \n\n"]) +def test_log_empty_fails(text): + assert cs.check_log(text) == ["log is empty"] + + +def test_links_zero_references_fails(tmp_path): + s = tmp_path / "s" + s.mkdir() + (s / "index.html").write_text('et

no refs

') + out = cs.check_links(s, BASE) + assert len(out) == 1 and "no root-relative references" in out[0] + + +# leak variants +@pytest.mark.parametrize( + "text,form", + [ + ('{"p": "\\/opt\\/homebrew\\/bin"}', "json-escaped"), + ("see%2Fopt%2Fhomebrew%2Fbin", "percent-encoded"), + ("see%2fopt%2fhomebrew", "percent-encoded"), + ("a%2FUsers%2Fz", "percent-encoded"), + ("Documents%2FGitHub", "percent-encoded"), + ('"Documents\\/GitHub"', "json-escaped"), + ("\\u002fvar\\u002ffolders", "unicode-escaped"), + ], +) +def test_leaks_encoded_variants(site, text, form): + (site / "ch01" / "enc.json").write_text(text) + out = cs.check_leaks(site) + assert out and all("ch01/enc.json" in f for f in out) and any(form in f for f in out) + + +def test_leaks_encoded_extra(site): + (site / "x.json").write_text("p=%2Fsome%2Fcwd") + assert cs.check_leaks(site) == [] + assert cs.check_leaks(site, ["/some/cwd"]) + + +# link reference forms +def link_site(tmp_path: Path, body: str) -> Path: + s = tmp_path / "ls" + s.mkdir(parents=True) + (s / "index.html").write_text(body) + (s / "img.png").write_text("x") + return s + + +def test_links_srcset_good_and_broken(tmp_path): + s = link_site(tmp_path, f'h') + assert cs.check_links(s, BASE) == [] + s = link_site(tmp_path / "b", f'h') + out = cs.check_links(s, BASE) + assert len(out) == 2 and any("gone.png" in f for f in out) and any("/bad/img.png" in f for f in out) + + +def test_links_meta_content(tmp_path): + body = ( + f'h' + '' + ) + assert cs.check_links(link_site(tmp_path, body), BASE) == [] + bad = f'h' + out = cs.check_links(link_site(tmp_path / "b", bad), BASE) + assert len(out) == 2 + + +def test_links_css_urls(tmp_path): + good = ( + f'h
' + f"" + ) + assert cs.check_links(link_site(tmp_path, good), BASE) == [] + bad = ( + f'h
' + f"" + ) + out = cs.check_links(link_site(tmp_path / "b", bad), BASE) + assert len(out) == 3 + + +# CLI +def run_cli(site, content, log, base=BASE, extra=()): + return cs.main(["--site", str(site), "--content", str(content), "--log", str(log), "--base-url", base, "--baseline", str(BASELINE), *extra]) + + +def test_cli_all_pass(site, tmp_path, capsys): + write_content(tmp_path / "c", {"a": 18}) + (tmp_path / "log").write_text("fine\n") + assert run_cli(site, tmp_path / "c", tmp_path / "log") == 0 + out = capsys.readouterr().out + assert out.count("PASS") == 5 and "FAIL" not in out + + +def test_cli_fails_and_names_check(site, tmp_path, capsys): + write_content(tmp_path / "c", {"a": 17}) + (tmp_path / "log").write_text("Jupyter server did not start\n") + (site / "build" / "DEFERRED.md").write_text("x") + assert run_cli(site, tmp_path / "c", tmp_path / "log", base="/wrong") == 1 + out = capsys.readouterr().out + for name in ("check_log", "check_figures", "check_published_files", "check_links"): + assert f"FAIL {name}" in out + assert "PASS check_leaks" in out + + +def test_cli_missing_site_exits_1(tmp_path, capsys): + write_content(tmp_path / "c", {"a": 18}) + (tmp_path / "log").write_text("fine\n") + assert run_cli(tmp_path / "nonexistent", tmp_path / "c", tmp_path / "log") == 1 + out = capsys.readouterr().out + for name in ("check_leaks", "check_published_files", "check_links"): + assert f"FAIL {name}" in out + assert "PASS check_log" in out and "PASS check_figures" in out + + +def test_cli_empty_site_exits_1(tmp_path): + (tmp_path / "s").mkdir() + write_content(tmp_path / "c", {"a": 18}) + (tmp_path / "log").write_text("fine\n") + assert run_cli(tmp_path / "s", tmp_path / "c", tmp_path / "log") == 1 + + +def test_cli_empty_log_exits_1(site, tmp_path, capsys): + write_content(tmp_path / "c", {"a": 18}) + (tmp_path / "log").write_text("") + assert run_cli(site, tmp_path / "c", tmp_path / "log") == 1 + assert "FAIL check_log" in capsys.readouterr().out + + +def test_cli_missing_log_exits_1(site, tmp_path): + write_content(tmp_path / "c", {"a": 18}) + assert run_cli(site, tmp_path / "c", tmp_path / "nolog") == 1 diff --git a/tests/test_check_terminology_edit.py b/tests/test_check_terminology_edit.py new file mode 100644 index 0000000..55f421b --- /dev/null +++ b/tests/test_check_terminology_edit.py @@ -0,0 +1,776 @@ +"""Tests for scripts/check-terminology-edit.py, the protected-token diff checker (contract OT-2). + +Every test builds a small git repository in tmp_path (git init, a base commit, a head commit), runs +the checker on base..head and asserts which rules pass and fail. Nothing here touches the network or +the real repository. +""" + +from __future__ import annotations + +import importlib.util +import json +import subprocess +import sys +from pathlib import Path + +import pytest + +SCRIPT = Path(__file__).resolve().parents[1] / "scripts" / "check-terminology-edit.py" +_spec = importlib.util.spec_from_file_location("check_terminology_edit", SCRIPT) +checker = importlib.util.module_from_spec(_spec) +sys.modules["check_terminology_edit"] = checker +_spec.loader.exec_module(checker) + +ALL_RULES = tuple(checker.RULES) + + +# --------------------------------------------------------------------------------------------- +# fixture repository + + +def notebook(markdown: str = "Prose about OpenSysML.\n", code: str = "import opensysml\n", output: str = "ok\n") -> str: + cells = [ + {"cell_type": "markdown", "id": "m1", "metadata": {}, "source": markdown.splitlines(keepends=True)}, + { + "cell_type": "code", + "id": "c1", + "metadata": {"tags": ["x"]}, + "execution_count": 3, + "source": code.splitlines(keepends=True), + "outputs": [{"output_type": "stream", "name": "stdout", "text": [output]}], + }, + ] + nb = {"cells": cells, "metadata": {"kernelspec": {"name": "python3"}}, "nbformat": 4, "nbformat_minor": 5} + return json.dumps(nb, indent=1) + "\n" + + +SKILL_MD = ( + "---\nname: demo\ndescription: A demo skill.\n---\n\n" + "# Demo\n\nProse about OpenSysML.\n\n```python\nimport opensysml\nprint(1)\n```\n\nMore prose.\n" +) +DEFERRED = ( + "# Deferred work\n\nNote at the top.\n\n## D-001: First\n\nBody that names the runtime.\n\n" + "## D-002: Second\n\nBody.\n" +) +SETUP = ( + "# Setup\n\nOpenSysML is used. See https://example.org/docs and https://example.org/other.\n\n" + "Tracker: Open-MBEE/OpenSysML#12 and sysml-toolkit#4 and toaster#7.\n" + "Package: opensysml.Model, `~/.opensysml`, OPENSYSML_VERSION, OPENSYSML_GRPC_VERSION,\n" + "Open-MBEE/sysml-toolkit, opensysml-api, opensysml-query.\n" +) + +BASE_FILES: dict[str, str] = { + "models/a.sysml": "package A { part def P; }\n", + "decisions/judgment-records/r1.json": '{"verdict": "ok"}\n', + "decisions/log.md": "# Log\n\nDL-1\n", + "decisions/notes.md": "# Notes\n", + "figures/f.svg": "\n", + "tests/test_x.py": "def test_x():\n pass\n", + "src/s.py": "X = 1\n", + "scripts/tool.py": "Y = 2\n", + "uv.lock": "version = 1\n", + "docs/superpowers/plan.md": "# Plan\n", + "docs/setup.md": SETUP, + "DEFERRED.md": DEFERRED, + "chapters/ch01/nb.ipynb": notebook(), + ".claude/skills/demo/SKILL.md": SKILL_MD, + ".claude/skills/demo/ref.md": "# Ref\n\n```\nfoo\n```\n", + ".claude/skills/demo/example.sysml": "package Demo { part def P; }\n", + ".claude/skills/other/SKILL.md": "---\nname: other\ndescription: Other.\n---\n\nProse.\n", +} + + +def run(repo: Path, *args: str) -> str: + done = subprocess.run( + ["git", "-c", "user.name=t", "-c", "user.email=t@t", "-c", "commit.gpgsign=false", *args], + cwd=repo, + capture_output=True, + text=True, + check=True, + ) + return done.stdout.strip() + + +def write_files(repo: Path, files: dict[str, str | None]) -> None: + for name, content in files.items(): + target = repo / name + if content is None: + target.unlink() + continue + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text(content, encoding="utf-8") + + +def commit(repo: Path, message: str) -> None: + run(repo, "add", "-A") + run(repo, "commit", "-q", "-m", message) + + +@pytest.fixture +def repo(tmp_path: Path) -> Path: + run(tmp_path, "init", "-q") + write_files(tmp_path, dict(BASE_FILES)) + commit(tmp_path, "base") + return tmp_path + + +def check_change(repo: Path, changes: dict[str, str | None]) -> dict[str, list[str]]: + """Apply `changes` (None deletes), commit, and run the checker on HEAD~1..HEAD.""" + write_files(repo, changes) + commit(repo, "head") + return checker.check(repo, "HEAD~1", "HEAD") + + +def failing(results: dict[str, list[str]]) -> set[str]: + return {letter for letter, found in results.items() if found} + + +# --------------------------------------------------------------------------------------------- +# passes + + +def test_clean_prose_change_passes_every_rule(repo: Path) -> None: + results = check_change( + repo, + { + "docs/setup.md": SETUP.replace("OpenSysML is used.", "The OpenSysML runtime is used."), + "chapters/ch01/nb.ipynb": notebook(markdown="Prose about the OpenSysML runtime.\n"), + "DEFERRED.md": DEFERRED.replace("Note at the top.", "Terminology note, dated.\nNote at the top."), + ".claude/skills/demo/SKILL.md": SKILL_MD.replace("Prose about OpenSysML.", "Prose about the runtime."), + }, + ) + assert failing(results) == set(), results + + +def test_new_files_in_decisions_and_superpowers_are_allowed(repo: Path) -> None: + results = check_change( + repo, + { + "decisions/opensysml-terminology/inventory.md": "| row |\nimport opensysml\n", + "docs/superpowers/plans/new.md": "# New\n", + }, + ) + assert failing(results) == set(), results + + +def test_own_files_may_change_in_scripts_and_tests(repo: Path) -> None: + results = check_change( + repo, + { + "scripts/check-terminology-edit.py": "# checker\n", + "tests/test_check_terminology_edit.py": "# tests\n", + }, + ) + assert failing(results) == set(), results + + +def test_adding_the_domain_link_does_not_change_the_api_token_count(repo: Path) -> None: + results = check_change( + repo, + {"docs/setup.md": SETUP + "The stack is described at https://opensysml.org/ and opensysml.org.\n"}, + ) + assert failing(results) == set(), results + + +def test_no_change_at_all_passes(repo: Path) -> None: + write_files(repo, {"unrelated.txt": "x\n"}) + commit(repo, "head") + assert failing(checker.check(repo, "HEAD~1", "HEAD")) == set() + + +# --------------------------------------------------------------------------------------------- +# rule (a) + + +@pytest.mark.parametrize( + "path", + [ + "models/a.sysml", + "decisions/judgment-records/r1.json", + "figures/f.svg", + "tests/test_x.py", + "src/s.py", + "scripts/tool.py", + "uv.lock", + "decisions/notes.md", + "docs/superpowers/plan.md", + ], +) +def test_rule_a_fails_on_a_changed_protected_file(repo: Path, path: str) -> None: + results = check_change(repo, {path: BASE_FILES[path] + "changed\n"}) + assert "a" in failing(results) + assert any(path in line for line in results["a"]) + + +@pytest.mark.parametrize( + "path", + ["models/new.sysml", "decisions/judgment-records/r2.json", "figures/g.svg", "src/new.py", "tests/test_new.py"], +) +def test_rule_a_fails_on_a_new_file_in_a_strict_zone(repo: Path, path: str) -> None: + results = check_change(repo, {path: "x\n"}) + assert "a" in failing(results) + + +LOG = BASE_FILES["decisions/log.md"] # "# Log\n\nDL-1\n" + + +def test_rule_a_allows_an_appended_log_entry(repo: Path) -> None: + results = check_change(repo, {"decisions/log.md": LOG + "\n## DL-2\n\nNew entry.\n"}) + assert failing(results) == set(), results + + +def test_rule_a_allows_log_append_when_base_lacked_a_trailing_newline(repo: Path) -> None: + write_files(repo, {"decisions/log.md": LOG.rstrip("\n")}) + commit(repo, "no trailing newline") + results = check_change(repo, {"decisions/log.md": LOG + "DL-2\n"}) + assert failing(results) == set(), results + + +@pytest.mark.parametrize( + "new", + [ + "# Log\n\nDL-1 edited\n", # edited existing line + "# Log\n\nDL-0\n\nDL-1\n", # line inserted in the middle + "New first line\n# Log\n\nDL-1\n", # line inserted at the start + "# Log\n\n", # last line deleted + "# Log\nDL-1\n", # blank line deleted + "", # emptied + ], +) +def test_rule_a_fails_on_a_non_append_change_to_the_log(repo: Path, new: str) -> None: + results = check_change(repo, {"decisions/log.md": new}) + assert failing(results) == {"a"}, results + assert any("decisions/log.md" in line for line in results["a"]) + + +def test_rule_a_append_exception_is_only_for_the_log(repo: Path) -> None: + results = check_change(repo, {"DEFERRED.md": DEFERRED, "docs/superpowers/plan.md": "# Plan\nappended\n"}) + assert failing(results) == {"a"}, results + + +def test_rule_a_fails_on_a_deleted_log(repo: Path) -> None: + results = check_change(repo, {"decisions/log.md": None}) + assert "a" in failing(results) + + +def test_rule_a_fails_on_a_deleted_decisions_file(repo: Path) -> None: + results = check_change(repo, {"decisions/notes.md": None}) + assert "a" in failing(results) + + +def test_rule_a_fails_on_a_changed_judgment_record_even_with_prose_elsewhere(repo: Path) -> None: + results = check_change( + repo, + { + "decisions/judgment-records/r1.json": '{"verdict": "changed"}\n', + "docs/setup.md": SETUP.replace("OpenSysML is used.", "The OpenSysML runtime is used."), + }, + ) + assert failing(results) == {"a"}, results + + +# --------------------------------------------------------------------------------------------- +# rule (b) + + +def test_rule_b_fails_on_a_changed_code_cell(repo: Path) -> None: + results = check_change(repo, {"chapters/ch01/nb.ipynb": notebook(code="import opensysml\nprint('x')\n")}) + assert "b" in failing(results) + assert any("code cell 1" in line for line in results["b"]) + + +def test_rule_b_fails_on_a_changed_output(repo: Path) -> None: + results = check_change(repo, {"chapters/ch01/nb.ipynb": notebook(output="changed\n")}) + assert failing(results) == {"b"}, results + assert any("outputs" in line for line in results["b"]) + + +def test_rule_b_fails_on_changed_execution_count_id_and_metadata(repo: Path) -> None: + base = json.loads(BASE_FILES["chapters/ch01/nb.ipynb"]) + for mutate in ( + lambda nb: nb["cells"][1].__setitem__("execution_count", 4), + lambda nb: nb["cells"][1].__setitem__("id", "c9"), + lambda nb: nb["cells"][1]["metadata"].__setitem__("tags", ["y"]), + lambda nb: nb["cells"][0].__setitem__("id", "m9"), + lambda nb: nb["cells"][0]["metadata"].__setitem__("k", 1), + lambda nb: nb["metadata"].__setitem__("language_info", {"name": "python"}), + ): + nb = json.loads(json.dumps(base)) + mutate(nb) + write_files(repo, {"chapters/ch01/nb.ipynb": json.dumps(nb, indent=1) + "\n"}) + assert checker.rule_b( + checker.changed_files(repo, "HEAD", None), + checker.Tree(repo, "HEAD"), + checker.Tree(repo, None), + ), mutate + run(repo, "checkout", "--", "chapters/ch01/nb.ipynb") + + +def test_rule_b_fails_on_cell_count_and_order(repo: Path) -> None: + nb = json.loads(BASE_FILES["chapters/ch01/nb.ipynb"]) + nb["cells"].append({"cell_type": "markdown", "id": "m2", "metadata": {}, "source": ["extra\n"]}) + results = check_change(repo, {"chapters/ch01/nb.ipynb": json.dumps(nb, indent=1) + "\n"}) + assert "b" in failing(results) + assert any("cell count" in line for line in results["b"]) + + nb = json.loads(BASE_FILES["chapters/ch01/nb.ipynb"]) + nb["cells"].reverse() + results = check_change(repo, {"chapters/ch01/nb.ipynb": json.dumps(nb, indent=1) + "\n"}) + assert "b" in failing(results) + + +def test_rule_b_allows_markdown_source_only(repo: Path) -> None: + results = check_change(repo, {"chapters/ch01/nb.ipynb": notebook(markdown="Entirely new prose.\n")}) + assert failing(results) == set(), results + + +def test_rule_b_ignores_whitespace_reformatting_of_the_json(repo: Path) -> None: + compact = json.dumps(json.loads(BASE_FILES["chapters/ch01/nb.ipynb"])) + results = check_change(repo, {"chapters/ch01/nb.ipynb": compact}) + assert failing(results) == set(), results + + +# --------------------------------------------------------------------------------------------- +# rule (c) + + +def test_rule_c_fails_when_a_token_is_removed(repo: Path) -> None: + results = check_change(repo, {"docs/setup.md": SETUP.replace("Open-MBEE/OpenSysML#12", "the tracker")}) + assert "c" in failing(results) + assert any("'Open-MBEE/OpenSysML'" in line for line in results["c"]) + assert any("'OpenSysML#12'" in line for line in results["c"]) + + +def test_rule_c_fails_when_an_issue_number_changes(repo: Path) -> None: + write_files(repo, {"docs/setup.md": SETUP + "See OpenSysML#590.\n"}) + commit(repo, "with 590") + results = check_change(repo, {"docs/setup.md": SETUP + "See OpenSysML#591.\n"}) + assert failing(results) == {"c"}, results + assert any("'OpenSysML#590' removed or altered" in line for line in results["c"]) + + +def test_rule_c_fails_when_an_api_name_changes(repo: Path) -> None: + write_files(repo, {"docs/setup.md": SETUP + "Call opensysml.load_model first.\n"}) + commit(repo, "with call") + results = check_change(repo, {"docs/setup.md": SETUP + "Call opensysml.load first.\n"}) + assert failing(results) == {"c"}, results + assert any("'opensysml.load_model'" in line for line in results["c"]) + + +def test_rule_c_fails_on_a_lookalike_swap_even_with_a_genuine_token_added_elsewhere(repo: Path) -> None: + write_files(repo, {"docs/setup.md": SETUP + "Call opensysml.load_model first.\nTracker OpenSysML#590.\n"}) + commit(repo, "with call") + swapped = SETUP + "Call opensysml.load_modell first.\nTracker OpenSysML#590, OpenSysML#700.\n" + results = check_change(repo, {"docs/setup.md": swapped}) + assert failing(results) == {"c"}, results + assert any("'opensysml.load_model'" in line for line in results["c"]) + # the same count as before (3 api/issue tokens each side) would have passed a count comparison + + +@pytest.mark.parametrize( + "old,new", + [ + ("opensysml.Model", "opensysml_Model"), + ("`~/.opensysml`", "`~/.cache`"), + ("OPENSYSML_VERSION", "OPENSYSML_PIN"), + ("OPENSYSML_GRPC_VERSION", "GRPC_PIN"), + ("Open-MBEE/sysml-toolkit", "the toolkit repository"), + ("sysml-toolkit#4", "sysml-toolkit issue 4"), + ("toaster#7", "toaster issue 7"), + ("opensysml-api", "the API skill"), + ("opensysml-query", "the query skill"), + ], +) +def test_rule_c_covers_each_protected_token(repo: Path, old: str, new: str) -> None: + assert old in SETUP + results = check_change(repo, {"docs/setup.md": SETUP.replace(old, new)}) + assert "c" in failing(results) + + +def test_rule_c_allows_an_added_token_and_reports_it_as_info(repo: Path) -> None: + write_files(repo, {"docs/setup.md": SETUP + "Run `import opensysml` first.\n"}) + commit(repo, "head") + results, all_info = checker.run_rules(repo, "HEAD~1", "HEAD") + info = all_info["c"] + assert failing(results) == set(), results + # the base SETUP has no 'import opensysml', so the addition is the only info line + assert info == ["docs/setup.md: token 'import opensysml' added (count 0 -> 1)"] + done = run_cli(repo, "--base", "HEAD~1", "--head", "HEAD") + assert done.returncode == 0 + assert " info (c) docs/setup.md: token 'import opensysml' added" in done.stdout + + +def test_rule_c_allows_adding_the_runtime_tracker_reference(repo: Path) -> None: + results = check_change(repo, {"docs/setup.md": SETUP + "the runtime's tracker (`Open-MBEE/OpenSysML#NNN`)\n"}) + assert failing(results) == set(), results + + +def test_rule_c_counts_notebook_code_cells_only(repo: Path) -> None: + # An API token in notebook markdown prose is not protected: it may be added or removed. The same + # token in a code cell is protected (rule (b) catches the code edit too). + markdown_token = notebook(markdown="Call opensysml.load later.\n") + results = check_change(repo, {"chapters/ch01/nb.ipynb": markdown_token}) + assert failing(results) == set(), results + results = check_change(repo, {"chapters/ch01/nb.ipynb": notebook(markdown="Call the loader later.\n")}) + assert failing(results) == set(), results + + results = check_change(repo, {"chapters/ch01/nb.ipynb": notebook(code="print('x')\n")}) + assert {"b", "c"} <= failing(results) + + +def test_rule_c_reads_issue_references_over_all_notebook_cells(repo: Path) -> None: + link_md = "See [toaster#19](https://example.org/t/19) and [OpenSysML#608](https://example.org/o/608).\n" + write_files(repo, {"chapters/ch01/nb.ipynb": notebook(markdown=link_md)}) + commit(repo, "with link text") + # removing the link text from a markdown cell fails, even though it is not a code cell + results = check_change(repo, {"chapters/ch01/nb.ipynb": notebook(markdown="See the issues.\n")}) + assert failing(results) == {"c", "g"}, results # (g): the link URLs went with the link text + assert any("'toaster#19'" in line for line in results["c"]) + assert any("'OpenSysML#608'" in line for line in results["c"]) + # so does altering the number + write_files(repo, {"chapters/ch01/nb.ipynb": notebook(markdown=link_md)}) + commit(repo, "restore link text") + results = check_change(repo, {"chapters/ch01/nb.ipynb": notebook(markdown=link_md.replace("#608", "#609"))}) + assert failing(results) == {"c"}, results + # leaving the link text alone while editing the prose passes + write_files(repo, {"chapters/ch01/nb.ipynb": notebook(markdown=link_md)}) + commit(repo, "restore link text") + results = check_change(repo, {"chapters/ch01/nb.ipynb": notebook(markdown="Also see [toaster#19](https://example.org/t/19) and [OpenSysML#608](https://example.org/o/608).\n")}) + assert failing(results) == set(), results + + +def test_rule_c_does_not_confuse_the_bare_name_with_a_token(repo: Path) -> None: + results = check_change(repo, {"docs/setup.md": SETUP.replace("OpenSysML is used", "OpenSysML runtime is used")}) + assert failing(results) == set(), results + + +# --------------------------------------------------------------------------------------------- +# rule (d) + + +def test_rule_d_fails_on_a_changed_heading(repo: Path) -> None: + results = check_change(repo, {"DEFERRED.md": DEFERRED.replace("## D-001: First", "## D-001: First (runtime)")}) + assert failing(results) == {"d"}, results + + +def test_rule_d_fails_on_a_removed_or_reordered_heading(repo: Path) -> None: + results = check_change(repo, {"DEFERRED.md": DEFERRED.replace("## D-002: Second\n\nBody.\n", "")}) + assert "d" in failing(results) + swapped = DEFERRED.replace("## D-001: First", "## D-00X").replace("## D-002: Second", "## D-001: First").replace( + "## D-00X", "## D-002: Second" + ) + results = check_change(repo, {"DEFERRED.md": swapped}) + assert "d" in failing(results) + + +def test_rule_d_allows_body_edits(repo: Path) -> None: + results = check_change(repo, {"DEFERRED.md": DEFERRED.replace("Body that names the runtime.", "Body that names the OpenSysML runtime.")}) + assert failing(results) == set(), results + + +# --------------------------------------------------------------------------------------------- +# rules (e) and (f) + + +def test_rule_e_fails_on_a_changed_skill_code_fence(repo: Path) -> None: + results = check_change(repo, {".claude/skills/demo/SKILL.md": SKILL_MD.replace("print(1)", "print(2)")}) + assert failing(results) == {"e"}, results + + +def test_rule_e_fails_on_a_changed_fence_in_a_reference_file(repo: Path) -> None: + results = check_change(repo, {".claude/skills/demo/ref.md": "# Ref\n\n```\nbar\n```\n"}) + assert failing(results) == {"e"}, results + + +def test_rule_e_fails_on_a_removed_or_added_fence(repo: Path) -> None: + results = check_change(repo, {".claude/skills/demo/SKILL.md": SKILL_MD + "\n```\nextra\n```\n"}) + assert "e" in failing(results) + results = check_change(repo, {".claude/skills/demo/SKILL.md": SKILL_MD.replace("```python\nimport opensysml\nprint(1)\n```\n", "")}) + assert "e" in failing(results) + + +def test_rule_e_ignores_prose_around_the_fence(repo: Path) -> None: + results = check_change(repo, {".claude/skills/demo/SKILL.md": SKILL_MD.replace("More prose.", "Different prose, same fence.")}) + assert failing(results) == set(), results + + +@pytest.mark.parametrize("change", ["modify", "delete", "add"]) +def test_rule_e_fails_on_any_change_to_a_non_markdown_skill_file(repo: Path, change: str) -> None: + path = ".claude/skills/demo/example.sysml" + if change == "modify": + changes = {path: BASE_FILES[path].replace("P;", "Q;")} + elif change == "delete": + changes = {path: None} + else: + changes = {".claude/skills/demo/extra.json": "{}\n"} + results = check_change(repo, changes) + assert failing(results) == {"e"}, results + assert any("non-markdown" in line for line in results["e"]) + + +def test_fenced_blocks_handles_tilde_fences_and_longer_fences() -> None: + text = "a\n~~~\none\n~~~\n````\ntwo\n```\nstill two\n````\nz\n" + assert checker.fenced_blocks(text) == ["~~~\none\n~~~", "````\ntwo\n```\nstill two\n````"] + + +def test_rule_f_fails_on_a_renamed_skill_directory(repo: Path) -> None: + run(repo, "mv", ".claude/skills/other", ".claude/skills/renamed") + commit(repo, "head") + results = checker.check(repo, "HEAD~1", "HEAD") + assert "f" in failing(results) + assert any("removed or renamed" in line for line in results["f"]) + assert any("added" in line for line in results["f"]) + + +def test_rule_f_fails_on_a_changed_name_frontmatter(repo: Path) -> None: + results = check_change(repo, {".claude/skills/other/SKILL.md": "---\nname: other-renamed\ndescription: Other.\n---\n\nProse.\n"}) + assert failing(results) == {"f"}, results + + +def test_rule_f_allows_a_description_change(repo: Path) -> None: + results = check_change(repo, {".claude/skills/other/SKILL.md": "---\nname: other\ndescription: The OpenSysML runtime.\n---\n\nProse.\n"}) + assert failing(results) == set(), results + + +# --------------------------------------------------------------------------------------------- +# rule (g) + + +def test_rule_g_fails_on_a_removed_url(repo: Path) -> None: + results = check_change(repo, {"docs/setup.md": SETUP.replace(" and https://example.org/other", "")}) + assert failing(results) == {"g"}, results + assert any("https://example.org/other" in line for line in results["g"]) + + +def test_rule_g_fails_on_a_url_removed_from_a_notebook_markdown_cell(repo: Path) -> None: + with_url = notebook(markdown="See https://example.org/nb for details.\n") + write_files(repo, {"chapters/ch01/nb.ipynb": with_url}) + commit(repo, "add url") + write_files(repo, {"chapters/ch01/nb.ipynb": notebook(markdown="See the page for details.\n")}) + commit(repo, "remove url") + results = checker.check(repo, "HEAD~1", "HEAD") + assert failing(results) == {"g"}, results + + +def test_rule_g_fails_when_one_of_two_identical_urls_is_removed(repo: Path) -> None: + twice = SETUP + "Again: https://example.org/docs\n" + write_files(repo, {"docs/setup.md": twice}) + commit(repo, "two occurrences") + results = check_change(repo, {"docs/setup.md": SETUP}) + assert failing(results) == {"g"}, results + assert any("count decreased (2 -> 1)" in line for line in results["g"]) + + +def test_rule_g_allows_an_added_occurrence_or_url(repo: Path) -> None: + results = check_change(repo, {"docs/setup.md": SETUP + "Again: https://example.org/docs and https://more.example/y\n"}) + assert failing(results) == set(), results + + +def test_rule_g_keeps_trailing_underscore_and_asterisk(repo: Path) -> None: + write_files(repo, {"docs/setup.md": SETUP + "See https://example.org/a_ and https://example.org/b*\n"}) + commit(repo, "with odd urls") + results = check_change(repo, {"docs/setup.md": SETUP + "See https://example.org/a and https://example.org/b\n"}) + assert failing(results) == {"g"}, results + + +def test_rule_g_allows_moving_or_adding_urls_and_trailing_punctuation(repo: Path) -> None: + changed = SETUP.replace("https://example.org/docs and https://example.org/other.", "(https://example.org/other) then https://example.org/docs, and https://new.example/x.") + results = check_change(repo, {"docs/setup.md": changed}) + assert failing(results) == set(), results + + +def test_fenced_blocks_recognise_any_indentation() -> None: + text = "- item\n\n ```python\n x = 1\n ```\n\ntext\n ~~~\n y\n ~~~\n" + assert checker.fenced_blocks(text) == [ + " ```python\n x = 1\n ```", + " ~~~\n y\n ~~~", + ] + + +def test_rule_e_fails_on_a_changed_deeply_indented_fence(repo: Path) -> None: + indented = SKILL_MD + "\n- item\n\n ```python\n x = 1\n ```\n" + write_files(repo, {".claude/skills/demo/SKILL.md": indented}) + commit(repo, "indented fence") + results = check_change(repo, {".claude/skills/demo/SKILL.md": indented.replace("x = 1", "x = 2")}) + assert failing(results) == {"e"}, results + + +# --------------------------------------------------------------------------------------------- +# strict zones added by ruling Q1, and --allow + + +@pytest.mark.parametrize( + "path", + [ + "glossary/lint.py", + "glossary/tests/test_lint.py", + "pyproject.toml", + "package.json", + "package-lock.json", + ".github/workflows/ci.yml", + ], +) +def test_rule_a_covers_the_q1_zones(repo: Path, path: str) -> None: + results = check_change(repo, {path: "x = 1\n"}) + assert "a" in failing(results) + assert any(path in line for line in results["a"]) + + +def test_rule_a_does_not_cover_glossary_data_files(repo: Path) -> None: + results = check_change(repo, {"glossary/lint_rules.toml": "[[rule]]\n", "glossary/README.md": "x\n"}) + assert "a" not in failing(results), results + + +def test_allow_exempts_matching_paths_from_rule_a_only(repo: Path) -> None: + changes = {"glossary/tests/test_lint.py": "x = 1\n", "glossary/lint.py": "y = 1\n", "models/a.sysml": "z\n"} + write_files(repo, changes) + commit(repo, "head") + results = checker.check(repo, "HEAD~1", "HEAD", allow=["glossary/tests/*"]) + assert [line.split(":")[0] for line in results["a"]] == ["glossary/lint.py", "models/a.sysml"] + results = checker.check(repo, "HEAD~1", "HEAD", allow=["glossary/tests/*", "glossary/lint.py", "models/*"]) + assert failing(results) == set(), results + + +def test_allow_does_not_exempt_other_rules(repo: Path) -> None: + results = checker.check( + repo, "HEAD", None, allow=["DEFERRED.md"] + ) + assert failing(results) == set() + write_files(repo, {"DEFERRED.md": DEFERRED.replace("## D-001: First", "## D-001: Renamed")}) + results = checker.check(repo, "HEAD", None, allow=["DEFERRED.md"]) + assert failing(results) == {"d"}, results + + +def test_cli_allow_option_is_repeatable(repo: Path) -> None: + write_files(repo, {"glossary/tests/test_lint.py": "x = 1\n", "glossary/lint.py": "y = 1\n"}) + commit(repo, "head") + base_args = ("--base", "HEAD~1", "--head", "HEAD") + assert run_cli(repo, *base_args).returncode == 1 + assert run_cli(repo, *base_args, "--allow", "glossary/tests/*").returncode == 1 + done = run_cli(repo, *base_args, "--allow", "glossary/tests/*", "--allow", "glossary/lint.py") + assert done.returncode == 0, done.stdout + assert "--allow" in run_cli(repo, "--help").stdout + + +def test_allow_is_never_silent(repo: Path) -> None: + write_files(repo, {"glossary/tests/test_lint.py": "x = 1\n", "glossary/lint.py": "y = 1\n", "docs/setup.md": SETUP + "extra\n"}) + commit(repo, "head") + base_args = ("--base", "HEAD~1", "--head", "HEAD") + done = run_cli(repo, *base_args, "--allow", "glossary/tests/*", "--allow", "glossary/lint.py", "--allow", "unused/*") + assert done.returncode == 0, done.stdout + lines = done.stdout.splitlines() + assert lines[0] == "allow: glossary/tests/*, glossary/lint.py, unused/*" + assert " info (a) exempted by --allow 'glossary/tests/*': glossary/tests/test_lint.py (A)" in lines + assert " info (a) exempted by --allow 'glossary/lint.py': glossary/lint.py (A)" in lines + assert lines[-1] == "RESULT: PASS (with 2 --allow exemption(s))" + # a glob that matches only non-violating paths waives nothing, so the result line stays plain + done = run_cli(repo, *base_args, "--allow", "docs/*") + assert "info (a)" not in done.stdout + assert done.stdout.splitlines()[0] == "allow: docs/*" + # a failing run still reports its waivers + write_files(repo, {"models/a.sysml": "changed\n"}) + commit(repo, "more") + done = run_cli(repo, "--base", "HEAD~2", "--head", "HEAD", "--allow", "glossary/*") + assert done.returncode == 1 + assert done.stdout.splitlines()[-1] == "RESULT: FAIL (with 2 --allow exemption(s))" + + +def test_allow_star_cannot_hide_the_waiver(repo: Path) -> None: + write_files(repo, {"models/a.sysml": "changed\n", "uv.lock": "version = 2\n"}) + commit(repo, "head") + done = run_cli(repo, "--base", "HEAD~1", "--head", "HEAD", "--allow", "*") + assert done.returncode == 0 + assert done.stdout.splitlines()[0] == "allow: *" + assert " info (a) exempted by --allow '*': models/a.sysml (M)" in done.stdout + assert " info (a) exempted by --allow '*': uv.lock (M)" in done.stdout + assert done.stdout.splitlines()[-1] == "RESULT: PASS (with 2 --allow exemption(s))" + + +def test_nothing_extra_is_printed_without_allow(repo: Path) -> None: + write_files(repo, {"docs/setup.md": SETUP.replace("OpenSysML is used.", "The OpenSysML runtime is used.")}) + commit(repo, "head") + done = run_cli(repo, "--base", "HEAD~1", "--head", "HEAD") + assert "allow" not in done.stdout and "info" not in done.stdout + assert done.stdout.splitlines()[-1] == "RESULT: PASS" + + +def test_help_tells_reviewers_to_run_from_a_pinned_revision() -> None: + assert "pinned revision" in checker.__doc__ + assert "SELF" in checker.__doc__ + + +# --------------------------------------------------------------------------------------------- +# repository root resolution + + +def test_check_works_from_a_subdirectory(repo: Path) -> None: + write_files(repo, {"models/a.sysml": "changed\n", "docs/setup.md": SETUP.replace("OpenSysML is used.", "The OpenSysML runtime is used.")}) + commit(repo, "head") + sub = repo / "docs" + results = checker.check(sub, "HEAD~1", "HEAD") + assert failing(results) == {"a"}, results + done = run_cli(sub, "--base", "HEAD~1", "--head", "HEAD") + assert done.returncode == 1 + assert "models/a.sysml" in done.stdout + + +def test_working_tree_head_from_a_subdirectory(repo: Path) -> None: + write_files(repo, {"docs/setup.md": SETUP.replace("Open-MBEE/OpenSysML#12", "the tracker"), "docs/untracked.md": "x\n"}) + results = checker.check(repo / "docs", "HEAD", None) + assert failing(results) == {"c"}, results + + +# --------------------------------------------------------------------------------------------- +# head defaults to the working tree; CLI + + +def test_head_defaults_to_the_working_tree(repo: Path) -> None: + write_files(repo, {"models/a.sysml": "changed\n", "docs/new.md": "x https://u.example/\n"}) + results = checker.check(repo, "HEAD", None) + assert failing(results) == {"a"}, results + assert any("models/a.sysml" in line for line in results["a"]) + run(repo, "checkout", "--", "models/a.sysml") + assert failing(checker.check(repo, "HEAD", None)) == set() + + +def test_working_tree_deletion_of_a_file_with_tokens_and_urls_fails_c_and_g(repo: Path) -> None: + (repo / "docs" / "setup.md").unlink() + results = checker.check(repo, "HEAD", None) + assert {"c", "g"} <= failing(results) + + +def run_cli(repo: Path, *args: str) -> subprocess.CompletedProcess[str]: + return subprocess.run( + [sys.executable, str(SCRIPT), "--repo", str(repo), *args], + capture_output=True, + text=True, + check=False, + ) + + +def test_cli_prints_pass_for_every_rule_and_exits_zero(repo: Path) -> None: + write_files(repo, {"docs/setup.md": SETUP.replace("OpenSysML is used.", "The OpenSysML runtime is used.")}) + commit(repo, "head") + done = run_cli(repo, "--base", "HEAD~1", "--head", "HEAD") + assert done.returncode == 0, done.stdout + done.stderr + lines = done.stdout.splitlines() + assert [line.split()[0] for line in lines[:-1]] == ["PASS"] * len(ALL_RULES) + assert lines[-1] == "RESULT: PASS" + + +def test_cli_prints_one_line_per_violation_and_exits_one(repo: Path) -> None: + write_files(repo, {"models/a.sysml": "changed\n", "uv.lock": "version = 2\n"}) + commit(repo, "head") + done = run_cli(repo, "--base", "HEAD~1", "--head", "HEAD") + assert done.returncode == 1 + assert "FAIL (a) protected zones unchanged: 2 violation(s)" in done.stdout + assert " (a) models/a.sysml: protected zone changed (M)" in done.stdout + assert " (a) uv.lock: protected zone changed (M)" in done.stdout + assert "PASS (b)" in done.stdout + assert done.stdout.splitlines()[-1] == "RESULT: FAIL" + + +def test_cli_reports_an_unknown_revision_with_exit_two(repo: Path) -> None: + done = run_cli(repo, "--base", "no-such-rev") + assert done.returncode == 2 + assert "error" in done.stderr diff --git a/tests/test_modelcheck.py b/tests/test_modelcheck.py index 608e4db..356f14d 100644 --- a/tests/test_modelcheck.py +++ b/tests/test_modelcheck.py @@ -1,9 +1,8 @@ """src/toaster/modelcheck.py: parses real sysml-toolkit `verify` CLI output (DEFERRED.md D-025, DL-046). Every test here runs the real `sysmlv2` binary — this module's whole job is correctly parsing real CLI -output, so mocking the subprocess would test nothing. `BINARY`/`LIB` point at the local build described in -the work contract; if they are not present on this machine, that is itself something to report, not paper -over. +output, so mocking the subprocess would test nothing. `BINARY`/`LIB` come from toaster.tools; if they +cannot be resolved on this machine, every test here is skipped with the actionable reason, not papered over. """ import os @@ -13,17 +12,62 @@ import pytest from toaster import modelcheck as mc - -BINARY = Path.home() / "Documents/GitHub/sysml-toolkit/target/release/sysmlv2" -LIB = ( - Path.home() - / "Documents/GitHub/sysml-toolkit/spec-refs/SysML-v2-Release/sysml.library" +from toaster.tools import ( + ToolNotFoundError, + resolve_library, + resolve_sysmlv2, + resolve_z3, + tool_env, ) -pytestmark = pytest.mark.skipif( - not BINARY.exists(), - reason=f"sysmlv2 binary not found at {BINARY} (see work contract PASS2-012)", -) +_PROVISION_HINT = "uv run python scripts/provision-tools.py" + + +def _skip_reason(exc: ToolNotFoundError) -> str: + return str(exc) if _PROVISION_HINT in str(exc) else f"{exc}; provision with `{_PROVISION_HINT}`" + + +def _require_tools() -> bool: + """TOASTER_REQUIRE_TOOLS=1 turns "tool missing, skip" into "tool missing, fail" (for CI).""" + return os.environ.get("TOASTER_REQUIRE_TOOLS") == "1" + + +def _resolve_tools(resolvers, require=None): + """Call each resolver; return (their results, None), or (None, the actionable skip reason). + + With `require` true (default: TOASTER_REQUIRE_TOOLS=1) an unresolved tool raises instead, so the + module fails to collect and CI cannot go green on silent skips. + """ + if require is None: + require = _require_tools() + try: + return tuple(resolver() for resolver in resolvers), None + except ToolNotFoundError as exc: + reason = _skip_reason(exc) + if require: + raise ToolNotFoundError( + f"{reason} (TOASTER_REQUIRE_TOOLS=1: a missing tool is a failure, not a skip)" + ) from exc + return None, reason + + +_TOOLS, _SKIP_REASON = _resolve_tools((resolve_sysmlv2, resolve_library, resolve_z3)) +BINARY, LIB = (_TOOLS[0], _TOOLS[1]) if _TOOLS else (None, None) + +# Tests whose subject is the switch itself; they run whether or not the tools resolve. +_NO_TOOL_TESTS = ("test_require_tools_switch",) + + +@pytest.fixture(autouse=True) +def _toolkit(monkeypatch, request): + """Skip when the toolkit is unresolved; otherwise put the resolved z3's directory on PATH, since + `sysmlv2 verify --solve` finds z3 there.""" + if request.function.__name__.startswith(_NO_TOOL_TESTS): + return + if _TOOLS is None: + pytest.skip(_SKIP_REASON) + monkeypatch.setenv("PATH", tool_env()["PATH"]) + TAUTOLOGY = """ package P { @@ -435,3 +479,29 @@ def test_notebook_call_shape(tmp_path): f = _write(tmp_path, "timely.sysml", TIMELY_TOAST) verdicts = mc.verify_holds(f, lib=str(LIB), binary=str(BINARY)) assert all(v.status in ("satisfied", "violated", "undecided") for v in verdicts) + + +# --- the TOASTER_REQUIRE_TOOLS switch ---------------------------------------------------------------------- + + +def _unresolvable(): + raise ToolNotFoundError("z3 not found: set the Z3 environment variable") + + +def test_require_tools_switch_default_skips(monkeypatch): + monkeypatch.delenv("TOASTER_REQUIRE_TOOLS", raising=False) + tools, reason = _resolve_tools((_unresolvable,)) + assert tools is None + assert "Z3" in reason and _PROVISION_HINT in reason + + +def test_require_tools_switch_set_raises(monkeypatch): + monkeypatch.setenv("TOASTER_REQUIRE_TOOLS", "1") + with pytest.raises(ToolNotFoundError, match="TOASTER_REQUIRE_TOOLS=1"): + _resolve_tools((_unresolvable,)) + + +def test_require_tools_switch_other_values_still_skip(monkeypatch): + monkeypatch.setenv("TOASTER_REQUIRE_TOOLS", "0") + tools, reason = _resolve_tools((_unresolvable,)) + assert tools is None and reason diff --git a/tests/test_no_local_paths.py b/tests/test_no_local_paths.py new file mode 100644 index 0000000..dc07c52 --- /dev/null +++ b/tests/test_no_local_paths.py @@ -0,0 +1,205 @@ +"""Guard: nothing shippable names a developer's home directory, Homebrew prefix or checkout path. + +Tools are found through `toaster.tools` (an argument, an environment variable, `.tools/`, then PATH), +never through a path baked into a notebook, a module, a test or a script. This test fails if any of +the forbidden strings below reappears in + + (a) a code cell of any notebook under chapters/ or exercises/, or + (b) any .py file under src/, tests/ or scripts/, or + (c) a markdown cell, or a stored output (stream text, text/plain, application/json, SVG text), of + any notebook under chapters/ or exercises/, or + (d) a published docs page: docs/**/*.md except docs/superpowers/ (plans and specs that myst.yml's + toc does not build). + +The allowlist is exactly: this file and the site-gate pair scripts/check-site.py and +tests/test_check_site.py (all three have to spell the strings out: they are the scanners and their +fixtures), and the one line in src/toaster/bootstrap.py that builds the `.opensysml` cache directory +under the user's home (`Path.home()` and `".opensysml"` on the same line; a cache location, not a tool +location). +""" + +import json +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parents[1] + +FORBIDDEN = ("Path.home()", "/opt/homebrew", "Documents/GitHub", "/Users/", "/home/") + +THIS_FILE = Path(__file__).resolve() +SPELLS_THE_STRINGS = { + THIS_FILE, + (ROOT / "scripts" / "check-site.py").resolve(), + (ROOT / "tests" / "test_check_site.py").resolve(), +} +BOOTSTRAP = ROOT / "src" / "toaster" / "bootstrap.py" +BOOTSTRAP_ALLOWED_MARKER = '".opensysml"' + + +def _is_bootstrap_cache_line(line: str) -> bool: + # Another line in bootstrap.py also mentions ".opensysml" but names no forbidden string; the + # allowlist is the one line that does (the home-directory cache location). + return BOOTSTRAP_ALLOWED_MARKER in line and "Path.home()" in line + + +def find_forbidden(text: str) -> list[str]: + """The forbidden strings present in `text` (in FORBIDDEN order, each once).""" + return [needle for needle in FORBIDDEN if needle in text] + + +def find_forbidden_in_py(path: Path, text: str) -> list[str]: + """As `find_forbidden`, for a .py file, honouring the allowlist.""" + if path.resolve() in SPELLS_THE_STRINGS: + return [] + if path.resolve() == BOOTSTRAP: + text = "\n".join( + line for line in text.splitlines() if not _is_bootstrap_cache_line(line) + ) + return find_forbidden(text) + + +def _text(value) -> str: + return "".join(value) if isinstance(value, list) else str(value) + + +def _output_texts(output: dict): + """The text a stored output would show a reader, one string per kind that carries text.""" + if "text" in output: # stream + yield "stream", _text(output["text"]) + data = output.get("data", {}) + if "text/plain" in data: + yield "text/plain", _text(data["text/plain"]) + if "application/json" in data: + yield "application/json", json.dumps(data["application/json"]) + if "image/svg+xml" in data: + yield "image/svg+xml", _text(data["image/svg+xml"]) + + +def _notebooks(root: Path): + for top in ("chapters", "exercises"): + for nb in sorted((root / top).rglob("*.ipynb")): + if ".ipynb_checkpoints" in nb.parts: + continue + yield nb, json.loads(nb.read_text(encoding="utf-8")).get("cells", []) + + +def _notebook_cases(root: Path): + for nb, cells in _notebooks(root): + for index, cell in enumerate(cells): + if cell.get("cell_type") != "code": + continue + yield pytest.param( + _text(cell.get("source", "")), id=f"{nb.relative_to(root).as_posix()}::cell-{index}" + ) + + +def _markdown_cases(root: Path): + for nb, cells in _notebooks(root): + for index, cell in enumerate(cells): + if cell.get("cell_type") != "markdown": + continue + yield pytest.param( + _text(cell.get("source", "")), + id=f"{nb.relative_to(root).as_posix()}::markdown-{index}", + ) + + +def _output_cases(root: Path): + for nb, cells in _notebooks(root): + for index, cell in enumerate(cells): + for n, output in enumerate(cell.get("outputs", [])): + for kind, text in _output_texts(output): + yield pytest.param( + text, + id=f"{nb.relative_to(root).as_posix()}::cell-{index}::output-{n}::{kind}", + ) + + +def _docs_cases(root: Path): + for md in sorted((root / "docs").rglob("*.md")): + if "superpowers" in md.relative_to(root / "docs").parts: + continue + yield pytest.param(md, id=md.relative_to(root).as_posix()) + + +def _python_cases(root: Path): + for top in ("src", "tests", "scripts"): + for py in sorted((root / top).rglob("*.py")): + if any(part in (".venv", "__pycache__") for part in py.parts): + continue + yield pytest.param(py, id=py.relative_to(root).as_posix()) + + +@pytest.mark.parametrize("source", list(_notebook_cases(ROOT))) +def test_notebook_code_cell_names_no_local_path(source): + assert find_forbidden(source) == [] + + +@pytest.mark.parametrize("source", list(_markdown_cases(ROOT))) +def test_notebook_markdown_cell_names_no_local_path(source): + assert find_forbidden(source) == [] + + +@pytest.mark.parametrize("text", list(_output_cases(ROOT))) +def test_notebook_stored_output_names_no_local_path(text): + assert find_forbidden(text) == [] + + +@pytest.mark.parametrize("path", list(_docs_cases(ROOT))) +def test_published_docs_page_names_no_local_path(path): + assert find_forbidden(path.read_text(encoding="utf-8")) == [] + + +@pytest.mark.parametrize("path", list(_python_cases(ROOT))) +def test_python_file_names_no_local_path(path): + text = path.read_text(encoding="utf-8") + assert find_forbidden_in_py(path, text) == [] + + +# --- the scanner itself ---------------------------------------------------------------------------- + + +@pytest.mark.parametrize("needle", FORBIDDEN) +def test_scanner_flags_each_forbidden_string(needle): + assert find_forbidden(f"x = '{needle}foo'") == [needle] + + +def test_scanner_passes_clean_text(): + assert find_forbidden("from toaster.tools import resolve_sysmlv2\n") == [] + + +def test_allowlist_covers_exactly_the_bootstrap_cache_line(): + allowed = 'return Path.home() / ".opensysml" / "bin"\n' + assert find_forbidden_in_py(BOOTSTRAP, allowed) == [] + other = allowed + 'other = Path.home() / "elsewhere"\n' + assert find_forbidden_in_py(BOOTSTRAP, other) == ["Path.home()"] + assert find_forbidden_in_py(ROOT / "src" / "toaster" / "other.py", allowed) == [ + "Path.home()" + ] + + +def test_bootstrap_has_exactly_one_allowlisted_line(): + lines = [ + line + for line in BOOTSTRAP.read_text(encoding="utf-8").splitlines() + if _is_bootstrap_cache_line(line) + ] + assert len(lines) == 1 + + +def test_output_scanner_reads_every_text_carrying_kind(): + outputs = [ + {"output_type": "stream", "text": ["a\n", "b\n"]}, + {"output_type": "display_data", "data": {"text/plain": "p", "image/png": "AAAA"}}, + {"output_type": "execute_result", "data": {"application/json": {"k": "/home/x"}}}, + {"output_type": "display_data", "data": {"image/svg+xml": ["", ""]}}, + ] + seen = [(kind, text) for out in outputs for kind, text in _output_texts(out)] + assert seen == [ + ("stream", "a\nb\n"), + ("text/plain", "p"), + ("application/json", '{"k": "/home/x"}'), + ("image/svg+xml", ""), + ] + assert find_forbidden(seen[2][1]) == ["/home/"] diff --git a/tests/test_provision_tools.py b/tests/test_provision_tools.py new file mode 100644 index 0000000..bd107dd --- /dev/null +++ b/tests/test_provision_tools.py @@ -0,0 +1,306 @@ +"""Offline tests for scripts/provision-tools.py: tiny fake archives, a fake pins file, injected downloaders.""" + +from __future__ import annotations + +import hashlib +import importlib.util +import io +import json +import os +import stat +import sys +import tarfile +import zipfile +from pathlib import Path + +import pytest + +SCRIPT = Path(__file__).resolve().parents[1] / "scripts" / "provision-tools.py" +spec = importlib.util.spec_from_file_location("provision_tools", SCRIPT) +pt = importlib.util.module_from_spec(spec) +sys.modules[spec.name] = pt +spec.loader.exec_module(pt) +REAL_CURRENT_PLATFORM = pt.current_platform + +REAL_PINS = SCRIPT.parent / "tool-pins.json" +KEY = "linux-x86_64" + + +def sha(data: bytes) -> str: + return hashlib.sha256(data).hexdigest() + + +def make_tar(path: Path, name: str, data: bytes, mode: int = 0o755) -> None: + with tarfile.open(path, "w:gz") as tf: + info = tarfile.TarInfo(name) + info.size = len(data) + info.mode = mode + tf.addfile(info, io.BytesIO(data)) + other = tarfile.TarInfo("pkg-1/README.md") + other.size = 2 + tf.addfile(other, io.BytesIO(b"hi")) + + +def make_zip(path: Path, name: str, data: bytes, mode: int = 0o755) -> None: + with zipfile.ZipFile(path, "w") as zf: + info = zipfile.ZipInfo(name) + info.external_attr = (stat.S_IFREG | mode) << 16 + zf.writestr(info, data) + zf.writestr("pkg-1/LICENSE.txt", "x") + + +@pytest.fixture +def world(tmp_path, monkeypatch): + """Fake release server: archives in tmp_path, a pins file naming them, and a counting downloader.""" + monkeypatch.setattr(pt, "current_platform", lambda: tuple(KEY.split("-"))) + blobs = tmp_path / "blobs" + blobs.mkdir() + make_tar(blobs / "toolkit.tar.gz", "pkg-1/sysmlv2", b"#!/bin/sh\necho sysmlv2 0.9.1\n") + make_zip(blobs / "z3.zip", "pkg-1/bin/z3", b"#!/bin/sh\necho Z3 5.1.0\n") + (blobs / "plantuml.jar").write_bytes(b"fake jar") + pins = { + "sysmlv2": {"version": "v0", "assets": {KEY: { + "url": "https://example.test/toolkit.tar.gz", + "sha256": sha((blobs / "toolkit.tar.gz").read_bytes()), + "member": "pkg-1/sysmlv2"}}}, + "z3": {"version": "z3-0", "assets": {KEY: { + "url": "https://example.test/z3.zip", + "sha256": sha((blobs / "z3.zip").read_bytes()), + "member": "pkg-1/bin/z3"}}}, + "plantuml": {"version": "v0", "url": "https://example.test/plantuml.jar", + "sha256": sha(b"fake jar")}, + "library": {"repo": "https://example.test/lib.git", "commit": "a" * 40}, + } + pins_path = tmp_path / "pins.json" + pins_path.write_text(json.dumps(pins)) + + class Net: + def __init__(self) -> None: + self.downloads: list[str] = [] + self.library_fetches = 0 + + def downloader(self, url: str, dest: Path) -> None: + self.downloads.append(url) + dest.write_bytes((blobs / url.rsplit("/", 1)[1]).read_bytes()) + + def library(self, repo: str, commit: str, dest: Path) -> None: + self.library_fetches += 1 + (dest / "Systems Library").mkdir(parents=True) + (dest / "Systems Library" / "Requirements.sysml").write_text(commit) + + net = Net() + + def run(*extra: str, dest: Path | None = None) -> tuple[int, str]: + out = io.StringIO() + code = pt.main( + ["--pins", str(pins_path), "--dest", str(dest or tmp_path / "dest"), *extra], + out=out, downloader=net.downloader, library_fetcher=net.library, + ) + return code, out.getvalue() + + return pins_path, pins, blobs, net, run, tmp_path / "dest" + + +# --- verify_sha256 ----------------------------------------------------------------------------- + + +def test_verify_sha256_passes_and_fails(tmp_path): + f = tmp_path / "f" + f.write_bytes(b"abc") + pt.verify_sha256(f, sha(b"abc")) + pt.verify_sha256(f, sha(b"abc").upper()) + with pytest.raises(ValueError, match="mismatch"): + pt.verify_sha256(f, sha(b"abd")) + + +# --- extract_member ---------------------------------------------------------------------------- + + +def test_extract_member_tar_by_suffix_keeps_exec_bit(tmp_path): + archive = tmp_path / "a.tar.gz" + make_tar(archive, "deep/dir/sysmlv2", b"binary", 0o755) + out = pt.extract_member(archive, "dir/sysmlv2", tmp_path / "out") + assert out == tmp_path / "out" / "sysmlv2" + assert out.read_bytes() == b"binary" + assert os.access(out, os.X_OK) + assert not list((tmp_path / "out").glob("*.part")) + + +def test_extract_member_zip_by_suffix_keeps_exec_bit(tmp_path): + archive = tmp_path / "a.zip" + make_zip(archive, "z3-1/bin/z3", b"binary", 0o755) + out = pt.extract_member(archive, "bin/z3", tmp_path / "out") + assert out == tmp_path / "out" / "z3" + assert os.access(out, os.X_OK) + + +def test_extract_member_does_not_invent_exec_bit(tmp_path): + archive = tmp_path / "a.tar.gz" + make_tar(archive, "d/data.txt", b"x", 0o644) + assert not os.access(pt.extract_member(archive, "data.txt", tmp_path / "o"), os.X_OK) + + +def test_extract_member_missing_ambiguous_and_component_boundary(tmp_path): + archive = tmp_path / "a.zip" + with zipfile.ZipFile(archive, "w") as zf: + zf.writestr("a/bin/z3", "1") + zf.writestr("b/bin/z3", "2") + zf.writestr("xbin/tool", "3") + with pytest.raises(FileNotFoundError): + pt.extract_member(archive, "nope", tmp_path / "o") + with pytest.raises(ValueError, match="ambiguous"): + pt.extract_member(archive, "bin/z3", tmp_path / "o") + with pytest.raises(FileNotFoundError): + pt.extract_member(archive, "bin/tool", tmp_path / "o") + + +# --- platform and pins ------------------------------------------------------------------------- + + +@pytest.mark.parametrize( + "system,machine,expected", + [("Linux", "x86_64", ("linux", "x86_64")), ("Darwin", "arm64", ("darwin", "arm64")), + ("Darwin", "x86_64", ("darwin", "x86_64")), ("Linux", "aarch64", ("linux", "arm64"))], +) +def test_current_platform(monkeypatch, system, machine, expected): + monkeypatch.setattr(pt.platform, "system", lambda: system) + monkeypatch.setattr(pt.platform, "machine", lambda: machine) + assert pt.current_platform() == expected + + +def test_current_platform_rejects_unknown(monkeypatch): + monkeypatch.setattr(pt.platform, "system", lambda: "Windows") + with pytest.raises(ValueError): + pt.current_platform() + + +def test_real_pins_file_is_valid_and_complete(): + pins = pt.load_pins(REAL_PINS) + for tool in ("sysmlv2", "z3"): + assert set(pins[tool]["assets"]) == {"linux-x86_64", "darwin-arm64", "darwin-x86_64"} + assert pins["library"]["commit"] == "de1070ae8e79c21532b8004fc663d47b35d0e9fa" + assert pins["plantuml"]["sha256"].startswith("5e1ecfa8") + + +def test_load_pins_rejects_bad_sha(tmp_path): + bad = json.loads(REAL_PINS.read_text()) + bad["plantuml"]["sha256"] = "xyz" + p = tmp_path / "p.json" + p.write_text(json.dumps(bad)) + with pytest.raises(ValueError, match="plantuml.sha256"): + pt.load_pins(p) + + +# --- provisioning ------------------------------------------------------------------------------ + + +def test_provision_installs_layout_and_prints_exports(world): + _, _, _, net, run, dest = world + code, out = run() + assert code == 0, out + assert os.access(dest / "bin" / "sysmlv2", os.X_OK) + assert os.access(dest / "bin" / "z3", os.X_OK) + assert (dest / "plantuml.jar").read_bytes() == b"fake jar" + assert (dest / "sysml.library" / "Systems Library" / "Requirements.sysml").is_file() + for var in ("SYSMLV2_BINARY", "Z3", "PLANTUML_JAR", "SYSMLV2_LIB_DIR"): + assert f"export {var}=" in out + assert len(net.downloads) == 3 and net.library_fetches == 1 + + +def test_wrong_sha_fails_before_anything_is_installed(world): + pins_path, pins, _, _, run, dest = world + # the jar is last in download order: sysmlv2 and z3 download fine first, then the bad pin fires. + pins["plantuml"]["sha256"] = "0" * 64 + pins_path.write_text(json.dumps(pins)) + code, out = run() + assert code != 0 + assert "mismatch" in out + assert "nothing was installed" in out + assert not dest.exists() or not any(dest.iterdir()) + + +def test_wrong_sha_on_existing_dest_leaves_it_untouched(world): + pins_path, pins, _, _, run, dest = world + dest.mkdir() + (dest / "keep.txt").write_text("mine") + pins["sysmlv2"]["assets"][KEY]["sha256"] = "1" * 64 + pins_path.write_text(json.dumps(pins)) + assert run()[0] != 0 + assert sorted(p.name for p in dest.iterdir()) == ["keep.txt"] + + +def test_second_run_downloads_nothing(world): + _, _, _, net, run, _ = world + assert run()[0] == 0 + net.downloads.clear() + fetches = net.library_fetches + code, out = run() + assert code == 0 + assert net.downloads == [] and net.library_fetches == fetches + assert out.count("kept") == 4 + + +def test_tampered_binary_is_replaced_on_rerun(world): + _, _, _, net, run, dest = world + run() + (dest / "bin" / "z3").write_bytes(b"tampered") + net.downloads.clear() + assert run()[0] == 0 + assert net.downloads == ["https://example.test/z3.zip"] + assert b"Z3 5.1.0" in (dest / "bin" / "z3").read_bytes() + + +# --- --check ----------------------------------------------------------------------------------- + + +def test_check_exit_codes(world): + _, _, _, net, run, dest = world + assert run("--check")[0] == 1 # absent + run() + assert run("--check")[0] == 0 + (dest / "plantuml.jar").write_bytes(b"other") + code, out = run("--check") + assert code == 1 and "plantuml" in out and "mismatch" in out + run() + (dest / "bin" / "sysmlv2").write_bytes(b"tampered") + assert run("--check")[0] == 1 + run() + (dest / "bin" / "z3").unlink() + assert run("--check")[0] == 1 + run() + (dest / "sysml.library" / "Systems Library" / "Requirements.sysml").write_text("changed") + assert run("--check")[0] == 1 + assert net.downloads # sanity: the fake downloader was the only network path + + +def test_check_downloads_nothing(world): + _, _, _, net, run, _ = world + run() + net.downloads.clear() + run("--check") + assert net.downloads == [] + + +# --- unsupported platform ------------------------------------------------------------------------ + + +def test_unsupported_platform_lists_keys_and_env_vars(world, monkeypatch): + _, _, _, net, run, dest = world + monkeypatch.setattr(pt, "current_platform", lambda: ("linux", "arm64")) + code, out = run() + assert code == 2 + assert "linux-arm64" in out and "linux-x86_64" in out + for var in pt.ENV_VARS: + assert var in out + assert "brew install z3" in out + assert net.downloads == [] and not dest.exists() + + +def test_unrecognised_machine_message(world, monkeypatch): + _, _, _, net, run, dest = world + monkeypatch.setattr(pt, "current_platform", REAL_CURRENT_PLATFORM) + monkeypatch.setattr(pt.platform, "system", lambda: "Windows") + code, out = run() + assert code == 2 + assert "unrecognised platform" in out and "linux-x86_64" in out and "SYSMLV2_BINARY" in out + assert net.downloads == [] and not dest.exists() diff --git a/tests/test_render_toolkit_interconnection.py b/tests/test_render_toolkit_interconnection.py index aed75d5..4230e5d 100644 --- a/tests/test_render_toolkit_interconnection.py +++ b/tests/test_render_toolkit_interconnection.py @@ -2,35 +2,79 @@ + PlantUML, used specifically when port identity itself is the pedagogical point (Ch5's conjugated port). This is a real external-tool pipeline (sysmlv2 binary, sysml.library, the PlantUML jar, a working `java`), not a packaged dependency -- every test here is skipped, with -a clear reason, if any of the four real paths below is missing on the machine running it. +a clear reason, if any of the four is not resolvable by toaster.tools on the machine running it. """ +import os +import subprocess from pathlib import Path import pytest from toaster.render import ToolkitRenderError, render_toolkit_interconnection - -BINARY = Path.home() / "Documents/GitHub/sysml-toolkit/target/release/sysmlv2" -LIB = ( - Path.home() - / "Documents/GitHub/sysml-toolkit/spec-refs/SysML-v2-Release/sysml.library" -) -PLANTUML_JAR = Path("/opt/homebrew/opt/plantuml/libexec/plantuml.jar") -JAVA = Path( - "/opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java" +from toaster.tools import ( + ToolNotFoundError, + resolve_java, + resolve_library, + resolve_plantuml_jar, + resolve_sysmlv2, ) MODEL = Path("models/ch05-cumulative.sysml") -pytestmark = pytest.mark.skipif( - not (BINARY.exists() and LIB.exists() and PLANTUML_JAR.exists() and JAVA.exists()), - reason=( - "sysml-toolkit binary, sysml.library, PlantUML jar or java not found at the real " - f"paths this test requires (binary={BINARY}, lib={LIB}, plantuml_jar={PLANTUML_JAR}, " - f"java={JAVA}); see work contract CH05-TOOLKIT-VIZ" - ), +_PROVISION_HINT = "uv run python scripts/provision-tools.py" + + +def _skip_reason(exc: ToolNotFoundError) -> str: + return str(exc) if _PROVISION_HINT in str(exc) else f"{exc}; provision with `{_PROVISION_HINT}`" + + +def _require_tools() -> bool: + """TOASTER_REQUIRE_TOOLS=1 turns "tool missing, skip" into "tool missing, fail" (for CI).""" + return os.environ.get("TOASTER_REQUIRE_TOOLS") == "1" + + +def _working_java() -> Path: + """The resolved java, proven to run: a PATH `java` can be a macOS stub that resolves but fails.""" + java = resolve_java() + try: + proc = subprocess.run([str(java), "-version"], capture_output=True, text=True, timeout=15, check=False) + failure = None if proc.returncode == 0 else f"exited {proc.returncode}" + except (OSError, subprocess.TimeoutExpired) as exc: + failure = str(exc) + if failure: + raise ToolNotFoundError( + f"java at {str(java)!r} does not run (`java -version`: {failure}); " + "set the JAVA environment variable to a working java" + ) + return java + + +def _resolve_tools(resolvers, require=None): + """Call each resolver; return (their results, None), or (None, the actionable skip reason). + + With `require` true (default: TOASTER_REQUIRE_TOOLS=1) an unresolved tool raises instead, so the + module fails to collect and CI cannot go green on silent skips. + """ + if require is None: + require = _require_tools() + try: + return tuple(resolver() for resolver in resolvers), None + except ToolNotFoundError as exc: + reason = _skip_reason(exc) + if require: + raise ToolNotFoundError( + f"{reason} (TOASTER_REQUIRE_TOOLS=1: a missing tool is a failure, not a skip)" + ) from exc + return None, reason + + +_TOOLS, _SKIP_REASON = _resolve_tools( + (resolve_sysmlv2, resolve_library, resolve_plantuml_jar, _working_java) ) +BINARY, LIB, PLANTUML_JAR, JAVA = _TOOLS if _TOOLS else (None, None, None, None) + +pytestmark = pytest.mark.skipif(_TOOLS is None, reason=_SKIP_REASON or "") def test_renders_real_svg(tmp_path): diff --git a/tests/test_tools.py b/tests/test_tools.py new file mode 100644 index 0000000..cf84a20 --- /dev/null +++ b/tests/test_tools.py @@ -0,0 +1,397 @@ +"""Tests for toaster.tools: resolution order, validation and tool_env. No network, no real tools.""" + +from __future__ import annotations + +import os +import stat +from pathlib import Path + +import pytest + +from toaster import tools +from toaster.tools import ToolNotFoundError + +ENV_VARS = ["SYSMLV2_BINARY", "SYSMLV2_LIB_DIR", "PLANTUML_JAR", "JAVA", "Z3"] + +# name -> (resolver, env var, path relative to .tools or None, PATH-resolvable name or None, kind) +SPECS = { + "sysmlv2": (tools.resolve_sysmlv2, "SYSMLV2_BINARY", "bin/sysmlv2", "sysmlv2", "exe"), + "library": (tools.resolve_library, "SYSMLV2_LIB_DIR", "sysml.library", None, "lib"), + "jar": (tools.resolve_plantuml_jar, "PLANTUML_JAR", "plantuml.jar", None, "jar"), + "java": (tools.resolve_java, "JAVA", None, "java", "exe"), + "z3": (tools.resolve_z3, "Z3", "bin/z3", "z3", "exe"), +} +PATH_NAMES = [n for n, s in SPECS.items() if s[3]] +NO_PATH_NAMES = [n for n, s in SPECS.items() if not s[3]] +PROVISIONED_NAMES = [n for n, s in SPECS.items() if s[2]] + + +def make(path: Path, kind: str, executable: bool = True) -> Path: + """Create a valid fake tool of the given kind at path.""" + path.parent.mkdir(parents=True, exist_ok=True) + if kind == "lib": + (path / "Systems Library").mkdir(parents=True) + return path + path.write_text("#!/bin/sh\nexit 0\n") + mode = path.stat().st_mode + if kind == "exe" and executable: + path.chmod(mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) + else: + path.chmod(mode & ~(stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)) + return path + + +@pytest.fixture(autouse=True) +def _fresh_java_probe_cache(): + tools._JAVA_PROBED_OK.clear() + yield + tools._JAVA_PROBED_OK.clear() + + +@pytest.fixture +def sandbox(tmp_path, monkeypatch): + """Clean env, REPO_ROOT and PATH all pointing into tmp_path.""" + for var in ENV_VARS: + monkeypatch.delenv(var, raising=False) + root = tmp_path / "repo" + root.mkdir() + pathdir = tmp_path / "pathdir" + pathdir.mkdir() + monkeypatch.setattr(tools, "REPO_ROOT", root) + monkeypatch.setenv("PATH", str(pathdir)) + return root, pathdir, tmp_path + + +def provisioned(root: Path, name: str) -> Path: + spec = SPECS[name] + return make(root / ".tools" / spec[2], spec[4]) + + +def on_path(pathdir: Path, name: str) -> Path: + return make(pathdir / SPECS[name][3], "exe") + + +def test_repo_root_default_is_repository(): + assert tools.REPO_ROOT == Path(tools.__file__).resolve().parents[2] + assert (tools.REPO_ROOT / "pyproject.toml").is_file() + + +def test_not_found_error_is_runtime_error(): + assert issubclass(ToolNotFoundError, RuntimeError) + + +@pytest.mark.parametrize("name", SPECS) +def test_explicit_wins_over_env(sandbox, monkeypatch, name): + resolver, env_var, _, _, kind = SPECS[name] + root, pathdir, tmp = sandbox + explicit = make(tmp / "explicit" / "thing.jar" if kind == "jar" else tmp / "explicit" / "thing", kind) + monkeypatch.setenv(env_var, str(make(tmp / "fromenv" / ("e.jar" if kind == "jar" else "e"), kind))) + assert resolver(explicit) == explicit + assert resolver(str(explicit)) == explicit + + +@pytest.mark.parametrize("name", SPECS) +def test_explicit_invalid_raises_and_does_not_fall_through(sandbox, monkeypatch, name): + resolver, env_var, *_ = SPECS[name] + root, pathdir, tmp = sandbox + monkeypatch.setenv(env_var, str(make(tmp / "e.jar", "jar") if name == "jar" else make(tmp / "e", SPECS[name][4]))) + with pytest.raises(ToolNotFoundError, match="explicit argument"): + resolver(tmp / "does-not-exist") + + +@pytest.mark.parametrize("name", PROVISIONED_NAMES) +def test_env_wins_over_provisioned(sandbox, monkeypatch, name): + resolver, env_var, _, _, kind = SPECS[name] + root, pathdir, tmp = sandbox + provisioned(root, name) + from_env = make(tmp / "fromenv" / ("e.jar" if kind == "jar" else "e"), kind) + monkeypatch.setenv(env_var, str(from_env)) + assert resolver() == from_env + + +def test_java_env_wins_over_path(sandbox, monkeypatch): + root, pathdir, tmp = sandbox + on_path(pathdir, "java") + from_env = make(tmp / "myjava", "exe") + monkeypatch.setenv("JAVA", str(from_env)) + assert tools.resolve_java() == from_env + + +@pytest.mark.parametrize("name", PROVISIONED_NAMES) +def test_provisioned_used_when_nothing_else_set(sandbox, name): + root, pathdir, tmp = sandbox + expected = provisioned(root, name) + assert SPECS[name][0]() == expected + + +@pytest.mark.parametrize("name", [n for n in PATH_NAMES if SPECS[n][2]]) +def test_provisioned_wins_over_path(sandbox, name): + root, pathdir, tmp = sandbox + on_path(pathdir, name) + expected = provisioned(root, name) + assert SPECS[name][0]() == expected + + +@pytest.mark.parametrize("name", PATH_NAMES) +def test_path_is_used_for_sysmlv2_java_z3(sandbox, name): + root, pathdir, tmp = sandbox + expected = on_path(pathdir, name) + assert SPECS[name][0]() == expected + + +@pytest.mark.parametrize("name", NO_PATH_NAMES) +def test_path_not_used_for_library_or_jar(sandbox, monkeypatch, name): + root, pathdir, tmp = sandbox + # Valid-looking candidates on PATH, including an executable plantuml.jar and a real library dir. + make(pathdir / "sysml.library", "lib") + make(pathdir / "plantuml.jar", "exe") + make(pathdir / "plantuml", "exe") + calls = [] + real_which = tools.shutil.which + + def recording_which(cmd, *args, **kwargs): + calls.append(cmd) + return real_which(cmd, *args, **kwargs) + + monkeypatch.setattr(tools.shutil, "which", recording_which) + with pytest.raises(ToolNotFoundError): + SPECS[name][0]() + assert calls == [] + + +@pytest.mark.parametrize("name", SPECS) +def test_env_set_but_missing_raises_naming_variable(sandbox, monkeypatch, name): + resolver, env_var, *_ = SPECS[name] + root, pathdir, tmp = sandbox + # Provisioned and PATH candidates exist: the bad env value must still be an error. + if SPECS[name][2]: + provisioned(root, name) + if SPECS[name][3]: + on_path(pathdir, name) + monkeypatch.setenv(env_var, str(tmp / "no-such-thing")) + with pytest.raises(ToolNotFoundError, match=env_var): + resolver() + + +def test_env_library_without_systems_library_rejected(sandbox, monkeypatch): + root, pathdir, tmp = sandbox + bad = tmp / "emptylib" + bad.mkdir() + monkeypatch.setenv("SYSMLV2_LIB_DIR", str(bad)) + with pytest.raises(ToolNotFoundError, match="SYSMLV2_LIB_DIR"): + tools.resolve_library() + + +def test_library_given_as_file_rejected(sandbox): + root, pathdir, tmp = sandbox + f = make(tmp / "libfile", "jar") + with pytest.raises(ToolNotFoundError): + tools.resolve_library(f) + + +def test_jar_must_end_in_jar(sandbox, monkeypatch): + root, pathdir, tmp = sandbox + notjar = make(tmp / "plantuml.zip", "jar") + monkeypatch.setenv("PLANTUML_JAR", str(notjar)) + with pytest.raises(ToolNotFoundError, match="PLANTUML_JAR"): + tools.resolve_plantuml_jar() + + +@pytest.mark.parametrize("name", [n for n, s in SPECS.items() if s[4] == "exe"]) +def test_non_executable_file_rejected(sandbox, monkeypatch, name): + resolver, env_var, *_ = SPECS[name] + root, pathdir, tmp = sandbox + plain = make(tmp / "plainfile", "exe", executable=False) + with pytest.raises(ToolNotFoundError): + resolver(plain) + monkeypatch.setenv(env_var, str(plain)) + with pytest.raises(ToolNotFoundError, match=env_var): + resolver() + + +@pytest.mark.parametrize("name", [n for n in PROVISIONED_NAMES if SPECS[n][4] == "exe"]) +def test_non_executable_provisioned_rejected(sandbox, name): + root, pathdir, tmp = sandbox + make(root / ".tools" / SPECS[name][2], "exe", executable=False) + with pytest.raises(ToolNotFoundError, match=r"\.tools") as info: + SPECS[name][0]() + assert "re-run `uv run python scripts/provision-tools.py`" in str(info.value) + + +def test_directory_given_as_executable_rejected(sandbox): + root, pathdir, tmp = sandbox + (tmp / "adir").mkdir() + with pytest.raises(ToolNotFoundError): + tools.resolve_z3(tmp / "adir") + + +@pytest.mark.parametrize("name", SPECS) +def test_missing_tool_message_names_variable_and_provision_command(sandbox, name): + resolver, env_var, *_ = SPECS[name] + with pytest.raises(ToolNotFoundError) as info: + resolver() + message = str(info.value) + assert env_var in message + assert "uv run python scripts/provision-tools.py" in message + + +def test_empty_env_value_counts_as_unset(sandbox, monkeypatch): + root, pathdir, tmp = sandbox + monkeypatch.setenv("Z3", "") + expected = on_path(pathdir, "z3") + assert tools.resolve_z3() == expected + + +def test_tool_env_prepends_z3_dir_and_does_not_mutate_environ(sandbox, monkeypatch): + root, pathdir, tmp = sandbox + z3 = make(tmp / "z3dir" / "z3", "exe") + monkeypatch.setenv("Z3", str(z3)) + monkeypatch.setenv("SOME_MARKER", "1") + before = dict(os.environ) + env = tools.tool_env() + assert env["PATH"].split(os.pathsep) == [str(z3.parent), str(pathdir)] + assert env["SOME_MARKER"] == "1" + assert dict(os.environ) == before + assert os.environ["PATH"] == str(pathdir) + assert env is not os.environ + + +def test_tool_env_with_base_leaves_base_and_environ_alone(sandbox): + root, pathdir, tmp = sandbox + z3 = provisioned(root, "z3") + base = {"PATH": "/base/bin", "KEEP": "x"} + before_environ = dict(os.environ) + env = tools.tool_env(base) + assert env["PATH"] == str(z3.parent) + os.pathsep + "/base/bin" + assert env["KEEP"] == "x" + assert base == {"PATH": "/base/bin", "KEEP": "x"} + assert dict(os.environ) == before_environ + + +def test_tool_env_base_without_path(sandbox): + root, pathdir, tmp = sandbox + z3 = provisioned(root, "z3") + assert tools.tool_env({"A": "b"}) == {"A": "b", "PATH": str(z3.parent)} + + +def test_tool_env_raises_when_z3_unresolvable(sandbox): + with pytest.raises(ToolNotFoundError, match="Z3"): + tools.tool_env() + + +# --- java must run: the macOS /usr/bin/java stub passes the executable check but exits 1 ----------------- + +JAVA_HINT = "set the JAVA environment variable to a working java" +STUB_MESSAGE = "Unable to locate a Java Runtime." + + +def fake_java(path: Path, body: str) -> Path: + """An executable shell script at path with the given body; `counter` lines are appended per run.""" + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("#!/bin/sh\n" + body) + path.chmod(path.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) + return path + + +def counting_java(path: Path, counter: Path, tail: str) -> Path: + return fake_java(path, f'echo run >> "{counter}"\n{tail}') + + +def java_via(source: str, path: Path, pathdir: Path, monkeypatch): + """Return a zero-argument call that resolves `path` through the given source.""" + if source == "explicit": + return lambda: tools.resolve_java(path), "the explicit argument" + if source == "env": + monkeypatch.setenv("JAVA", str(path)) + return tools.resolve_java, "the JAVA environment variable" + link = pathdir / "java" + link.symlink_to(path) + return tools.resolve_java, "PATH" + + +@pytest.mark.parametrize("source", ["explicit", "env", "path"]) +def test_working_java_resolves(sandbox, monkeypatch, source): + root, pathdir, tmp = sandbox + java = fake_java(tmp / "bin" / "java", 'echo "openjdk version" >&2\nexit 0\n') + resolve, _ = java_via(source, java, pathdir, monkeypatch) + resolved = resolve() + assert resolved.samefile(java) + + +@pytest.mark.parametrize("source", ["explicit", "env", "path"]) +def test_failing_java_raises_with_stderr_and_hint(sandbox, monkeypatch, source): + root, pathdir, tmp = sandbox + java = fake_java(tmp / "bin" / "java", f'echo "{STUB_MESSAGE}" >&2\necho second line >&2\nexit 1\n') + resolve, where = java_via(source, java, pathdir, monkeypatch) + with pytest.raises(ToolNotFoundError) as info: + resolve() + message = str(info.value) + assert where in message + assert "java did not run" in message + assert STUB_MESSAGE in message + assert "second line" not in message + assert JAVA_HINT in message + + +def test_failing_java_without_stderr_reports_exit_status(sandbox): + root, pathdir, tmp = sandbox + java = fake_java(tmp / "bin" / "java", "exit 3\n") + with pytest.raises(ToolNotFoundError, match="exit status 3") as info: + tools.resolve_java(java) + assert JAVA_HINT in str(info.value) + + +def test_hanging_java_raises(sandbox, monkeypatch): + root, pathdir, tmp = sandbox + monkeypatch.setattr(tools, "_JAVA_PROBE_TIMEOUT", 0.3) + java = fake_java(tmp / "bin" / "java", "exec /bin/sleep 30\n") + with pytest.raises(ToolNotFoundError) as info: + tools.resolve_java(java) + message = str(info.value) + assert "java did not run" in message + assert "did not finish" in message + assert JAVA_HINT in message + + +def test_unrunnable_java_oserror_raises(sandbox): + root, pathdir, tmp = sandbox + # Executable bit set, but the kernel cannot exec it (no shebang, not a binary): OSError from subprocess. + java = tmp / "bin" / "java" + java.parent.mkdir() + java.write_bytes(b"\x00\x01\x02 not a program\n") + java.chmod(java.stat().st_mode | stat.S_IXUSR) + with pytest.raises(ToolNotFoundError) as info: + tools.resolve_java(java) + assert "java did not run" in str(info.value) + assert JAVA_HINT in str(info.value) + + +def test_successful_probe_is_cached(sandbox): + root, pathdir, tmp = sandbox + counter = tmp / "counter" + java = counting_java(tmp / "bin" / "java", counter, "exit 0\n") + for _ in range(3): + assert tools.resolve_java(java) == java + assert counter.read_text().splitlines() == ["run"] + + +def test_failed_probe_is_not_cached(sandbox): + root, pathdir, tmp = sandbox + counter = tmp / "counter" + java = counting_java(tmp / "bin" / "java", counter, f'echo "{STUB_MESSAGE}" >&2\nexit 1\n') + for _ in range(2): + with pytest.raises(ToolNotFoundError): + tools.resolve_java(java) + assert counter.read_text().splitlines() == ["run", "run"] + # Once java is fixed, the next call re-probes and succeeds. + counting_java(java, counter, "exit 0\n") + assert tools.resolve_java(java) == java + assert counter.read_text().splitlines() == ["run", "run", "run"] + + +def test_other_resolvers_do_not_probe(sandbox): + root, pathdir, tmp = sandbox + counter = tmp / "counter" + z3 = counting_java(tmp / "bin" / "z3", counter, "exit 1\n") + assert tools.resolve_z3(z3) == z3 + assert not counter.exists()