From e6e928be5cdd134648a5e698421c6b260a50f769 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 14:07:16 -0400 Subject: [PATCH 01/61] Spec and Phase A plan: make the book publishable on GitHub Pages --- ...026-10-03-pages-publishing-phase-a-plan.md | 249 ++++++++++++++++++ .../2026-10-03-pages-publishing-design.md | 76 ++++++ 2 files changed, 325 insertions(+) create mode 100644 docs/superpowers/plans/2026-10-03-pages-publishing-phase-a-plan.md create mode 100644 docs/superpowers/specs/2026-10-03-pages-publishing-design.md diff --git a/docs/superpowers/plans/2026-10-03-pages-publishing-phase-a-plan.md b/docs/superpowers/plans/2026-10-03-pages-publishing-phase-a-plan.md new file mode 100644 index 0000000..78f439c --- /dev/null +++ b/docs/superpowers/plans/2026-10-03-pages-publishing-phase-a-plan.md @@ -0,0 +1,249 @@ +# Pages Publishing Implementation Plan (Phase A: discovery; Phase B outline) + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. This repo's own harness applies: every task is a CONTRACT per `decisions/work-contract-template.md`, run through `decisions/task-states.md`, author and reviewer on different pinned models, worktree per task created by the orchestrator, plain commits (no trailers), subagents never merge or push. + +**Goal:** Find out exactly what stops the book from building and publishing on a clean GitHub Pages pipeline, and fix it, so a green pull-request build on a clean runner yields the published site. + +**Architecture:** Two phases, as in the diagram-text-integration and judgment-record-store initiatives. Phase A is read-mostly discovery on a clean machine, a real `ubuntu-latest` runner, and a content scan; its output is `decisions/pages-publishing-survey.md`. Phase B (outlined here, specified as full contracts in a second plan once the survey exists) adds a tool resolver, removes hard-coded paths, provisions the toolkit by pinned download, adds the CI build and Pages deploy, and cleans dangling references. + +**Tech Stack:** MyST (`mystmd` 1.11.0), `uv`, Python >= 3.12, `opensysml==0.9.0`, sysml-toolkit `sysmlv2` release binary, GitHub Actions (`actions/upload-pages-artifact`, `actions/deploy-pages`), Graphviz, Java + PlantUML. + +**Spec:** `docs/superpowers/specs/2026-10-03-pages-publishing-design.md` (read it first; decisions 1-6 and Q1-Q3 apply to every task). + +## Global Constraints + +- Notebook edits follow the minimal-diff standard (DL-099): patch only the named cells' `source`; never `jupyter nbconvert --execute --inplace` a tracked notebook; run notebooks only in throwaway copies. +- Commit messages are plain: no `Co-Authored-By` or any trailer. Subagents do not push, merge or open PRs. +- No PDF or secret is ever committed. Downloaded tool binaries stay in scratch directories or caches, never in the repo. +- Phase A changes no learner-facing file. Its only tracked outputs are the findings files named in each contract and one throwaway workflow file on the branch `pub/diagnose`. +- Do not invent URLs, versions or hashes: every one is read from the live source and recorded with how it was obtained. +- Python commands are run with `uv run`; the repo's checks (`uv run pytest tests/ glossary/tests/ -q`, `uv run python -m glossary check`, `uv run python scripts/check_construction.py --check`) must stay clean at every merge. + +## Review Focus + +1. A clean runner has no `~/Documents/GitHub/...` and no `/opt/homebrew/...`: every notebook cell that names one must be found, not only the ones already known (Ch5-03 cell 16, Ch8-02 cells 11/13/15, Ch10-01 cells 25/46/48, `exercises/ch08`). +2. A figure cell with empty stored output silently produces nothing if the site is built without executing; the site-level figure count is the check, not "build exited 0". +3. The release binary may differ from Z's local build (`0.9.1-1-gaf839f0`), changing verdict strings that chapter prose quotes; equivalence must be measured, not assumed. +4. `BASE_URL=/toaster` changes every asset and internal link; absolute `/...` links and local `../../docs/glossary.md#...` links must be checked on the built site. +5. The first Pages deploy exposes everything in the build to the public: a scan of the built HTML for local paths, usernames and internal-only text is a release check. + +--- + +## Task 1: Clean-checkout dry run on Z's machine (PA-1) + +**Files:** Create `decisions/pages-publishing/a1-clean-checkout.md` (findings). Scratch outside the repo. + +**Interfaces:** Produces the failure list for tasks that follow; consumes nothing. + +``` +CONTRACT PA-1 | 2026-10-03 +Role: builder, model claude-sonnet-5, effort medium +Reviewer: reviewer, model claude-opus-5-5 +State: ready +Task: Reproduce, as closely as possible on this Mac, what a contributor or CI sees on a clean + machine, and record exactly what breaks. Do not fix anything. +Context: docs/superpowers/specs/2026-10-03-pages-publishing-design.md; docs/setup.md; ci.yml; + scripts/check-tools.py; src/toaster/bootstrap.py. +Non-goals: No edits to any tracked file except the findings file. No downloads into the repo. +Blast zone: decisions/pages-publishing/a1-clean-checkout.md -- on branch pub/a1 in worktree + .claude/worktrees/pub-a1. All experiments run in a scratch dir from `mktemp -d`. +Acceptance: The findings file contains, from one real run of this sequence: + T=$(mktemp -d); git clone --no-local /Users/z/Documents/GitHub/toaster $T/repo + mkdir $T/home; cd $T/repo + env HOME=$T/home uv sync --locked + env HOME=$T/home uv run python scripts/check-tools.py + npm ci + env HOME=$T/home BASE_URL=/toaster npx myst build --html --execute 2>&1 | tee $T/build.log + (verify the flags with `npx myst build --help` first; if `--html --execute` is not valid, + use the documented equivalent and say so). Report: (a) wall time of each step; (b) every + notebook that fails and the first error line of each failing cell, with notebook path and + cell id; (c) the count of warnings by type; (d) from the built `_build/html`: number of + or inline figures per page for pages that have figure cells, total figures, and + any page whose figure cell produced no figure; (e) `grep -rIl` of the built tree for + "/Users/", "/opt/homebrew", "Documents/GitHub" (list files and counts); (f) a link check of + internal links under BASE_URL=/toaster (use a small script; list broken ones). Also run + the same build once more with HOME unset to the real home and PATH containing the toolkit + (i.e. Z's normal environment) to confirm the baseline builds cleanly, so failures are + attributable to the clean environment. Commands and outputs quoted, not summarized. +Premises: The hard-coded paths named in the spec (verify each by grep before the run and list every + occurrence of Path.home(), "/opt/homebrew" and "Documents/GitHub" in chapters/ and + exercises/ with notebook, cell id and line). +Questions to: the orchestrator +Report: branch and commit, model run on, the findings file path, every flagged-not-fixed item. +``` + +- [ ] **Step 1:** Orchestrator creates worktree: `git worktree add .claude/worktrees/pub-a1 -b pub/a1 main`; writes the contract above to `CONTRACT.md` in it. +- [ ] **Step 2:** Dispatch builder; wait for report. +- [ ] **Step 3:** Dispatch reviewer (different model): re-run the grep inventory and the step-(d) figure count from the findings' own commands on a fresh scratch checkout, and confirm at least three failing cells reproduce. +- [ ] **Step 4:** Merge on PASS; remove worktree and branch. + +## Task 2: Real-runner diagnostic workflow (PA-2) + +**Files:** Create `.github/workflows/pages-diagnose.yml` on branch `pub/diagnose` only (never merged to main). + +**Interfaces:** Produces the authoritative result for ubuntu-latest; consumes nothing from Task 1 (runs in parallel). + +``` +CONTRACT PA-2 | 2026-10-03 +Role: builder, model claude-sonnet-5, effort medium +Reviewer: reviewer, model claude-opus-5-5 +State: ready +Task: Write a throwaway GitHub Actions workflow that builds the UNMODIFIED book on a clean + ubuntu-latest runner, so the real failures are measured before any fix. To let unmodified + notebooks find their hard-coded paths, the workflow creates those exact paths as symlinks + to freshly provisioned tools (this is deliberate and only for diagnosis). +Context: Spec "Context" items 3 and 4; Ch5-03 cell 16, Ch8-02 cells 11/13/15, Ch10-01 cells + 25/46/48 for the exact paths. sysml-toolkit release v0.9.1 asset + sysmlv2-0.9.1-x86_64-unknown-linux-gnu.tar.gz and its SHA256SUMS + (https://github.com/Open-MBEE/sysml-toolkit/releases/tag/v0.9.1). Library: + https://github.com/Systems-Modeling/SysML-v2-Release at commit + de1070ae8e79c21532b8004fc663d47b35d0e9fa, directory sysml.library. Existing steps to copy: + .github/workflows/ci.yml (uv 0.5.x, setup-node with .nvmrc, npm ci, graphviz). +Non-goals: Do not edit ci.yml or any other file. Do not add deploy steps. Do not put secrets or tokens + in the file. +Blast zone: .github/workflows/pages-diagnose.yml -- on branch pub/diagnose in worktree + .claude/worktrees/pub-diagnose. +Acceptance: The workflow has triggers `workflow_dispatch` and `push` limited to branch pub/diagnose. + Steps, in order: checkout; astral-sh/setup-uv; actions/setup-node (node-version-file + .nvmrc); `uv sync --locked`; `npm ci`; `sudo apt-get update && sudo apt-get install -y + graphviz openjdk-17-jre-headless plantuml`; download the v0.9.1 linux toolkit asset and + SHA256SUMS with curl, verify with `sha256sum -c` (fail the step on mismatch), extract the + `sysmlv2` executable; sparse-clone SysML-v2-Release and `git checkout` the pinned commit; + create these paths with `sudo mkdir -p` and `ln -sf` so unmodified notebooks work: + $HOME/Documents/GitHub/sysml-toolkit/target/release/sysmlv2, + $HOME/Documents/GitHub/sysml-toolkit/spec-refs/SysML-v2-Release/sysml.library, + /opt/homebrew/opt/plantuml/libexec/plantuml.jar (point at the apt plantuml jar: find it + with `dpkg -L plantuml | grep '\.jar$'`), and + /opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java (point at the real + java); `uv run python scripts/check-tools.py`; `BASE_URL=/toaster npx myst build --html + --execute 2>&1 | tee build.log` with `continue-on-error: true`; a final step that writes + to $GITHUB_STEP_SUMMARY the failing notebook/cell lines (grep the log) and the built + figure count (count `) + under that HOME. Then, cell by cell, compare each executed copy's code-cell outputs with + the tracked notebook's stored outputs. Report: per notebook, the number of cells whose + outputs differ, and for each differing cell the stored versus fresh text (quoted). Classify + each difference: cosmetic (whitespace, ordering, timing), verdict-changing (a verdict + string, count, status or proof result differs), or figure (compare the two SVGs by parsing + their node and edge labels, not bytes). State plainly whether v0.9.1 release is + equivalent for every claim chapter prose makes about these outputs (cite the prose + sentence when a difference touches one). Also run `sysmlv2 --version` for both binaries. +Premises: The five notebooks execute successfully with Z's local build first (run one baseline copy + and confirm zero error cells before comparing), so differences are attributable to the + binary. +Questions to: the orchestrator +Report: branch and commit, model run on, findings path, the asset name and sha256 used, every + verdict-changing difference, anything flagged and not fixed. +``` + +- [ ] **Step 1:** Create worktree `pub-a3` (branch `pub/a3`), write contract, dispatch builder, dispatch reviewer (re-run one notebook's comparison independently and re-verify the sha256), merge on PASS. + +## Task 4: Dangling-reference inventory (PA-4) + +**Files:** Create `decisions/pages-publishing/a4-dangling-references.md`. + +**Interfaces:** Produces the table that Phase B's reference-cleanup contract edits from. + +``` +CONTRACT PA-4 | 2026-10-03 +Role: builder, model claude-sonnet-5, effort low +Reviewer: reviewer, model claude-opus-5-5 +State: ready +Task: List every place the published site shows a reference to something that will not exist + on the site, and propose the fix for each. Read-only survey. +Context: The published page set is myst.yml's toc: docs/index, setup, reproducibility, glossary, + references, contributor, docs/case-studies/2026-09-30-energy-conservation-requirement-tie, + and every chapter index/notebook/conclusion. Internal artifacts that will not exist on + the site: DEFERRED.md entries (D-001..D-0nn), decisions/log.md entries (DL-nnn), + decisions/*.md, AGENTS.md, CLAUDE.md, .claude/ (skills, agents), standing-assumption + tags (SA-n), work-contract ids, toaster#n / OpenSysML#n issue numbers (these resolve on + GitHub and are acceptable if written as links), repo-relative paths. +Non-goals: Edit nothing but the findings file. Do not judge whether the content is correct. +Blast zone: decisions/pages-publishing/a4-dangling-references.md -- on branch pub/a4 in worktree + .claude/worktrees/pub-a4. +Acceptance: Scan, with a script committed into the findings file as a code block, ALL text a reader + sees: markdown cells, index.md and conclusion.md, docs pages in the toc, code comments and + string literals in code cells (readers see source), and stored output text. A table with + one row per occurrence: file | cell id or line | exact quoted sentence | internal artifact + | class | proposed fix. Classes: LINK (the reader benefits from the full text; propose the + exact github.com/Open-MBEE/toaster/blob/main/ URL and verify the path exists at + main), REWORD (process jargon; propose replacement wording that keeps the sentence's + meaning and stands alone), KEEP (intentional, e.g. docs/contributor.md describing how the + project works; say why). A count by class and by artifact type. Verify each LINK path + exists with `test -e` and each issue number resolves with `gh issue view`. List the + items where the class is ambiguous for Z (Spec Q2). +Premises: The earlier scan found these artifact types in learner content: SA-7, DL-070/071/072, + D-001/023/025/026/028/029/030/031/033, DEFERRED.md, AGENTS.md, decisions/ paths, "skill", + ".claude/", "orchestrator"; confirm and extend, do not assume the list is complete. +Questions to: the orchestrator +Report: branch and commit, model run on, findings path, counts, ambiguous items. +``` + +- [ ] **Step 1:** Create worktree `pub-a4` (branch `pub/a4`), write contract, dispatch builder, dispatch reviewer (independently re-scan one chapter and one docs page and compare to the table), merge on PASS. + +## Task 5: Survey compilation and Phase B specification (orchestrator) + +**Files:** Create `decisions/pages-publishing-survey.md`; later `docs/superpowers/plans/2026-10-03-pages-publishing-phase-b-plan.md`. + +- [ ] **Step 1:** Compile A1-A4 into the survey: per question, evidence and file reference; the failure list from the real runner; the pin recommendation (toolkit version and asset sha256, library commit); equivalence verdict; reference table with counts; list of decisions for Z/ACE. +- [ ] **Step 2:** Route judgment items to the ACE (reference policy, whether any verdict-changing difference changes chapter prose); ask Z the spec's open questions that Phase A did not settle. +- [ ] **Step 3:** Write the Phase B plan as full contracts (no placeholders), using the survey's exact paths, hashes and cell ids; log a DL entry; ask Z to review before Phase B starts. + +--- + +## Phase B outline (to be written as full contracts after the survey; the shape is fixed now so the dependencies are visible) + +| Contract | Deliverable | Depends on | Blast zone (intended) | +|---|---|---|---| +| PUB-1 | `src/toaster/tools.py`: `resolve_sysmlv2()`, `resolve_library()`, `resolve_plantuml_jar()`, `resolve_java()` from env (`SYSMLV2_BINARY`, `SYSMLV2_LIB_DIR`, `PLANTUML_JAR`, `JAVA`), then PATH and platform locations, raising an error that names the variable and the provisioning command; unit tests with tmp paths | survey (A1, A2) | `src/toaster/tools.py`, `tests/test_tools.py` | +| PUB-2 | Replace every hard-coded path in the notebooks with PUB-1 calls (cells from A1's list); a test that fails if any notebook contains `Path.home()`, `/opt/homebrew` or `Documents/GitHub`; stored outputs unchanged | PUB-1 | the listed notebook cells, `tests/test_no_local_paths.py` | +| PUB-3 | Provisioning: `scripts/provision-toolkit.py` (or extension of `toaster.bootstrap`) downloads the pinned toolkit release asset for the platform, verifies the pinned sha256, fetches the pinned `sysml.library` commit, and prints the env exports; `check-tools.py` calls it; pins recorded in one file | PUB-1, A3 | `scripts/`, `src/toaster/bootstrap.py`, a pins file, tests | +| PUB-4 | CI: `ci.yml` build job executes the book (`BASE_URL=/toaster myst build --html --execute`), installs graphviz/java/plantuml, runs PUB-3; deploy job on `main` only with `upload-pages-artifact` and `deploy-pages`, `pages: write`, `id-token: write`, concurrency group; PRs build without deploying; built-site checks (figure count, no local paths, internal links) as a script run in CI | PUB-2, PUB-3, A2 | `.github/workflows/ci.yml`, `scripts/check-site.py` | +| PUB-5 | Dangling-reference edits from A4's table (LINK and REWORD rows), minimal diffs, outputs untouched | A4, Z/ACE policy | listed pages and notebook cells | +| PUB-6 | Docs: `docs/setup.md` and `README.md` publishing section (how the site builds, the env vars, how to preview with `BASE_URL`), `DEFERRED.md` entry recording the toolkit dependency of Ch5/8/10, DL entries | PUB-4 | `docs/setup.md`, `README.md`, `DEFERRED.md`, `decisions/log.md` | +| PUB-7 | First deploy: Z enables Pages (Settings, Pages, Source: GitHub Actions); orchestrator merges the PR, watches the deploy run, then runs the built-site checks against the live URL | PUB-4..6, Z | none (verification) | + +Merge order: PUB-1, then PUB-2 and PUB-3 in parallel, then PUB-4, PUB-5 in parallel with PUB-4, then PUB-6, then PUB-7. diff --git a/docs/superpowers/specs/2026-10-03-pages-publishing-design.md b/docs/superpowers/specs/2026-10-03-pages-publishing-design.md new file mode 100644 index 0000000..ee5c0a1 --- /dev/null +++ b/docs/superpowers/specs/2026-10-03-pages-publishing-design.md @@ -0,0 +1,76 @@ +# Publishing the book to GitHub Pages: design + +**Date:** 2026-10-03 +**Status:** draft for Z's review (architectural path: spec, then plan, then contracts) +**Plan:** `docs/superpowers/plans/2026-10-03-pages-publishing-phase-a-plan.md` + +## Context + +Z asked whether the repo is ready to publish as a GitHub Pages site. The readiness review (this session) found +the content is publishable but the build and deploy path is not: + +1. No Pages pipeline. Pages is not enabled on `Open-MBEE/toaster`; `.github/workflows/ci.yml` runs tests only, and + its deploy job is `if: false` ("WP-8 placeholder"). The site has only ever been built on Z's machine + (`npx myst start --execute`). +2. Figures exist only when notebooks execute. 17 of 18 figure cells have empty stored outputs, by repo + convention (diagram cells stay `execution_count: null`, `outputs: []`; the minimal-diff rule, DL-099, forbids + whole-notebook re-execution). A build that publishes stored outputs without executing would drop nearly every + diagram. +3. Executing needs a toolchain CI does not have: the OpenSysML binary (`scripts/check-tools.py` provisions it), + Graphviz (CI installs it), and, for Ch5-03, Ch8, Ch10 and `exercises/ch08`, a `sysmlv2` binary from + sysml-toolkit, its `sysml.library`, a PlantUML jar and a `java` executable. +4. Those notebooks hard-code locations: `Path.home() / "Documents/GitHub/sysml-toolkit/target/release/sysmlv2"`, + `.../spec-refs/SysML-v2-Release/sysml.library`, `/opt/homebrew/opt/plantuml/libexec/plantuml.jar`, + `/opt/homebrew/opt/openjdk/.../bin/java`. They fail on any other machine and publish the author's directory + layout. `toaster.modelcheck` already accepts `SYSMLV2_BINARY` / `SYSMLV2_LIB_DIR`; `toaster.render. + render_toolkit_interconnection` deliberately accepts only explicit paths. +5. Learner pages cite internal artifacts that will not exist on the site: `SA-7`, `DL-070..072`, `D-001..D-033`, + `DEFERRED.md`, `AGENTS.md`, `decisions/...` paths. +6. The site will live at `https://open-mbee.github.io/toaster/` (a subpath); nothing sets `BASE_URL`. + +Facts established: sysml-toolkit (public, Apache-2.0) publishes prebuilt `sysmlv2` binaries per release with a +`SHA256SUMS` (v0.9.1: x86_64-unknown-linux-gnu, aarch64/x86_64-apple-darwin, ...). Z's local binary is `sysmlv2 0.9.1` +built from `v0.9.1-1-gaf839f0`. The `sysml.library` is the `spec-refs/SysML-v2-Release` submodule +(`Systems-Modeling/SysML-v2-Release`, commit `de1070ae8e79c21532b8004fc663d47b35d0e9fa`). Docker and +`gh` with the `workflow` scope are available on Z's machine. + +## Decisions + +1. **Execute the book in CI.** The published site is built with `myst build --html --execute`. Not "publish stored + outputs": that drops the figures, and committing executed outputs would break the minimal-diff convention. +2. **One tool resolver.** A single module (`toaster.tools`) resolves the `sysmlv2` binary, its library, the + PlantUML jar and `java` from environment variables, then PATH and platform-known locations, and fails with + an actionable message. Notebooks call it; no notebook names a home or Homebrew path. Env names: + `SYSMLV2_BINARY`, `SYSMLV2_LIB_DIR` (both already used by `modelcheck`), `PLANTUML_JAR`, `JAVA`. +3. **Provision by pinned download.** Locally and in CI the toolkit comes from its GitHub release, verified against + the release `SHA256SUMS` pinned in this repo; the library from the pinned SysML-v2-Release commit. Nothing is + compiled from Rust in CI. Phase A decides the exact version (v0.9.1 expected, since the DEFERRED findings were + made on 0.9.1) after checking the release binary reproduces the stored outputs. +4. **Deploy only from `main`.** Pull requests run the full build (so a broken book fails review) but do not + deploy. Deploy uses the official `actions/upload-pages-artifact` / `actions/deploy-pages` flow with + `BASE_URL=/toaster`. +5. **Dangling references:** each learner-visible reference to an internal artifact is either linked to its GitHub + file (stable `blob/main` URL) or reworded so the page stands alone. Phase A inventories and classifies them; + Z picks the policy for the ambiguous classes. +6. **Z-only step:** enabling Pages (Settings, Pages, Source: GitHub Actions) is a repository admin action; it is + the last step, after a green PR build. + +## Non-goals + +No chapter content changes beyond the reference cleanup; no custom domain; no versioned docs; no change to which +notebooks are in the book; no Rust build in CI; no change to the minimal-diff or glossary rules. + +## Open questions for Z (answered at plan review, defaults given) + +- Q1. CI time: executing all notebooks plus provisioning may take several minutes per run. Acceptable? Default yes. +- Q2. Dangling references to `DEFERRED.md` and `decisions/`: link to GitHub files, or reword to stand alone? + Default: link when the reader benefits from the full text (deferred-gap entries), reword when it is process + jargon (`SA-7`, `DL-nnn`). +- Q3. Should the book repo expose the agent roles page (`docs/contributor.md` describes `.claude/agents/`)? + Default: yes, it is accurate and the project is public. + +## Verification + +A pull-request build on a clean `ubuntu-latest` runner executes every notebook with zero error cells and +produces the site; the built site's figure count equals the figure count of the local build; no built page +contains `/Users/`, `/opt/homebrew`, or `Documents/GitHub`; all internal links resolve with `BASE_URL=/toaster`. From c37793e23cfc39065d3eba85d7495d4cb96d3fb4 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 14:18:01 -0400 Subject: [PATCH 02/61] PA-3: measure release v0.9.1 binary output equivalence on toolkit-dependent notebooks --- .../pages-publishing/a3-output-equivalence.md | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 decisions/pages-publishing/a3-output-equivalence.md diff --git a/decisions/pages-publishing/a3-output-equivalence.md b/decisions/pages-publishing/a3-output-equivalence.md new file mode 100644 index 0000000..d285bd4 --- /dev/null +++ b/decisions/pages-publishing/a3-output-equivalence.md @@ -0,0 +1,130 @@ +# PA-3: does the published v0.9.1 release binary reproduce the toolkit-dependent notebook outputs? + +Date: 2026-10-03. Builder: claude-sonnet-5 (effort medium). Base: main at e6e928b (branch pub/a3). +Nothing tracked other than this file was changed. All runs were on throwaway copies of the repository +in a scratch directory. + +## Verdict + +Equivalent. For all five notebooks, executing with the release binary produced code-cell outputs +identical to the outputs from Z's locally built binary (zero differing cells, SVG bytes included), and +identical to every output stored in the tracked notebooks once adjacent stdout stream chunks are joined. +There is no verdict-changing difference. Every quoted verdict in these notebooks (satisfied, violated, +undecided, `[holds]`, `[FAILS]`, the Z3 witness values, the port-type check results) is reproduced +exactly. Scope limit: this was measured on macOS arm64 only; see Flags. Any CI check that compares raw nbformat stream outputs must first join adjacent same-stream chunks, or it will report false diffs. + +## What was run + +| Item | Value | +|---|---| +| Release asset | `sysmlv2-0.9.1-aarch64-apple-darwin.tar.gz` (machine: `uname -m` = arm64) | +| Asset sha256 | `ad0204041c95ce9817d398420e5057132a1378d53a812172cbd207cc40100c4a` | +| Verification | `shasum -a 256 -c SHA256SUMS --ignore-missing` printed `sysmlv2-0.9.1-aarch64-apple-darwin.tar.gz: OK` | +| Release binary sha256 (extracted) | `af1c32f5108c3a671df0a38afd1b6264ca5ad0a8a215c36cc1d374d530957b4f` | +| Z's local binary sha256 | `c21982704fd23d612df605d48f065bf3b64fd04b49368a81aa12a1642c6cd126` (different bytes, as expected) | +| `sysmlv2 --version`, local build | `sysmlv2 0.9.1` (sysml-toolkit checkout at `af839f0`, which is v0.9.1 plus one merge commit; `git describe`: `v0.9.1-1-gaf839f0`) | +| `sysmlv2 --version`, release | `sysmlv2 0.9.1` | +| Library | SysML-v2-Release at `de1070ae8e79c21532b8004fc663d47b35d0e9fa` (shallow fetch; HEAD verified). Z's `spec-refs/SysML-v2-Release` is at the same commit, so the library was held constant and only the binary differed. | + +Scratch HOME: `$HOME/Documents/GitHub/sysml-toolkit/target/release/sysmlv2` pointed at the release +binary (through a logging wrapper that `exec`s it) and +`$HOME/Documents/GitHub/sysml-toolkit/spec-refs/SysML-v2-Release/sysml.library` was a symlink to the +fetched library. The wrapper log proves the release binary served 9 CLI calls in the five notebooks: +`viz` once (ch05), and `verify` eight times (ch08/02 six times, ch10 twice). Notebooks were executed with +nbclient (kernel python3 from the repo venv, `PYTHONPATH` set to the copy's `src`, cwd set to the notebook's directory). + +Baseline (premise): all five notebooks executed with Z's local build with zero error cells. +Release run: all five executed with zero error cells. + +## Premise that did not fully hold: most ch05 stored outputs are empty + +The contract compares with "the tracked notebook's stored outputs". Stored outputs are absent in some +cells, so those cells cannot be compared to a stored value: + +- `ch05-architecture/03-interfaces.ipynb`: no code cell has stored outputs at all (cells 2, 4, 6, 8, 12, 14, 16 + are empty in the tracked file). +- `ch08-checking/01-assert-constraint-def.ipynb` cell 5 and `ch10.../01-traceability-graph.ipynb` cell 6 + (the Graphviz structure figures, which do not use the toolkit binary) have no stored output. + +For these, the substitute stored artefacts are the tracked figure files: the ch05 cell 16 run rewrote +`figures/ch05-interconnection.puml` and `figures/ch05-interconnection.svg`, and the ch08 and ch10 cells +rewrote `figures/ch08-structure.svg` and `figures/ch10-structure.svg`. The wrapper log was cleared, the two ch05 +files deleted, and the notebook re-run to confirm they are regenerated by the release binary (`sysmlv2 viz ... --view +interconnection --element ToasterDemo::Toaster`). All four regenerated files are byte-identical +(`cmp`) to the tracked ones. + +## Per notebook + +Counts are code cells whose outputs differ (after joining adjacent same-stream chunks; the unjoined +chunking differences are listed under Cosmetic). "Local vs release" is the attributable-to-binary measure. + +| Notebook | Code cells | Local vs release | Stored vs release (cells with stored output) | Notes | +|---|---|---|---|---| +| ch05/03-interfaces | 8 | 0 | 0 compared; all 8 code cells have no stored output (7 produce output when run) | figure files byte-identical (above) | +| ch08/01-assert-constraint-def | 7 | 0 | 0 (5 compared, 1 stored-empty) | binary not invoked by this notebook | +| ch08/02-violation-witness | 15 | 0 | 0 (13 compared) | | +| ch08/03-revision-flow | 4 | 0 | 0 (3 compared) | binary not invoked by this notebook | +| ch10/01-traceability-graph | 23 | 0 | 0 (21 compared, 1 stored-empty) | cells 25, 46, 48 all equal | + +### Cosmetic differences (stdout chunk boundaries only) + +Before joining chunks, some cells differ from the stored value in how the same text is split into stream +messages. The same split also appears with Z's local binary in one run, and it varied between two runs of the +same binary (the first release run showed cells 31 and 44, the second showed 29 and 44), so it is scheduling +noise in how the kernel batches stdout, not a property of either binary. The concatenated text is identical. +Example, ch10 cell 29, stored versus fresh (release): + +- stored (one stream message): `the lemma itself (...): assert satisfy energyConservationReq by deliveredEnergyBoundedBySupply;\nthe lemma's own free-standing usage (heatGenCheck): assert satisfy energyConservationReq by heatGenCheck;\nan unrelated real candidate (rated): assert satisfy energyConservationReq by rated;\n` +- fresh (two messages, same text): the first two lines, then `an unrelated real candidate (rated): assert satisfy energyConservationReq by rated;\n` + +This list is not exhaustive: chunk boundaries vary from run to run and were seen at other cells too, e.g. ch08/02 cells 11, 13, 15 and +ch10 cells 29, 31, 44 in the reviewer's release run (and ch08/02 cell 15 and ch10 cells 20, 29, 31, 44 in my local-build run). No other +kind of cosmetic difference (whitespace, ordering, timing) was found. Any CI check that compares raw nbformat stream outputs must first +join adjacent same-stream chunks, or it will report false diffs. + +### Verdict-changing differences + +None. + +### Figure differences + +None. ch05 interconnection SVG from local and release runs: identical bytes (sha256 prefix `7efd253019a1`); +node/edge text labels (11 text elements): `«part def»`, `Toaster`, `«part»`, `heating : HeatingSystem`, `«part»`, +`control : ControlSystem`, `durationIn`, `: ~DurationPort`, `durationOut`, `: DurationPort`, +`«interface» durationInterface`. The PlantUML source is also byte-identical to the tracked +`figures/ch05-interconnection.puml`. The ch08 and ch10 structure SVGs (21 text labels each) are identical +bytes between local and release runs and to the tracked files; they are produced by `model_to_dot` plus +Graphviz, not by the toolkit binary. + +## Verdicts reproduced (examples; all equal to stored) + +- ch08/02 cell 11: `[satisfied] deliveredEnergyBoundedBySupply (z3: holds for all values of unbound features)` +- ch08/02 cell 13: `[violated] deliveredEnergyExceedsSupply (z3: unsatisfiable -- no assignment can make this hold)` +- ch08/02 cell 15: `[undecided] deliveredEnergyBoundedBySupply (result is indeterminate over unbound features -- z3: satisfiable, e.g. heatGenCheck.efficiency = 0, heatGenCheck.power = 0 [W], heatGenCheckDuration = 1 [s])` +- ch10 cell 46: `../../models/ch10-cumulative.sysml:323:9 deliveredEnergyBoundedBySupply (AssertConstraintUsage): satisfied (z3: holds for all values of unbound features)` +- ch10 cell 48: `companion-check-scratch/ch10_with_satisfy.sysml:323:9 c (ConstraintUsage, satisfies ToasterDemo::energyConservationReq): undecided (result is indeterminate over unbound features)` + +## Does chapter prose still hold? + +Because no output differs from the stored output, every prose claim that is true of the stored outputs is +true of the release run. No prose sentence is touched by a difference, so none is cited. For the ch05 cells that +have no stored output, the prose claim (that the toolkit draws the port names as their own boxes) is supported +by the tracked and regenerated figure being byte-identical. + +## Flags (not fixed) + +1. Platform scope. Only `aarch64-apple-darwin` was measured. GitHub Pages CI will use a Linux asset + (x86_64-unknown-linux-gnu or musl); this finding does not cover it. The most platform-sensitive claim is the + Z3 witness in ch08/02 cell 15 (`heatGenCheck.efficiency = 0, heatGenCheck.power = 0 [W], heatGenCheckDuration = 1 [s]`), + a model of a satisfiable formula, which a different Z3 build could in principle pick differently; and the + figures also depend on the PlantUML jar, Java and Graphviz on the CI runner, which this test did not vary. + Recommend repeating this measurement on the CI runner, or on a Linux container with the Linux asset. +2. Stored outputs are empty for all of ch05/03 and for two figure cells (see Premise above), so the stored-output + comparison there is vacuous and rests on the tracked figure files. +3. ch08/01 and ch08/03 never invoke the sysmlv2 binary (the wrapper log has no calls from them), so they are + "toolkit-dependent" only through the Python binding, which this test did not vary. +4. Local build is one merge commit past the v0.9.1 tag (`af839f0`, "Merge pull request #4 from Open-MBEE/release/v0.9.1"), + both report `sysmlv2 0.9.1`. +5. The notebooks hard-code `Path.home() / "Documents/GitHub/sysml-toolkit/..."`, `/opt/homebrew/...` PlantUML and + Java paths, so they cannot run unmodified on CI; the scratch HOME shim here worked only for the toolkit paths. + The existing stored outputs need not change; the path handling is a separate publishing task. From 5a9e2a064ea69db858e1077e3b0b778a0f963b80 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 14:26:48 -0400 Subject: [PATCH 03/61] A1: record clean-checkout reproduction of the Pages build (findings only) --- .../pages-publishing/a1-clean-checkout.md | 287 ++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 decisions/pages-publishing/a1-clean-checkout.md 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 From 0198bf6204c21903e4dfa42b6f3f6d6dbc65eadf Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 16:16:50 -0400 Subject: [PATCH 14/61] Allowlist the site-gate scripts in the no-local-paths guard (integration of PUB-3 and PUB-5) --- tests/test_no_local_paths.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/test_no_local_paths.py b/tests/test_no_local_paths.py index 062e873..dc07c52 100644 --- a/tests/test_no_local_paths.py +++ b/tests/test_no_local_paths.py @@ -11,9 +11,11 @@ (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 two things: this file (it has to spell the strings out), 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). +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 @@ -26,6 +28,11 @@ 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"' @@ -42,8 +49,8 @@ def find_forbidden(text: str) -> list[str]: def find_forbidden_in_py(path: Path, text: str) -> list[str]: - """As `find_forbidden`, for a .py file, honouring the two-item allowlist.""" - if path.resolve() == THIS_FILE: + """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( From 47485e805d449174f306b272c4de00718501459d Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 16:19:39 -0400 Subject: [PATCH 15/61] Make Chapters 1-4 stand alone: link exercises, models, scripts, deferred gaps and issues to GitHub (PUB-6A) --- chapters/ch01-system-purpose/01-abstract-def.ipynb | 4 ++-- chapters/ch01-system-purpose/02-part-def.ipynb | 2 +- chapters/ch01-system-purpose/03-specialization.ipynb | 2 +- chapters/ch01-system-purpose/04-composition.ipynb | 4 ++-- chapters/ch01-system-purpose/conclusion.md | 2 +- chapters/ch01-system-purpose/index.md | 2 +- chapters/ch02-requirements/01-requirement-def.ipynb | 4 ++-- chapters/ch02-requirements/02-assumptions.ipynb | 4 ++-- chapters/ch02-requirements/03-judgment-context.ipynb | 6 +++--- chapters/ch02-requirements/conclusion.md | 2 +- chapters/ch02-requirements/index.md | 2 +- chapters/ch03-measures/01-moe-definition.ipynb | 4 ++-- chapters/ch03-measures/02-mop-candidate-eval.ipynb | 4 ++-- chapters/ch03-measures/03-threshold-judgment.ipynb | 6 +++--- chapters/ch03-measures/04-verification-case.ipynb | 6 +++--- chapters/ch03-measures/conclusion.md | 2 +- chapters/ch03-measures/index.md | 2 +- chapters/ch04-functional-decomp/01-action-def-ffbd.ipynb | 6 +++--- chapters/ch04-functional-decomp/02-heating-refinement.ipynb | 2 +- chapters/ch04-functional-decomp/03-completeness-check.ipynb | 4 ++-- chapters/ch04-functional-decomp/conclusion.md | 2 +- chapters/ch04-functional-decomp/index.md | 2 +- 22 files changed, 37 insertions(+), 37 deletions(-) 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..f9e9d80 100644 --- a/chapters/ch01-system-purpose/index.md +++ b/chapters/ch01-system-purpose/index.md @@ -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..403abfd 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)." ] }, { @@ -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..c825b9d 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 OpenSysML 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..bfdc5b6 100644 --- a/chapters/ch03-measures/index.md +++ b/chapters/ch03-measures/index.md @@ -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..c09bdef 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 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](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 tool inconsistency 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..316fccd 100644 --- a/chapters/ch04-functional-decomp/conclusion.md +++ b/chapters/ch04-functional-decomp/conclusion.md @@ -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..5f6e9e2 100644 --- a/chapters/ch04-functional-decomp/index.md +++ b/chapters/ch04-functional-decomp/index.md @@ -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. From 9a33d91261345e628b39bac99d63fb68350a8f24 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 16:23:06 -0400 Subject: [PATCH 16/61] Make Chapters 5-8 stand alone: link repo files, exercises and deferred gaps on GitHub, reword setup labels --- chapters/ch05-architecture/01-model-navigation.ipynb | 4 ++-- chapters/ch05-architecture/02-allocate.ipynb | 4 ++-- chapters/ch05-architecture/03-interfaces.ipynb | 2 +- chapters/ch05-architecture/conclusion.md | 4 ++-- chapters/ch05-architecture/index.md | 4 ++-- .../01-subsystem-requirements.ipynb | 2 +- chapters/ch06-recursive-decomp/02-second-level.ipynb | 6 +++--- .../ch06-recursive-decomp/03-stopping-judgment.ipynb | 4 ++-- chapters/ch06-recursive-decomp/conclusion.md | 2 +- chapters/ch06-recursive-decomp/index.md | 4 ++-- chapters/ch07-execution/01-calc-energy.ipynb | 6 +++--- chapters/ch07-execution/02-state-traces.ipynb | 10 +++++----- chapters/ch07-execution/03-param-sweep.ipynb | 2 +- chapters/ch07-execution/conclusion.md | 4 ++-- chapters/ch07-execution/index.md | 6 +++--- chapters/ch08-checking/01-assert-constraint-def.ipynb | 6 +++--- chapters/ch08-checking/02-violation-witness.ipynb | 11 ++++++----- chapters/ch08-checking/03-revision-flow.ipynb | 6 +++--- chapters/ch08-checking/conclusion.md | 6 +++--- chapters/ch08-checking/index.md | 10 +++++----- 20 files changed, 52 insertions(+), 51 deletions(-) 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 c745a47..ab7f732 100644 --- a/chapters/ch05-architecture/03-interfaces.ipynb +++ b/chapters/ch05-architecture/03-interfaces.ipynb @@ -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..a51d179 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 (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](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..dda14bc 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 OpenSysML 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 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." ] }, { @@ -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. 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](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 tool 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." + "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](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-023-opensysml-does-not-resolve-state-machine-transition-trigger-names)). 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." ] }, { @@ -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 OpenSysML v0.9.0, `execute_state`'s `performer` argument has no effect on the result (a documented tool 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..5609125 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. 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](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..f596f86 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 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](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..752fedb 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." ] }, { @@ -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 73eba00..30f87db 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, 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](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." ] }, { @@ -247,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", @@ -504,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." ] }, { @@ -832,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..e1b0598 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: 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](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..1d829d9 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`: this toolchain 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. From 28e1b11d341b7302a9b59758c701c6659be6d898 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 16:19:30 -0400 Subject: [PATCH 17/61] Make Chapter 9 pages stand alone: link repo files, reword process references (PUB-6C) --- .../01-requirement-coverage.ipynb | 14 +++++++------- .../02-evidence-completeness.ipynb | 12 ++++++------ .../03-stale-detection.ipynb | 8 ++++---- chapters/ch09-coverage-sufficiency/conclusion.md | 6 +++--- chapters/ch09-coverage-sufficiency/index.md | 6 +++--- 5 files changed, 23 insertions(+), 23 deletions(-) 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). From 91194c33267bcdf2624b840d18e1fb068b1604b0 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 16:23:03 -0400 Subject: [PATCH 18/61] Chapter 10: make published pages stand alone (PUB-6D LINK and REWORD rows) --- .../01-traceability-graph.ipynb | 28 +++++++++---------- .../02-judgment-synthesis.ipynb | 10 +++---- .../03-engineering-signoff.ipynb | 12 ++++---- .../ch10-traceability-signoff/conclusion.md | 4 +-- chapters/ch10-traceability-signoff/index.md | 6 ++-- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/chapters/ch10-traceability-signoff/01-traceability-graph.ipynb b/chapters/ch10-traceability-signoff/01-traceability-graph.ipynb index 8cd8712..80dc580 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." ] }, { @@ -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." ] }, { @@ -530,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 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." ] }, { @@ -651,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", @@ -711,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." ] }, { @@ -719,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." ] }, { @@ -823,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." ] }, { @@ -1023,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 `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](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." ] }, { @@ -1088,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." ] }, { @@ -1288,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 definition, \"context or assumption is asserted to be appropriate for the argument elements it applies to\" (see the [asserted context](../../docs/glossary.md#asserted-context) glossary entry), 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." ] }, { @@ -1349,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`." ] }, { @@ -1357,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..2d27c7b 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." ] }, { @@ -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..1d91d57 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 @@ -24,4 +24,4 @@ This is the tutorial's last chapter. What continues from here is not another cha ## 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). From a6a59563a7b35655b3ea2e706ed8202959c5fec5 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 16:27:56 -0400 Subject: [PATCH 19/61] resolve_java: reject a java that does not run -version (macOS stub), cache successful probes --- src/toaster/tools.py | 50 ++++++++++++++++- tests/test_tools.py | 125 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+), 2 deletions(-) diff --git a/src/toaster/tools.py b/src/toaster/tools.py index 4ac8b3f..afd792a 100644 --- a/src/toaster/tools.py +++ b/src/toaster/tools.py @@ -15,6 +15,11 @@ exception is step 3, where an absent file just means "not provisioned" and resolution continues to step 4. An environment variable set to the empty string counts as unset. +`java` has one more check. A candidate that passes the executable test is run as `java -version` (10 s +timeout); one that does not run (macOS ships `/usr/bin/java` as a stub that exits 1 with "Unable to +locate a Java Runtime") raises `ToolNotFoundError`. A successful probe is cached per resolved path; +failures are not cached. + If nothing is found, `ToolNotFoundError` names the environment variable and the provisioning command. """ @@ -22,6 +27,7 @@ import os import shutil +import subprocess from collections.abc import Callable, Mapping from pathlib import Path @@ -29,6 +35,9 @@ _PROVISION_HINT = "uv run python scripts/provision-tools.py" +_JAVA_PROBE_TIMEOUT: float = 10.0 +_JAVA_PROBED_OK: set[Path] = set() + class ToolNotFoundError(RuntimeError): """Raised when a tool cannot be resolved, or a candidate for it is set but invalid.""" @@ -58,6 +67,7 @@ def _resolve( provisioned: str | None, which: str | None, kind: tuple[str, Callable[[Path], bool]], + probe: Callable[[Path, str], None] | None = None, ) -> Path: what, is_valid = kind @@ -66,6 +76,8 @@ def checked(path: Path, source: str, hint: str = "") -> Path: raise ToolNotFoundError( f"{tool} from {source} is {str(path)!r}, which is not {what}{hint}" ) + if probe is not None: + probe(path, source) return path if explicit is not None: @@ -95,6 +107,40 @@ def checked(path: Path, source: str, hint: str = "") -> Path: ) +def _probe_java(path: Path, source: str) -> None: + """Run `path -version`; raise `ToolNotFoundError` if java does not run. Cache successes only.""" + if path in _JAVA_PROBED_OK: + return + hint = "set the JAVA environment variable to a working java" + try: + result = subprocess.run( + [str(path), "-version"], + capture_output=True, + text=True, + timeout=_JAVA_PROBE_TIMEOUT, + check=False, + # posix_spawn instead of fork: a fork child runs gRPC's atfork handlers (gRPC is loaded by + # other parts of the tutorial) and those print to the child's stderr, which we report. + close_fds=False, + ) + except subprocess.TimeoutExpired: + raise ToolNotFoundError( + f"java from {source} is {str(path)!r}, but java did not run: " + f"`-version` did not finish within {_JAVA_PROBE_TIMEOUT:g} s; {hint}" + ) from None + except OSError as error: + raise ToolNotFoundError( + f"java from {source} is {str(path)!r}, but java did not run: {error}; {hint}" + ) from error + if result.returncode != 0: + lines = [line.strip() for line in result.stderr.splitlines() if line.strip()] + first = lines[0] if lines else f"exit status {result.returncode}, no stderr output" + raise ToolNotFoundError( + f"java from {source} is {str(path)!r}, but java did not run: {first}; {hint}" + ) + _JAVA_PROBED_OK.add(path) + + def resolve_sysmlv2(explicit: str | Path | None = None) -> Path: """The `sysmlv2` executable: explicit, `SYSMLV2_BINARY`, `.tools/bin/sysmlv2`, then PATH.""" return _resolve("sysmlv2", explicit, "SYSMLV2_BINARY", "bin/sysmlv2", "sysmlv2", _EXECUTABLE) @@ -111,8 +157,8 @@ def resolve_plantuml_jar(explicit: str | Path | None = None) -> Path: def resolve_java(explicit: str | Path | None = None) -> Path: - """The `java` executable: explicit, `JAVA`, then PATH (java is not provisioned).""" - return _resolve("java", explicit, "JAVA", None, "java", _EXECUTABLE) + """The `java` executable: explicit, `JAVA`, then PATH (java is not provisioned); it must run `-version`.""" + return _resolve("java", explicit, "JAVA", None, "java", _EXECUTABLE, _probe_java) def resolve_z3(explicit: str | Path | None = None) -> Path: diff --git a/tests/test_tools.py b/tests/test_tools.py index 237284d..cf84a20 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -41,6 +41,13 @@ def make(path: Path, kind: str, executable: bool = True) -> Path: 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.""" @@ -270,3 +277,121 @@ def test_tool_env_base_without_path(sandbox): 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() From 72451f970aed37f22c4d1b4ef33ea4f2c31d3fc7 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 16:21:01 -0400 Subject: [PATCH 20/61] Docs: deployment truth, uv run preview, tools section, link harness files, record who Z is (PUB-7) --- AGENTS.md | 2 + README.md | 17 ++- ...-30-energy-conservation-requirement-tie.md | 17 ++- docs/contributor.md | 101 +++++++++------ docs/references.md | 6 +- docs/reproducibility.md | 76 ++++++----- docs/setup.md | 122 ++++++++++++++---- 7 files changed, 230 insertions(+), 111 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index df85a68..7872f95 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: diff --git a/README.md b/README.md index 6ef96dc..17fd976 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 ``` See [docs/setup.md](docs/setup.md) for full setup instructions and the fork-and-exercise workflow, @@ -43,7 +52,7 @@ scripts/ — pre-flight and build utilities decisions/ — ACE decision log ``` -`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. diff --git a/docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md b/docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md index 3fccd21..7efc38b 100644 --- a/docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md +++ b/docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md @@ -3,7 +3,7 @@ **Date:** 2026-09-30 **Status:** resolved — direction adopted (B, revised); `assert satisfy` drop confirmed by direct test, not argument alone; implementation pending integration -**Related:** `decisions/next-passes.md` item 29; `decisions/log.md` DL-070, DL-071, DL-072 (and the entry recording this decision); SysML v2 formal/2026-03-02 §7.20–7.21, §7.24 +**Related:** [`decisions/next-passes.md`](https://github.com/Open-MBEE/toaster/blob/main/decisions/next-passes.md) item 29; [`decisions/log.md`](https://github.com/Open-MBEE/toaster/blob/main/decisions/log.md) DL-070, DL-071, DL-072 (and the entry recording this decision); SysML v2 formal/2026-03-02 §7.20–7.21, §7.24 ## Why this document exists @@ -13,22 +13,21 @@ settle on its own, using the spec's letter, the spec's expressed intent, two ind candidate models, live tool behavior, and a direct argument about what our own model actually means. The question itself — *does a formal tie between a proved property and a stated requirement actually say what we mean it to say* — is exactly the kind of judgment this tutorial -teaches (AGENTS.md: "judgment is the engineer's expertise, exercised with justification and never +teaches ([AGENTS.md](https://github.com/Open-MBEE/toaster/blob/main/AGENTS.md): "judgment is the engineer's expertise, exercised with justification and never eliminated"). This document is the full working, kept because the working is the point, not just the two lines of SysML it ends in. ## The problem Chapter 8 proves a real-arithmetic lemma, `deliveredEnergyBoundedBySupply` -(`models/ch08-cumulative.sysml`), for every value its unbound features admit, using +([`models/ch08-cumulative.sysml`](https://github.com/Open-MBEE/toaster/blob/main/models/ch08-cumulative.sysml)), for every value its unbound features admit, using `sysml-toolkit`'s Z3-backed `verify --solve`. Chapter 10's own traceability search — -`requirement_ties`/`tied_to_any_requirement` (`src/toaster/query.py`, finalized at DL-070/DL-071 -after several rounds of broadening and then deliberately narrowing) — correctly reports that this +`requirement_ties`/`tied_to_any_requirement` ([`src/toaster/query.py`](https://github.com/Open-MBEE/toaster/blob/main/src/toaster/query.py), finalized after several rounds of broadening and then deliberately narrowing) — correctly reports that this lemma is tied to no stated requirement at all. This was originally treated as intentional pedagogy: the inverse of Douglas's own "unjustified widget" concern (a design element with no requirement behind it), here a piece of formal evidence with no requirement behind *it*. -Z reconsidered this (chat, 2026-09-30): using something that plausibly *should* be tied as the +mzargham (Z) reconsidered this (chat, 2026-09-30): using something that plausibly *should* be tied as the worked example of something that is *not* tied is confusing. The decision was to add a real tie, and to preserve the "unjustified widget" pedagogy separately, with a freshly constructed fixture built specifically to be untied. @@ -61,10 +60,10 @@ subject-conformance *spirit*, found by re-reading the spec directly, not by any an `assert satisfy` line at all, so the OMG pilot had no live binding to flag on it, and running the pilot against Approach A's own committed model directly confirms 0 issues.** The pilot diagnostic ("Bound features should have conforming types") belongs to a different draft: -Approach B's *own first commit* (`26e184e`) paired a typed subject with an `assert satisfy +Approach B's *own first commit* paired a typed subject with an `assert satisfy energyConservationReq by deliveredEnergyBoundedBySupply;` line, and *that* combination is what the pilot actually flagged — confirmed directly against that commit's own model. B's author fixed it -two commits later (`56100bb`) by dropping the subject declaration. So two different defects, in two +two commits later by dropping the subject declaration. So two different defects, in two different places, were each found a different way: Approach A's (a declared-but-unused subject, no live binding) by direct spec reading; Approach B's own first draft's (a typed subject *plus* a real, type-inconsistent binding) by the pilot's own mechanical check. Neither tool nor either @@ -213,7 +212,7 @@ confirm this claim the way the tutorial has already taught them to — by runnin `verify_satisfaction()` — gets an error, not the pass a skim of the model would suggest. This also sharpens what `AC-C10`'s own cited evidence (`requirement_coverage(...)` reporting -`covered=True`) actually is. `requirement_coverage()` (`src/toaster/query.py:258`) never runs the +`covered=True`) actually is. `requirement_coverage()` ([`src/toaster/query.py`](https://github.com/Open-MBEE/toaster/blob/main/src/toaster/query.py)) never runs the constraint at all — it checks only whether a non-negated `SatisfyRequirementUsage` node *exists* in the API-JSON export. `AC-C10` already describes this carefully as "a point-evaluation claim about the assert satisfy declaration's own success, not the same claim as the solver output," which is diff --git a/docs/contributor.md b/docs/contributor.md index dab503d..31c79be 100644 --- a/docs/contributor.md +++ b/docs/contributor.md @@ -4,6 +4,15 @@ This page is for maintainers working on the tutorial itself, not learners workin It assumes you can read Python and SysML and that you have the environment from [Getting Started](setup.md) already set up. +## Who is Z + +"Z" is the contributor identity `mzargham` (Michael Zargham, GitHub user +[`mzargham`](https://github.com/mzargham)), the project's author and chief engineer. Project files +such as [`AGENTS.md`](https://github.com/Open-MBEE/toaster/blob/main/AGENTS.md) and the decision log in +[`decisions/`](https://github.com/Open-MBEE/toaster/tree/main/decisions) say "Z" for that person. Published pages that mention +"Z" name the handle at first mention, as "mzargham (Z)". Other contributors are named by their +own handles, and "Z" is never reused for anyone else. + ## How this repo is built, tested, and reviewed The tutorial's content — every chapter notebook, exercise, and model file — is built and @@ -11,27 +20,27 @@ reviewed through a small multi-agent harness that lives alongside the content it root files and two directories carry that harness, and they're worth knowing about before you touch anything, even if you never run an agent yourself: -- **`AGENTS.md`** states what the tutorial teaches (the functional/logical/physical layering, +- **[`AGENTS.md`](https://github.com/Open-MBEE/toaster/blob/main/AGENTS.md)** states what the tutorial teaches (the functional/logical/physical layering, 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`** is the entry point: read order, the glossary CLI, and the skill index below. -- **`DEFERRED.md`** tracks known gaps in the toolchain (OpenSysML, sysml-toolkit) that the +- **[`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. +- **[`DEFERRED.md`](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md)** tracks known gaps in the toolchain (OpenSysML, sysml-toolkit) that the tutorial works around — what the workaround is, why it's needed, and the condition under which it comes out once the upstream gap closes. -- **`.claude/agents/`** defines the roles that do the work: an `orchestrator` that turns a +- **[`.claude/agents/`](https://github.com/Open-MBEE/toaster/tree/main/.claude/agents)** defines the roles that do the work: an `orchestrator` that turns a request into scoped contracts and integrates results; `builder`/`reviewer` pairs that implement and independently check each change (always on different models, never the same one reviewing its own work); a `layer-auditor` that classifies model elements against the functional/logical/physical boundaries; a `simulated-learner` that executes a chapter as a persona-assigned reader and reports what it found; and the `ace`, which triages questions between the team and the tutorial's author, ruling where it can and escalating what it can't. -- **`.claude/skills/`** holds the how-to for each kind of work — the sub-notebook template and +- **[`.claude/skills/`](https://github.com/Open-MBEE/toaster/tree/main/.claude/skills)** holds the how-to for each kind of work — the sub-notebook template and pacing rules (`toaster-recipe`), the boundary tests for classifying a model element (`architecture-layers`), the glossary's own usage rules (`tutorial-glossary`), the simulated learner protocol (`user-testing`), and more — each one a reference an agent (or a human contributor) reads before doing that kind of work, not after. -- **`decisions/`** is the record of what was decided and why: `log.md` (the running decision +- **[`decisions/`](https://github.com/Open-MBEE/toaster/tree/main/decisions)** is the record of what was decided and why: `log.md` (the running decision log, one entry per substantive ruling or escalation), `work-contract-template.md` (the shape of a task handed to a builder or reviewer), and `task-states.md` (what state a task is in and what moves it to the next one). @@ -42,45 +51,45 @@ improvising a workflow from scratch. The sections below cover specific maintenan 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). +(deployment-status)= ## Deployment status -Deployment to GitHub Pages is deliberately disabled (`.github/workflows/ci.yml`, the `deploy` -job's `if: false`), not merely unfinished. It stays off until the tutorial has complete, -end-to-end content ready to publish. Enabling it is a decision the maintainer makes explicitly -when that bar is met, by removing the `if: false` guard, not something a passing build should -trigger on its own. - -Once enabled, the pipeline runs in seven steps, all defined in `.github/workflows/ci.yml`: - -1. Provision the environment (`uv sync --locked`, `npm ci`, `scripts/check-tools.py`) and - verify tool versions. -2. Execute every chapter notebook in a fresh kernel, with a timeout, excluding `exercises/`. -3. Assert expected outputs, diagnostics, negative controls, and review-record integrity. -4. Stage the executed notebooks, generated models, figures, and a provenance manifest. -5. Build the MyST site (`npx mystmd build`). -6. Check navigation, code, outputs, figures, and downloads render correctly under the - `/toaster` base path. -7. Deploy to Pages. This step alone carries the `pages: write` permission; the `build` job - does not. - -**Before enabling deployment, add a branch guard the current YAML does not have.** The -workflow triggers on both push-to-`main` and `pull_request` (`on:` at the top of the file), -and the `deploy` job's only gate today is `needs: build` plus the disabled `if: false`. Simply -removing `if: false` would let `deploy` run on a successful pull-request build too, not only on -`main`, which is very unlikely to be intended. Add `if: github.ref == 'refs/heads/main'` (or -equivalent) to the `deploy` job at the same time you remove `if: false`, not as a separate, -later fix. - -Enabling deployment, once that guard is in place: confirm the current `main` branch builds and -executes cleanly end to end (steps 1 through 6 above, run locally or via a scratch branch's CI -run), then push to `main`. +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). + +The `build` job provisions the tools with +[`scripts/provision-tools.py`](https://github.com/Open-MBEE/toaster/blob/main/scripts/provision-tools.py) +(see [Getting Started](#tools-for-chapters)), runs the test suite, builds the +book with every notebook executed, and then runs the release gate, +[`scripts/check-site.py`](https://github.com/Open-MBEE/toaster/blob/main/scripts/check-site.py). +The gate fails the build unless all five checks pass: + +1. the build log has no code-execution or Jupyter-session failure (MyST exits 0 even when a cell + raised, so the exit code alone proves nothing); +2. the built content has the expected number of figures, recorded in + [`scripts/site-baseline.json`](https://github.com/Open-MBEE/toaster/blob/main/scripts/site-baseline.json); +3. no built text file contains a host path (a home directory, a Homebrew prefix or a CI runner path); +4. MyST has not published `exercises/` files or `DEFERRED.md` as raw downloads under `build/`; +5. every root-relative link and asset in the HTML carries the `/toaster` base path and points at + a file that exists in the built site. + +To reproduce the build and the gate locally, from a clone with the tools provisioned: + +```sh +BASE_URL=/toaster uv run --frozen npx myst build --html --execute 2>&1 | tee build.log +uv run python scripts/check-site.py --site _build/html --content _build/site/content \ + --log build.log --base-url /toaster +``` ## 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`. -3. Rebuild the local preview (`npx mystmd start --execute`) and spot-check a chapter that +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 @@ -92,8 +101,8 @@ run), then push to `main`. 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` for how that invariant is checked. -3. Register the new notebooks in `scripts/check_construction.py`'s `CONSTRUCTION_NOTEBOOKS` + [`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 @@ -101,6 +110,7 @@ run), then push to `main`. 5. Get an independent review on a different model than whoever authored the chapter, per `decisions/task-states.md`'s merge gate. +(change-a-model-element)= ## Change a model element and review stale judgment records A `ReviewRecord`'s `content_hash` is computed from the model source it was written against. @@ -119,10 +129,15 @@ Changing that source without updating the record leaves it silently stale. ```sh uv sync --locked npm ci +uv run python scripts/provision-tools.py uv run python scripts/check-tools.py uv run pytest tests/ glossary/tests/ -v -npx mystmd start --execute +uv run npx mystmd start --execute ``` -This mirrors the `build` job's steps 1 through 6. It does not run step 7 (deploy); there is no -local equivalent, and there should not be, since publishing is a decision, not a build artifact. +The first five commands mirror the `build` job's provisioning and tests; the last serves the book +for preview rather than building it. To make a missing external tool fail the tests instead of +skipping them, prefix the pytest line with `TOASTER_REQUIRE_TOOLS=1`. To run the build itself and +the release gate, use the two commands under [Deployment status](#deployment-status). There is no +local equivalent of the `deploy` job, and there should not be, since publishing is a decision, not +a build artifact. diff --git a/docs/references.md b/docs/references.md index f11daa0..884686c 100644 --- a/docs/references.md +++ b/docs/references.md @@ -8,7 +8,7 @@ This page lists the primary sources this tutorial draws on. *Guide to the Systems Engineering Body of Knowledge (SEBoK)*, version 2.14. BKCASE / INCOSE / IEEE Computer Society / SERC. -The tutorial's conceptual source, cited first in this tutorial's citation order (AGENTS.md Part 1): the ideas behind functional, logical and physical architecture, MoE/MoP/TPM, allocation, and the "what/how/where" progression this tutorial refines. SEBoK itself nests the functional view inside the logical architecture (PDF 587, 593, 1554), which is why the tutorial's own logical/functional split is a recorded departure (`differsFrom`, approved by Z), not a restatement. +The tutorial's conceptual source, cited first in this tutorial's citation order ([AGENTS.md Part 1](https://github.com/Open-MBEE/toaster/blob/main/AGENTS.md)): the ideas behind functional, logical and physical architecture, MoE/MoP/TPM, allocation, and the "what/how/where" progression this tutorial refines. SEBoK itself nests the functional view inside the logical architecture (PDF 587, 593, 1554), which is why the tutorial's own logical/functional split is a recorded departure (a departure from SEBoK's use of the term, stated in the [logical architecture](glossary.md#logical-architecture) entry and approved by mzargham (Z)), not a restatement. ## Åström and Murray — Feedback Systems @@ -24,7 +24,7 @@ The canonical anchor for **policy**: a rule for choosing actions given states ( ## Brian Douglas — Systems Engineering: Managing System Complexity -A MATLAB Tech Talk series by Brian Douglas, published by MathWorks in 2020; the playlist lists five parts (verified 2026-09-26, `glossary/sources/notes/reading-notes.md`). Parts 3 and 4 both use a domestic toaster as the worked example and establish the engineering ground truth this tutorial re-implements in SysML v2 and Python. +A MATLAB Tech Talk series by Brian Douglas, published by MathWorks in 2020; the playlist lists five parts (verified 2026-09-26, [`glossary/sources/notes/reading-notes.md`](https://github.com/Open-MBEE/toaster/blob/main/glossary/sources/notes/reading-notes.md)). Parts 3 and 4 both use a domestic toaster as the worked example and establish the engineering ground truth this tutorial re-implements in SysML v2 and Python. **Part 3 — The Benefits of Functional Architectures** Brian Douglas. MathWorks, October 15, 2020. 14:24. @@ -64,7 +64,7 @@ The normative specification for all SysML v2 constructs used in this tutorial. C Open-MBEE/OpenSysML. -The Python library (`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 library's current API and the SysML v2 specification are tracked in [DEFERRED.md](../DEFERRED.md) and as issues in this repository and upstream. +The Python library (`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 library'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. --- diff --git a/docs/reproducibility.md b/docs/reproducibility.md index 4536e5e..bbc802b 100644 --- a/docs/reproducibility.md +++ b/docs/reproducibility.md @@ -7,49 +7,63 @@ and where that guarantee currently stops. It doesn't repeat the setup steps them ## What's pinned, and why that's most of the guarantee -Reproducing this tutorial's outputs depends on reproducing three things exactly: the Python -environment, the OpenSysML binary, and (only if you're building the rendered book) the Node -toolchain. +Reproducing this tutorial's outputs depends on reproducing four things exactly: the Python +environment, the OpenSysML binary, the external tools some chapters call, and (only if you're +building the rendered book) the Node toolchain. -- **Python dependencies** are pinned by `uv.lock`, installed with `uv sync --locked` (not +- **Python dependencies** are pinned by [`uv.lock`](https://github.com/Open-MBEE/toaster/blob/main/uv.lock), installed with `uv sync --locked` (not `uv sync`, which would let versions drift). Every chapter and every test runs against the exact versions recorded there. - **The OpenSysML binary** is pinned by version string (`v0.9.0` as of this tutorial), downloaded - by `scripts/check-tools.py` rather than resolved from a floating "latest." Every model-loading + by [`scripts/check-tools.py`](https://github.com/Open-MBEE/toaster/blob/main/scripts/check-tools.py) rather than resolved from a floating "latest." Every model-loading call in every notebook goes through this one pinned binary; there's no code path that reaches a different version. +- **The external tools** that chapters 5, 8 and 10 call (the `sysmlv2` command-line tool, Z3, the + PlantUML jar and the SysML v2 standard library) are pinned in + [`scripts/tool-pins.json`](https://github.com/Open-MBEE/toaster/blob/main/scripts/tool-pins.json) and installed by + `scripts/provision-tools.py`. The `sysmlv2` tool, Z3 and the PlantUML jar are pinned by version + and sha256 hash, and the script refuses to install a download whose hash does not match; the + standard library is pinned by git commit and checked out at exactly that commit, with no hash + (see [Getting Started](#tools-for-chapters)). Java and Graphviz come from your + system and are not pinned. - **Node dependencies**, if you're building the rendered book rather than just running notebooks, - are pinned by `package-lock.json` (`npm ci`, not `npm install`) and the Node version itself by - `.nvmrc`. This only affects how the book *looks*; it has no bearing on what any notebook computes. + are pinned by [`package-lock.json`](https://github.com/Open-MBEE/toaster/blob/main/package-lock.json) (`npm ci`, not `npm install`) and the Node version itself by + [`.nvmrc`](https://github.com/Open-MBEE/toaster/blob/main/.nvmrc). This only affects how the book *looks*; it has no bearing on what any notebook computes. -Given the same three pins, the same model source text should produce the same loaded model, the +Given the same pins, the same model source text should produce the same loaded model, the same diagnostics, and the same evaluated results, because nothing in the load-and-evaluate path reaches the network, reads wall-clock time, or depends on iteration order over an unordered collection. That's a design property of how the notebooks are written, not something this tutorial has independently verified by running the same notebook on multiple machines or operating systems side by side — stated as a claim about the code, not a measured guarantee across environments. -## What CI actually checks today +## What CI actually checks -`.github/workflows/ci.yml`'s `build` job runs the pinned-dependency install, `check-tools.py`, and -the full test suite (`pytest tests/ glossary/tests/`) on every push and pull request. It does **not** -yet execute the chapter notebooks or build the rendered book — those steps are scaffolded in the -workflow file but not active. Until they are, "the tests pass" and "every chapter notebook executes -cleanly end to end" are checked by different means: the former continuously in CI, the latter by -whoever re-derives or reviews a chapter, locally, as part of that chapter's own acceptance checks -(see `decisions/pass4-run-*.md` for what that's looked like in practice). `docs/contributor.md`'s -"Run the full CI pipeline locally" section gives the exact commands to run both together. +[`.github/workflows/ci.yml`](https://github.com/Open-MBEE/toaster/blob/main/.github/workflows/ci.yml) builds the book on every pull request. +The `build` job installs the pinned dependencies, provisions the external tools with +[`scripts/provision-tools.py`](https://github.com/Open-MBEE/toaster/blob/main/scripts/provision-tools.py), runs the full test suite +(`pytest tests/ glossary/tests/`), builds the rendered book with every chapter notebook executed, +and then runs the release gate, [`scripts/check-site.py`](https://github.com/Open-MBEE/toaster/blob/main/scripts/check-site.py). +The gate fails the build on any cell that raised an error, on a figure count that differs from the +recorded baseline, on a host path in any built file, on `exercises/` files or `DEFERRED.md` +published as raw downloads, and on a root-relative link or asset reference that does not carry the base path or does not +resolve (it reads `href`, `src`, `srcset`, `meta` content and CSS `url()` values; relative links +and `#fragment` links are not checked). It does not compare +each executed output with the output stored in the notebook, and it does not run the notebooks on +more than one operating system. The [Contributor Guide](#deployment-status) says how +to run the same build and gate locally. -Deployment to a published site is disabled outright (`docs/contributor.md`'s "Deployment status"), -so nothing about reproducibility here depends on a hosted build ever having existed; every notebook -output referenced anywhere in this tutorial was produced by running it locally, the same way a -reader would. +Pull-request builds do not deploy. A push to `main` that passes the same build is deployed to +. The stored notebook outputs in the repository were +produced locally; the published book is produced by CI executing the notebooks again, so a +reader of the site is looking at the CI run's output. Chapter-level acceptance runs from before CI +executed notebooks are recorded among [the run records in `decisions/`](https://github.com/Open-MBEE/toaster/tree/main/decisions). ## Judgment records are reproducible in a different sense: by evidence, not by re-running code A `ReviewRecord`'s `content_hash` is computed from the exact model source it was written against -(`docs/contributor.md`'s "Change a model element and review stale judgment records" describes the -mechanism). That hash doesn't make the *judgment* reproducible the way a computation is; it makes +(the [Contributor Guide](#change-a-model-element)'s section on +changing a model element describes the mechanism). That hash doesn't make the *judgment* reproducible the way a computation is; it makes the judgment's own evidentiary basis checkable: given the same model source and the same record, a reader can re-run the cited evaluation, re-read the cited assumptions, and reach their own view on whether the record's claim still holds. Every record in this tutorial is `record_kind: @@ -57,9 +71,14 @@ whether the record's claim still holds. Every record in this tutorial is `record settled, accepted conclusion, so nothing here claims a reproducibility that would require trusting a verdict rather than checking the evidence yourself. +Some record text quotes identifiers such as `D-029`. These refer to entries in +[`DEFERRED.md`](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md), the register of known +toolchain gaps; they are not links on the page because the record text is stored exactly as the +notebook wrote it. + ## Where the reproducibility guarantee currently stops -- **Cited source texts are pinned by hash, not distributed.** `glossary/sources/sources.ttl` +- **Cited source texts are pinned by hash, not distributed.** [`glossary/sources/sources.ttl`](https://github.com/Open-MBEE/toaster/blob/main/glossary/sources/sources.ttl) records a sha256 hash for each source this tutorial cites (SEBoK, the SysML v2 and KerML specs, Hawkins et al. 2011, and so on), but the PDFs themselves are gitignored, not committed, because most of them are under copyright the tutorial doesn't hold. `uv run python -m glossary check` @@ -69,13 +88,12 @@ verdict rather than checking the evidence yourself. obtain their own copy of that same source and check its hash against the recorded one; it is not something cloning this repository alone reproduces. - **A gap fixed upstream doesn't silently change what's here.** Where OpenSysML or sysml-toolkit - doesn't yet support something the spec allows, `DEFERRED.md` records the gap together with the + doesn't yet support something the spec allows, [`DEFERRED.md`](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md) records the gap together with the exact version it was found against (down to a commit hash, for the one case built from source rather than a tagged release). If a later version of either tool closes that gap, this tutorial's own behavior doesn't change until someone deliberately bumps the pinned version and updates the affected notebooks; the recorded gap is what tells a maintainer, later, exactly which patch to remove and why. -- **The rendered book isn't rebuilt automatically.** Because the notebook-execution and book-build - steps aren't yet part of CI (see above), a change to a notebook's own code doesn't automatically - re-verify that the book still renders correctly; that's a manual step today, tracked as its own - open item, not a silent gap in what's claimed. +- **The published book is one environment's run.** CI builds it on one Linux runner with the + pinned tools. That the same notebooks reproduce their stored outputs on other machines is a + property of how they are written (above), not something CI measures. diff --git a/docs/setup.md b/docs/setup.md index 2322f1a..18b2250 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -10,17 +10,22 @@ Node.js or npm; those are only for previewing the rendered book, covered further - Python 3.12 or later - [uv](https://docs.astral.sh/uv/) for Python dependency management - Graphviz (`dot` on your PATH): several chapters render diagrams with it +- Java 17 or later (`java` on your PATH): chapter 5 runs the PlantUML jar with it ```sh 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 ``` -`check-tools.py` verifies Graphviz is installed and downloads the OpenSysML binary this -tutorial's Python package connects to. It prints each tool's version; if anything is missing, -it names what to install. +`provision-tools.py` downloads the pinned external tools into `.tools/` (see +[Tools for chapters 5, 8 and 10](#tools-for-chapters) below). +[`check-tools.py`](https://github.com/Open-MBEE/toaster/blob/main/scripts/check-tools.py) +prints where each tool resolves and its version, verifies Graphviz is installed, and downloads +the OpenSysML binary this tutorial's Python package connects to. If anything is missing, it +names what to install. `uv sync --locked` also installs JupyterLab and the kernel this project uses (both are declared dependencies, not a separate install step). Open any chapter or exercise notebook with: @@ -35,28 +40,96 @@ Run the test suite to confirm the environment is working: uv run pytest tests/ -v ``` -The glossary's source checks (`uv run python -m glossary verify-sources`) also need the copyrighted source PDFs, which are not in the repository. The section "Getting the source files" in `glossary/README.md` says where to get each one and where to put it. +The glossary's source checks (`uv run python -m glossary verify-sources`) also need the copyrighted source PDFs, which are not in the repository. The section "Getting the source files" in [`glossary/README.md`](https://github.com/Open-MBEE/toaster/blob/main/glossary/README.md) says where to get each one and where to put it. ## Preview the rendered book locally -There is no published site yet: deployment stays off until the tutorial has complete, -end-to-end content ready to publish (see `docs/contributor.md`). Building it yourself, here, -is currently the only way to see the tutorial as a rendered book rather than as raw notebook -files. This needs Node.js in addition to the Python setup above. +CI builds this book on every pull request and deploys it from `main` to +; see [the contributor guide](#deployment-status). +To see it rendered from your own checkout, with your own changes, preview it locally. This needs +Node.js in addition to the Python setup above. -**Additional prerequisite:** Node.js 22 (see `.nvmrc`) and npm. +**Additional prerequisite:** Node.js 22 (see [`.nvmrc`](https://github.com/Open-MBEE/toaster/blob/main/.nvmrc)) and npm. ```sh -npm install -npx mystmd start --execute +npm ci +uv run npx mystmd start --execute ``` `--execute` runs every notebook and renders its real output. Without it, MyST renders the stored cell content only, and a freshly-cloned notebook has none, so every code cell appears with no output at all. +`uv run` is needed so that MyST finds this project's Jupyter, the one `uv sync --locked` +installed with the project's kernel. Without it, MyST looks for a Jupyter on your `PATH`, which +is usually a different installation and does not have the tutorial's dependencies. + +To build the static site the way CI does, rather than serve it, use the build form. The base +URL is the path the site is served under on GitHub Pages: + +```sh +BASE_URL=/toaster uv run --frozen npx myst build --html --execute +``` + Building and deploying the GitHub Pages site itself is a maintainer task, not something you -need for the tutorial; see `docs/contributor.md`. +need for the tutorial; see [the contributor guide](#deployment-status). + +(tools-for-chapters)= +## Tools for chapters 5, 8 and 10 + +Some chapters call external programs that are not Python packages. Chapters 5, 8 and 10 run the +`sysmlv2` command-line tool against the SysML v2 standard library; chapter 5 also draws its +interconnection diagram with PlantUML, and chapters 8 and 10 prove constraints with Z3 through +`sysmlv2 verify --solve`. One command downloads pinned versions of these into `.tools/`, a +directory git ignores: + +```sh +uv run python scripts/provision-tools.py +``` + +It installs the pinned `sysmlv2` command-line tool (sysml-toolkit), the Z3 solver, the PlantUML +jar and the SysML v2 standard library (`sysml.library`), as pinned in +[`scripts/tool-pins.json`](https://github.com/Open-MBEE/toaster/blob/main/scripts/tool-pins.json). +It checks the `sysmlv2` tool, Z3 and the PlantUML jar against a sha256 hash before installing +them; the library is pinned by git commit and checked out at exactly that commit, with no hash. +Re-running it keeps what is already installed and matches its pin. +`uv run python scripts/provision-tools.py --check` verifies the installed files without +downloading anything, and `--dest DIR` installs somewhere other than `.tools/`. Tools installed +with `--dest` elsewhere are found only if you apply the `export` lines the script prints at the +end (the environment variables in the table below); otherwise they are not seen. + +Two tools come from your system instead. **Java 17 or later** (`java`) runs the PlantUML jar, +and **Graphviz** (`dot`) lays out the other diagrams; install them with your package manager. + +Then verify: + +```sh +uv run python scripts/check-tools.py +``` + +It prints where each tool resolved from and its version, and exits non-zero, naming the +provisioning command, if one is missing. + +Each tool is found the same way: an explicit argument, then an environment variable, then +`.tools/`, then your `PATH` (the library and the jar are never searched for on `PATH`; Java is +never provisioned). Set a variable to use a tool installed somewhere else. A variable that names +a missing or invalid file is an error; it does not fall back to the next place. + +| Variable | Overrides | +|---|---| +| `SYSMLV2_BINARY` | the `sysmlv2` executable (default `.tools/bin/sysmlv2`, then `PATH`) | +| `SYSMLV2_LIB_DIR` | the `sysml.library` directory, which must contain a `Systems Library` folder (default `.tools/sysml.library`) | +| `PLANTUML_JAR` | the PlantUML jar (default `.tools/plantuml.jar`) | +| `JAVA` | the `java` executable (default: `PATH`) | +| `Z3` | the `z3` executable (default `.tools/bin/z3`, then `PATH`) | + +When a test needs one of these tools and cannot find it, it skips by default. Set +`TOASTER_REQUIRE_TOOLS=1` to make a missing tool a failure instead, for example before you +trust a green test run: + +```sh +TOASTER_REQUIRE_TOOLS=1 uv run pytest tests/ glossary/tests/ -v +``` ## The tools this tutorial uses, and why @@ -64,9 +137,9 @@ This tutorial models a system in SysML v2 and runs that model with Python. Two t work, and neither implements the full SysML v2 specification yet. Both are under active development, and this tutorial tracks what each one can currently do. -**OpenSysML** (`opensysml`, installed automatically by `check-tools.py`) is the primary tool: it +**OpenSysML** (`opensysml`, installed automatically by [`check-tools.py`](https://github.com/Open-MBEE/toaster/blob/main/scripts/check-tools.py)) is the primary tool: it loads, validates, queries, and evaluates every model in this tutorial. Every chapter needs it. -`scripts/check-tools.py` also provisions a second OpenSysML binary, the +[`scripts/check-tools.py`](https://github.com/Open-MBEE/toaster/blob/main/scripts/check-tools.py) also provisions a second OpenSysML binary, the render-capable CLI (distinct from the service binary the Python package itself talks to) — chapters that render an action-flow or state-transition diagram need it; nothing else does. @@ -77,17 +150,20 @@ Chapter 8 uses it directly (`toaster.modelcheck.verify_holds`, wrapping its `sys --solve` CLI) to prove `deliveredEnergyBoundedBySupply` for every value its unbound features admit. It is not on crates.io. The name `sysmlv2` is reserved on PyPI by sysml-toolkit's own maintaining organization, but the package published there today is a placeholder, not the real -thing; do not `pip install` it. Get a working binary instead from -[its GitHub releases page](https://github.com/Open-MBEE/sysml-toolkit/releases) (macOS, Linux, -and Windows builds are published there). 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` (`DEFERRED.md` D-025) with a direct Python call. +thing; do not `pip install` it. Get a working binary instead from `scripts/provision-tools.py` +(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) +([`DEFERRED.md` D-025](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-025-toastermodelcheck-wraps-sysmlv2-verify---solve-via-subprocess-intended-for-deprecation)) +with a direct Python call. 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` is one example: it will call +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`, with the specific gap +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. @@ -96,12 +172,12 @@ reaches the same capability, the wrapper is replaced with a direct call to it. 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: `exercises/ch{N}/exercise.ipynb`. +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/` notebooks are blank workspaces. They are not pre-executed and not part of the +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. **Keep your model between chapters.** Each exercise's first cell asks you to paste in your own From dff99ad13f2d8224e32ea5bd3d1a17d7a4671747 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 16:49:39 -0400 Subject: [PATCH 21/61] CI: real pipeline (provision tools, strict MyST build with execution, release gate, Pages deploy from main only) (PUB-8) --- .github/workflows/ci.yml | 86 +++++++++++++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 111e4ac..20eee98 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,56 +2,114 @@ name: CI on: push: - branches: [main] + branches: [main, pages-publishing] pull_request: 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 and the + # pages-publishing validation pushes 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 From d5291399654b249cd3910f7627fbc86cdbb57176 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 16:59:27 -0400 Subject: [PATCH 22/61] Docs: show --strict in the CI-style build commands, describe the gate and TOASTER_REQUIRE_TOOLS as CI runs them --- README.md | 2 +- docs/contributor.md | 13 ++++++++----- docs/setup.md | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 17fd976..7c86b35 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ looks for a Jupyter on your `PATH`, which is usually a different installation. T static site the way CI does: ```sh -BASE_URL=/toaster uv run --frozen npx myst build --html --execute +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, diff --git a/docs/contributor.md b/docs/contributor.md index 31c79be..6651259 100644 --- a/docs/contributor.md +++ b/docs/contributor.md @@ -67,7 +67,8 @@ book with every notebook executed, and then runs the release gate, The gate fails the build unless all five checks pass: 1. the build log has no code-execution or Jupyter-session failure (MyST exits 0 even when a cell - raised, so the exit code alone proves nothing); + raised unless it is run with `--strict`, which CI does; the log check backs that up, so a + failed cell is caught even if the flag is ever dropped); 2. the built content has the expected number of figures, recorded in [`scripts/site-baseline.json`](https://github.com/Open-MBEE/toaster/blob/main/scripts/site-baseline.json); 3. no built text file contains a host path (a home directory, a Homebrew prefix or a CI runner path); @@ -78,7 +79,8 @@ The gate fails the build unless all five checks pass: To reproduce the build and the gate locally, from a clone with the tools provisioned: ```sh -BASE_URL=/toaster uv run --frozen npx myst build --html --execute 2>&1 | tee build.log +set -o pipefail +BASE_URL=/toaster uv run --frozen npx myst build --html --execute --strict 2>&1 | tee build.log uv run python scripts/check-site.py --site _build/html --content _build/site/content \ --log build.log --base-url /toaster ``` @@ -135,9 +137,10 @@ uv run pytest tests/ glossary/tests/ -v uv run npx mystmd start --execute ``` -The first five commands mirror the `build` job's provisioning and tests; the last serves the book -for preview rather than building it. To make a missing external tool fail the tests instead of -skipping them, prefix the pytest line with `TOASTER_REQUIRE_TOOLS=1`. To run the build itself and +The first five commands mirror the `build` job's provisioning and tests (the job also installs +Graphviz and a Java runtime from apt); the last serves the book for preview rather than building +it. CI runs pytest with `TOASTER_REQUIRE_TOOLS=1`, so a missing external tool fails the tests +instead of skipping them; prefix the pytest line with it to get the same behavior locally. To run the build itself and the release gate, use the two commands under [Deployment status](#deployment-status). There is no local equivalent of the `deploy` job, and there should not be, since publishing is a decision, not a build artifact. diff --git a/docs/setup.md b/docs/setup.md index 18b2250..ec3c624 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -68,7 +68,7 @@ To build the static site the way CI does, rather than serve it, use the build fo URL is the path the site is served under on GitHub Pages: ```sh -BASE_URL=/toaster uv run --frozen npx myst build --html --execute +BASE_URL=/toaster uv run --frozen npx myst build --html --execute --strict ``` Building and deploying the GitHub Pages site itself is a maintainer task, not something you From afe8768ab96c12e7d276d466c0233fee6641d1d2 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 18:48:09 -0400 Subject: [PATCH 23/61] DL-115: A7 gate ruling for the Pages publication --- decisions/log.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/decisions/log.md b/decisions/log.md index cb4f678..040c811 100644 --- a/decisions/log.md +++ b/decisions/log.md @@ -1578,3 +1578,13 @@ Reasoning: Phase A showed the work divides cleanly: tooling (PUB-1..5), content 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. From 343223d3c8cc0ec272b5e1edfd38f461c90f251a Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 18:51:22 -0400 Subject: [PATCH 24/61] Record the opensysml.org review for the terminology revision --- .../opensysml-terminology/website-review.md | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 decisions/opensysml-terminology/website-review.md 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. From 484b8c7d1b8ab7bf892b293981824745c14e72c2 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 18:51:55 -0400 Subject: [PATCH 25/61] Chapter 10: link D-030, D-031 and D-038 beside their code cells; quote Hawkins verbatim for asserted context (PUB-6E) --- .../ch10-traceability-signoff/01-traceability-graph.ipynb | 6 +++--- .../ch10-traceability-signoff/03-engineering-signoff.ipynb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/chapters/ch10-traceability-signoff/01-traceability-graph.ipynb b/chapters/ch10-traceability-signoff/01-traceability-graph.ipynb index 80dc580..67e65d4 100644 --- a/chapters/ch10-traceability-signoff/01-traceability-graph.ipynb +++ b/chapters/ch10-traceability-signoff/01-traceability-graph.ipynb @@ -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 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))." ] }, { @@ -932,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)." ] }, { @@ -1288,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\" (see the [asserted context](../../docs/glossary.md#asserted-context) glossary entry), 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." ] }, { diff --git a/chapters/ch10-traceability-signoff/03-engineering-signoff.ipynb b/chapters/ch10-traceability-signoff/03-engineering-signoff.ipynb index 2d27c7b..4106de7 100644 --- a/chapters/ch10-traceability-signoff/03-engineering-signoff.ipynb +++ b/chapters/ch10-traceability-signoff/03-engineering-signoff.ipynb @@ -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)." ] }, { From 66531c9440d7caedc24a9ef35fa53e9e61fb33cb Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 18:58:59 -0400 Subject: [PATCH 26/61] DL-116: OpenSysML terminology ruling --- decisions/log.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/decisions/log.md b/decisions/log.md index 040c811..c16856c 100644 --- a/decisions/log.md +++ b/decisions/log.md @@ -1588,3 +1588,16 @@ Reasoning: (1) the ids in doc and code comments are the SS1.9(c) gap-tracking co 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] From 1649785b1d18d263f4214f749262ea0a8ff83737 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 18:59:59 -0400 Subject: [PATCH 27/61] Plan: OpenSysML terminology revision (contracts OT-1 to OT-8) --- .../2026-10-03-opensysml-terminology-plan.md | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 docs/superpowers/plans/2026-10-03-opensysml-terminology-plan.md diff --git a/docs/superpowers/plans/2026-10-03-opensysml-terminology-plan.md b/docs/superpowers/plans/2026-10-03-opensysml-terminology-plan.md new file mode 100644 index 0000000..9dab46d --- /dev/null +++ b/docs/superpowers/plans/2026-10-03-opensysml-terminology-plan.md @@ -0,0 +1,170 @@ +# OpenSysML Terminology Revision Implementation Plan + +> **For agentic workers:** every task is a CONTRACT per `decisions/work-contract-template.md`, run through `decisions/task-states.md`: author and reviewer on different pinned models, one worktree per task created by the orchestrator, plain commits (no trailers), subagents never push or merge, judgment goes to the ACE. Steps use checkbox syntax. + +**Goal:** Revise this repository's prose so "OpenSysML" names the open-source SysML v2 tool stack (opensysml.org) and every claim that is true of one tool names that tool (the OpenSysML runtime or sysml-toolkit), without changing any code, model, record, stored output or published anchor. + +**Architecture:** One integration branch `terminology` (off `pages-publishing`). Stage 1 is read-only inventory (one table row per occurrence, classified, with the proposed replacement), plus a protected-token diff checker. Stage 2 is edit contracts by area that apply inventory rows exactly. Stage 3 is the skills (ACE only) and the lasting lint guard. Stage 4 is a whole-branch regression gate. The branch merges back into `pages-publishing` only after the gate passes. + +**Authority:** Z's direction of 2026-10-03 (`decisions/opensysml-terminology/website-review.md`); ACE ruling DL-116 (`decisions/log.md`). Pilot membership is escalated to Z with default OUT; nothing in this plan depends on the answer. + +**Why heavy:** the umbrella meaning changes truth conditions. "OpenSysML cannot X" is true of the runtime and false where sysml-toolkit does X (DEFERRED D-017, D-023 and others). So this is a scope correction of claims, not a find-and-replace. + +## Normative convention (builders quote this; from DL-116) + +> 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; 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. A bare "OpenSysML" is used only for a statement about the whole stack. Any claim that is true of one component, or that was probed against one component (a gap, a limitation, an API, a version), names that component; a version number always attaches to a component name. Contrasts name both components ("the OpenSysML runtime accepts X; sysml-toolkit v0.9.1 rejects it"); "OpenSysML or/and/versus sysml-toolkit", "neither OpenSysML nor", "OpenSysML alone/itself/cannot" are not written. On a published page the first mention uses the full component name; "the runtime" and "the toolkit" may follow on that page. Code identifiers, imports, environment variables, package, directory and file names, URLs, repo and issue references, `models/*.sysml`, judgment records, stored outputs, DEFERRED.md headings, the decision log and dated evidence under `decisions/`, and quoted tool output never change. Before DL-116, a bare "OpenSysML" in this repository means the runtime. + +Additional rules: running text says "sysml-toolkit" (its own name), never "the OpenSysML toolkit" except as a first-mention gloss; "the OpenSysML runtime" in running text, then "the runtime" within the page; the runtime's tracker is "the runtime's tracker (`Open-MBEE/OpenSysML#NNN`)" where prose names it; the Pilot is always "the OMG SysML v2 Pilot Implementation" (then "the pilot") and is never counted among "both tools". The stack is defined once each on `docs/setup.md`, `docs/references.md` (its "OpenSysML" section becomes the definition) and `docs/reproducibility.md`, with a link to https://opensysml.org/. + +## Protected zones (reviewer-checked in every contract) + +Never change: code cells and their stored outputs in notebooks; `models/**`; `decisions/judgment-records/**`; `figures/**`; `decisions/log.md` and every dated file under `decisions/` (append-only; DL-116 carries the reading rule); `docs/superpowers/**`; `tests/**`, `src/**`, `scripts/**` (including comments); `uv.lock`; DEFERRED.md headings (their GitHub anchors are linked from published pages); skill directory names and `name:` frontmatter; code fences inside skills (executed by `tests/test_skill_snippets.py`). Protected tokens whose per-file counts must be unchanged: `import opensysml`, `opensysml.` (API calls), `OPENSYSML_VERSION`, `OPENSYSML_GRPC_VERSION`, `~/.opensysml`, `Open-MBEE/OpenSysML`, `Open-MBEE/sysml-toolkit`, `OpenSysML#NNN`, `sysml-toolkit#N`, `toaster#N`, `opensysml-api`, `opensysml-query`, and every URL. + +Repo checks that must stay clean at every merge: `uv run pytest tests/ glossary/tests/ -q`, `uv run python -m glossary check`, `uv run python scripts/check_construction.py --check`. + +## Review Focus + +1. A claim that was true for the runtime becomes false or unprobed for the stack; the truth classification (toolkit-differs / toolkit-same / toolkit-unprobed against the DEFERRED entry) is the main correctness risk, not typography. +2. A notebook edit that touches a code cell or output changes `AS-C08`'s content hash or a persisted record; only markdown cells and `index.md`/`conclusion.md` are editable. +3. A DEFERRED.md heading edit breaks published anchors; headings stay byte-identical. +4. A skill edit that alters a code fence breaks `tests/test_skill_snippets.py`, or a rename breaks CLAUDE.md/AGENTS.md references. +5. Replacing the bare name with "the OpenSysML runtime" in a sentence that really is about the stack makes the stack sound smaller than it is; sentences that are truly about the stack keep the bare name. + +--- + +## Task 0: Branch and ordering (orchestrator, done) + +- [x] `terminology` branch off `pages-publishing` (66531c9, includes DL-115 and DL-116). Worktrees: `git worktree add .claude/worktrees/ -b term/ terminology`; merges into `terminology` use `--no-ff`; `pages-publishing` is untouched until the Task 8 gate passes. +- [ ] Order: OT-1a, OT-1b and OT-2 in parallel; orchestrator merges the two inventories into `decisions/opensysml-terminology/inventory.md`; then OT-3A, OT-3B, OT-4, OT-5 in parallel; then OT-6 (ACE edits skills); then OT-7 (lint rules); then OT-8 (gate). + +## Task 1: Inventory (OT-1a, OT-1b), read-only + +Rows follow `decisions/pages-publishing/a4-dangling-references.md`'s style: `| row | file | locator | quoted sentence | class | proposed replacement |`. Classes: `KEEP-ID` (protected token or zone), `KEEP-STACK` (truly about the stack), `RUNTIME` (rewrite to name the runtime), `TOOLKIT` (rewrite to name sysml-toolkit), `BOTH` (name both), `FALSE-UNDER-STACK` / `SAME` / `UNPROBED` (the truth class for capability claims, against the DEFERRED entry id), `DEFINE` (stack definition site), `AMBIGUOUS` (needs the ACE; give the options). Every `RUNTIME`/`TOOLKIT`/`BOTH` row has the exact replacement wording, applying the normative convention. + +``` +CONTRACT OT-1a | 2026-10-03 +Role: general-purpose research agent (read-only), model claude-sonnet-5 +Reviewer: reviewer, model claude-opus-5-5 (samples 40 rows, rechecks every FALSE-UNDER-STACK and AMBIGUOUS) +State: ready +Task: Inventory every occurrence of OpenSysML / opensysml / "Open-MBEE/OpenSysML" and every sentence that + contrasts or conflates the runtime with sysml-toolkit in the LEARNER-FACING surface: chapters/** + (markdown cells and index.md/conclusion.md; list code-cell occurrences as KEEP-ID with a note when a + markdown cell must clarify them), exercises/**, docs/*.md, docs/case-studies/*.md, README.md. +Context: DL-116 and the normative convention; DEFERRED.md entries D-014, D-017, D-019, D-020, D-023, D-024, + D-025, D-032..D-036 (they hold both tools' probe results); decisions/opensysml-terminology/website-review.md. +Non-goals: No edits. No decisions on ambiguous rows (classify AMBIGUOUS with options). +Blast zone: read-only; output file decisions/opensysml-terminology/inventory-a.md in worktree .claude/worktrees/inv-a. +Acceptance: Every file's occurrences accounted for (report counts per file, class totals, and a machine check: + the number of lines in the surface that match /opensysml/i equals the number of rows plus + explicitly listed duplicates); each capability claim carries its DEFERRED entry id and truth class; + each proposed replacement quotes the convention rule it applies. +Report: branch/commit, counts, AMBIGUOUS list, FALSE-UNDER-STACK list. +``` + +``` +CONTRACT OT-1b | 2026-10-03 +Role: general-purpose research agent (read-only), model claude-sonnet-5 +Reviewer: reviewer, model claude-opus-5-5 +Task: Same inventory for the BINDING surface: AGENTS.md (Part 1 and Part 2), CLAUDE.md, DEFERRED.md (headings + protected; list bodies that name only the runtime; propose the dated note for the top), .claude/skills/* + (14 skills; separate prose from code fences; list name/description frontmatter), .claude/agents/*, + glossary/README.md and glossary definitions mentioning OpenSysML, docs/superpowers/** (KEEP, count only). + Include the ACE's wording for AGENTS.md 1.2/1.7/1.9 and the CLAUDE.md sources line as DEFINE rows. +Blast zone: read-only; output decisions/opensysml-terminology/inventory-b.md in worktree .claude/worktrees/inv-b. +Acceptance: as OT-1a; plus for each skill: lines of prose vs inside code fences, and the exact set of snippets run by + tests/test_skill_snippets.py (so skill edits cannot touch them). +``` + +- [ ] Orchestrator verifies counts (git grep -ic per file) against each report before merging the inventory files. + +## Task 2: Protected-token diff checker (OT-2) + +``` +CONTRACT OT-2 | 2026-10-03 +Role: builder, model claude-sonnet-5, effort medium +Reviewer: reviewer, model claude-opus-5-5 +State: ready +Task: Write scripts/check-terminology-edit.py and tests/test_check_terminology_edit.py. Usage: + `uv run python scripts/check-terminology-edit.py --base [--head ]` (head defaults to the working + tree/HEAD). It fails (exit 1, one line per violation) if between base and head: (a) any protected zone + changed (models/**, decisions/judgment-records/**, figures/**, decisions/** except new files, + tests/**, src/**, scripts/** other than this checker's own files, uv.lock, docs/superpowers/**); (b) any + notebook code cell's source, any cell outputs, execution_count, id, metadata, cell count or order changed + (compare with json; markdown cell `source` is the only editable notebook field); (c) the per-file count + of any protected token changed (list in the plan; counted with word-boundary regexes over the whole file + for non-notebooks and over code cells for notebooks); (d) any `## D-0nn` heading line in DEFERRED.md + changed; (e) any fenced code block in .claude/skills/**/SKILL.md or reference files changed (compare the + fenced blocks as a list); (f) any skill directory or `name:` frontmatter changed; (g) any URL string + present in base is missing in head in a changed file. It prints PASS/FAIL per rule. +Non-goals: Does not judge prose. Does not edit anything. +Blast zone: scripts/check-terminology-edit.py, tests/test_check_terminology_edit.py -- on branch term/guard, + worktree .claude/worktrees/guard. NOTE tests/test_no_local_paths.py scans scripts/ and tests/: do not write + forbidden strings (use string concatenation in fixtures or add nothing to the allowlist). +Acceptance: Offline tests build small git repos in tmp_path (git init, two commits) and assert each rule passes on a + clean prose-only change and fails on: a changed model file, a changed judgment record, a changed code + cell, a changed cell output, a changed DEFERRED heading, a changed skill code fence, a removed URL, a + changed protected-token count, a renamed skill directory. Run against the real repo: base=pages-publishing + head=terminology must PASS (no changes yet except decisions/ and docs/superpowers/ additions, which are + new files). Mutation-check three rules. Repo checks clean. +Report: branch/commit, tests, real-repo run output, mutation evidence. +``` + +## Task 3: Learner prose edits (OT-3A, OT-3B), Task 4: docs (OT-4), Task 5: binding docs (OT-5) + +All four apply `decisions/opensysml-terminology/inventory.md` rows exactly (the file is the source of truth, as `a4-dangling-references.md` was for PUB-6). Rules common to every edit contract: + +``` +Rules: 1. Apply every row in the blast zone whose class is RUNTIME, TOOLKIT, BOTH, DEFINE, FALSE-UNDER-STACK, + SAME or UNPROBED using the row's replacement wording exactly. KEEP-* rows: no change. AMBIGUOUS rows: + no change, listed for the orchestrator (the ACE rules them). If applying a replacement would change + a number, a verdict, a pinned version or a claim beyond naming the component, STOP and report that row. + 2. Notebooks: patch only the `source` of MARKDOWN cells named by rows, by direct JSON text substitution + asserting exactly one match; no nbconvert; code cells, outputs, ids, metadata untouched. Where a + code-cell string is wrong under the umbrella (protected), the row names an adjacent MARKDOWN cell to + carry the clarification: edit that markdown cell only. + 3. Commits are plain one-line messages with no trailers. Put the mapping from each edit to the convention + rule it applies in the report. + 4. Protected zones and tokens untouched (checker: scripts/check-terminology-edit.py --base terminology). +Acceptance (all): (1) counts of rows applied by class and skipped by reason; sum equals the rows for the blast-zone files; + (2) the checker passes against base=terminology; (3) cell-by-cell json comparison for each notebook: only + named markdown cells differ, only in `source`; (4) site build `BASE_URL=/toaster npx myst build --html` + (npm ci first) has the same 98 warnings as the parent; (5) full suite, glossary check, check_construction; + (6) `git grep -n` of the edited files for the banned patterns (OpenSysML or/and/vs/nor sysml-toolkit, + "OpenSysML v0.9", "OpenSysML alone|itself|cannot") returns nothing except in KEEP-ID rows. +``` + +- **OT-3A:** `chapters/ch01..ch06/**` markdown and index/conclusion; branch `term/ch-a`. +- **OT-3B:** `chapters/ch07..ch10/**`, `exercises/**` markdown cells; branch `term/ch-b`. Includes the toolkit-differs cells in ch07 nb02 (cells 9, 23, 25 and the markdown clarification for code cell 22) per D-023. +- **OT-4:** `docs/setup.md` (line ~147 "does one thing OpenSysML cannot yet" is self-contradictory under the umbrella), `docs/references.md` (its OpenSysML section becomes the stack definition with the opensysml.org link), `docs/reproducibility.md`, `docs/case-studies/*.md`, `README.md`; also add the stack definition sentence to setup and reproducibility; branch `term/docs`. +- **OT-5:** `AGENTS.md` (Part 1 1.2, 1.7, 1.9; Part 2 only if a row says so), `CLAUDE.md` sources line, and one dated terminology note at the top of `DEFERRED.md` (headings untouched). Wording for the Part 1 edits is in DL-116's ACE report (quoted in the inventory-b DEFINE rows); branch `term/binding`. The orchestrator reads the final Part 1 text itself before merge (Part 1 governs the project). + +## Task 6: Skills (OT-6), ACE only + +Per DL-116 and skill-editor: the ACE edits the 14 skills' PROSE as one logical change in one session, after OT-3..OT-5 merge (no mid-loop edit), with DL-116 as the pre-edit record and the revert record the commit preceding the first skill edit (record its SHA in the report). `opensysml-api` and `opensysml-query` names and frontmatter `name:` stay; `opensysml-query` H1 and description say "the OpenSysML runtime v0.9.0"; `opensysml-api` description stays ("opensysml v0.9.0 interface" uses the package name). No code fence changes. Reviewer: `reviewer` on a different model than the ACE, running the checker (rule e,f) and `tests/test_skill_snippets.py`. + +## Task 7: Lasting guard (OT-7) + +``` +CONTRACT OT-7 | 2026-10-03 +Role: builder, model claude-sonnet-5, effort medium +Reviewer: reviewer, model claude-opus-5-5 +State: blocked_on OT-3A, OT-3B, OT-4 merged +Task: Add learner-scope error rules to glossary/lint_rules.toml (data-driven, see glossary/lint.py) and tests: + 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 . Each rule carries a message pointing at the convention (AGENTS.md 1.2). +Blast zone: glossary/lint_rules.toml, glossary/tests/. +Acceptance: the lint is clean on the real learner surface (chapters, docs) at this commit; a test per rule fails on a + fixture sentence and passes on the sanctioned rewrite; `uv run python -m glossary lint` error count does not + increase versus base (record the base count; the repo has a known large baseline of other findings: + compare per-rule counts, not the total); glossary check and the full suite clean. +``` + +## Task 8: Whole-branch regression gate (reviewer, no builder) + +- [ ] Reviewer (Opus, fresh): (1) `scripts/check-terminology-edit.py --base pages-publishing --head terminology` PASS; (2) protected-token counts per file identical (independent grep); (3) `git diff pages-publishing..terminology --stat` touches only the editable surface; (4) full suite with the tool env vars and `TOASTER_REQUIRE_TOOLS=1`; (5) executed build `BASE_URL=/toaster uv run --frozen npx myst build --html --execute --strict` with provisioned tools: exit 0, `scripts/check-site.py` passes the checks it passes at base (the two exercise/DEFERRED published-file checks are cleared by earlier work: record actual), figures == 18; stored outputs of every executed notebook equal to base (join adjacent same-stream chunks); `AS-C08.json` content_hash still equals sha256 of `models/ch08-cumulative.sysml`; (6) built-page text diff base vs head limited to rows in the inventory (script: extract text per page, diff, map each differing line to an inventory row, report orphans); (7) every FALSE-UNDER-STACK row re-verified against its DEFERRED entry by reading the entry; (8) `uv run python -m glossary lint` per-rule counts not worse than base except the new rules at 0; (9) 20 random edited sentences read for grammar and meaning. +- [ ] Orchestrator: merge `terminology` into `pages-publishing` (`--no-ff`), run the three repo checks, log the outcome as DL-117. + +## Out of scope + +Pilot membership (Z's answer may change only the definition sentence in AGENTS.md 1.2 and `docs/references.md`); renaming skills or packages; re-running notebooks; editing `decisions/` evidence; closing stale upstream-issue wording (separate DEFERRED follow-up). From 7d8a4ffd37786bcb97d3de281458721dca4cc032 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 19:05:29 -0400 Subject: [PATCH 28/61] OT-1b: inventory of the binding surface for the OpenSysML terminology revision --- .../opensysml-terminology/inventory-b.md | 339 ++++++++++++++++++ 1 file changed, 339 insertions(+) create mode 100644 decisions/opensysml-terminology/inventory-b.md 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. | From e3ff1223ec942ad4baba50b243616d79e9954983 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 19:06:40 -0400 Subject: [PATCH 29/61] OT-1a: learner-facing OpenSysML terminology inventory --- .../opensysml-terminology/inventory-a.md | 289 ++++++++++++++++++ 1 file changed, 289 insertions(+) create mode 100644 decisions/opensysml-terminology/inventory-a.md 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. From 38b68441e33a68e5d342cd264296976eb0579887 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 19:15:14 -0400 Subject: [PATCH 30/61] DL-117: ACE triage of the terminology inventories; final texts --- decisions/log.md | 10 ++ .../opensysml-terminology/final-texts.md | 97 +++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 decisions/opensysml-terminology/final-texts.md diff --git a/decisions/log.md b/decisions/log.md index c16856c..63edb3f 100644 --- a/decisions/log.md +++ b/decisions/log.md @@ -1601,3 +1601,13 @@ Provenance: Z's direction 2026-10-03; decisions/opensysml-terminology/website-re 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. 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. From 767f3da3144ab2962ddcd5d4743ec54f1f06643a Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 19:04:26 -0400 Subject: [PATCH 31/61] Add protected-token diff checker for the terminology revision (OT-2) --- scripts/check-terminology-edit.py | 588 ++++++++++++++++++++ tests/test_check_terminology_edit.py | 776 +++++++++++++++++++++++++++ 2 files changed, 1364 insertions(+) create mode 100644 scripts/check-terminology-edit.py create mode 100644 tests/test_check_terminology_edit.py diff --git a/scripts/check-terminology-edit.py b/scripts/check-terminology-edit.py new file mode 100644 index 0000000..58559fd --- /dev/null +++ b/scripts/check-terminology-edit.py @@ -0,0 +1,588 @@ +#!/usr/bin/env python3 +"""Guard for the OpenSysML terminology revision (DL-116): did an edit stay on the editable surface? + +Usage: + uv run python scripts/check-terminology-edit.py --base [--head ] [--repo ] + [--allow GLOB ...] + +`--head` defaults to the working tree (tracked changes against `--base` plus untracked files). +`--repo` may be any directory inside the repository; the repository root is resolved with git. +`--allow GLOB` (repeatable) exempts matching paths from rule (a) for this run only; `*` matches +across `/`, so `glossary/tests/*` covers the whole tree below it. A run with --allow is never +silent: the output starts with an `allow: ...` line, rule (a) lists each exempted violation as an +`info (a) exempted by --allow ...` line, and the final line reads `RESULT: PASS (with N --allow +exemption(s))`. The exit code is unchanged. + +SELF: reviewers must run this checker from a pinned revision (the branch named `terminology` +after the guard merges), never from the branch under review, because an edit can change the checker +itself (scripts/ is a protected zone, with this checker's own files the only exemption). +Exit 0 only if every rule passes; exit 1 with one line per violation otherwise; exit 2 on a usage or +git error. Each rule prints PASS or FAIL. The checker judges no prose and edits nothing. + +Rules (letters follow contract OT-2): + (a) no protected zone changed: models/, decisions/judgment-records/, figures/, tests/, src/, + scripts/, glossary/*.py, glossary/tests/, .github/, uv.lock, pyproject.toml, package.json, + package-lock.json (any change at all), and decisions/ and docs/superpowers/ (new files only, + except decisions/log.md, which may also grow by appended lines only); this checker's own two + files are exempt + (b) no notebook changed except the `source` of markdown cells (cell count, order, types, ids, + metadata, code sources, outputs and execution counts compared as JSON) + (c) protected tokens: per file, the multiset of matched token strings at base must be contained + in the multiset at head (nothing removed or altered; adding a token is allowed and reported + as an info line). Whole file; for notebooks code cells, except the issue references + (OpenSysML#N, sysml-toolkit#N, toaster#N), which are read over all cells + (d) every `## D-nnn` heading line in DEFERRED.md unchanged + (e) the list of fenced code blocks in each changed .claude/skills/**/*.md file unchanged, and no + change at all to a non-markdown file under .claude/skills/ (such files are executed by tests) + (f) the set of skill directories and each SKILL.md `name:` frontmatter unchanged + (g) no URL's occurrence count in a changed file decreases (additions are fine) +""" + +from __future__ import annotations + +import argparse +import fnmatch +import json +import re +import subprocess +import sys +from collections import Counter +from collections.abc import Sequence +from pathlib import Path + +RULES: dict[str, str] = { + "a": "protected zones unchanged", + "b": "notebook edits limited to markdown cell source", + "c": "protected tokens not removed or altered", + "d": "DEFERRED.md D-nnn headings unchanged", + "e": "skill fenced code blocks unchanged", + "f": "skill directories and name: frontmatter unchanged", + "g": "no URL occurrence removed from a changed file", +} + +OWN_FILES = frozenset( + {"scripts/check-terminology-edit.py", "tests/test_check_terminology_edit.py"} +) + +# Zones governed by rule (a). `strict` zones allow no change at all; `new_ok` zones allow added files. +STRICT_PREFIXES = ( + "decisions/judgment-records/", + "models/", + "figures/", + "tests/", + "src/", + "scripts/", + ".github/", + "glossary/tests/", +) +STRICT_FILES = ("uv.lock", "pyproject.toml", "package.json", "package-lock.json") +NEW_OK_PREFIXES = ("decisions/", "docs/superpowers/") + +# Protected tokens, case-sensitive. Each regex captures the whole identifier, and rule (c) compares the +# matched strings, so changing an issue number or an API name is a change. `opensysml.` is the API-call +# token, so the domain opensysml.org (linked from the new stack definition) is not one; rule (g) +# covers it. +PROTECTED_TOKENS: dict[str, re.Pattern[str]] = { + "import opensysml": re.compile(r"\bimport opensysml\b"), + "opensysml.": re.compile(r"\bopensysml\.(?!org\b)\w+(?:\.\w+)*"), + "OPENSYSML_VERSION": re.compile(r"\bOPENSYSML_VERSION\b"), + "OPENSYSML_GRPC_VERSION": re.compile(r"\bOPENSYSML_GRPC_VERSION\b"), + "~/.opensysml": re.compile(r"~/\.opensysml\b"), + "Open-MBEE/OpenSysML": re.compile(r"\bOpen-MBEE/OpenSysML\b"), + "Open-MBEE/sysml-toolkit": re.compile(r"\bOpen-MBEE/sysml-toolkit\b"), + "OpenSysML#NNN": re.compile(r"\bOpenSysML#\w+"), + "sysml-toolkit#N": re.compile(r"\bsysml-toolkit#\w+"), + "toaster#N": re.compile(r"\btoaster#\w+"), + "opensysml-api": re.compile(r"\bopensysml-api\b"), + "opensysml-query": re.compile(r"\bopensysml-query\b"), +} +# In notebooks these are read over all cells (they appear as markdown link text); the rest over code cells. +ISSUE_TOKENS = frozenset({"OpenSysML#NNN", "sysml-toolkit#N", "toaster#N"}) + +URL_RE = re.compile(r"https?://[^\s<>\"'`\\)\]}]+") +URL_TRAILING = ".,;:!?" +DEFERRED_HEADING_RE = re.compile(r"^## D-\d+.*$", re.MULTILINE) +FENCE_OPEN_RE = re.compile(r"^\s*(`{3,}|~{3,})") + +SKILLS_DIR = ".claude/skills/" +APPEND_ONLY_FILE = "decisions/log.md" + + +class GitError(RuntimeError): + pass + + +def _git(repo: Path, *args: str) -> bytes: + proc = subprocess.run( + ["git", "-C", str(repo), *args], capture_output=True, check=False + ) + if proc.returncode != 0: + raise GitError(proc.stderr.decode("utf-8", "replace").strip() or "git failed") + return proc.stdout + + +class Tree: + """Read access to the files of one side of the comparison.""" + + def __init__(self, repo: Path, rev: str | None) -> None: + self.repo = repo + self.rev = rev # None means the working tree + self._files: list[str] | None = None + + def read(self, path: str) -> bytes | None: + if self.rev is None: + target = self.repo / path + return target.read_bytes() if target.is_file() else None + try: + return _git(self.repo, "show", f"{self.rev}:{path}") + except GitError: + return None + + def text(self, path: str) -> str | None: + raw = self.read(path) + return None if raw is None else raw.decode("utf-8", "replace") + + def files(self) -> list[str]: + if self._files is None: + if self.rev is None: + out = _git( + self.repo, "ls-files", "-z", "--cached", "--others", "--exclude-standard" + ) + names = [n for n in out.decode("utf-8", "replace").split("\0") if n] + self._files = sorted(n for n in set(names) if (self.repo / n).is_file()) + else: + out = _git(self.repo, "ls-tree", "-r", "-z", "--name-only", self.rev) + self._files = sorted( + n for n in out.decode("utf-8", "replace").split("\0") if n + ) + return self._files + + +def changed_files(repo: Path, base: str, head: str | None) -> list[tuple[str, str]]: + """(status, path) for each changed file; status is A, M or D. Renames count as D plus A.""" + args = ["diff", "--name-status", "--no-renames", "-z", base] + if head is not None: + args.append(head) + parts = _git(repo, *args).decode("utf-8", "replace").split("\0") + pairs = [ + (parts[i][:1], parts[i + 1]) for i in range(0, len(parts) - 1, 2) if parts[i] + ] + if head is None: + out = _git(repo, "ls-files", "-z", "--others", "--exclude-standard") + pairs += [("A", n) for n in out.decode("utf-8", "replace").split("\0") if n] + return sorted(set(pairs), key=lambda p: (p[1], p[0])) + + +def zone_of(path: str) -> str | None: + """'strict', 'new_ok' or None (outside the protected zones).""" + if path in OWN_FILES: + return None + if path in STRICT_FILES or path.startswith(STRICT_PREFIXES): + return "strict" + if path.startswith("glossary/") and path.endswith(".py") and "/" not in path[len("glossary/"):]: + return "strict" + if path.startswith(NEW_OK_PREFIXES): + return "new_ok" + return None + + +def in_zone(path: str) -> bool: + return zone_of(path) is not None or path in OWN_FILES + + +# --------------------------------------------------------------------------------------------- +# notebook helpers + + +def load_notebook(raw: bytes | None) -> dict | None: + if raw is None: + return None + try: + data = json.loads(raw.decode("utf-8")) + except (UnicodeDecodeError, json.JSONDecodeError): + return None + return data if isinstance(data, dict) and isinstance(data.get("cells"), list) else None + + +def _source_text(cell: dict) -> str: + src = cell.get("source", "") + return "".join(src) if isinstance(src, list) else str(src) + + +def notebook_code_text(nb: dict) -> str: + return "\n".join( + _source_text(c) for c in nb["cells"] if c.get("cell_type") == "code" + ) + + +def notebook_all_text(nb: dict) -> str: + chunks: list[str] = [] + + def walk(node: object) -> None: + if isinstance(node, str): + chunks.append(node) + elif isinstance(node, list): + if node and all(isinstance(x, str) for x in node): + chunks.append("".join(node)) + else: + for item in node: + walk(item) + elif isinstance(node, dict): + for value in node.values(): + walk(value) + + walk(nb) + return "\n".join(chunks) + + +# --------------------------------------------------------------------------------------------- +# rules; each returns a list of violation strings "path: reason" + + +def is_append_only(old: str | None, new: str | None) -> bool: + """True if every base line is kept, in order and unchanged, with lines only added at the end.""" + if old is None or new is None: + return False + old_lines, new_lines = old.splitlines(), new.splitlines() + return new_lines[: len(old_lines)] == old_lines + + +def a_violation(status: str, path: str, base: Tree, head: Tree) -> str | None: + """The rule (a) violation for one changed path, or None.""" + if path == APPEND_ONLY_FILE and status == "M": + if not is_append_only(base.text(path), head.text(path)): + return f"{path}: existing lines changed, deleted or inserted; append-only file" + return None + zone = zone_of(path) + if zone == "strict": + return f"{path}: protected zone changed ({status})" + if zone == "new_ok" and status != "A": + return f"{path}: protected zone changed ({status}); only new files are allowed" + return None + + +def first_matching_glob(path: str, allow: Sequence[str]) -> str | None: + return next((g for g in allow if fnmatch.fnmatchcase(path, g)), None) + + +def rule_a( + changes: list[tuple[str, str]], base: Tree, head: Tree, allow: Sequence[str] = () +) -> tuple[list[str], list[str]]: + """(violations, info lines). Each violation waived by --allow is listed as an info line.""" + out, info = [], [] + for status, path in changes: + violation = a_violation(status, path, base, head) + if violation is None: + continue + glob = first_matching_glob(path, allow) + if glob is None: + out.append(violation) + else: + info.append(f"exempted by --allow '{glob}': {path} ({status})") + return out, info + + +def rule_b(changes: list[tuple[str, str]], base: Tree, head: Tree) -> list[str]: + out = [] + for status, path in changes: + if not path.endswith(".ipynb") or status == "A": + continue + old = load_notebook(base.read(path)) + if status == "D": + out.append(f"{path}: notebook deleted") + continue + new = load_notebook(head.read(path)) + if old is None or new is None: + out.append(f"{path}: notebook is not readable JSON with a cells list") + continue + for key in sorted((set(old) | set(new)) - {"cells"}): + if old.get(key) != new.get(key): + out.append(f"{path}: notebook field '{key}' changed") + if len(old["cells"]) != len(new["cells"]): + out.append( + f"{path}: cell count changed ({len(old['cells'])} -> {len(new['cells'])})" + ) + continue + for i, (oc, nc) in enumerate(zip(old["cells"], new["cells"])): + if oc.get("cell_type") != nc.get("cell_type"): + out.append( + f"{path}: cell {i} type changed ({oc.get('cell_type')} -> {nc.get('cell_type')})" + ) + continue + kind = oc.get("cell_type") + ignore = {"source"} if kind == "markdown" else set() + differing = sorted( + k + for k in (set(oc) | set(nc)) - ignore + if oc.get(k) != nc.get(k) + ) + if differing: + out.append(f"{path}: {kind} cell {i} changed field(s) {', '.join(differing)}") + return out + + +def notebook_sources_text(nb: dict) -> str: + return "\n".join(_source_text(c) for c in nb["cells"]) + + +def token_matches(path: str, raw: bytes | None) -> Counter[str]: + """Multiset of matched protected-token strings in one file.""" + found: Counter[str] = Counter() + if raw is None: + return found + text = code_text = raw.decode("utf-8", "replace") + if path.endswith(".ipynb"): + nb = load_notebook(raw) + if nb is not None: + text, code_text = notebook_sources_text(nb), notebook_code_text(nb) + for name, rx in PROTECTED_TOKENS.items(): + source = text if name in ISSUE_TOKENS or not path.endswith(".ipynb") else code_text + found.update(m.group(0) for m in rx.finditer(source)) + return found + + +def rule_c( + changes: list[tuple[str, str]], base: Tree, head: Tree +) -> tuple[list[str], list[str]]: + """(violations, info lines). Base tokens must all survive at head; additions are informational.""" + out, info = [], [] + for _status, path in changes: + if in_zone(path): + continue # rule (a) governs these paths + old = token_matches(path, base.read(path)) + new = token_matches(path, head.read(path)) + for token in sorted(old): + if new[token] < old[token]: + out.append( + f"{path}: token '{token}' removed or altered (count {old[token]} -> {new[token]})" + ) + for token in sorted(new): + if new[token] > old[token]: + info.append(f"{path}: token '{token}' added (count {old[token]} -> {new[token]})") + return out, info + + +def deferred_headings(text: str | None) -> list[str]: + return DEFERRED_HEADING_RE.findall(text or "") + + +def rule_d(changes: list[tuple[str, str]], base: Tree, head: Tree) -> list[str]: + if not any(path == "DEFERRED.md" for _s, path in changes): + return [] + old = deferred_headings(base.text("DEFERRED.md")) + new = deferred_headings(head.text("DEFERRED.md")) + if old == new: + return [] + out = [] + for line in old: + if line not in new: + out.append(f"DEFERRED.md: heading changed or removed: {line}") + for line in new: + if line not in old: + out.append(f"DEFERRED.md: heading added or changed: {line}") + if not out: + out.append("DEFERRED.md: heading order or multiplicity changed") + return out + + +def fenced_blocks(text: str | None) -> list[str]: + """Fenced code blocks (fence lines included), in order. An unclosed fence runs to the end.""" + blocks: list[list[str]] = [] + current: list[str] | None = None + fence = "" + for line in (text or "").splitlines(): + if current is None: + m = FENCE_OPEN_RE.match(line) + if m: + fence = m.group(1) + current = [line] + else: + current.append(line) + stripped = line.strip() + if ( + stripped + and set(stripped) == {fence[0]} + and len(stripped) >= len(fence) + ): + blocks.append(current) + current = None + if current is not None: + blocks.append(current) + return ["\n".join(b) for b in blocks] + + +def rule_e(changes: list[tuple[str, str]], base: Tree, head: Tree) -> list[str]: + out = [] + for status, path in changes: + if not path.startswith(SKILLS_DIR): + continue + if not path.endswith(".md"): + out.append(f"{path}: non-markdown skill file changed ({status})") + continue + old = fenced_blocks(base.text(path)) + new = fenced_blocks(head.text(path)) + if old != new: + out.append(f"{path}: fenced code blocks changed ({len(old)} -> {len(new)} blocks)") + return out + + +def skill_dirs(tree: Tree) -> set[str]: + dirs = set() + for name in tree.files(): + if name.startswith(SKILLS_DIR): + parts = name[len(SKILLS_DIR):].split("/") + if len(parts) >= 2: + dirs.add(parts[0]) + return dirs + + +def skill_name(text: str | None) -> str | None: + if not text: + return None + lines = text.splitlines() + if not lines or lines[0].strip() != "---": + return None + for line in lines[1:]: + if line.strip() == "---": + return None + m = re.match(r"name:\s*(.*?)\s*$", line) + if m: + return m.group(1).strip("'\"") + return None + + +def rule_f(base: Tree, head: Tree) -> list[str]: + out = [] + old_dirs, new_dirs = skill_dirs(base), skill_dirs(head) + for d in sorted(old_dirs - new_dirs): + out.append(f"{SKILLS_DIR}{d}: skill directory removed or renamed") + for d in sorted(new_dirs - old_dirs): + out.append(f"{SKILLS_DIR}{d}: skill directory added") + for d in sorted(old_dirs & new_dirs): + path = f"{SKILLS_DIR}{d}/SKILL.md" + old, new = skill_name(base.text(path)), skill_name(head.text(path)) + if old != new: + out.append(f"{path}: frontmatter name changed ({old!r} -> {new!r})") + return out + + +def urls_in(path: str, raw: bytes | None) -> Counter[str]: + """Occurrence count of each URL in one file.""" + if raw is None: + return Counter() + if path.endswith(".ipynb"): + nb = load_notebook(raw) + text = notebook_all_text(nb) if nb is not None else raw.decode("utf-8", "replace") + else: + text = raw.decode("utf-8", "replace") + return Counter(u.rstrip(URL_TRAILING) for u in URL_RE.findall(text)) + + +def rule_g(changes: list[tuple[str, str]], base: Tree, head: Tree) -> list[str]: + out = [] + for status, path in changes: + if status == "A" or in_zone(path): + continue + old, new = urls_in(path, base.read(path)), urls_in(path, head.read(path)) + for url in sorted(old): + if new[url] < old[url]: + out.append(f"{path}: URL count decreased ({old[url]} -> {new[url]}): {url}") + return out + + +# --------------------------------------------------------------------------------------------- + + +def repo_root(path: Path) -> Path: + """The repository root containing `path` (any directory inside the work tree).""" + return Path(_git(path, "rev-parse", "--show-toplevel").decode("utf-8", "replace").strip()) + + +def run_rules( + repo: Path, base_rev: str, head_rev: str | None, allow: Sequence[str] = () +) -> tuple[dict[str, list[str]], dict[str, list[str]]]: + """Run every rule; returns ({rule letter: violation lines}, {rule letter: info lines}).""" + repo = repo_root(repo) + base, head = Tree(repo, base_rev), Tree(repo, head_rev) + changes = changed_files(repo, base_rev, head_rev) + a_violations, a_info = rule_a(changes, base, head, allow) + c_violations, c_info = rule_c(changes, base, head) + results = { + "a": a_violations, + "b": rule_b(changes, base, head), + "c": c_violations, + "d": rule_d(changes, base, head), + "e": rule_e(changes, base, head), + "f": rule_f(base, head), + "g": rule_g(changes, base, head), + } + return results, {"a": a_info, "c": c_info} + + +def check( + repo: Path, base_rev: str, head_rev: str | None, allow: Sequence[str] = () +) -> dict[str, list[str]]: + """Run every rule; maps rule letter to its violation lines (empty list means PASS).""" + return run_rules(repo, base_rev, head_rev, allow)[0] + + +def render( + results: dict[str, list[str]], + info: dict[str, list[str]] | None = None, + allow: Sequence[str] = (), +) -> str: + info = info or {} + lines = [f"allow: {', '.join(allow)}"] if allow else [] + for letter, desc in RULES.items(): + found = results[letter] + if found: + lines.append(f"FAIL ({letter}) {desc}: {len(found)} violation(s)") + lines += [f" ({letter}) {v}" for v in found] + else: + lines.append(f"PASS ({letter}) {desc}") + lines += [f" info ({letter}) {i}" for i in info.get(letter, [])] + waived = len(info.get("a", [])) + suffix = f" (with {waived} --allow exemption(s))" if waived else "" + lines.append("RESULT: " + ("FAIL" if any(results.values()) else "PASS") + suffix) + return "\n".join(lines) + + +def main(argv: Sequence[str] | None = None) -> int: + parser = argparse.ArgumentParser( + description=__doc__.split("\n\n")[0], + epilog="SELF: reviewers must run this checker from a pinned revision (the branch named " + "'terminology' after the guard merges), not from the branch under review.", + ) + parser.add_argument("--base", required=True, help="revision to compare against") + parser.add_argument("--head", default=None, help="revision to check (default: working tree)") + parser.add_argument( + "--repo", default=".", help="a directory inside the repository (default: current)" + ) + parser.add_argument( + "--allow", + action="append", + default=[], + metavar="GLOB", + help="exempt paths matching GLOB (relative to the repository root; '*' matches across '/') " + "from rule (a) for this run only; repeatable, e.g. --allow 'glossary/tests/*'", + ) + args = parser.parse_args(argv) + repo = Path(args.repo) + try: + repo = repo_root(repo) + for rev in (args.base, args.head): + if rev is not None: + try: + _git(repo, "rev-parse", "--verify", "--quiet", f"{rev}^{{commit}}") + except GitError: + raise GitError(f"unknown revision: {rev}") from None + results, info = run_rules(repo, args.base, args.head, args.allow) + except (GitError, OSError) as exc: + print(f"error: {exc}", file=sys.stderr) + return 2 + print(render(results, info, args.allow)) + return 1 if any(results.values()) else 0 + + +if __name__ == "__main__": + sys.exit(main()) 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 From 7cf6392f079c9fd34d8f0cb8cc081f780f7084b3 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 19:56:47 -0400 Subject: [PATCH 32/61] OT-3A: name the OpenSysML runtime in chapters 1-6 prose --- chapters/ch01-system-purpose/index.md | 2 +- chapters/ch03-measures/04-verification-case.ipynb | 2 +- chapters/ch03-measures/index.md | 2 +- chapters/ch04-functional-decomp/01-action-def-ffbd.ipynb | 2 +- chapters/ch04-functional-decomp/conclusion.md | 2 +- chapters/ch04-functional-decomp/index.md | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/chapters/ch01-system-purpose/index.md b/chapters/ch01-system-purpose/index.md index f9e9d80..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 diff --git a/chapters/ch03-measures/04-verification-case.ipynb b/chapters/ch03-measures/04-verification-case.ipynb index c825b9d..93e91fa 100644 --- a/chapters/ch03-measures/04-verification-case.ipynb +++ b/chapters/ch03-measures/04-verification-case.ipynb @@ -56,7 +56,7 @@ "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](https://github.com/Open-MBEE/toaster/issues/19) / [OpenSysML#608](https://github.com/Open-MBEE/OpenSysML/issues/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", diff --git a/chapters/ch03-measures/index.md b/chapters/ch03-measures/index.md index bfdc5b6..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 diff --git a/chapters/ch04-functional-decomp/01-action-def-ffbd.ipynb b/chapters/ch04-functional-decomp/01-action-def-ffbd.ipynb index c09bdef..a21846e 100644 --- a/chapters/ch04-functional-decomp/01-action-def-ffbd.ipynb +++ b/chapters/ch04-functional-decomp/01-action-def-ffbd.ipynb @@ -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](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 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`." ] }, { diff --git a/chapters/ch04-functional-decomp/conclusion.md b/chapters/ch04-functional-decomp/conclusion.md index 316fccd..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 diff --git a/chapters/ch04-functional-decomp/index.md b/chapters/ch04-functional-decomp/index.md index 5f6e9e2..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 From dcf7aa5fea29e82ce2a451a9ae7864297c944d92 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 19:56:53 -0400 Subject: [PATCH 33/61] OT-4: apply OpenSysML terminology rows to docs pages and case study --- .../2026-09-30-energy-conservation-requirement-tie.md | 10 +++++----- docs/contributor.md | 2 +- docs/references.md | 10 ++++++++-- docs/reproducibility.md | 8 +++++--- docs/setup.md | 10 ++++++---- 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md b/docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md index 7efc38b..b07830a 100644 --- a/docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md +++ b/docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md @@ -57,7 +57,7 @@ subject (`heatGen : HeatGenerator`) was never actually used by its own required is stated purely over unrelated free-standing elements. This is a real violation of §7.21.1's subject-conformance *spirit*, found by re-reading the spec directly, not by any tool diagnostic — **correction (found during review of the reconciliation contract, see below): Approach A never had -an `assert satisfy` line at all, so the OMG pilot had no live binding to flag on it, and running +an `assert satisfy` line at all, so the OMG SysML v2 Pilot Implementation (the pilot) had no live binding to flag on it, and running the pilot against Approach A's own committed model directly confirms 0 issues.** The pilot diagnostic ("Bound features should have conforming types") belongs to a different draft: Approach B's *own first commit* paired a typed subject with an `assert satisfy @@ -101,7 +101,7 @@ single worked example, and the answer required looking at the same fact from sev angles before it became clear enough to act on. **The letter of the rule.** `assert satisfy energyConservationReq by deliveredEnergyBoundedBySupply;` -is grammatically legal and loads cleanly under OpenSysML. The one binding constraint the spec +is grammatically legal and loads cleanly under the OpenSysML runtime. The one binding constraint the spec states — §7.21.1's "a requirement usage can only be satisfied by an entity that conforms to the definition of its subject" — is satisfied once the subject is left undeclared (inheriting `Anything`, which everything conforms to). Nothing in the grammar or the type system forbids this @@ -121,13 +121,13 @@ never mentions its subject anywhere — the lemma it subsets is a closed proposi free-standing elements. Binding anything to the subject changes nothing about whether the constraint evaluates true. The construct's grammar is satisfied; its purpose is not exercised. -**Tool support.** Neither OpenSysML nor the OMG pilot flags *this specific line* (Check A as finally +**Tool support.** Neither the OpenSysML runtime nor the pilot flags *this specific line* (Check A as finally written, subject-less). The pilot does catch a live binding type-mismatch — confirmed directly against Approach B's own first draft, which paired a typed subject with this same `assert satisfy` line and drew "Bound features should have conforming types" — but there is no tool check for "this satisfy usage's binding is causally irrelevant to the requirement's own truth value" even when the types happen to line up, which is the finally-written version's own problem. That is a semantic -property no diagnostic in this toolchain computes. This matters for the +property no diagnostic of the OpenSysML runtime or the pilot computes. This matters for the methodology, not just the conclusion: a construct passing every available tool check is evidence it is *legal*, not evidence it is *doing what it looks like it is doing*. The absence of a tool complaint was never going to settle this question. @@ -274,7 +274,7 @@ verification case both need to come out. subject-type oddness as an open question without identifying it as a defect, and no tool flagged it either, because Approach A never attempted a live binding for any tool to check — its problem was a declared-but-unused subject, invisible to a diagnostic that only fires on an actual - type-mismatched binding. The OMG pilot *does* catch a live type mismatch, as it did on Approach + type-mismatched binding. The pilot *does* catch a live type mismatch, as it did on Approach B's own first draft, but a clean pilot run is not evidence a construct is doing its job, only that nothing it actually tried to bind was mistyped. Every available tool check passing is necessary, never sufficient. diff --git a/docs/contributor.md b/docs/contributor.md index 6651259..9e48bf8 100644 --- a/docs/contributor.md +++ b/docs/contributor.md @@ -25,7 +25,7 @@ touch anything, even if you never run an agent yourself: 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. -- **[`DEFERRED.md`](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md)** tracks known gaps in the toolchain (OpenSysML, sysml-toolkit) that the +- **[`DEFERRED.md`](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md)** tracks known gaps in the toolchain (the OpenSysML runtime, sysml-toolkit) that the tutorial works around — what the workaround is, why it's needed, and the condition under which it comes out once the upstream gap closes. - **[`.claude/agents/`](https://github.com/Open-MBEE/toaster/tree/main/.claude/agents)** defines the roles that do the work: an `orchestrator` that turns a diff --git a/docs/references.md b/docs/references.md index 884686c..5749d08 100644 --- a/docs/references.md +++ b/docs/references.md @@ -62,9 +62,15 @@ The normative specification for all SysML v2 constructs used in this tutorial. C ## OpenSysML -Open-MBEE/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 Python library (`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 library'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. +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. --- diff --git a/docs/reproducibility.md b/docs/reproducibility.md index bbc802b..3cf815b 100644 --- a/docs/reproducibility.md +++ b/docs/reproducibility.md @@ -8,13 +8,15 @@ and where that guarantee currently stops. It doesn't repeat the setup steps them ## What's pinned, and why that's most of the guarantee Reproducing this tutorial's outputs depends on reproducing four things exactly: the Python -environment, the OpenSysML binary, the external tools some chapters call, and (only if you're +environment, the OpenSysML runtime binary, the external tools some chapters call, and (only if you're building the rendered book) the Node toolchain. +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. + - **Python dependencies** are pinned by [`uv.lock`](https://github.com/Open-MBEE/toaster/blob/main/uv.lock), installed with `uv sync --locked` (not `uv sync`, which would let versions drift). Every chapter and every test runs against the exact versions recorded there. -- **The OpenSysML binary** is pinned by version string (`v0.9.0` as of this tutorial), downloaded +- **The OpenSysML runtime binary** is pinned by version string (`v0.9.0` as of this tutorial), downloaded by [`scripts/check-tools.py`](https://github.com/Open-MBEE/toaster/blob/main/scripts/check-tools.py) rather than resolved from a floating "latest." Every model-loading call in every notebook goes through this one pinned binary; there's no code path that reaches a different version. @@ -87,7 +89,7 @@ notebook wrote it. reader can independently confirm the tutorial cites the edition it says it does, provided they obtain their own copy of that same source and check its hash against the recorded one; it is not something cloning this repository alone reproduces. -- **A gap fixed upstream doesn't silently change what's here.** Where OpenSysML or sysml-toolkit +- **A gap fixed upstream doesn't silently change what's here.** Where the OpenSysML runtime or sysml-toolkit doesn't yet support something the spec allows, [`DEFERRED.md`](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md) records the gap together with the exact version it was found against (down to a commit hash, for the one case built from source rather than a tagged release). If a later version of either tool closes that gap, this tutorial's diff --git a/docs/setup.md b/docs/setup.md index ec3c624..db5ca66 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -24,7 +24,7 @@ uv run python scripts/check-tools.py [Tools for chapters 5, 8 and 10](#tools-for-chapters) below). [`check-tools.py`](https://github.com/Open-MBEE/toaster/blob/main/scripts/check-tools.py) prints where each tool resolves and its version, verifies Graphviz is installed, and downloads -the OpenSysML binary this tutorial's Python package connects to. If anything is missing, it +the OpenSysML runtime binary this tutorial's Python package connects to. If anything is missing, it names what to install. `uv sync --locked` also installs JupyterLab and the kernel this project uses (both are @@ -133,18 +133,20 @@ TOASTER_REQUIRE_TOOLS=1 uv run pytest tests/ glossary/tests/ -v ## The tools this tutorial uses, and why +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. + This tutorial models a system in SysML v2 and runs that model with Python. Two tools do that work, and neither implements the full SysML v2 specification yet. Both are under active development, and this tutorial tracks what each one can currently do. -**OpenSysML** (`opensysml`, installed automatically by [`check-tools.py`](https://github.com/Open-MBEE/toaster/blob/main/scripts/check-tools.py)) is the primary tool: it +**The OpenSysML runtime** (`opensysml`, installed automatically by [`check-tools.py`](https://github.com/Open-MBEE/toaster/blob/main/scripts/check-tools.py)) is the primary tool: it loads, validates, queries, and evaluates every model in this tutorial. Every chapter needs it. -[`scripts/check-tools.py`](https://github.com/Open-MBEE/toaster/blob/main/scripts/check-tools.py) also provisions a second OpenSysML binary, the +[`scripts/check-tools.py`](https://github.com/Open-MBEE/toaster/blob/main/scripts/check-tools.py) also provisions a second OpenSysML runtime binary, the render-capable CLI (distinct from the service binary the Python package itself talks to) — chapters that render an action-flow or state-transition diagram need it; nothing else does. -**sysml-toolkit** does one thing OpenSysML cannot yet: prove that a constraint holds for every +**sysml-toolkit** does one thing 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. Chapter 8 uses it directly (`toaster.modelcheck.verify_holds`, wrapping its `sysmlv2 verify --solve` CLI) to prove `deliveredEnergyBoundedBySupply` for every value its unbound features From a4d61e9e25215fc684b3009f1e3e239adb69b620 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 19:57:06 -0400 Subject: [PATCH 34/61] OT-5: name the OpenSysML runtime in AGENTS.md 1.2/1.7/1.9, CLAUDE.md sources and reading-notes --- AGENTS.md | 8 ++++---- CLAUDE.md | 3 +-- glossary/sources/notes/reading-notes.md | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 7872f95..97f1a7b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -40,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". @@ -124,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. @@ -136,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. 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/glossary/sources/notes/reading-notes.md b/glossary/sources/notes/reading-notes.md index 94ea129..dc3e447 100644 --- a/glossary/sources/notes/reading-notes.md +++ b/glossary/sources/notes/reading-notes.md @@ -32,6 +32,6 @@ Notes made while seeding the glossary (2026-09-26). They record what was read, w - The playlist lists five videos; `docs/references.md` says a six-part series. ## Left out on purpose -- OpenSysML, sysml-toolkit and the Pilot Implementation are toolchain, cited only to flag spec gaps. They define no terms. +- 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. - Tall's three worlds and the optimization and control lens are builder-facing and never appear in learner content, so they are neither sources nor terms. - *Declarative*, *executable specification* and *model checking* have no canonical definition in the sources read, so they are not seeded. They can join if a source is chosen for them. From a9c3ed10d1e4a9f02f9475d3c962cd2641e3014c Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 19:57:57 -0400 Subject: [PATCH 35/61] OT-3B: apply terminology rows to chapters 7-10 prose and exercises --- chapters/ch07-execution/01-calc-energy.ipynb | 2 +- chapters/ch07-execution/02-state-traces.ipynb | 10 +++++----- chapters/ch07-execution/conclusion.md | 2 +- chapters/ch07-execution/index.md | 2 +- chapters/ch08-checking/01-assert-constraint-def.ipynb | 2 +- chapters/ch08-checking/conclusion.md | 2 +- chapters/ch08-checking/index.md | 2 +- .../01-traceability-graph.ipynb | 4 ++-- exercises/ch07/exercise.ipynb | 4 ++-- exercises/ch08/exercise.ipynb | 6 +++--- exercises/ch10/exercise.ipynb | 2 +- 11 files changed, 19 insertions(+), 19 deletions(-) diff --git a/chapters/ch07-execution/01-calc-energy.ipynb b/chapters/ch07-execution/01-calc-energy.ipynb index a51d179..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](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." + "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." ] }, { diff --git a/chapters/ch07-execution/02-state-traces.ipynb b/chapters/ch07-execution/02-state-traces.ipynb index dda14bc..d8939d6 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](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 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 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." ] }, { @@ -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](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 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](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-023-opensysml-does-not-resolve-state-machine-transition-trigger-names)). 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 does: `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](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." + "`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 tool 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." ] }, { diff --git a/chapters/ch07-execution/conclusion.md b/chapters/ch07-execution/conclusion.md index 5609125..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](https://github.com/Open-MBEE/toaster/blob/main/DEFERRED.md#d-023-opensysml-does-not-resolve-state-machine-transition-trigger-names)). +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 diff --git a/chapters/ch07-execution/index.md b/chapters/ch07-execution/index.md index f596f86..85251e8 100644 --- a/chapters/ch07-execution/index.md +++ b/chapters/ch07-execution/index.md @@ -28,7 +28,7 @@ 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](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. +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 diff --git a/chapters/ch08-checking/01-assert-constraint-def.ipynb b/chapters/ch08-checking/01-assert-constraint-def.ipynb index 752fedb..61381cb 100644 --- a/chapters/ch08-checking/01-assert-constraint-def.ipynb +++ b/chapters/ch08-checking/01-assert-constraint-def.ipynb @@ -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." ] }, { diff --git a/chapters/ch08-checking/conclusion.md b/chapters/ch08-checking/conclusion.md index e1b0598..17d77de 100644 --- a/chapters/ch08-checking/conclusion.md +++ b/chapters/ch08-checking/conclusion.md @@ -10,7 +10,7 @@ title: Conclusion ## 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](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. +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 diff --git a/chapters/ch08-checking/index.md b/chapters/ch08-checking/index.md index 1d829d9..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](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)). +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 diff --git a/chapters/ch10-traceability-signoff/01-traceability-graph.ipynb b/chapters/ch10-traceability-signoff/01-traceability-graph.ipynb index 67e65d4..890b1d6 100644 --- a/chapters/ch10-traceability-signoff/01-traceability-graph.ipynb +++ b/chapters/ch10-traceability-signoff/01-traceability-graph.ipynb @@ -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). This is a workaround for a gap in the 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))." + "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))." ] }, { @@ -667,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`." ] }, { diff --git a/exercises/ch07/exercise.ipynb b/exercises/ch07/exercise.ipynb index 6850805..294736b 100644 --- a/exercises/ch07/exercise.ipynb +++ b/exercises/ch07/exercise.ipynb @@ -52,8 +52,8 @@ " triggered transitions (reuse Chapter 4's own `BrewStart`/`BrewFinish`/\n", " `BrewCancel` item defs as the `accept` triggers — name the \"finished\"\n", " exit state something other than `done`: `done` collides with a member\n", - " `StateAction` already inherits, which OpenSysML loads silently but\n", - " sysml-toolkit and the pilot both flag), and untriggered completion\n", + " `StateAction` already inherits, which the OpenSysML runtime loads silently but\n", + " sysml-toolkit and the OMG SysML v2 Pilot Implementation both flag), and untriggered completion\n", " transitions from both exit states back to `idle` — what makes\n", " `BrewCycle` actually cycle, mirroring `ready`/`cancelled` both looping\n", " back to `idle` with no `accept` clause. Exhibit it on `BrewingSystem`\n", diff --git a/exercises/ch08/exercise.ipynb b/exercises/ch08/exercise.ipynb index 2cc5931..117ab23 100644 --- a/exercises/ch08/exercise.ipynb +++ b/exercises/ch08/exercise.ipynb @@ -257,7 +257,7 @@ "id": "cell-14", "metadata": {}, "source": [ - "D-030 says this toolchain's Z3 backend does not compose two separately\n", + "D-030 says sysml-toolkit's Z3 backend does not compose two separately\n", "declared `assert constraint`s: a constraint that depends on a sibling\n", "constraint's own stated bound, without restating that bound itself, is\n", "not helped by the sibling being declared true alongside it. The next\n", @@ -349,8 +349,8 @@ "This is possible because `reliesOnSibling`'s own antecedent never\n", "mentions `transferEfficiency`'s bound at all: it depends entirely on\n", "`transferEfficiencyBounded`, declared alongside it in the same file, to\n", - "narrow that feature before its own conclusion is checked. This\n", - "toolchain's Z3 backend never composes two separately declared `assert\n", + "narrow that feature before its own conclusion is checked.\n", + "sysml-toolkit's Z3 backend never composes two separately declared `assert\n", "constraint`s (`DEFERRED.md` D-030), so nothing wires the sibling's\n", "stated bound into `reliesOnSibling`'s own check, and the witness above\n", "(`transferEfficiency = 2`) is exactly the value `transferEfficiencyBounded`\n", diff --git a/exercises/ch10/exercise.ipynb b/exercises/ch10/exercise.ipynb index 5ed4b73..01ef304 100644 --- a/exercises/ch10/exercise.ipynb +++ b/exercises/ch10/exercise.ipynb @@ -501,7 +501,7 @@ "all, so its own defect (a declared, unused subject) was found by direct\n", "spec reading, not by any tool diagnostic; a separate, independently-built\n", "draft paired a typed subject with a real `assert satisfy` line, which the\n", - "real OMG pilot did flag directly, fixed by dropping the subject before this\n", + "real OMG SysML v2 Pilot Implementation did flag directly, fixed by dropping the subject before this\n", "reconciliation began. Do not repeat either mistake here: no declared\n", "subject, and (see below) no `assert satisfy` line either. A `require\n", "constraint` subsetting `deliveredMassBoundedBySupply` directly from within\n", From fd0fb23dfbee38c798782182a855a7fafc643b6f Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 19:59:34 -0400 Subject: [PATCH 36/61] Plan: queue the contribution-policy revision after the terminology contracts --- .../2026-10-03-opensysml-terminology-plan.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/superpowers/plans/2026-10-03-opensysml-terminology-plan.md b/docs/superpowers/plans/2026-10-03-opensysml-terminology-plan.md index 9dab46d..3150a5b 100644 --- a/docs/superpowers/plans/2026-10-03-opensysml-terminology-plan.md +++ b/docs/superpowers/plans/2026-10-03-opensysml-terminology-plan.md @@ -168,3 +168,20 @@ Acceptance: the lint is clean on the real learner surface (chapters, docs) a ## Out of scope Pilot membership (Z's answer may change only the definition sentence in AGENTS.md 1.2 and `docs/references.md`); renaming skills or packages; re-running notebooks; editing `decisions/` evidence; closing stale upstream-issue wording (separate DEFERRED follow-up). + +## Queued after OT-8 (requested by mzargham, 2026-10-03; NOT started, specify as its own contract when the terminology contracts clear) + +**Contribution-policy revision.** The contribution sections, both 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 otherwise improved against the project's existing priorities: +1. conformance with the SysML v2 specifications (all three OMG PDFs: the SysML v2 language spec, the API and Services spec, KerML); +2. didactic clarity; +3. effective, demonstrative use of tools from the OpenSysML ecosystem. +An improvement is acceptable if it is **strictly dominant**: it makes at least one of these better without making any of them worse. + +Scope to inventory when specified: `docs/contributor.md`, `docs/setup.md` (fork-and-exercise workflow), `README.md`, any "contribute" +text in chapter `index.md`/`conclusion.md` and notebook markdown cells, the exercise-pointer cells, and `AGENTS.md` where it states +contribution scope. Judgment items for the ACE: the exact "strictly dominant" test wording; how it reconciles with the +chapter-conclusion "what comes next" sentences and with `DEFERRED.md` (which tracks tool gaps); whether adding a chapter or +exercise is ever allowed. Edits fall under the same protections as the terminology pass (protected zones, markdown-only for +notebooks, guard checker, independent reviewer), and the contribution wording must use the OpenSysML convention (DL-116/DL-117). From d447ce3a4f477ae96c04ecbd9f6033873af93e61 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 20:20:04 -0400 Subject: [PATCH 37/61] DL-118: ACE batch 2 rulings; final texts 2 --- decisions/log.md | 10 +++++++ .../opensysml-terminology/final-texts-2.md | 26 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 decisions/opensysml-terminology/final-texts-2.md diff --git a/decisions/log.md b/decisions/log.md index 63edb3f..e1fd4ac 100644 --- a/decisions/log.md +++ b/decisions/log.md @@ -1611,3 +1611,13 @@ Reasoning: (1) The top note makes every pre-DL-116 bare name in a field line una 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. 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. From 4b990a996e5a01c0c7f363e87ad06dfd94299da2 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 19:57:44 -0400 Subject: [PATCH 38/61] OT-5: DEFERRED.md terminology note and runtime naming in gap sentences --- DEFERRED.md | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/DEFERRED.md b/DEFERRED.md index 16a64dd..65facb7 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 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. **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. From e54c2ba292da7b755628daa074ebd78a4ae2c47f Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 19:57:58 -0400 Subject: [PATCH 39/61] OT-5: correct D-035 sentence citing D-032 and D-033 as one-of-three cases (DL-117) --- DEFERRED.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEFERRED.md b/DEFERRED.md index 65facb7..2c96d97 100644 --- a/DEFERRED.md +++ b/DEFERRED.md @@ -877,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 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 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. From 0fbe4b0f4430af4777e3143525e8da2b24daca8a Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 20:21:14 -0400 Subject: [PATCH 40/61] OT-3C: apply batch-2 terminology wording to docs and chapter markdown --- chapters/ch07-execution/02-state-traces.ipynb | 4 ++-- chapters/ch08-checking/02-violation-witness.ipynb | 2 +- .../ch10-traceability-signoff/01-traceability-graph.ipynb | 6 +++--- .../2026-09-30-energy-conservation-requirement-tie.md | 6 +++--- docs/references.md | 2 +- docs/reproducibility.md | 2 +- docs/setup.md | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/chapters/ch07-execution/02-state-traces.ipynb b/chapters/ch07-execution/02-state-traces.ipynb index d8939d6..fe3d759 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 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 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." ] }, { @@ -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 the OpenSysML runtime v0.9.0, `execute_state`'s `performer` argument has no effect on the result (a documented tool 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." + "`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." ] }, { diff --git a/chapters/ch08-checking/02-violation-witness.ipynb b/chapters/ch08-checking/02-violation-witness.ipynb index 30f87db..dadae07 100644 --- a/chapters/ch08-checking/02-violation-witness.ipynb +++ b/chapters/ch08-checking/02-violation-witness.ipynb @@ -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](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, 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](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." + "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." ] }, { diff --git a/chapters/ch10-traceability-signoff/01-traceability-graph.ipynb b/chapters/ch10-traceability-signoff/01-traceability-graph.ipynb index 890b1d6..9ebd248 100644 --- a/chapters/ch10-traceability-signoff/01-traceability-graph.ipynb +++ b/chapters/ch10-traceability-signoff/01-traceability-graph.ipynb @@ -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?" ] }, { @@ -530,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`](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 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." + "`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." ] }, { @@ -1023,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](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." + "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." ] }, { diff --git a/docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md b/docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md index b07830a..81d6907 100644 --- a/docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md +++ b/docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md @@ -66,7 +66,7 @@ pilot actually flagged — confirmed directly against that commit's own model. B two commits later by dropping the subject declaration. So two different defects, in two different places, were each found a different way: Approach A's (a declared-but-unused subject, no live binding) by direct spec reading; Approach B's own first draft's (a typed subject *plus* a -real, type-inconsistent binding) by the pilot's own mechanical check. Neither tool nor either +real, type-inconsistent binding) by the pilot's own mechanical check. 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. @@ -186,7 +186,7 @@ a second, quieter instance of the same failure mode in reverse. **Empirical confirmation, not just argument.** Z was not convinced by the argument above on its own — correctly: an abstract claim that a construct "does no evaluative work" deserves to be -checked against the tool, not just read off the spec text. Two things were verified directly +checked against the OpenSysML runtime, not just read off the spec text. Two things were verified directly rather than asserted. First, the base library itself settles where subject-dependence actually comes from. @@ -198,7 +198,7 @@ reference the subject's own features (exactly what the spec's worked example, `m via `:>> mass = massActual`, and exactly what `EnergyConservationReq`'s own `require constraint c :> deliveredEnergyBoundedBySupply` does not do). -Second, this was tested directly against `model.verify_satisfaction()` — the tool's own +Second, this was tested directly against `model.verify_satisfaction()` — the runtime's own point-evaluation engine, the same one a reader would reach for expecting confirmation, the same way `heatGenerationReq`'s own real `assert satisfy ... by rated` / `by weak` claims are confirmed elsewhere in this model. Three variants of `assert satisfy energyConservationReq by X;` were built diff --git a/docs/references.md b/docs/references.md index 5749d08..579f2d5 100644 --- a/docs/references.md +++ b/docs/references.md @@ -70,7 +70,7 @@ The OpenSysML runtime's Python package (`opensysml==0.9.0`), used to load, valid 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 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. --- diff --git a/docs/reproducibility.md b/docs/reproducibility.md index 3cf815b..b439417 100644 --- a/docs/reproducibility.md +++ b/docs/reproducibility.md @@ -19,7 +19,7 @@ OpenSysML ([opensysml.org](https://opensysml.org/)) is the open-source SysML v2 - **The OpenSysML runtime binary** is pinned by version string (`v0.9.0` as of this tutorial), downloaded by [`scripts/check-tools.py`](https://github.com/Open-MBEE/toaster/blob/main/scripts/check-tools.py) rather than resolved from a floating "latest." Every model-loading call in every notebook goes through this one pinned binary; there's no code path that reaches a - different version. + different version of the runtime. Chapters 5, 8 and 10 also hand model text to sysml-toolkit's `sysmlv2`, pinned by the next item. - **The external tools** that chapters 5, 8 and 10 call (the `sysmlv2` command-line tool, Z3, the PlantUML jar and the SysML v2 standard library) are pinned in [`scripts/tool-pins.json`](https://github.com/Open-MBEE/toaster/blob/main/scripts/tool-pins.json) and installed by diff --git a/docs/setup.md b/docs/setup.md index db5ca66..a988d42 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -146,7 +146,7 @@ render-capable CLI (distinct from the service binary the Python package itself talks to) — chapters that render an action-flow or state-transition diagram need it; nothing else does. -**sysml-toolkit** does one thing the OpenSysML runtime cannot yet: prove that a constraint holds for every +**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. Chapter 8 uses it directly (`toaster.modelcheck.verify_holds`, wrapping its `sysmlv2 verify --solve` CLI) to prove `deliveredEnergyBoundedBySupply` for every value its unbound features From 949826e00660ffd0cdbfa13a7f62ba1ae356b6ed Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 20:25:26 -0400 Subject: [PATCH 41/61] OT-7: lint gains per-rule ignore_code, docs/superpowers exclusion, six OpenSysML naming guard rules --- glossary/README.md | 2 +- glossary/lint.py | 49 +++++++++-- glossary/lint_rules.toml | 56 ++++++++++++ glossary/tests/test_lint.py | 170 ++++++++++++++++++++++++++++++++++++ 4 files changed, 271 insertions(+), 6 deletions(-) diff --git a/glossary/README.md b/glossary/README.md index a17b649..9094238 100644 --- a/glossary/README.md +++ b/glossary/README.md @@ -75,6 +75,6 @@ Do not add a term without a canonical source. If a word has no canonical definit ## Lint -`uv run python -m glossary lint [--json] [--baseline FILE] [--write-baseline FILE]` scans learner-facing content (markdown cells of `chapters/**/*.ipynb`, `chapters/**/*.md`, `docs/**/*.md` except the generated `docs/glossary.md`) against the rules in `lint_rules.toml`. Each rule has `id`, `regex` (case-insensitive), `message`, `why`, `severity` (`error` or `warn`) and `scope` (`learner`); a malformed rules file exits 2. Each hit reports file, cell (notebooks), line, rule, matched text and severity, followed by per-rule counts. Without `--baseline` the exit code is 1 if any error hit exists. `--write-baseline` saves the current hits; with `--baseline`, hits matching a saved entry by file, rule and matched text (not line) are "baselined", the rest "new", and only a new error exits 1. +`uv run python -m glossary lint [--json] [--baseline FILE] [--write-baseline FILE]` scans learner-facing content (markdown cells of `chapters/**/*.ipynb`, `chapters/**/*.md`, `docs/**/*.md` except the generated `docs/glossary.md` and everything under `docs/superpowers/`) against the rules in `lint_rules.toml`. Each rule has `id`, `regex` (case-insensitive), `message`, `why`, `severity` (`error` or `warn`) and `scope` (`learner`), plus an optional boolean `ignore_code` (default `false`; any other type exits 2). A rule with `ignore_code = true` is matched against a copy of each unit in which fenced blocks (` ``` ` and `~~~`) and inline code spans (including double-backtick spans) are replaced by spaces of equal length, newlines kept, so line numbers are unchanged; rules without it see the text as written. A backtick-fenced MyST directive (for example ```{note}) is masked as a fenced block, so prose inside it is invisible to `ignore_code` rules. A malformed rules file exits 2. Each hit reports file, cell (notebooks), line, rule, matched text and severity, followed by per-rule counts. Without `--baseline` the exit code is 1 if any error hit exists. `--write-baseline` saves the current hits; with `--baseline`, hits matching a saved entry by file, rule and matched text (not line) are "baselined", the rest "new", and only a new error exits 1. The baseline is count-based: a key (file, rule, matched text) is baselined only up to the number of times it appears in the baseline file (duplicates count), so a further identical hit in that file is new. Fewer hits than baselined is fine (exit 0). Rules-file and baseline errors (non-string fields, `rule` not a list of tables, zero rules, unknown top-level key, duplicate ids, empty regex, unwritable baseline path) exit 2 with a message naming the problem. diff --git a/glossary/lint.py b/glossary/lint.py index 48cf466..5c3a98d 100644 --- a/glossary/lint.py +++ b/glossary/lint.py @@ -1,7 +1,9 @@ """Lint learner-facing content against rules kept in data (lint_rules.toml). Scope: markdown cells of chapters/**/*.ipynb, chapters/**/*.md and docs/**/*.md, -except docs/glossary.md (generated). Nothing else is scanned. +except docs/glossary.md (generated) and everything under docs/superpowers/. Nothing else is +scanned. A rule with ignore_code = true is matched against a copy of each unit in which fenced +blocks and inline code spans are replaced by spaces (same length, newlines kept). """ from __future__ import annotations @@ -19,7 +21,12 @@ FIELDS = ("id", "regex", "message", "why", "severity", "scope") SEVERITIES = ("error", "warn") SCOPES = ("learner",) +OPTIONAL_BOOL_FIELDS = ("ignore_code",) EXCLUDED = ("docs/glossary.md",) +EXCLUDED_PREFIXES = ("docs/superpowers/",) + +_FENCE_OPEN = re.compile(r"^ {0,3}(`{3,}|~{3,})(.*)$") +_CODE_SPAN = re.compile(r"(? list[Rule]: raise LintConfigError(f"rule {name!r}: missing field {f!r}") if not isinstance(raw[f], str): raise LintConfigError(f"rule {name!r}: field {f!r} must be a string, got {type(raw[f]).__name__}") + for f in OPTIONAL_BOOL_FIELDS: + if f in raw and not isinstance(raw[f], bool): + raise LintConfigError(f"rule {name!r}: field {f!r} must be a boolean, got {type(raw[f]).__name__}") if not raw["id"]: raise LintConfigError(f"rule {name!r}: id must not be empty") if raw["id"] in seen: @@ -83,7 +94,7 @@ def load_rules(path: Path = RULES_FILE) -> list[Rule]: pattern = re.compile(raw["regex"], re.IGNORECASE) except re.error as e: raise LintConfigError(f"rule {name!r}: regex does not compile: {e}") from e - rules.append(Rule(raw["id"], pattern, raw["message"], raw["why"], raw["severity"], raw["scope"])) + rules.append(Rule(raw["id"], pattern, raw["message"], raw["why"], raw["severity"], raw["scope"], raw.get("ignore_code", False))) return rules @@ -92,7 +103,7 @@ def _units(repo: Path): files = [p for pat in ("chapters/**/*.ipynb", "chapters/**/*.md", "docs/**/*.md") for p in repo.glob(pat)] for p in sorted(set(files)): rel = p.relative_to(repo).as_posix() - if rel in EXCLUDED or ".ipynb_checkpoints" in p.parts: + if rel in EXCLUDED or rel.startswith(EXCLUDED_PREFIXES) or ".ipynb_checkpoints" in p.parts: continue try: if p.suffix == ".ipynb": @@ -107,13 +118,41 @@ def _units(repo: Path): raise LintConfigError(f"cannot read {rel}: {e}") from e +def _blank(s: str) -> str: + """Replace every character except line breaks with a space (same length).""" + return re.sub(r"[^\r\n]", " ", s) + + +def mask_code(text: str) -> str: + """Blank out fenced blocks (``` and ~~~) and inline code spans; length and line breaks are preserved.""" + out: list[str] = [] + fence: tuple[str, int] | None = None + for line in text.splitlines(keepends=True): + body = line.rstrip("\r\n") + if fence is None: + m = _FENCE_OPEN.match(body) + if m and not (m.group(1)[0] == "`" and "`" in m.group(2)): + fence = (m.group(1)[0], len(m.group(1))) + out.append(_blank(line)) + else: + out.append(line) + else: + out.append(_blank(line)) + c, n = fence + if re.fullmatch(rf" {{0,3}}{re.escape(c)}{{{n},}}[ \t]*", body): + fence = None + return _CODE_SPAN.sub(lambda m: _blank(m.group(0)), "".join(out)) + + def scan(repo: Path, rules: list[Rule]) -> list[Hit]: hits = [] for rel, cell, text in _units(repo): + masked = mask_code(text) if any(r.ignore_code for r in rules) else text for r in rules: - for m in r.pattern.finditer(text): + subject = masked if r.ignore_code else text + for m in r.pattern.finditer(subject): line = text.count("\n", 0, m.start()) + 1 - hits.append(Hit(rel, cell, line, r.id, m.group(0), r.severity)) + hits.append(Hit(rel, cell, line, r.id, text[m.start():m.end()], r.severity)) return hits diff --git a/glossary/lint_rules.toml b/glossary/lint_rules.toml index 713c313..bc69521 100644 --- a/glossary/lint_rules.toml +++ b/glossary/lint_rules.toml @@ -2,6 +2,8 @@ # Every rule needs: id, regex (matched case-insensitively), message, why, severity, scope. # severity: "error" | "warn". scope: "learner". # Use (?-i:...) inside a regex to require exact case for part of it. +# Optional: ignore_code = true (boolean, default false) matches the rule against a copy of the text in which +# fenced blocks and inline code spans are blanked out (same length, line numbers unchanged). [[rule]] id = "tall-named" @@ -58,3 +60,57 @@ message = "No judgment record disposition is \"accepted\"." why = "SA-7" severity = "warn" scope = "learner" + +[[rule]] +id = "opensysml-contrast-or-and" +regex = '''OpenSysML\s+(or|and|nor|vs\.?|versus)\s+sysml-toolkit''' +message = "Bare \"OpenSysML\" names the stack, so it cannot be contrasted with or joined to sysml-toolkit. Name the component: \"the OpenSysML runtime\" and sysml-toolkit." +why = "AGENTS.md 1.2 (OpenSysML naming convention); DL-116 (guard), DL-118 (ignore_code scope)" +severity = "error" +scope = "learner" +ignore_code = true + +[[rule]] +id = "toolkit-and-opensysml" +regex = '''sysml-toolkit\s+(or|and)\s+OpenSysML''' +message = "Bare \"OpenSysML\" names the stack, so it cannot be joined to sysml-toolkit. Name the component: sysml-toolkit and \"the OpenSysML runtime\"." +why = "AGENTS.md 1.2 (OpenSysML naming convention); DL-116 (guard), DL-118 (ignore_code scope)" +severity = "error" +scope = "learner" +ignore_code = true + +[[rule]] +id = "neither-opensysml" +regex = '''neither\s+OpenSysML''' +message = "A \"neither\" claim needs two named components: \"neither the OpenSysML runtime nor sysml-toolkit\", or say \"no tool\"." +why = "AGENTS.md 1.2 (OpenSysML naming convention); DL-116 (guard), DL-118 (ignore_code scope)" +severity = "error" +scope = "learner" +ignore_code = true + +[[rule]] +id = "not-opensysml" +regex = '''not\s+OpenSysML''' +message = "A negative claim needs the component that was probed: say \"not the OpenSysML runtime\" (or name sysml-toolkit)." +why = "AGENTS.md 1.2 (OpenSysML naming convention); DL-116 (guard), DL-118 (ignore_code scope)" +severity = "error" +scope = "learner" +ignore_code = true + +[[rule]] +id = "opensysml-capability" +regex = '''OpenSysML\s+(alone|itself|cannot|can't|does\s+not|doesn't|only)''' +message = "A capability claim names the component that was probed: \"the OpenSysML runtime cannot ...\" or \"sysml-toolkit does not ...\"." +why = "AGENTS.md 1.2 (OpenSysML naming convention); DL-116 (guard), DL-118 (ignore_code scope)" +severity = "error" +scope = "learner" +ignore_code = true + +[[rule]] +id = "opensysml-version" +regex = '''OpenSysML\s+v0\.9''' +message = "Versions attach to component names: \"the OpenSysML runtime v0.9.0\", \"sysml-toolkit v0.9.1\"." +why = "AGENTS.md 1.2 (OpenSysML naming convention); DL-116 (guard), DL-118 (ignore_code scope)" +severity = "error" +scope = "learner" +ignore_code = true diff --git a/glossary/tests/test_lint.py b/glossary/tests/test_lint.py index f854e89..ec13337 100644 --- a/glossary/tests/test_lint.py +++ b/glossary/tests/test_lint.py @@ -66,6 +66,20 @@ def ids(hs: list[lint.Hit]) -> list[str]: ("stale-partition", "was partitioned into implementation-agnostic", "departitioned into implementation-agnostic"), ("accepted-disposition", "The disposition is accepted here.", "The reviewer accepted the invoice; a disposition is recorded."), ("accepted-disposition", "an accepted disposition", "accepted practice, and a disposition. One two three four five six accepted"), + # OT-7 guard rules: hit, and the sanctioned rewrite as the near miss + ("opensysml-contrast-or-and", "OpenSysML or sysml-toolkit accepts it", "The OpenSysML runtime or sysml-toolkit accepts it"), + ("opensysml-contrast-or-and", "OpenSysML and sysml-toolkit agree; also OpenSysML vs. sysml-toolkit", "the OpenSysML runtime and sysml-toolkit agree"), + ("opensysml-contrast-or-and", "OpenSysML versus sysml-toolkit", "OpenSysML, the stack, includes sysml-toolkit"), + ("opensysml-contrast-or-and", "neither OpenSysML nor sysml-toolkit; OpenSysML nor sysml-toolkit", "the OpenSysML runtime nor sysml-toolkit"), + ("toolkit-and-opensysml", "sysml-toolkit or OpenSysML does it", "sysml-toolkit or the OpenSysML runtime does it"), + ("toolkit-and-opensysml", "sysml-toolkit and OpenSysML agree", "sysml-toolkit and the OpenSysML runtime agree"), + ("neither-opensysml", "Neither OpenSysML nor the pilot flags it", "Neither the OpenSysML runtime nor the pilot flags it"), + ("not-opensysml", "this is not OpenSysML behavior", "this is not the OpenSysML runtime's behavior"), + ("opensysml-capability", "OpenSysML cannot prove it", "the OpenSysML runtime cannot prove it"), + ("opensysml-capability", "OpenSysML itself, OpenSysML alone, OpenSysML only, OpenSysML can't", "the OpenSysML runtime itself, alone, or only"), + ("opensysml-capability", "OpenSysML does not accept it; OpenSysML doesn't either", "the OpenSysML runtime does not accept it; sysml-toolkit doesn't"), + ("opensysml-version", "OpenSysML v0.9.0 loads it", "the OpenSysML runtime accepts X; sysml-toolkit v0.9.1 rejects it"), + ("opensysml-version", "OpenSysML v0.9.1", "OpenSysML runtime v0.9.0 and the stack OpenSysML v1"), ] @@ -277,3 +291,159 @@ def test_non_utf8_rules_file_is_exit_2(tmp_path: Path) -> None: p.write_bytes(b"[[rule]]\nid='r'\nmessage='caf\xe9'\n") r = runner.invoke(app, ["lint", "--repo", str(tmp_path), "--rules", str(p)]) assert r.exit_code == 2 and "rules.toml" in r.output and "Traceback" not in r.output + + +# --- ignore_code (OT-7) --------------------------------------------------------------------- + +PLAIN = "plain-sub" +MASKED = "masked-sub" + + +def two_rule_file(tmp: Path) -> Path: + base = "message = 'm'\nwhy = 'w'\nseverity = 'error'\nscope = 'learner'\n" + p = tmp / "two.toml" + p.write_text( + f"[[rule]]\nid = '{PLAIN}'\nregex = 'foo'\n{base}\n" + f"[[rule]]\nid = '{MASKED}'\nregex = 'foo'\nignore_code = true\n{base}") + return p + + +def scan_two(tmp: Path, text: str, rel: str = "docs/x.md") -> list[lint.Hit]: + make_repo(tmp, {rel: text}) + return lint.scan(tmp, lint.load_rules(two_rule_file(tmp))) + + +def test_ignore_code_skips_inline_span_for_masked_rule_only(tmp_path: Path) -> None: + hs = scan_two(tmp_path, "see `foo` here\n") + assert ids(hs) == [PLAIN] + + +@pytest.mark.parametrize("text", [ + "a ``foo`` b\n", + "a ``x ` foo`` b\n", + "a `foo\nbar` b\n", + "```\nfoo\n```\n", + "```python\nfoo\n```\n", + "~~~\nfoo\n~~~\n", + " ```\nfoo\n ```\n", + "````\n```\nfoo\n```\n````\n", + "```\nfoo\n", # unclosed fence runs to the end +]) +def test_ignore_code_masks_fences_and_spans(tmp_path: Path, text: str) -> None: + assert ids(scan_two(tmp_path, text)) == [PLAIN] + + +@pytest.mark.parametrize("text", [ + "foo outside `code` foo\n", + "a `foo\n\nbar` b\n", # a span cannot cross a blank line + "a `foo`` b\n", # unequal backtick runs do not close + "a `foo b\n", # unclosed span is literal + "~~~\ncode\n```\n~~~\nfoo\n", # a ``` line does not close a ~~~ fence; the fence ended at ~~~ + "``` `\nfoo\n", # backtick fence info string may not contain a backtick: not a fence +]) +def test_ignore_code_does_not_over_mask(tmp_path: Path, text: str) -> None: + got = ids(scan_two(tmp_path, text)) + assert MASKED in got + + +@pytest.mark.parametrize(("text", "visible"), [ + ("a `foo\r\n\r\nbar` b\r\n", "foo"), # a span cannot cross a CRLF blank line + ("a `x\r\n\r\nOpenSysML cannot` b\r\n", "OpenSysML cannot"), # the probe: second paragraph stays visible + ("a `x\r\n \t\r\nfoo` b\r\n", "foo"), # whitespace-only CRLF blank line +]) +def test_crlf_blank_line_stops_code_span(text: str, visible: str) -> None: + masked = lint.mask_code(text) + assert len(masked) == len(text) and visible in masked + assert lint.mask_code(text.replace("\r\n", "\n")).count(visible) == 1 # same answer as LF + + +def test_crlf_notebook_cell_hit_after_paragraph_break_is_reported(tmp_path: Path) -> None: + # Path.read_text() normalizes CRLF in .md files, but a notebook cell string keeps its \r\n + text = "a `x\r\n\r\nOpenSysML cannot` b\r\n" + make_repo(tmp_path, {"chapters/ch/a.ipynb": json.dumps({"cells": [{"cell_type": "markdown", "source": text}]})}) + hs = lint.scan(tmp_path, lint.load_rules()) + assert [(h.rule, h.cell, h.line) for h in hs] == [("opensysml-capability", 0, 3)] + crlf_fence = "```\r\nfoo\r\n```\r\nfoo\r\n" + make_repo(tmp_path, {"chapters/ch/a.ipynb": json.dumps({"cells": [{"cell_type": "markdown", "source": crlf_fence}]})}) + hs = lint.scan(tmp_path, lint.load_rules(two_rule_file(tmp_path))) + assert [(h.rule, h.line) for h in hs if h.rule == MASKED] == [(MASKED, 4)] + + +def test_mask_preserves_length_and_line_structure() -> None: + text = "a `x`\n```\ny\nz\n```\n~~~\nw\n~~~\nlast ``q `r`` end\r\nfoo\n" + masked = lint.mask_code(text) + assert len(masked) == len(text) + assert [i for i, c in enumerate(masked) if c in "\r\n"] == [i for i, c in enumerate(text) if c in "\r\n"] + assert "x" not in masked and "y" not in masked and "w" not in masked and "q" not in masked and "r" not in masked + assert masked.endswith("foo\n") and masked.startswith("a ") + + +def test_masking_leaves_line_numbers_and_text_unchanged(tmp_path: Path) -> None: + text = "line1 `foo`\n```\nfoo\nfoo\n```\nline6 foo and `x`\n" + hs = scan_two(tmp_path, text) + plain = [h.line for h in hs if h.rule == PLAIN] + masked = [h for h in hs if h.rule == MASKED] + assert plain == [1, 3, 4, 6] + assert [(h.line, h.text) for h in masked] == [(6, "foo")] + + +def test_masked_rule_in_notebook_markdown_cell(tmp_path: Path) -> None: + make_repo(tmp_path, {"chapters/ch/a.ipynb": nb(("markdown", "intro\n\nuse `foo` then foo\n"))}) + hs = lint.scan(tmp_path, lint.load_rules(two_rule_file(tmp_path))) + assert [(h.rule, h.cell, h.line) for h in hs if h.rule == MASKED] == [(MASKED, 0, 3)] + assert [h.line for h in hs if h.rule == PLAIN] == [3, 3] + + +def test_default_rules_hit_sets_ignore_code_false_for_existing_rules() -> None: + flags = {r.id: r.ignore_code for r in lint.load_rules()} + existing = ("tall-named", "no-em-dash", "concept-selection", "sub-behavior", + "stale-physical-layer", "stale-partition", "accepted-disposition") + assert all(flags[i] is False for i in existing) + new = [i for i in flags if i not in existing] + assert len(new) == 6 and all(flags[i] for i in new) + assert all(r.severity == "error" and "AGENTS.md 1.2" in r.why and "DL-116" in r.why + for r in lint.load_rules() if r.id in new) + + +def test_existing_rule_still_sees_code_text(tmp_path: Path) -> None: + # existing rules are not masked: an em-dash inside backticks is still reported + assert "no-em-dash" in ids(hits_for(tmp_path, "see `a\u2014b` here\n")) + + +@pytest.mark.parametrize("value", ["'yes'", "1", "[true]", "'true'"]) +def test_non_bool_ignore_code_is_exit_2(tmp_path: Path, value: str) -> None: + p = tmp_path / "rules.toml" + p.write_text("[[rule]]\nid='r'\nregex='x'\nmessage='m'\nwhy='w'\nseverity='error'\nscope='learner'\n" + f"ignore_code={value}\n") + r = runner.invoke(app, ["lint", "--repo", str(tmp_path), "--rules", str(p)]) + assert r.exit_code == 2 and "field 'ignore_code' must be a boolean" in r.output and "Traceback" not in r.output + + +def test_bool_ignore_code_accepted_and_default_false(tmp_path: Path) -> None: + base = "message='m'\nwhy='w'\nseverity='error'\nscope='learner'\n" + p = tmp_path / "rules.toml" + p.write_text(f"[[rule]]\nid='a'\nregex='x'\n{base}\n[[rule]]\nid='b'\nregex='x'\nignore_code=false\n{base}\n" + f"[[rule]]\nid='c'\nregex='x'\nignore_code=true\n{base}") + assert [r.ignore_code for r in lint.load_rules(p)] == [False, False, True] + + +def test_docs_superpowers_is_not_scanned(tmp_path: Path) -> None: + bad = "sub-behavior and concept selection\n" + make_repo(tmp_path, {"docs/superpowers/specs/s.md": bad, "docs/superpowers/plans/deep/p.md": bad}) + assert lint.scan(tmp_path, lint.load_rules()) == [] + make_repo(tmp_path, {"docs/superpowers.md": bad, "docs/superpowersx/y.md": bad, "chapters/superpowers/z.md": bad}) + assert {h.file for h in lint.scan(tmp_path, lint.load_rules())} == { + "docs/superpowers.md", "docs/superpowersx/y.md", "chapters/superpowers/z.md"} + + +def test_guard_rules_ignore_code_in_markdown_but_not_prose(tmp_path: Path) -> None: + # the protected stored-output pattern: a code span quoting a bare name is not reported + make_repo(tmp_path, {"docs/a.md": "The output reads `OpenSysML cannot do it` verbatim.\n```\nOpenSysML v0.9.0\n```\n"}) + assert lint.scan(tmp_path, lint.load_rules()) == [] + make_repo(tmp_path, {"docs/a.md": "OpenSysML cannot do it, and OpenSysML v0.9.0 is old.\n"}) + assert ids(lint.scan(tmp_path, lint.load_rules())) == ["opensysml-capability", "opensysml-version"] + + +def test_guard_rules_sanctioned_sentence_passes(tmp_path: Path) -> None: + ok = "the OpenSysML runtime accepts X; sysml-toolkit v0.9.1 rejects it\n" + assert hits_for(tmp_path, ok) == [] From 7c5bd7c5dd4e86348417a952d25678de814f0429 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 20:45:32 -0400 Subject: [PATCH 42/61] DL-119: Z rules the Pilot Implementation is outside OpenSysML; push and contribution-policy sequencing --- decisions/log.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/decisions/log.md b/decisions/log.md index e1fd4ac..3efb9aa 100644 --- a/decisions/log.md +++ b/decisions/log.md @@ -1621,3 +1621,15 @@ Reasoning: (1) DL-117 (1) states the rule for field lines and its reason; keepin 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. From 758bdb39060d6c90268f244fa4abad9bee1b3b72 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 20:48:06 -0400 Subject: [PATCH 43/61] OT-6: skills prose names the OpenSysML runtime, sysml-toolkit and the Pilot per DL-116/DL-117; SA-6 parenthetical and Z-12 reading note --- .claude/skills/ace-protocol/SKILL.md | 6 +++--- .claude/skills/ace-protocol/z-model.md | 2 +- .claude/skills/opensysml-api/SKILL.md | 2 +- .claude/skills/opensysml-query/SKILL.md | 8 ++++---- .claude/skills/sysml-diagrams/SKILL.md | 18 +++++++++--------- .../sysml-diagrams/references/recipes.md | 16 ++++++++-------- .claude/skills/sysml-v2-toaster-model/SKILL.md | 2 +- .claude/skills/tutorial-glossary/SKILL.md | 2 +- .claude/skills/tutorial-style-guide/SKILL.md | 2 +- .../skills/tutorial-supporting-pages/SKILL.md | 2 +- 10 files changed, 30 insertions(+), 30 deletions(-) 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..6fce11e 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 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 runtime 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 runtime render forms. 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..683dba1 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 \ @@ -97,7 +97,7 @@ 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 \ @@ -107,7 +107,7 @@ 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 +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 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 @@ -116,7 +116,7 @@ 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..9d2c3ab 100644 --- a/.claude/skills/tutorial-supporting-pages/SKILL.md +++ b/.claude/skills/tutorial-supporting-pages/SKILL.md @@ -12,7 +12,7 @@ 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) | From e8b70f685cb402a0185548a6a539f0ba20309034 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 21:06:44 -0400 Subject: [PATCH 44/61] DL-120: OT-6 skills terminology pass --- decisions/log.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/decisions/log.md b/decisions/log.md index 3efb9aa..dcc06b0 100644 --- a/decisions/log.md +++ b/decisions/log.md @@ -1633,3 +1633,14 @@ 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. From d84c1d34fbaecce5cf3cd91021c4e2c8d9e3974a Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 21:20:48 -0400 Subject: [PATCH 45/61] DL-121: terminology gate passed; gate notes --- decisions/log.md | 10 ++++++++++ decisions/opensysml-terminology/gate-notes.md | 15 +++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 decisions/opensysml-terminology/gate-notes.md diff --git a/decisions/log.md b/decisions/log.md index dcc06b0..75f040e 100644 --- a/decisions/log.md +++ b/decisions/log.md @@ -1644,3 +1644,13 @@ Reasoning: A bare "OpenSysML" no longer identifies the subject of a runtime clai 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. 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. From 563214c35375bd3adb5adc49389082030fcd6ec0 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 21:24:59 -0400 Subject: [PATCH 46/61] CT-1: contribution-policy inventory (read-only) --- decisions/contribution-policy/inventory.md | 303 +++++++++++++++++++++ 1 file changed, 303 insertions(+) create mode 100644 decisions/contribution-policy/inventory.md 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. From cfb3672b9f0426bd8cb0e1b708fc7669a7fdbd79 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 21:38:22 -0400 Subject: [PATCH 47/61] DL-122, DL-123: contribution policy rulings; final texts --- decisions/contribution-policy/final-texts.md | 195 +++++++++++++++++++ decisions/log.md | 18 ++ 2 files changed, 213 insertions(+) create mode 100644 decisions/contribution-policy/final-texts.md 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/log.md b/decisions/log.md index 75f040e..dcce325 100644 --- a/decisions/log.md +++ b/decisions/log.md @@ -1654,3 +1654,21 @@ Reasoning: Every contract passed an independent review on a different model, and 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. From 02d8e9cc5dc6d293ba17d8d21d17dc69775b7cdf Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 21:40:20 -0400 Subject: [PATCH 48/61] CT-3: tutorial-supporting-pages skill aligned to the contribution policy (DL-122) --- .claude/skills/tutorial-supporting-pages/SKILL.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.claude/skills/tutorial-supporting-pages/SKILL.md b/.claude/skills/tutorial-supporting-pages/SKILL.md index 9d2c3ab..91581ef 100644 --- a/.claude/skills/tutorial-supporting-pages/SKILL.md +++ b/.claude/skills/tutorial-supporting-pages/SKILL.md @@ -14,7 +14,7 @@ description: docs/ page inventory, reproducibility statement structure, fork-and | `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 (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) From 2230e335518bfe4d7599debd67dfb65074126381 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 21:41:20 -0400 Subject: [PATCH 49/61] CT-4: PR template; lint rejects unknown rule fields --- .github/PULL_REQUEST_TEMPLATE.md | 25 +++++++++++++++++++++++++ glossary/README.md | 4 ++-- glossary/lint.py | 3 +++ glossary/tests/test_lint.py | 15 +++++++++++++++ 4 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..e38fa90 --- /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 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 diff --git a/glossary/README.md b/glossary/README.md index 9094238..521ab49 100644 --- a/glossary/README.md +++ b/glossary/README.md @@ -75,6 +75,6 @@ Do not add a term without a canonical source. If a word has no canonical definit ## Lint -`uv run python -m glossary lint [--json] [--baseline FILE] [--write-baseline FILE]` scans learner-facing content (markdown cells of `chapters/**/*.ipynb`, `chapters/**/*.md`, `docs/**/*.md` except the generated `docs/glossary.md` and everything under `docs/superpowers/`) against the rules in `lint_rules.toml`. Each rule has `id`, `regex` (case-insensitive), `message`, `why`, `severity` (`error` or `warn`) and `scope` (`learner`), plus an optional boolean `ignore_code` (default `false`; any other type exits 2). A rule with `ignore_code = true` is matched against a copy of each unit in which fenced blocks (` ``` ` and `~~~`) and inline code spans (including double-backtick spans) are replaced by spaces of equal length, newlines kept, so line numbers are unchanged; rules without it see the text as written. A backtick-fenced MyST directive (for example ```{note}) is masked as a fenced block, so prose inside it is invisible to `ignore_code` rules. A malformed rules file exits 2. Each hit reports file, cell (notebooks), line, rule, matched text and severity, followed by per-rule counts. Without `--baseline` the exit code is 1 if any error hit exists. `--write-baseline` saves the current hits; with `--baseline`, hits matching a saved entry by file, rule and matched text (not line) are "baselined", the rest "new", and only a new error exits 1. +`uv run python -m glossary lint [--json] [--baseline FILE] [--write-baseline FILE]` scans learner-facing content (markdown cells of `chapters/**/*.ipynb`, `chapters/**/*.md`, `docs/**/*.md` except the generated `docs/glossary.md` and everything under `docs/superpowers/`) against the rules in `lint_rules.toml`. Each rule has `id`, `regex` (case-insensitive), `message`, `why`, `severity` (`error` or `warn`) and `scope` (`learner`), plus an optional boolean `ignore_code` (default `false`; any other type exits 2). Any other key in a `[[rule]]` table (for example a misspelled `ignore_cod`) is an unknown rule field and exits 2. A rule with `ignore_code = true` is matched against a copy of each unit in which fenced blocks (` ``` ` and `~~~`) and inline code spans (including double-backtick spans) are replaced by spaces of equal length, newlines kept, so line numbers are unchanged; rules without it see the text as written. A backtick-fenced MyST directive (for example ```{note}) is masked as a fenced block, so prose inside it is invisible to `ignore_code` rules. A malformed rules file exits 2. Each hit reports file, cell (notebooks), line, rule, matched text and severity, followed by per-rule counts. Without `--baseline` the exit code is 1 if any error hit exists. `--write-baseline` saves the current hits; with `--baseline`, hits matching a saved entry by file, rule and matched text (not line) are "baselined", the rest "new", and only a new error exits 1. -The baseline is count-based: a key (file, rule, matched text) is baselined only up to the number of times it appears in the baseline file (duplicates count), so a further identical hit in that file is new. Fewer hits than baselined is fine (exit 0). Rules-file and baseline errors (non-string fields, `rule` not a list of tables, zero rules, unknown top-level key, duplicate ids, empty regex, unwritable baseline path) exit 2 with a message naming the problem. +The baseline is count-based: a key (file, rule, matched text) is baselined only up to the number of times it appears in the baseline file (duplicates count), so a further identical hit in that file is new. Fewer hits than baselined is fine (exit 0). Rules-file and baseline errors (non-string fields, `rule` not a list of tables, zero rules, unknown top-level key, unknown rule field, duplicate ids, empty regex, unwritable baseline path) exit 2 with a message naming the problem. diff --git a/glossary/lint.py b/glossary/lint.py index 5c3a98d..3dd2b2d 100644 --- a/glossary/lint.py +++ b/glossary/lint.py @@ -76,6 +76,9 @@ def load_rules(path: Path = RULES_FILE) -> list[Rule]: raise LintConfigError(f"rule {name!r}: missing field {f!r}") if not isinstance(raw[f], str): raise LintConfigError(f"rule {name!r}: field {f!r} must be a string, got {type(raw[f]).__name__}") + unknown = sorted(set(raw) - set(FIELDS) - set(OPTIONAL_BOOL_FIELDS)) + if unknown: + raise LintConfigError(f"rule {name!r}: unknown field(s) {unknown}") for f in OPTIONAL_BOOL_FIELDS: if f in raw and not isinstance(raw[f], bool): raise LintConfigError(f"rule {name!r}: field {f!r} must be a boolean, got {type(raw[f]).__name__}") diff --git a/glossary/tests/test_lint.py b/glossary/tests/test_lint.py index ec13337..b384e78 100644 --- a/glossary/tests/test_lint.py +++ b/glossary/tests/test_lint.py @@ -419,6 +419,21 @@ def test_non_bool_ignore_code_is_exit_2(tmp_path: Path, value: str) -> None: assert r.exit_code == 2 and "field 'ignore_code' must be a boolean" in r.output and "Traceback" not in r.output +def test_unknown_rule_field_is_exit_2(tmp_path: Path) -> None: + # a misspelled optional key must fail closed, not be silently ignored + p = tmp_path / "rules.toml" + p.write_text("[[rule]]\nid='r'\nregex='x'\nmessage='m'\nwhy='w'\nseverity='error'\nscope='learner'\n" + "ignore_cod=true\n") + with pytest.raises(lint.LintConfigError, match=r"rule 'r': unknown field\(s\) \['ignore_cod'\]"): + lint.load_rules(p) + r = runner.invoke(app, ["lint", "--repo", str(tmp_path), "--rules", str(p)]) + assert r.exit_code == 2 and "unknown field(s) ['ignore_cod']" in r.output and "Traceback" not in r.output + + +def test_shipped_rules_file_has_no_unknown_fields() -> None: + assert lint.load_rules(lint.RULES_FILE) + + def test_bool_ignore_code_accepted_and_default_false(tmp_path: Path) -> None: base = "message='m'\nwhy='w'\nseverity='error'\nscope='learner'\n" p = tmp_path / "rules.toml" From 4f775d597dc9bc2d19a9e7467dea55e36b3b5c94 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 21:42:28 -0400 Subject: [PATCH 50/61] CT-3b: sysml-diagrams action-flow and state rows corrected against D-037 --- .claude/skills/sysml-diagrams/SKILL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.claude/skills/sysml-diagrams/SKILL.md b/.claude/skills/sysml-diagrams/SKILL.md index 6fce11e..ce7e26b 100644 --- a/.claude/skills/sysml-diagrams/SKILL.md +++ b/.claude/skills/sysml-diagrams/SKILL.md @@ -15,8 +15,8 @@ Use one default pipeline for each figure type. Read only the relevant recipe in |---|---|---| | 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 runtime 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 runtime 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 runtime render forms. The pilot (this table's earlier default) fails on all real chapter content; do not use it. | +| 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. | From f36c1dc5bf4861cb3fb21fed9e6cd318b172f2c5 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 21:40:31 -0400 Subject: [PATCH 51/61] CT-2: contribution policy in AGENTS.md 1.12, contributor guide, setup and README (DL-122) --- AGENTS.md | 6 ++++ README.md | 8 +++-- docs/contributor.md | 77 +++++++++++++++++++++++++++++++-------------- docs/setup.md | 7 ++++- 4 files changed, 71 insertions(+), 27 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 97f1a7b..9dd99bd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -162,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/README.md b/README.md index 7c86b35..57284c1 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ 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 @@ -54,5 +54,7 @@ decisions/ — ACE decision log [`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/docs/contributor.md b/docs/contributor.md index 9e48bf8..a96b743 100644 --- a/docs/contributor.md +++ b/docs/contributor.md @@ -4,6 +4,15 @@ This page is for maintainers working on the tutorial itself, not learners workin It assumes you can read Python and SysML and that you have the environment from [Getting Started](setup.md) already set up. +(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 mzargham (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 "Z" is the contributor identity `mzargham` (Michael Zargham, GitHub user @@ -45,11 +54,12 @@ touch anything, even if you never run an agent yourself: of a task handed to a builder or reviewer), and `task-states.md` (what state a task is in and what moves it to the next one). -If you want to extend a chapter, clarify a definition, or review didactic content, the harness +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 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). +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). (deployment-status)= ## Deployment status @@ -85,31 +95,49 @@ uv run python scripts/check-site.py --site _build/html --content _build/site/con --log build.log --base-url /toaster ``` -## Update a dependency and regenerate outputs +(keep-current)= +## Keep current: update a dependency or tool pin 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` +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. Commit the lockfile alongside the version change; never bump a version without - regenerating and committing the matching lockfile. - -## 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 +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. + +## 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. 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 + After changing a notebook's construction cells or a fixture, run + `uv run python scripts/check_construction.py --check --chapter=N` + ([`scripts/check_construction.py`](https://github.com/Open-MBEE/toaster/blob/main/scripts/check_construction.py)): + it executes the registered construction zones and loads each `TOASTER_INCREMENT` and the + cumulative fixture. Update a notebook's existing `CONSTRUCTION_NOTEBOOKS` entry only if its + stubs change; new entries are for new notebooks, which are not accepted. +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. (change-a-model-element)= @@ -118,6 +146,9 @@ uv run python scripts/check-site.py --site _build/html --content _build/site/con 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. +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: + 1. Find every `ReviewRecord` whose `model_ref` touches the element you are changing (`grep -rl model_ref= chapters/`). 2. After changing the model, recompute each record's `content_hash` and re-run diff --git a/docs/setup.md b/docs/setup.md index a988d42..7237946 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -146,7 +146,7 @@ render-capable CLI (distinct from the service binary the Python package itself talks to) — chapters that render an action-flow or state-transition diagram need it; nothing else does. -**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 value of an unbound quantity, not just check it against one fixed value, using the Z3 solver. Chapter 8 uses it directly (`toaster.modelcheck.verify_holds`, wrapping its `sysmlv2 verify --solve` CLI) to prove `deliveredEnergyBoundedBySupply` for every value its unbound features @@ -182,6 +182,11 @@ Fork the repository, provision the environment (above), then: 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. +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). + **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 From 4c58ca61950fe0fea88343137b7ba689b76a9479 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 21:40:31 -0400 Subject: [PATCH 52/61] CT-2: chapter pointers to the contribution policy and two note fixes (DL-122) --- chapters/ch03-measures/03-threshold-judgment.ipynb | 2 +- chapters/ch07-execution/02-state-traces.ipynb | 2 +- chapters/ch10-traceability-signoff/conclusion.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/chapters/ch03-measures/03-threshold-judgment.ipynb b/chapters/ch03-measures/03-threshold-judgment.ipynb index 403abfd..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`](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)." + "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))." ] }, { diff --git a/chapters/ch07-execution/02-state-traces.ipynb b/chapters/ch07-execution/02-state-traces.ipynb index fe3d759..2af719b 100644 --- a/chapters/ch07-execution/02-state-traces.ipynb +++ b/chapters/ch07-execution/02-state-traces.ipynb @@ -452,7 +452,7 @@ "id": "cell-23", "metadata": {}, "source": [ - "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 does: `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." + "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." ] }, { diff --git a/chapters/ch10-traceability-signoff/conclusion.md b/chapters/ch10-traceability-signoff/conclusion.md index 1d91d57..a7534fe 100644 --- a/chapters/ch10-traceability-signoff/conclusion.md +++ b/chapters/ch10-traceability-signoff/conclusion.md @@ -20,7 +20,7 @@ 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 From d0c2645166d4afa094762a0fb01bae2d2aa152fd Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 22:00:16 -0400 Subject: [PATCH 53/61] DL-124..DL-126: contribution amendments after CT-2 review; CT-6 pre-edit; CT-3/3b complete --- .../contribution-policy/final-texts-2.md | 56 +++++++++++++++++++ decisions/log.md | 24 ++++++++ 2 files changed, 80 insertions(+) create mode 100644 decisions/contribution-policy/final-texts-2.md diff --git a/decisions/contribution-policy/final-texts-2.md b/decisions/contribution-policy/final-texts-2.md new file mode 100644 index 0000000..4743b30 --- /dev/null +++ b/decisions/contribution-policy/final-texts-2.md @@ -0,0 +1,56 @@ +# 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) diff --git a/decisions/log.md b/decisions/log.md index dcce325..bcd944f 100644 --- a/decisions/log.md +++ b/decisions/log.md @@ -1672,3 +1672,27 @@ Decision (intent, PENDING): in .claude/skills/sysml-diagrams/SKILL.md replace th 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. From 243bc9e3be05daa3e8d373bc543bf757c3c606da Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 22:01:02 -0400 Subject: [PATCH 54/61] CT-5: contributor guide amendments F1-F4, Q2 reviewer table, PR template learning outcome (DL-124) --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- docs/contributor.md | 42 +++++++++++++++++++++++--------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e38fa90..650289f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -20,6 +20,6 @@ Not worse on each of the others (one line each, with how you checked): ## Protections -- [ ] 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 - [ ] `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 diff --git a/docs/contributor.md b/docs/contributor.md index a96b743..9eca865 100644 --- a/docs/contributor.md +++ b/docs/contributor.md @@ -9,9 +9,19 @@ It assumes you can read Python and SysML and that you have the environment from 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 mzargham (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. +- **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, 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); 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 mzargham (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 AI 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; only Z decides that, and a glossary term is confirmed only by 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 | ## Who is Z @@ -39,7 +49,7 @@ touch anything, even if you never run an agent yourself: which it comes out once the upstream gap closes. - **[`.claude/agents/`](https://github.com/Open-MBEE/toaster/tree/main/.claude/agents)** defines the roles that do the work: an `orchestrator` that turns a request into scoped contracts and integrates results; `builder`/`reviewer` pairs that - implement and independently check each change (always on different models, never the same + implement and independently check each change (always on different AI models, never the same one reviewing its own work); a `layer-auditor` that classifies model elements against the functional/logical/physical boundaries; a `simulated-learner` that executes a chapter as a persona-assigned reader and reports what it found; and the `ace`, which triages questions @@ -100,9 +110,13 @@ uv run python scripts/check-site.py --site _build/html --content _build/site/con 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/`. + (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. 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)). @@ -110,13 +124,17 @@ uv run python scripts/check-site.py --site _build/html --content _build/site/con 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 + entry's resolution condition, 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), + 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. + `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. ## How a change is built and reviewed @@ -137,7 +155,7 @@ building the existing ones govern every change to them: 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 +4. Get an independent review on a different AI model than whoever authored the change, per `decisions/task-states.md`'s merge gate. (change-a-model-element)= From d14bc6aee3536ba0b67e94aebf121b6032bbbb80 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 22:06:16 -0400 Subject: [PATCH 55/61] CT-6: sysml-diagrams recipes.md action-flow and state paragraphs corrected against D-037 (DL-125) --- .../sysml-diagrams/references/recipes.md | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/.claude/skills/sysml-diagrams/references/recipes.md b/.claude/skills/sysml-diagrams/references/recipes.md index 683dba1..542b73c 100644 --- a/.claude/skills/sysml-diagrams/references/recipes.md +++ b/.claude/skills/sysml-diagrams/references/recipes.md @@ -89,9 +89,12 @@ 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. @@ -106,12 +109,13 @@ 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 runtime 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. From e972bf74aad86ee58da9fc0b1a6d294688efcd73 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 22:07:14 -0400 Subject: [PATCH 56/61] CT-5: gloss the ACE at first use in contributor guide; README decisions/ row says decision log (DL-124) --- README.md | 2 +- docs/contributor.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 57284c1..d7beee4 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ src/toaster/— Python package (bootstrap, connect, query, check, conformance, e 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`](AGENTS.md), [`CLAUDE.md`](CLAUDE.md), and [`DEFERRED.md`](DEFERRED.md) at the repo root are not learner material — they're diff --git a/docs/contributor.md b/docs/contributor.md index 9eca865..42e562f 100644 --- a/docs/contributor.md +++ b/docs/contributor.md @@ -10,7 +10,7 @@ It assumes you can read Python and SysML and that you have the environment from 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, 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); 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 mzargham (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 AI 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. +- **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 AI model checks the statement; what the reviewer cannot tell goes to the ACE (the project's triage role, described below). 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; only Z decides that, and a glossary term is confirmed only by Z. The reviewer applies this test. A pull request passes when one "better" line (or, for keeping From 517b72b85678753529fce8793503659e04f9738d Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 22:09:28 -0400 Subject: [PATCH 57/61] DL-127: CT-5 and CT-6 complete; record corrected recipes text --- decisions/contribution-policy/final-texts-2.md | 11 +++++++++++ decisions/log.md | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/decisions/contribution-policy/final-texts-2.md b/decisions/contribution-policy/final-texts-2.md index 4743b30..f2a1bc8 100644 --- a/decisions/contribution-policy/final-texts-2.md +++ b/decisions/contribution-policy/final-texts-2.md @@ -54,3 +54,14 @@ change is not accepted; what the reviewer cannot tell goes to the ACE. 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/log.md b/decisions/log.md index bcd944f..1437f9d 100644 --- a/decisions/log.md +++ b/decisions/log.md @@ -1696,3 +1696,13 @@ Status: PENDING until the edit lands; the ACE or orchestrator records COMPLETE w 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. From 4620a9773d000ec646b12966125ea0248d9f7115 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 22:22:28 -0400 Subject: [PATCH 58/61] DL-128: final-gate amendments (B1, B2) ruled; final texts 3 --- .../contribution-policy/final-texts-3.md | 34 +++++++++++++++++++ decisions/log.md | 10 ++++++ 2 files changed, 44 insertions(+) create mode 100644 decisions/contribution-policy/final-texts-3.md 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/log.md b/decisions/log.md index 1437f9d..45fef3e 100644 --- a/decisions/log.md +++ b/decisions/log.md @@ -1706,3 +1706,13 @@ Reasoning: The independent reviewer and the ACE editor each caught an inaccuracy 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. From c3b393125184216f4cd1a02fb6fe0586264bc143 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 22:23:23 -0400 Subject: [PATCH 59/61] CT-7: contributor guide and PR template final-gate amendments (DL-128 B1, B2, n2) --- .github/PULL_REQUEST_TEMPLATE.md | 4 ++-- docs/contributor.md | 22 +++++++++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 650289f..7531d70 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -13,7 +13,7 @@ Better on (name it, with evidence): - [ ] (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): +Not worse on any priority (one line each, with how you checked): - (1) ___ - (2) ___ - (3) ___ @@ -22,4 +22,4 @@ Not worse on each of the others (one line each, with how you checked): - [ ] 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/` and `uv run python -m glossary lint` pass locally +- [ ] `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/docs/contributor.md b/docs/contributor.md index 42e562f..ee6536e 100644 --- a/docs/contributor.md +++ b/docs/contributor.md @@ -10,7 +10,7 @@ It assumes you can read Python and SysML and that you have the environment from 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, 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); 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 mzargham (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 AI model checks the statement; what the reviewer cannot tell goes to the ACE (the project's triage role, described below). A trade-off (better on one priority, worse on another) is not an improvement under this policy: open an issue and Z decides. +- **Improve what is here.** State in the pull request which priority improves and the evidence, and why the change is worse on none of the three; 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 AI model checks the statement; what the reviewer cannot tell goes to the ACE (the project's triage role, described below). 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; only Z decides that, and a glossary term is confirmed only by Z. The reviewer applies this test. A pull request passes when one "better" line (or, for keeping @@ -115,8 +115,12 @@ uv run python scripts/check-site.py --site _build/html --content _build/site/con `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. + `src/toaster/connect.py`, 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. 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)). @@ -132,9 +136,10 @@ uv run python scripts/check-site.py --site _build/html --content _build/site/con 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. + 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. ## How a change is built and reviewed @@ -152,7 +157,10 @@ building the existing ones govern every change to them: it executes the registered construction zones and loads each `TOASTER_INCREMENT` and the cumulative fixture. Update a notebook's existing `CONSTRUCTION_NOTEBOOKS` entry only if its stubs change; new entries are for new notebooks, which are not accepted. -3. Run `uv run python -m glossary lint` before committing prose; run the pacing check in +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 `tutorial-style-guide` (consecutive code cells with no markdown between them) on every notebook you touched. 4. Get an independent review on a different AI model than whoever authored the change, per From c3a91be28f8db936081da3bdb6df242014e91cb5 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 22:28:03 -0400 Subject: [PATCH 60/61] DL-129: contribution-policy pass complete --- decisions/log.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/decisions/log.md b/decisions/log.md index 45fef3e..c83404e 100644 --- a/decisions/log.md +++ b/decisions/log.md @@ -1716,3 +1716,12 @@ Reasoning: Verified counts: 42 notebooks with the escaped connect(version="v0.9. 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. From 9633208360b06936e016583ac0a229d91faa5511 Mon Sep 17 00:00:00 2001 From: Michael Zargham Date: Sat, 3 Oct 2026 23:57:51 -0400 Subject: [PATCH 61/61] CI: remove integration-branch trigger --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20eee98..1f78c4f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: CI on: push: - branches: [main, pages-publishing] + branches: [main] pull_request: jobs: @@ -92,8 +92,8 @@ jobs: retention-days: 7 if-no-files-found: warn - # Only a push to main produces the Pages artifact; pull requests and the - # pages-publishing validation pushes stop after the gate above. + # 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