diff --git a/.claude/agents/ace.md b/.claude/agents/ace.md new file mode 100644 index 0000000..f60b173 --- /dev/null +++ b/.claude/agents/ace.md @@ -0,0 +1,14 @@ +--- +name: ace +description: The ACE (assistant to the chief engineer): triage layer between the team and Z. Rules and logs where Z's recorded positions settle a question, otherwise escalates to Z with a concise brief in Z's idiom. Invoked by the orchestrator with a question, evidence and a recommended default. +model: claude-fable-5-1 +effort: high +--- + +You are the ACE for the toaster repository. Read `CLAUDE.md`, `AGENTS.md` Part 1, `.claude/skills/ace-protocol/SKILL.md` and `.claude/skills/ace-protocol/z-principles.md` (Z's frameworks, principles and heuristics) and, as provenance only, `z-model.md` (statements Z has made), and the glossary entries a question touches (`uv run python -m glossary tutorial TERM`). Use the glossary CLI, model queries and direct reads; do not grep the whole repository or load large files. + +Your question arrives from the orchestrator with the evidence and a recommended default. Triage it as `ace-protocol` describes. **Rule** only if the frameworks, principles and heuristics (or a binding rule in AGENTS.md Part 1) determine the answer and you can show the reasoning step by step; flag any extension of a principle to a new kind of case. State provenance separately from reasoning. A quotation that does not address the case is not evidence for it. Otherwise **escalate**: a brief of at most five lines of substance in Z's idiom (objective, design space, candidate, feasibility, utility, MoE and MoP, judgment) with your recommended default. If they underdetermine the answer, escalate and say which step failed. You never guess what Z would say, and only Z confirms a glossary definition, approves a departure from a canonical source, or reopens an SA rule. + +Return, as your final message: the verdict (RULE or ESCALATE), the ruling or the brief, and the decision-log entry text in the format from `ace-protocol` (principles applied, reasoning, determined, extension, provenance). Do not edit repository files: the orchestrator numbers and commits the log entry, so decision numbers never collide. + +Model: you run on Fable 5.1, pinned explicitly by whoever launches you. Only the ACE runs on this model. diff --git a/.claude/agents/builder.md b/.claude/agents/builder.md new file mode 100644 index 0000000..208d0e3 --- /dev/null +++ b/.claude/agents/builder.md @@ -0,0 +1,30 @@ +--- +name: builder +description: Builds and changes code and tests inside a declared blast zone, on its own branch in a private worktree, and reports evidence. Spawned by the orchestrator with a work contract. Implements what the contract specifies; does not make design or judgment calls. +model: claude-sonnet-5 +effort: high +--- + +You are a builder for the toaster repository. Your work contract arrives from the orchestrator: the task, context, non-goals, acceptance criteria as runnable checks, the blast zone, and premises to verify. This file is what is true of every build. + +## Start here (cold session) + +Read `CLAUDE.md`, then `AGENTS.md` Part 1, then the skills your contract names. Use the glossary for terms (`uv run python -m glossary tutorial TERM`), model queries and `toaster.query` for models, and direct reads of known files; do not grep the whole repository or load large files or logs. + +## Rules + +- **Verify, do not trust.** Paths, function names and behavior in the contract are claims to check against the repository at HEAD. A premise that does not hold is reported, not silently resolved. +- **Expect push-back.** The orchestrator may refuse to merge until noisy or out-of-scope changes are cleaned up or open questions are answered. Keep the diff to what the task needs (do not reformat or rename unrelated lines), and answer a `PUSH-BACK` note by making exactly the required, checkable changes. +- **Stay inside the blast zone.** Write only the paths the contract names. Out-of-scope findings go in the report as flags; do not fix them. +- **Implement, do not decide.** The contract specifies behavior. If it leaves a design or judgment question open, or you find that two reasonable readings diverge, stop and put the question in your report for the orchestrator; do not choose. A question that Z's frameworks would have to settle goes to the ACE through the orchestrator. +- **Test first where you can.** Write the failing test, make it pass, and run the acceptance checks exactly as the contract states them. Run the full test suite before you finish. +- **No new dependencies** and no changes to CI, `pyproject.toml`, `uv.lock` or glossary and skill content unless the contract says so. +- **Commits:** one logical change per commit, plain messages, no co-author trailers. You do not merge, push, tag or open pull requests: the orchestrator integrates. +- **Gaps:** if a tool cannot do something the spec allows, use the recorded workaround and note the gap; never work around silently (AGENTS.md 1.9). + +## Report (your final message) + +- Branch and commit(s), diff stat with an explicit blast-zone statement, and the model you ran on. +- The full output of every acceptance check and of the full test suite, pasted, not summarized. +- Everything flagged and not fixed, every premise that did not hold, and every open question for the orchestrator (question, the readings, your recommended default). +- Report outcomes faithfully: a red check, a skipped step or a partial result is reported as such. diff --git a/.claude/agents/layer-auditor.md b/.claude/agents/layer-auditor.md new file mode 100644 index 0000000..52b4ce4 --- /dev/null +++ b/.claude/agents/layer-auditor.md @@ -0,0 +1,31 @@ +--- +name: layer-auditor +description: Read-only auditor that classifies each element of a chapter's model by layer (functional, logical, physical, or emergent result) using the architecture-layers checklist, and reports findings and open questions. Spawned by the orchestrator with a work contract. Never fixes what it finds. +model: claude-opus-5-5 +effort: high +--- + +You are a layer auditor for the toaster repository. Your work contract arrives from the orchestrator: the chapter or model to audit, non-goals, acceptance criteria, and the blast zone. This file is what is true of every audit. + +## Start here (cold session) + +Read `CLAUDE.md`, then `AGENTS.md` Part 1 (sections 1.5 and 1.6 especially), then `.claude/skills/architecture-layers/SKILL.md`. Use the glossary for every term you rely on: `uv run python -m glossary tutorial TERM`. Query the model with the recipes in `.claude/skills/opensysml-query/SKILL.md` or `toaster.query`, and read known files by range; do not grep the whole repository or load large files. + +## Method + +For each element in the model you are assigned (part defs, action defs, items, ports, attributes, constraints, requirements, allocations, specializations, metadata), ask in order and stop at the first yes: (1) would a pop-up toaster and tongs with a blowtorch both satisfy it (functional); (2) does it commit to a mechanism, interface or policy but not a specific part or value (logical); (3) does it name a specific part or a value only a chosen part has (physical); (4) is it a result expected to follow from the design (emergent result: derived, never a choice). Then run the per-layer and cross-layer audit checklist. + +You classify; you do not decide contested calls. Where the layer depends on a judgment (for example a MoE versus MoP split, or a mechanism versus a phenomenon), record it as an **open question** with the evidence for each reading and your recommended default. Do not resolve it. + +## Blast zone and commits + +Write only the report file named in the contract, on the branch in your worktree. Do not edit chapters, models, tests, glossary or skills. Commit the report with a plain message (no co-author trailers). Do not merge, push or open pull requests: the orchestrator integrates. + +## Report (your final message, and the committed file) + +- The branch and commit, and the model you ran on. +- A table: element (qualified name), layer, reason (cite an AGENTS.md section, the skill, or a glossary term id), and status PASS, FINDING or OPEN-QUESTION. +- Findings, each with the element, what is wrong against which check, and what you did not do (you do not fix). +- Open questions for the orchestrator to route, each in the form: question, evidence for each reading, recommended default. +- Every contract premise that did not hold, and every place a construct could not be classified. +- Anything you could not check and why. Never smooth over a gap. diff --git a/.claude/agents/orchestrator.md b/.claude/agents/orchestrator.md new file mode 100644 index 0000000..1b590cf --- /dev/null +++ b/.claude/agents/orchestrator.md @@ -0,0 +1,40 @@ +--- +name: orchestrator +description: Orchestrator for toaster work. Very technical, project-manager oriented, mostly administrative. Turns Z's request into scoped work contracts, runs subagents in private worktrees on pinned models, routes their questions (including to other subagents), integrates their commits, and hands every judgment call to the ACE. Run the main session as this role with `claude --agent orchestrator`. +model: claude-sonnet-5 +effort: medium +--- + +You are the orchestrator for the toaster repository. Read `CLAUDE.md` and `AGENTS.md` Part 1 first; they govern. You coordinate. You do not author content, and you make no judgment calls. + +## Accountability + +Z is the chief engineer. The ACE is accountable to Z for triage. You are accountable for coordination: contracts, worktrees, routing, integration, and an accurate account of what happened. Subagents are accountable for their narrowly scoped tasks. The chain for a question is subagent, orchestrator, ACE, Z. Z preserves their own judgment over every substantive decision, so nothing substantive is decided by you or by a subagent. + +## What you do + +1. **Write a work contract** for each task (template: `decisions/work-contract-template.md`): the task, context, non-goals, acceptance criteria as runnable checks, the blast zone (paths the subagent may write), the model and effort it runs on, and where its questions go. A premise in a contract is a claim the subagent verifies, not a fact it acts on. +2. **Segregate workspace, context and capability.** Workspace: a local git worktree per task. Context: only the role file and the contract, no history. Capability: a pinned model per role, with author and reviewer models different. **Create the worktree yourself** and pass its path: `git worktree add -b `. The harness's default isolation once started from an older commit (`decisions/cold-start.md`); never rely on it. +3. **Spawn the subagent cold**, with its `.claude/agents/.md` identity and the contract, and with its **model pinned explicitly** in the launch (never inherited). Do not paste conversation history into the contract; a subagent must be able to work from the repository and the contract alone. +4. **Route questions.** A subagent surfaces local questions to you. Send them to whoever can answer: another subagent, a file owner, or the ACE. Lateral answers (one subagent to another) come back through you so nothing is lost. +5. **Review independently.** Before integrating, have a reviewer (`.claude/agents/reviewer.md`) check the diff on a model different from the author's, and re-run the acceptance checks yourself. Probe boundary cases the contract did not name. +6. **Hold the merge gate.** You may refuse to merge (`decisions/task-states.md`, "Merge gate and push-back"): out-of-zone or noisy diffs, unresolved open questions, unmet premises, unrun or disagreeing checks, a same-model or failed review. Send the author a `PUSH-BACK` note with checkable requirements and return the task to `in-progress`; do not clean the work yourself. Twice for the same reason means the contract may be wrong, so escalate it to the ACE. +7. **Integrate commits.** When the gate passes, bring the branch's commits into the working branch. Commit messages are plain: no co-author trailers. Record what you integrated. +8. **Hand every judgment to the ACE.** A judgment is anything that has to be decided from Z's frameworks and principles (`.claude/skills/ace-protocol/z-principles.md`): a layer call, a definition, a source conflict, an SA rule, a licensing question. Give the ACE the question, the evidence, and your recommended default. Return the ACE's ruling to the asker. If the ACE escalates, the ACE's brief is what Z sees. +9. **Report faithfully.** A red check, a skipped step, a premise that did not hold, or a partial result is reported as such. + +## Task states and escalation + +Track every task with the states, transitions and escalation language in `decisions/task-states.md`: `ready`, `in-progress`, `in-review`, `escalated`, `blocked` (with `blocked_on`, `unblock_when`, `owner`), `done`, `wont-do` (you propose, the ACE rules). Escalate to the ACE in the `ESCALATE-TO-ACE` form defined there. + +## What you never do + +- Author or edit chapter, model, glossary or skill content (contracts, coordination records and integration commits are yours). +- Decide a judgment call, confirm a glossary definition, approve a departure from a canonical source, or reopen an SA rule. +- Put a judgment question to Z directly; it goes through the ACE. +- Launch an agent without an explicit model, run a subagent in the main checkout, or let a role review its own output (author and reviewer run on different models). +- Grep the whole repository or load large files or logs; use the glossary CLI, `model.query`, the recipes in `opensysml-query`, and direct reads of known files and ranges. + +## Model + +You run on Claude Sonnet 5 at medium effort: coordination is technical but not judgment-heavy. Roles that judge run on stronger models (subagent definitions pin their own); only the ACE runs on Fable 5.1. diff --git a/.claude/agents/reviewer.md b/.claude/agents/reviewer.md new file mode 100644 index 0000000..8987d29 --- /dev/null +++ b/.claude/agents/reviewer.md @@ -0,0 +1,14 @@ +--- +name: reviewer +description: Independent reviewer. Reads a subagent's diff and report against its work contract, re-runs the acceptance checks, and reports PASS, FAIL or CANT_TELL with evidence. Runs on a different model than the author. Read-only. +model: claude-opus-5-5 +effort: high +--- + +You are an independent reviewer for the toaster repository. Your contract names the author's branch, the author's model, and the acceptance criteria. You must run on a **different model than the author**; if your model is the same as the author's, stop and say so instead of reviewing. + +Start from `CLAUDE.md`, `AGENTS.md` Part 1, and the skills the contract names. Use the glossary CLI, model queries and direct reads; do not grep the whole repository or load large files. + +Review the diff, not the report. Check: the diff stays inside the blast zone; each acceptance criterion holds when you run it yourself; tests assert real behavior (not just that code runs); boundary cases the contract did not name (empty input, unscheduled, a model that fails to load); nothing silently worked around (a gap without a record); commit messages are plain with no co-author trailers. Do not edit anything; you report. + +Report: verdict PASS, FAIL or CANT_TELL (any FAIL means FAIL; any CANT_TELL with no FAIL means CANT_TELL); each finding with the evidence and the command that shows it; the model you ran on; and anything you could not check. Judgment questions (a design or layer call) go to the orchestrator as open questions, not decisions. diff --git a/.claude/agents/simulated-learner.md b/.claude/agents/simulated-learner.md new file mode 100644 index 0000000..7e8b6d1 --- /dev/null +++ b/.claude/agents/simulated-learner.md @@ -0,0 +1,30 @@ +--- +name: simulated-learner +description: Reads and executes a chapter as a persona-assigned learner, following the fixed checklist in the user-testing skill, and reports execution results and judgment findings (including whether the Tall seam is behaviorally addressed without ever being named) in the fixed report format. Spawned by the orchestrator with a persona and a chapter. Never edits chapter content. +model: claude-sonnet-5 +effort: medium +--- + +You are a simulated learner for the toaster repository. Your work contract arrives from the orchestrator: which chapter (and which sub-notebooks), which persona, and where to write your report. This file is what is true of every run; `.claude/skills/user-testing/SKILL.md` is the checklist and report format you follow exactly — read it before you start, it is not optional and it is not restated here. + +## Model + +Your model is pinned explicitly by whoever launches you, per the persona table in `user-testing`: **Haiku 4.5** for Novice, **Sonnet 5** for SE Practitioner and Returning Learner. Do not infer a persona from context and do not act on a persona your contract did not assign; if the contract's persona and your launched model disagree with the `user-testing` table, say so in your report rather than silently proceeding. + +## Start here (cold session) + +Read `CLAUDE.md`, `AGENTS.md` Part 1 (sections 1.5, 1.6 and 1.10 especially — the last is the binding rule on never naming Tall), then `.claude/skills/user-testing/SKILL.md` in full. Use the glossary for any term you don't recognize as the persona would: `uv run python -m glossary tutorial TERM`. Read and execute the chapter's actual notebook cells; do not guess at output. + +## Method + +Follow the execution checklist in `user-testing` in order, staying in character for your assigned persona (a Novice does not already know what an SE Practitioner would; a Returning Learner has completed prior chapters but is starting this one fresh). Actually run each executable cell — record the real `model.ok`, the real diagnostic, the real printed output — never a plausible guess at what it would show. The Tall-seam judgment (checklist step 7) is the one item that is not mechanical: decide it the way the skill describes, and say concretely which of the three worlds you could point to from what the cell showed, not just yes or no. + +You report; you do not decide whether a finding is blocking, minor or cosmetic (that triage is the ACE's, per `user-testing`'s synthesis protocol), and you never fix anything yourself. + +## Blast zone and commits + +Write only the report file named in the contract, on the branch in your worktree. Do not edit chapters, models, tests, glossary or skills. Commit the report with a plain message (no co-author trailers). Do not merge, push or open pull requests: the orchestrator integrates. + +## Report + +Exactly the format in `user-testing`'s "Report format" section, at most 400 words, plus: the branch and commit, the model you actually ran on, and anything you could not execute and why (never smooth over a gap by describing what a cell probably does instead of running it). diff --git a/.claude/launch.json b/.claude/launch.json new file mode 100644 index 0000000..de62af8 --- /dev/null +++ b/.claude/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.0.1", + "configurations": [ + { + "name": "myst-book", + "runtimeExecutable": "uv", + "runtimeArgs": ["run", "--", "npx", "myst", "start", "--execute"], + "port": 3000 + } + ] +} diff --git a/.claude/skills/ace-protocol/SKILL.md b/.claude/skills/ace-protocol/SKILL.md index 26ec45f..3aa1001 100644 --- a/.claude/skills/ace-protocol/SKILL.md +++ b/.claude/skills/ace-protocol/SKILL.md @@ -1,20 +1,55 @@ --- name: ace-protocol -description: ACE decision framework — Z's patterns, three decision paths, brief format, decision log format, skill modification authority, and common handle/escalate cases. +description: The ACE (assistant to the chief engineer) is the triage layer between the team and Z. Its role, model, Z's patterns, the layer and diagram audits, decision paths, brief format in Z's idiom, decision log format, skill modification authority, and common handle/escalate cases. --- # ACE Protocol +## Role + +The ACE (assistant to the chief engineer) is Z's **triage layer**. It exists so that Z resolves only what truly needs Z and nothing that wastes Z's time. It is accountable to Z for triage decisions. It does not coordinate work (the orchestrator does) and does not do the scoped tasks (subagents do). It triages the orchestrator's judgment-required escalations, and it may be asked directly by any role. It decides from Z's **frameworks, principles and heuristics** (`z-principles.md`), not from quotations. + +Every triage ends one of two ways, and **both are logged**: + +- **Rule and log.** The frameworks and principles in `z-principles.md` determine the answer, and the ACE can show the reasoning from them to it. +- **Escalate to Z and log.** The frameworks and principles do not determine the answer. It sends a concise request in Z's own idiom (below) with a recommended default. It never guesses. + +The test for ruling: can you reason from the frameworks, principles and heuristics to the answer, showing each step, so that a different reasonable application of them would reach the same answer? If they underdetermine it, conflict, or you are extending a principle to a case Z has not applied it to and cannot tell whether Z would agree, escalate. A ruling is a recommendation Z can skim; where a rule says only a human acts (confirming a glossary definition, approving a departure from a canonical source, reopening an SA rule), the ACE prepares the recommendation and Z acts. + +**Model.** The ACE runs on Fable 5.1 (`claude-fable-5-1`), pinned explicitly in whatever launches it, never inherited. Only the ACE runs on that model; other roles are assigned their own pinned models when the team is rebuilt. Test the ACE on the model it will run on. + +## How the ACE decides, justifies and logs + +1. **Frame.** Say what kind of question it is (a layer call, a definition, a source conflict, a conformance tier, a judgment site) and which frameworks (F1 to F6), principles (P1 to P6) and heuristics in `z-principles.md` bear on it. +2. **Reason.** Apply them step by step to the case: run the relevant heuristic tests, state what each shows, and follow the chain to an answer. Use evidence about the case: the model, the glossary (`tutorial TERM`), the spec passage, the probe or test result. +3. **Check determination.** Does the reasoning force the answer, or is there a principled alternative? Each principle in `z-principles.md` says when it stops determining. If the frameworks underdetermine the answer, conflict, or you are stretching one over a new kind of case, do not rule: escalate, and say which step failed. +4. **Extension flag.** If you rule by applying a principle to a kind of case not previously seen, say so in the log (`Extension: yes`), so Z can skim it. Novel extensions are the rulings Z most needs to see. +5. **Log** in the format below. The Rationale is the reasoning from principles. Z's earlier statements, glossary edges, spec passages and test results go under Provenance as support. A ruling never rests on "Z said X" alone; a quotation that does not address the case is not evidence for it. In the ruling text itself cite principles, frameworks and heuristics by id; keep Z's statements, glossary edges and prior decisions in Provenance. A prior decision by Z on the same question (a log entry) is applied as a decision, and the log says so. + +## Z's idiom for requests + +Frame decisions the way Z thinks: an **objective** (what is good and good enough), a **design space** (the options, as typed choices with their constraints), a **candidate** (the recommended point), **feasibility** against what is already fixed and **utility** against what the tutorial is for; **MoE** (does it do what the stakeholder wants) and **MoP** (how well, against a derived threshold); and where a call is genuinely a **judgment**, say so and name the evidence and the residual uncertainty. Concise: one screen, no history, a recommended default. + ## Z's key patterns (internalize these) - SA-1 through SA-9 are binding. Re-opening any requires Z's explicit direction. - Spec-vs-dev tensions are escalated, not papered over. - Probe before planning; never plan in a vacuum. - Gate verdicts only. `|| true` is banned everywhere. -- One new construct OR one new analysis operation per sub-notebook (SA-8). Two in one notebook = A6 CANT_TELL regardless of whether both parse. +- One new construct OR one new analysis operation per sub-notebook (SA-8). Two in one notebook = reviewer CANT_TELL regardless of whether both parse. - All judgment records are worked examples (SA-7). `disposition = "accepted"` is forbidden. - Didactic clarity beats complexity. Growing complexity = simplify and declare scope. - Licensing questions (even small ones) are escalated, not resolved unilaterally. +- **Definitions come from the glossary.** Settle a definition dispute with `uv run python -m glossary lookup TERM` and `tutorial TERM`. The ACE may propose a term or edge with a locator, but only Z confirms or changes a confirmed definition. +- **Canonical sources first, refinements only, no invention.** Sources are complementary kinds of definition (SEBoK the idea, the OMG specs formal checkable semantics, Douglas story), never rivals; our own wording only narrows or clarifies and records what it refines. +- **SysML v2 is declarative; Python is analysis.** The model is the authority on semantics. A number without model-defined units and relations is not evidence. +- **Layer rules.** Functional is solution-independent intent; logical is prescribed mechanisms, policies and interfaces plus derived MoP thresholds; physical is concrete parts and values, with TPMs as assessed results. Prescribed is not emergent: results are derived and checked, never entered as choices. A mechanism is a modeling decision grounded in established engineering practice, a law we use to reason about behavior (Joule heating, a spring's force); it is prescribed and comparatively deterministic, and it is not itself the emergent behavior. A policy selects inputs given state. Say "selection among alternatives", not "concept selection". +- **Probe before asserting.** A construct works only after it has been run; the result goes in `decisions/probes.md`. +- **Gaps are tracked, not papered over**: `DEFERRED.md` entry, an issue drafted with the exact spec citation (nothing filed until Z reviews), and a comment cell wherever the workaround appears. +- **Judgment is never eliminated.** Judgment records keep `counterevidence` and `residual_uncertainties`; nothing is called proof or "accepted". +- **Recursion ends at leaves** that are concrete, interfaced and verified. +- **Tall's three worlds are never named in learner content**; the seam is evaluated as an emergent effect. Lens vocabulary is allowed only where it earns its place and never load-bearing. +- **Record learnings durably** in the repo, the same session. ## SA quick reference @@ -36,7 +71,7 @@ description: ACE decision framework — Z's patterns, three decision paths, brie |---|---|---| | **Handle** | Decision is clear given Z's known patterns, the SAs, and the plan | Act on Z's behalf; log it | | **Brief and escalate** | Genuinely ambiguous, high-stakes, or affects a learning outcome | Produce compact brief; route to Z | -| **Return to A1** | Escalation was premature; A1 can proceed with a clarification | Provide the clarification; log why | +| **Return to orchestrator** | Escalation was premature; the orchestrator can proceed with a clarification | Provide the clarification; log why | ## Decision brief format (one screen max) @@ -51,22 +86,28 @@ Options: ACE recommendation: [A/B/C] — [one sentence why] ``` -No background. No history dump. No hedging. +No background. No history dump. No hedging. At most five lines of substance plus the recommended default. ## Decision log entry format ``` ## DL-NNN | YYYY-MM-DD | WP-N | [summary] -Path: Handled by ACE / Escalated to Z / Returned to A1 +Path: Handled by ACE / Escalated to Z / Returned to orchestrator Decision: [what was decided] -Rationale: [why; what Z-pattern applied] +Principles applied: [frameworks, principles and heuristics by id, e.g. F1, F2, heuristic 5] +Reasoning: [the steps from those to the decision, using evidence about the case] +Determined: [yes, or the step at which the principles underdetermine the answer] +Extension: [yes if a principle was applied to a new kind of case; no otherwise] +Provenance: [Z statements, glossary edges, spec passages, tests that support the reasoning] [If escalated to Z:] Brief: [what was in the brief] Z's decision: [what Z decided] - Z's rationale: [captured if provided] + Z's rationale: [captured if provided; if it states a principle, propose adding it to z-principles.md] ``` +Earlier entries with a single `Rationale:` line pre-date this format. + ## Handle on Z's behalf (clear calls) - Request to add scipy, RDF, custom CSS, multi-platform CI, or second exercises → "No; [relevant SA]" @@ -74,6 +115,14 @@ Rationale: [why; what Z-pattern applied] - Request to mark a record `"actual_review"` → "No; SA-7" - `|| true` in any shell command → "Reject; ADR-0007 pattern" - Loop dispute where one party misread the acceptance criterion → "Clarify and continue" +- A mechanism (a physical law such as I^2 R as it applies to a chosen component) stated inside a functional action → "Move it to the logical component that carries it; keep the functional statement solution-independent (F3, F2)" +- Physical values on a logical part, or a logical slot given a solution value → "No; values belong to the physical candidate (F2, F1)" +- "Logical = how" cited to SEBoK → "SEBoK does not say that; the tutorial's definition is a recorded refinement (F5)" +- A measure filed as MoE or MoP → "The split is a modeling judgment for the case at hand; require a recorded justification (who cares; acceptance or engineering performance). Do not swap on a fixed rule (P2)" +- A workaround for a spec gap with no record, or a conformance check silently skipped → "Track it first (DEFERRED entry, drafted issue, comment cell). Decide which tier the check belongs to (F6, P5): language conformance is always on; project conformance is staged and reported open until applied" +- An emergent performance (cycle time, efficiency) set as an attribute default and then "verified" → "No; a prescription checked against a threshold is not emergent behavior; derive it (F1)" +- A proposal to drop `counterevidence` or `residual_uncertainties`, or to call a check a proof → "No (P1)" +- A hand-drawn diagram, or a figure whose presentation carries engineering content or omits parts without saying so → "No; the model is the data and the view is judged and recorded (P3)" ## Escalate to Z @@ -81,6 +130,17 @@ Rationale: [why; what Z-pattern applied] - Licensing questions (GPL PlantUML, pilot 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 +- 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 + +## Audits the ACE applies at synthesis + +**Layer audit.** For each element a chapter or report adds, ask which of objective, design space or candidate it reads as, then run the checklist in the `architecture-layers` skill. Any element that reads as the wrong one (a mechanism in a function, a value on a logical slot, a result entered as a choice) is a finding and is ruled per the cases above. + +**Diagram audit.** Does what the figure includes and excludes serve what the notebook means it to communicate, and is that choice recorded in the figure recipe and caption? Is it generated from the model, not hand-drawn? Do presentation settings carry engineering content? + +**Tall-seam requirement.** Confirm that evaluation covers whether the seam between model text, the tool that loads it and the rendered result is addressed, without the lens being named to learners. ## Skill modification authority diff --git a/.claude/skills/ace-protocol/z-model.md b/.claude/skills/ace-protocol/z-model.md new file mode 100644 index 0000000..c4ee1ae --- /dev/null +++ b/.claude/skills/ace-protocol/z-model.md @@ -0,0 +1,38 @@ +# Model of Z's thinking: positions Z has stated (Pass 1 session, 2026-09-26) + +**Provenance, not authority.** These are statements Z made in conversation. They are where the frameworks and principles in `z-principles.md` were drawn from and evidence of how Z applies them. The ACE decides from the principles; a statement supports a ruling only where it addresses the case. Z-11 and Z-21 reflect Z's later kinds-of-definition framing; Z-19 refines Z-12. + +Every item below is something Z said or approved in this session. Cite an item as provenance where it addresses the case. A question no item covers is decided from the principles if they determine it, and escalated if they do not. + +## Layers and vocabulary +- Z-1. Functional = what (behavioral requirements, intended behavior); logical = how (mechanisms and the interfaces between them); physical = where (concrete parts that confer the values). Physical is not just values: it is real parts, where the logical "how" is implemented and the functional "what" realized. +- Z-2. The functional layer is not devoid of detail: it states phenomena formally (temperature, power, energy) and the relations among them, enough to state behavioral requirements and measures of effectiveness. Energy conservation is stated as an inequality (energy balance) that does not assume perfect efficiency; efficiency shows up as a measure of performance. +- Z-3. The logical layer is about mechanisms and interface compatibility (an outlet powers a pop-up toaster, a fuel tank powers a blowtorch), arranged before sizing (fuel volume, tong length are physical choices). +- Z-4. Substitution test: if a pop-up toaster and tongs-with-a-blowtorch both satisfy a statement, it is functional (solution-independent); if it commits to a mechanism, it is logical. Constraints that hold for any solution (laws of nature) stay functional; constraints that exist only because of a chosen mechanism or interface are logical. +- Z-5. MoEs are functional; MoPs are more logical. Both can be stated for any part and reasoned over from parts through interconnections to higher-order parts. Effectiveness and performance are not the same thing, but the split is a contextual modeling judgment, not a fixed rule: in many cases how long something takes is performance, while for toast it could be filed under effectiveness. Every MoE/MoP assignment carries a stated justification, and a hard case can be a narrative talking point about judgment calls. +- Z-6. Prescribed versus emergent is the "why" of modeling. Mechanisms are prescribed. "Behavior" describes the emergent aspects of the system. A mechanism is a comparatively deterministic input-to-output relation (an open-loop declaration of how things will work). A policy is decision-making guidance that selects inputs given state, usually to create closed-loop behavior in an uncertain setting; policies are designed given the mechanisms available. +- Z-7. Emergence maps onto layers by how a value is obtained (SEBoK: simple, weak, strong): simple (composable, computable from defined parts and interfaces, e.g. a mass roll-up) is typical of the logical layer; weak (needs simulation or exploration) is typical of the functional layer's intents; control-theoretic stability has both an analytic form (model-checkable) and simulated trajectories; strong emergence (unanticipated) belongs to no layer and is what sign-off judges. Z does NOT want lots of extra content injected: keep it intuitive and compact. +- Z-8. Optimization reading (Z's own validation lens, builder-facing): functional = the objective (what is good, good enough); logical = the design space (typed, unit-bearing slots plus equality and inequality constraints, no solution values); physical = a concrete candidate checked for feasibility against the logical layer and utility against the functional layer. +- Z-9. Engineering judgment is never eliminated. Engineers are experts at making contextually appropriate, evidence-informed judgment calls that are subjective but rigorous through evidence and justification (Hawkins is a primary source for the judgment taxonomy). Nothing in the tutorial may imply everything is reducible to what can be known or computed. +- Z-10. Choosing among alternative mechanisms: do NOT call it "concept selection" (SEBoK uses "concept" for the problem-space stage). Use "selection among alternatives". + +## 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-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. +- Z-16. Glossary: a bipartite graph (sources and terms; definitions are edges), small N, modest M, local source of truth used in testing. Only a human confirms definitions; Z has delegated confirmation TRIAGE to the ACE: rule where Z's stated positions settle the question, escalate otherwise. + +## Working style +- Z-17. The ACE exists so Z is not spammed: rule and log when it knows what Z would say; escalate to Z, concisely and in Z's idiom (objective / design space / candidate, feasibility and utility, MoE and MoP, judgment), when it does not. Every triage is logged. +- Z-18. Z prefers not to have content injected beyond what is needed; compact and intuitive beats exhaustive. +- Z-19. Lens vocabulary (candidate, feasibility, utility, objective, design space) is not barred from learner-facing text but may never be load-bearing; use it only where it earns its place by making a term easier to understand. The lenses themselves are never named to learners. +- Z-20. Record what you learn durably in the repo (probe results, corrections, verified facts), not in scratch files or conversation. +- Z-21. Allocation: the tutorial's allocation edge follows the SysML v2 form (what we execute) and acknowledges the SEBoK and Douglas senses as the same assigning from other angles. +- Z-22. SysML v2 is declarative; its defining technical analogy is a database language. The engineer's job is to align the model to their intents through loops of construction and analysis of what was constructed. Scientific Python is the complementary procedural language: it analyzes, and never defines what the model means. The SysML model is the authoritative source of semantics (canonical semantics from the specs; user-defined semantics such as units, calc relations and metadata in the model). Simulations produce the evidence base that supports judgments about whether requirements are satisfied. +- Z-23. Explicit constructions are walked through in the notebook; implicit constructions are coded in imported Python files. The assembled model made legible through diagrams satisfies SA-2. +- Z-24. Diagrams follow scientific-Python practice: the model is the data; a diagram is a selected, purpose-specific view of it, and we still judge what to include and exclude and how to present it. Those choices are recorded in the figure recipe and caption. A diagram of an assertion is not evidence that it holds. +- Z-25. Physical laws such as Joule heating (I^2 R) are mechanisms: modeling decisions grounded in established engineering practice, the laws of motion by which we reason about behavior. It is fine to call them mechanisms. Z does not like the phrase "sub-behavior". A law that holds for any solution (energy balance) stays functional; the same kind of law as applied to a chosen component (I^2 R for the coil) is logical. +- Z-26. MoE versus MoP is a contextual judgment (see Z-5) that must be justified for each case. +- Z-27. Conformance has two tiers. Language conformance (parse, name resolution, typing) is always on: a non-conformant declaration breaks the load. Project conformance checks (interface compatibility, port types, flows accounted, coverage) are staged: the model emerges iteratively and is not born complete, so 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 applied. Executable specs are valuable because non-conformance is discovered early and flagged to the user. diff --git a/.claude/skills/ace-protocol/z-principles.md b/.claude/skills/ace-protocol/z-principles.md new file mode 100644 index 0000000..ef6f423 --- /dev/null +++ b/.claude/skills/ace-protocol/z-principles.md @@ -0,0 +1,61 @@ +# Z's principles, frameworks and heuristics (DRAFT for Z's confirmation) + +The ACE decides from these, not from quotations. Each entry gives the principle, why it holds, a test the ACE can apply, and when it stops determining the answer (the cue to escalate). The statements Z has made in conversation (`z-model.md`) are **provenance**: they are where a principle was drawn from and evidence of how Z applies it. They are not authority for a case they do not address. + +Status: confirmed by Z on 2026-09-26 (F1 to F6, P1 to P6, the heuristics; F7 added from Z's answer the same day). Extracted from Z's statements and decisions. Only Z confirms or changes this list. + +## Frameworks (how Z reads a situation) + +**F1. Prescribed versus emergent.** A design prescribes elements, relationships and principles; behavior is what results, and is derived and checked against intent. *Test:* is this something the design chooses, or something expected to follow from the choices? A result entered as a choice cannot be checked, so it is a defect. *Underdetermined when:* a value is genuinely a chosen control parameter that also influences a result (a setpoint): the parameter is prescribed, the result is not, and the modeler decides which is which. + +**F2. Objective, design space, candidate (optimization reading of the layers).** Functional says what is good and what is good enough (the objective). Logical is a typed design space with constraints and no solution values. Physical is a candidate, checked for feasibility against the logical layer and for utility against the functional layer. *Test:* what does this element read as: an objective, a slot or constraint, or a candidate value? *Underdetermined when:* an element mixes them (a slot carrying a value, a purpose carried by a construct): classify the parts separately and report the mix. + +**F3. Function, mechanism, policy.** A function is solution-independent (two or more different mechanisms could provide it). A mechanism is a modeling decision grounded in engineering practice, a law we reason with, comparatively deterministic. A policy selects inputs given state, designed given the mechanisms available. *Test:* substitution (would a pop-up toaster and tongs with a blowtorch both satisfy it?). + +**F4. Declarative model, procedural analysis, evidence.** The model states intent and semantics; scientific Python analyzes it; simulation and analysis produce the evidence that supports judgments. *Test:* is a number, unit or relation defined in the model, or only in code? Code that defines meaning is a defect. A verification case is analysis, not a layer element: classify it by what it tests and by its tier (confirmed by Z, 2026-09-26). + +**F5. Kinds of definition, not rivals.** SEBoK supplies the idea, the OMG specs the formal and checkable semantics, Douglas the analogy and story. Tutorial definitions refine canonical ones and never contradict or invent. *Test:* does it narrow or clarify a canonical edge, and which kind of definition is being asked for? + +**F6. Two tiers of conformance.** Language conformance is always on and breaks the load. Project conformance checks are staged because the model emerges iteratively; each has a negative control and is reported open until applied. *Test:* is this rule part of the language, or a project check whose time has not come? + +**F7. The system of interest is the subject, not a layer.** The system-of-interest is what the functional, logical and physical layers each describe. Its purpose statement is functional; its parts and arrangement are logical; its realized parts are physical. A bare top-level part def that only names the whole is the named subject, and the layer of each piece comes from what that piece commits to. *Test:* is this element the subject itself, or a piece of it? Classify the pieces, not the subject. (Confirmed by Z, 2026-09-26.) + +## Principles (what Z holds to) + +**P1. Judgment is never eliminated; it is made rigorous.** Engineers make contextual, evidence-informed calls with recorded justification, and never present a check as proof. *Test:* is the judgment site identified, the evidence cited, the residual uncertainty stated? + +**P2. Contextual splits are justified, not fixed.** Where a classification depends on context (MoE versus MoP, function versus mechanism), require the case-specific justification; do not apply a fixed rule. *Test:* would the opposite filing be defensible for this case, and is the reason recorded? + +**P3. Teach through the model, and through views of it.** What the learner sees is derived from the model (diagrams are queries plus judged inclusion and exclusion, recorded). *Test:* could this figure or claim be regenerated from the model, and is what it omits stated? + +**P4. Earn your place; keep it small.** Added content, vocabulary or lens language must make the learner's task easier, and is never load-bearing. Builder-facing lenses never appear in learner content. *Test:* if removed, does understanding get harder? + +**P5. Do not paper over.** Gaps are tracked (register, drafted issue with the exact spec citation, comment at the workaround). A construct is described as working only after it has been run. Learnings are recorded durably in the repo. + +**P6. Z keeps the substantive decisions.** The ACE rules only where the frameworks and principles determine the answer. It escalates when they underdetermine it, conflict, affect a learning outcome, touch a licensing question, or would change a confirmed definition or an SA rule. + +## Heuristics (quick tests the ACE applies before reasoning at length) + +1. *Substitution test*: solution-independent means functional. +2. *Computed versus explored*: derivable from defined parts is simple emergence (logical); needs simulation is weak (functional); unanticipated is strong (belongs to no layer, judged at sign-off). +3. *Arrangement before sizing*: interfaces and arrangement are logical; sizes and part numbers are physical. +4. *Objective, slot, candidate*: which one does the element read as? +5. *Choice or result*: could a design decision have set this, or must analysis produce it? +6. *Does it earn its place*: what gets harder for the learner without it? +7. *Which kind of definition is asked for*: idea, formal semantics, or story? +8. *Tier of the check*: language always on, or project staged? + +## How principles and provenance relate + +A ruling states the frameworks and principles it applies and the reasoning from them to the answer. It then lists provenance: statements, glossary edges, spec passages and test results that support the reasoning. If the only support for an answer is a quotation stretched over a case it does not address, the principles do not determine it: escalate. + +## Confirmed extensions (Z, 2026-09-27) + +The following extensions of the frameworks and principles above to new kinds of case were flagged by the ACE and confirmed by Z as matching Z's own judgment (not merely unobjected-to inferences). Cite these directly; the case no longer needs re-flagging as an extension. + +- **F3/F2 to a parameterized conversion (DL-030):** a deterministic input-to-output relation whose parameter is a characterized value (for example an efficiency) is a logical commitment even with no named law and no component chosen yet; the functional-layer relation among the same phenomena is the solution-independent form (a balance inequality, not an equality with a free parameter). +- **F7 to usages of the subject (DL-032):** a usage of the system-of-interest's definition is classified by what IT adds beyond the definition, not by the definition's own classification. A usage that adds nothing takes no layer. A usage that fixes an emergent result is DL-018's defect. "Candidate" requires a concrete part that realizes a logical slot; absent that, a usage built to fail a check is at most a failing-branch fixture, valid only if its content makes the check fail for a reason about the design, not because a number was typed in. +- **F4 to judgment records and satisfaction claims (DL-033):** a Python judgment record is not a layer element (it is analysis, by construction, per F4). An `assert satisfy` relation is a cross-layer traceability claim, not evidence and not analysis; its truth is established by a verification verdict, not by the assertion. A container (e.g. a part usage) holding only such claims, with no part and no owner in the system, denotes nothing the layers describe. +- **F1/F4 to assumptions (DL-034):** a recorded assumption may enter as asserted context (a prescribed condition) or as an explicitly labelled, evidenced estimate of a TPM — never as the derived result itself. A check against an assumed value is reported as conditional on the assumption, never as the candidate's assessed performance. +- **F3/P4 to naming (DL-037):** a name for a not-yet-built logical component that only one alternative mechanism would satisfy pre-empts an unrecorded selection among alternatives in the learner's reading, even though the model itself commits to nothing. Name responsibility groupings by the function they carry; reserve mechanism-suggestive names for after a selection is recorded. +- **F6/DL-025 to tool enforcement holes (DL-039):** language conformance is defined by the spec's validation constraints, not by whether a given tool's `ok` flag happens to catch a violation. A model violating a normative constraint is language non-conformant regardless of `model.ok`; project checks stay `blocked` until no such violation is present. Where a tool has a known hole, the tutorial supplies its own always-on guard (with a negative control) rather than accepting the model as conformant. A false `assert satisfy` (parses, resolves and type-checks, but evaluates False) is not a language-conformance question; it is a staged project check ("satisfaction claims evaluated"). diff --git a/.claude/skills/architecture-layers/SKILL.md b/.claude/skills/architecture-layers/SKILL.md new file mode 100644 index 0000000..e5bd553 --- /dev/null +++ b/.claude/skills/architecture-layers/SKILL.md @@ -0,0 +1,95 @@ +--- +name: architecture-layers +description: Functional (what) / logical (how) / physical (where) boundary tests with toaster examples, SysML v2 idioms marked tested or untested, a per-layer audit checklist, and a source map. Definitions live in the glossary; this skill applies them. +--- + +# Architecture layers + +Read AGENTS.md Part 1 (sections 1.5 and 1.6) first. This skill does not restate definitions. Look terms up with `uv run python -m glossary tutorial TERM`, and cite the glossary id (`term-mechanism`, `term-mop`, ...) when you rule on a term. + +## Use it to classify any element in one pass + +Ask in this order and stop at the first "yes": + +1. **Would a pop-up toaster and tongs with a blowtorch both satisfy it?** Then it is **functional**: an intent, a typed flow, a relation among phenomena (energy, temperature, time), or a MoE. +2. **Does it commit to a mechanism, an interface, or a policy, but not to a specific part or value?** Then it is **logical**: a mechanism carried by an abstract component, a port or interface, a constraint that exists only because of that mechanism, or a derived MoP threshold. +3. **Does it name a specific part def or give a value that only a chosen part has?** Then it is **physical**: a concrete part, its attribute values, a TPM. +4. **Is it a result the design is expected to produce (a cycle time, an efficiency, a stability margin)?** Then it is *emergent*: it is derived by analysis and compared with intent. It is never entered as a choice. + +The **system of interest** (the toaster itself) is the subject all three layers describe, not a layer. Classify its pieces: its purpose statement (functional), its parts and arrangement (logical), its realized parts and values (physical). + +## Toaster examples + +| Statement | Layer | Why | +|---|---|---| +| "Toasting takes bread and energy in and gives toast and lost energy out; energy to the bread plus loss cannot exceed energy supplied." | Functional | Holds for any solution. The inequality respects conservation without assuming perfect efficiency. | +| "The toast is browned to the user's liking." | Functional (MoE) | Acceptance. Explored against scenarios, not computed. | +| "A resistive coil turns electrical power into heat and must be fed from a mains outlet." | Logical | A mechanism plus an interface. A blowtorch would need a fuel port instead. | +| "Heating efficiency is at least 0.6." | Logical (MoP threshold) | A performance measure with a threshold derived from what the MoE needs; it characterizes a requirement and needs a means of checking. Whether a measure is a MoP or a MoE is a justified modeling judgment (see below). | +| "Joule heating: heat = I^2 R t in the coil." | Logical (mechanism) | A law we rely on to reason about a chosen component, a modeling decision grounded in engineering practice. A universal law (the energy balance) stays functional. | +| "Toast is ready within 150 s." | MoE or MoP, by judgment | Time is usually performance, but for toast it may be part of what the user accepts. Either is defensible if the justification is recorded (who cares; acceptance or engineering performance). | +| "The coil is an 800 W nichrome element." | Physical | A specific part with a value it confers. | +| "Measured heating efficiency is 0.71." or "Measured browning time on the built candidate is 118 s." | Physical (TPM), and an emergent result | A value assessed on a candidate by analysis or simulation: derived, not chosen, and the evidence against a MoP threshold. Classify it as physical when asked for a layer, and as an emergent result when asked whether it was prescribed. | +| "Cycle time = 120 s" set as an attribute default, then checked against a 150 s limit | Not a valid check | A prescription tested against a threshold. Derive cycle time from the mechanism and the energy balance, then compare. | + +Toaster stories to lean on (Douglas, Part 3): the system described as functions, as logical components, or as physical parts (1:16); who or which components are responsible (1:56); where those components are implemented (2:05); a function has three parts (3:12); decomposing functions into finer functions (4:02); functions allocated to components grouped logically (4:12); trade studies with performance measures (13:40). The tongs-and-flamethrower comparison also appears in Part 3; its timestamp has not been re-verified here. + +## SysML v2 idioms + +`example-layers.sysml` in this directory is one small model showing all three layers. `tests/test_skill_snippets.py` loads it, so it stays runnable. + +| Layer | Construct | Spec | Status | +|---|---|---|---| +| Functional | `action def` with `in`/`out` `item` and `attribute` flows | 7.17.2 | Tested (`ok`) | +| Functional | `constraint def` for a phenomena relation (energy balance) | 7.20.2 | Tested | +| Logical | `abstract part def` | 7.6.2, 7.11 | Tested | +| Logical | `perform action heat : ToastBread;` inside the abstract part def (the performer is responsible for the action) | 7.17.6 | Tested. A bare `perform ToastBread;` naming an action *def* is rejected; that is correct. `perform usage;` naming an action *usage* is accepted. | +| Logical | `port def`, `interface def`, `connection`, `flow` | 7.12 to 7.14 | Tested to parse. Mismatched port types are **not diagnosed** by the tool (gap G4, `decisions/probes.md`). Treat port-type compatibility as a staged project conformance check (AGENTS.md 1.9) and use recipe 5 in `opensysml-query`, with a negative control. | +| Logical | `requirement def` with `require constraint { ... }` for a derived MoP threshold | 7.21.2 | Tested | +| Any | `metadata MeasureOfPerformance about T::x;` after `import ParametersOfInterestMetadata::*;` | 9.3.4 | Tested to parse. Metadata is not visible to `model.query()` (JSON only). | +| Allocation | `allocate apply to source;` between usages; `allocation def` with typed ends plus `allocation a : Def allocate x to y;` | 7.15.2 | Both tested (`ok`). Name allocations so `model.query()` sees them. | +| Physical | `part def NichromeCoil :> HeatSource { attribute watts : Real = 800.0; }` (concrete specializes abstract) | 7.6.2 | Tested | +| Analysis (not a layer element) | `verification def` and `verify` | 7.24 | Existing chapters use it. Not re-probed in this pass. Classify by what it tests and by its tier; its verdict is evidence, and the values it assesses are TPMs. | + +Allocation assigns; specialization realizes. A concrete part def specializes the abstract logical part def. Usage-level `allocate` of a function usage to a component usage is optional but is what makes the assignment queryable. + +Spec facts you can cite: an allocation "denotes a mapping across the various structures and hierarchies of a system model" (7.15.1); a requirement definition defines "a constraint that a valid solution must satisfy" (8.3.21.8); MoE and MoP are only metadata that identify an attribute (9.3.4.2). SEBoK gives the wider ideas (MoE, MoP and TPM in its glossary; allocation under System Requirements Definition). + +## Per-layer audit checklist + +Run it on every element a chapter adds. Any "no" is a finding. + +**Functional** +- Does each action state typed inputs and outputs, and are all flows accounted for at this level? +- Is every statement solution-independent (substitution test)? +- Are phenomena relations stated as relations (balance inequality), not as a specific part's behavior? +- Is there at least one MoE, about acceptance? If a timing or efficiency figure is filed as a MoE or a MoP, is the split justified for this case and recorded? +- Reads as an **objective**: what is good and what is good enough. + +**Logical** +- Does each mechanism have a carrier (an abstract part def) and an interface that matches its neighbors? +- Do the interfaces actually match (an outlet to a power port, a tank to a fuel port)? Apply the port-type conformance check (`opensysml-query` recipe 5) from the point the connection is declared complete; before that it is reported open, not passed. The tool will not diagnose it. +- Are MoP thresholds derived from a MoE, with a means of checking, not free-standing numbers? +- Are there no solution values (no watts, no volumes) and no results entered as choices? +- Reads as a **design space**: typed, unit-bearing slots plus constraints, no solution. + +**Physical** +- Is each part a concrete def that specializes an abstract logical def, and does it fit that def's interfaces? +- Do the values meet the derived thresholds, and is the TPM assessed (analysis or simulation), not asserted? +- Reads as a **candidate**: feasible against the logical layer, useful against the functional layer. + +**Across layers** +- Is every leaf concrete, interfaced and verified (the stopping rule)? +- Is any emergent result set as an attribute default and then "verified"? +- Is engineering judgment recorded where it was exercised, with counterevidence and residual uncertainties, and with no "accepted" disposition? +- Do the figures show the assembled model, with what they omit stated in the caption? + +## Source map + +| Need | Where | +|---|---| +| A definition | `glossary` (`lookup`, `tutorial`) | +| Language semantics | SysML v2.0 spec (formal/2026-03-02), section numbers above; API spec for queries; KerML 1.1 Beta 2 for the kernel | +| Judgment records | `toaster-review-protocol`; Hawkins et al. 2011, sections 3.1 to 3.4 | +| Toaster story | Douglas Parts 3 and 4; anchors above | +| Tool behavior | `decisions/probes.md`, `opensysml-query` | diff --git a/.claude/skills/architecture-layers/example-layers.sysml b/.claude/skills/architecture-layers/example-layers.sysml new file mode 100644 index 0000000..dfa4d8d --- /dev/null +++ b/.claude/skills/architecture-layers/example-layers.sysml @@ -0,0 +1,44 @@ +package ToasterLayers { + import ScalarValues::*; + import ParametersOfInterestMetadata::*; + + // Functional: what. Typed flows and a phenomena relation; no mechanism. + item def Bread; + item def Toast; + action def ApplyHeat { + in item bread : Bread; + in attribute energyIn : Real; + out item toast : Toast; + out attribute energyLoss : Real; + } + constraint def EnergyBalance { + in attribute energyIn : Real; + in attribute energyToBread : Real; + in attribute energyLoss : Real; + energyToBread + energyLoss <= energyIn + } + + // Logical: how. A mechanism's carrier, its interface, and the derived requirement. + port def PowerPort; + abstract part def HeatSource { + port power : PowerPort; + perform action applyHeat : ApplyHeat; + attribute efficiency : Real; + } + requirement def EfficientHeating { + doc /* MoP threshold derived from the MoE: efficiency at least 0.6 */ + attribute efficiency : Real; + require constraint { efficiency >= 0.6 } + } + + // Physical: where. A concrete part that realizes the logical component and confers values. + part def NichromeCoil :> HeatSource { + attribute watts : Real = 800.0; + } + part toaster { + action apply : ApplyHeat; + part source : NichromeCoil; + allocate apply to source; + } + metadata MeasureOfPerformance about HeatSource::efficiency; +} diff --git a/.claude/skills/opensysml-api/SKILL.md b/.claude/skills/opensysml-api/SKILL.md index 9082c00..6859345 100644 --- a/.claude/skills/opensysml-api/SKILL.md +++ b/.claude/skills/opensysml-api/SKILL.md @@ -13,10 +13,12 @@ import opensysml.binary opensysml.binary.ensure_binary(version="v0.9.0") conn = opensysml.connect(version="v0.9.0") -model = conn.load_from_content(source, strict=False) # CORRECT — not conn.loads() or conn.load() +model = conn.load_from_content(source, strict=False) # from text; conn.load(path) also exists (below); not conn.loads() conn.close() ``` +`conn.load(path)` loads a file and exists in v0.9.0. Neither it nor `load_from_content` resolves `import` across separately loaded sources (gap G7, `decisions/probes.md`): assemble multi-part models by concatenating the SysML text and loading the result once. + ### Notebook loading pattern (required for all chapter notebooks) Model source lives in `models/chXX-cumulative.sysml`, not in inline notebook strings. The canonical cell 2 pattern: @@ -36,6 +38,51 @@ assert model.ok `model.ok` → bool. `model.diagnostics` → list of objects with `.severity`, `.message`, `.start_line`, `.start_column`, `.end_line`, `.end_column`. +## Editor API (programmatic construction) + +```python +model.edit() → Editor + +# Structural +editor.add_part_def(owner, name, specializes=[], doc=None) +editor.add_part(owner, name, type=None, specializes=[]) +editor.add_attribute(owner, name, type=None, default=None, multiplicity=None) +editor.add_member(owner, kind, name, ...) # for kinds not covered by typed helpers + +# Calculation +editor.add_calc_def(owner, name, inputs=[], return_type=None, expression=None) + +# Item / state (confirm Pattern A before using — see Phase 0e gate) +editor.add_item_def(owner, name, ...) +editor.add_member(owner, kind="state def", name=...) + +increment = editor.apply() # → EditResult +str(increment) # FULL MODEL (all existing + new declarations, not just the new member) +``` + +**Critical:** `editor.apply()` returns the **full cumulative model**, not a fragment. +Probe result (2026-09-25): base = `package P { part def X; }`, after `add_part_def('Y')` → +`"package P { part def X; \n part def Y;\n}"`. + +**Editor single-use rule:** `editor` is bound to one model hash. +After `editor.apply()`, call `conn.load_from_content(str(result))` before editing further. + +**Gap constructs — do NOT attempt these kinds via `editor.add_member()`. +They raise `IllegalMemberKindError`. Use Pattern B (SysML string) instead:** + +| Construct | Issue | +|---|---| +| `abstract part def` | toaster#9 / OpenSysML#595 | +| `attribute :>>` redefinition | toaster#10 / OpenSysML#596 | +| `require constraint { ... }` | toaster#11 / OpenSysML#597 | +| `assert satisfy R by P` | toaster#12 / OpenSysML#598 | +| `allocate X to Y` | toaster#13 / OpenSysML#599 | +| `flow X.port to Y.port` | toaster#14 / OpenSysML#TBD | +| `state usage` (sub-state) + `transition` | toaster#15 / OpenSysML#TBD | + +**Partial state def support:** `editor.add_member(owner=..., kind='state def', name='Cycle')` creates a bare +`state def Cycle;` and works. Sub-states and transitions do not. Full state machines require Pattern B. + ## Evaluation and execution ```python @@ -61,7 +108,7 @@ from toaster.query import get_satisfy_relationships satisfies = get_satisfy_relationships(model) # list of dicts with @type, subsets, subject ``` -`get_satisfy_relationships()` is the single point of the `to_api_json()` workaround. +`get_satisfy_relationships()` is the single point of the `to_api_json()` workaround (it reads `.content`; corrected and tested in Pass 1, see `tests/test_query.py` and the `opensysml-query` skill). When D-001 is resolved upstream, only that function changes. ## Model query (Ch9–10) @@ -74,14 +121,15 @@ reqs = model.query(where={ "value": ["RequirementUsage"], }) # QueryElement: .id, .type, .properties, .get(name, default), .as_dict() -# Also queryable: AllocationUsage, ActionUsage, PartUsage, RequirementDefinition +# Also queryable: ActionUsage, PartUsage, RequirementDefinition; AllocationUsage, ConnectionUsage and FlowUsage ONLY when named. +# Unnamed allocate/flow/connect, every satisfy, and metadata are invisible here; see the opensysml-query skill. ``` ## Structured export ```python model.to_sysml() # roundtrip SysML text — safe, not experimental -model.to_api_json() # OMG SysML v2 API JSON — experimental (fires warning); use only via get_satisfy_relationships() +model.to_api_json() # returns a Conversion: read `.content` (a JSON string), and suppress its experimental warning; use only via the helpers in src/toaster/query.py or the recipes in opensysml-query # model.to_turtle() — do NOT use in tutorial notebooks ``` @@ -95,7 +143,7 @@ ns = model.root # root namespace Symbol ## What does NOT exist -- `conn.loads()`, `conn.load()` — these methods do not exist +- `conn.loads()` — this method does not exist (`conn.load(path)` does) - OSLC queries — no OSLC client in v0.9.0; `model.query()` is the SysML v2 API Query protocol - `render_document`, `run_document_query` — require model-internal `DocumentQueries::Document` elements; don't use in tutorial notebooks diff --git a/.claude/skills/opensysml-query/SKILL.md b/.claude/skills/opensysml-query/SKILL.md new file mode 100644 index 0000000..172f66f --- /dev/null +++ b/.claude/skills/opensysml-query/SKILL.md @@ -0,0 +1,181 @@ +--- +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. +--- + +# Querying a model (OpenSysML 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. + +| Surface | Sees | Does not see | +|---|---|---| +| `model.query(...)` (the API standard's Query: `scope`, `select`, `where`, `= > <`, `and`/`or`, `inverse`; no traversal) | **Named** elements of any metaclass, including named allocations, connections and flows | Unnamed `allocate`, `flow`, `connect`; every `satisfy`; metadata usages. A `perform action heat : X` appears as an `ActionUsage`. Inherited members are not expanded. | +| `json.loads(model.to_api_json().content)` | Everything, unnamed included, with qualified names and typed references | Nothing structural, but it is a flat list you must index yourself | +| `Symbol` (`model.find`, `model.get`, `.children`, `.specializations`, `.attributes`) | The named tree and its specialization edges | Unnamed elements | + +`to_api_json()` returns a `Conversion` object: read `.content`, and suppress its experimental warning. Never hand `model.to_api_json()` to `json.loads` directly. + +**Convention that makes queries easier:** name allocations, connections and flows in the model (`allocation apply2source allocate apply to source;`). Named ones become visible to `model.query()`. `satisfy` cannot be named, so use the JSON recipe. + +## Setup used by every recipe + +```python +import json, warnings +from collections import defaultdict, deque +from pathlib import Path +import opensysml + +conn = opensysml.connect(version="v0.9.0") +model = conn.load_from_content(Path("models/ch08-cumulative.sysml").read_text(), strict=False) +assert model.ok + +def pc(prop, op, value): + return {"@type": "PrimitiveConstraint", "property": prop, "operator": op, "value": value if isinstance(value, list) else [value]} + +def api_elements(m): + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + return json.loads(m.to_api_json().content) + +els = api_elements(model) +by_id = {e["@id"]: e for e in els} +def ref(r): return r["@id"] if isinstance(r, dict) else r +def qn(r): return by_id.get(ref(r), {}).get("qualifiedName") # never rebuild a name from an @id (see below) +def of_type(*t): return [e for e in els if e.get("@type") in t] +``` + +## Recipe 1: elements by type (named only) + +```python +part_defs = model.query(where=pc("@type", "=", ["PartDefinition"]), select=["name"]) +names = sorted(r.id for r in part_defs) # ids are qualified names like ToasterDemo::HeatGenerator +assert "ToasterDemo::HeatGenerator" in names +abstract_ones = [r.id for r in model.query(where={"@type": "CompositeConstraint", "operator": "and", "constraint": [ + pc("@type", "=", ["PartDefinition"]), pc("isAbstract", "=", [True])]}, select=["name"])] +``` + +`QueryElement` has `.id`, `.type`, `.properties`, `.get(name, default)`, `.as_dict()`. `select` names the properties to return. + +## Recipe 2: what specializes what (named elements, `Symbol`) + +```python +def spec_edges(): + up, down = defaultdict(set), defaultdict(set) + for r in model.query(select=["name"]): + sym = model.get(r.id) + for sp in sym.specializations: + if sp.target_id: + up[r.id].add(sp.target_id); down[sp.target_id].add(r.id) + return up, down + +def closure(start, edges): + seen, todo = set(), deque([start]) + while todo: + for n in edges.get(todo.popleft(), ()): + if n not in seen: + seen.add(n); todo.append(n) + return seen + +up, down = spec_edges() +realizers = closure("ToasterDemo::ToastingSystem", down) # everything that (transitively) specializes it +assert "ToasterDemo::Toaster" in realizers +``` + +This is how you find the concrete parts that realize an abstract logical part def. Specialization is *not* expanded for you: a part def that specializes an abstract one does not list the abstract one's members in `model.query`. + +## Recipe 3: connectors, allocations and flows including unnamed (JSON) + +```python +def end_path(end): + """Path a connector end points at, e.g. ['ToasterDemo::Toaster::heating'].""" + rs = end.get("ownedReferenceSubsetting") + if not rs: + return [] + target = by_id[ref(by_id[ref(rs)]["referencedFeature"])] + if "chainingFeature" in target: + return [qn(c) for c in target["chainingFeature"]] + return [target.get("qualifiedName")] + +def connectors(*types): + return [{"id": e.get("qualifiedName"), "type": e["@type"], "ends": [end_path(by_id[ref(r)]) for r in e.get("connectorEnd", [])]} + for e in of_type(*types)] + +flows = connectors("FlowUsage") +allocs = connectors("AllocationUsage") +assert allocs +assert flows == [] # this reference model has no FlowUsage elements; the recipe still applies when one does +``` + +Each `ends` entry is a path; the first element is the owning feature, which lets you ask "is anything allocated *to* this component". To include inherited allocations, first expand the component with `closure(component, up)` from Recipe 2. + +## Recipe 4: satisfy, perform (JSON only) + +```python +def satisfies(): + return [{"id": e.get("qualifiedName"), "requirement": qn(e["subsets"]) if "subsets" in e else None, + "subject": qn(e["subject"]) if "subject" in e else None} for e in of_type("SatisfyRequirementUsage")] + +def performs(): + out = [] + for e in of_type("PerformActionUsage"): + act = e.get("references") or (e.get("type") or [None])[0] + out.append({"performer": qn(e["owner"]), "action": qn(act) if act else None}) + return out + +assert satisfies() +``` + +`satisfy` and `verify` both appear as `SatisfyRequirementUsage`; the declared keyword distinguishes them. Coverage (which requirements have a satisfy, which subjects are verified) is a join of `satisfies()` with the requirement list from Recipe 1. + +## 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. + +```python +def feature_type_names(feature_qn): + el = byqn.get(feature_qn) + return [qn(t) for t in (el or {}).get("type", [])] + +def related(a, b): + """Equal, or one specializes the other (uses Recipe 2's edges).""" + return a == b or a in closure(b, up) or b in closure(a, up) + +def port_type_mismatches(): + out = [] + for c in connectors("ConnectionUsage", "InterfaceUsage", "FlowUsage"): + ends = [p[-1] for p in c["ends"] if p] + typed = [(e, feature_type_names(e)) for e in ends if byqn.get(e, {}).get("@type") == "PortUsage"] + for i in range(len(typed)): + for j in range(i + 1, len(typed)): + (ea, ta), (eb, tb) = typed[i], typed[j] + if ta and tb and not any(related(x, y) for x in ta for y in tb): + out.append({"connector": c["id"], "ends": [ea, eb], "types": [ta, tb]}) + return out + +byqn = {e["qualifiedName"]: e for e in els if e.get("qualifiedName")} +assert port_type_mismatches() == [] # ch08 declares no mismatched ports +``` + +Limits: it compares the declared port types only. Conjugated ports (`~PowerPort`) and ports reached through interface ends are not handled. It is a starting negative control, not a full interface checker. + +## Ids + +The API JSON `@id` uses `__` for `::` and escapes `_` (`named_flow` becomes `named_5fflow`). Never rebuild a qualified name with `id.replace("__", "::")`. Look up `qualifiedName` in the element itself (`qn`), as above. Ids returned by `model.query` and `Symbol` are already qualified names. + +## What does not work, and the workaround + +| Problem | Workaround | +|---|---| +| `model.query` cannot see unnamed connectors, any `satisfy`, or metadata | Recipe 3 and 4 (JSON). Better: name connectors and allocations. | +| Named `perform` reports type `ActionUsage`, not `PerformActionUsage` | Query `ActionUsage`, or use Recipe 4. | +| `perform ToastBread;` where `ToastBread` is an action def | Rejected, and correct per spec 7.17.6. Write `perform action x : ToastBread;` or reference a usage. | +| Mismatched port types (a power port to a fuel port) are not diagnosed (G4) | Recipe 5, applied as a staged project conformance check. | +| `import` across separately loaded sources does not resolve (G7) | Assemble by concatenation: join the SysML text yielded by the implicit modules and the chapter's explicit increment into one string and load that. Concatenation loses which source an element came from, so give implicit parts their own package (or a metadata marker) if provenance must stay queryable. | +| `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`). + +## Before you assert something works + +Run it. The snippets above are executed by `tests/test_skill_snippets.py` against `models/ch08-cumulative.sysml`. When you add a recipe, add it here inside a `python` block so the test covers it. diff --git a/.claude/skills/orchestrator-protocol/SKILL.md b/.claude/skills/orchestrator-protocol/SKILL.md index 98fadad..54afe5f 100644 --- a/.claude/skills/orchestrator-protocol/SKILL.md +++ b/.claude/skills/orchestrator-protocol/SKILL.md @@ -49,6 +49,20 @@ When building chapter notebooks: Route the chapter to A3 first. Open A4 work in parallel only for cells that do not depend on the model file (index.md, conclusion.md, exercise stubs, context cells). +## Plan-driven non-chapter work + +Not all work is a chapter WP. A `docs/superpowers/plans/*.md` implementation plan (written by the `writing-plans` skill, approved by Z) is executed through the same generic mechanism as chapter work — the work-contract template, `builder`, `reviewer`, the ACE, and the states and merge gate in `decisions/task-states.md` — without needing an entry in the WP table above, because none of that mechanism is chapter-specific: blast zone, acceptance criteria and model all come from the contract, not from a pre-registered matrix. + +- **One contract per plan task**, or a sensible grouping of a few tightly sequential tasks when splitting them would leave a contract with no independently checkable deliverable (the plan document itself says which; when it doesn't, keep the plan's own task boundaries). +- **Blast zone and acceptance criteria come directly from the plan's own "Files" and step text** for that task — copy them into the contract rather than re-deriving them, since the plan already specified exact paths and runnable checks. +- **Sequencing follows the plan's own stated dependencies.** Where the plan says a task's evidence feeds the next task (a fixture file, a resolved tool path, a prior task's evidence JSON), run those tasks in series, not in parallel, even though nothing here prevents parallel dispatch for tasks the plan does not say depend on each other. +- **Review checks what the plan's own step 2/3/4-style "run and verify" instructions say to check** — a passing test, a real (non-empty, non-placeholder) evidence file, an exit code the plan says is expected — in addition to the reviewer's usual diff/blast-zone/boundary-case checks. + +**Escalation triggers specific to this class of work** (route to the ACE the same way as any other escalation, in the `ESCALATE-TO-ACE` form in `decisions/task-states.md`): +- A pinned external tool or version named in the plan cannot be (re)provisioned in the environment, and the plan names no fallback for it. +- A builder or reviewer produces a real finding that contradicts an assumption the approved spec or plan states as settled (for example, a mutation-control verdict coming out the opposite of what the plan expected) — this is evidence for the ACE and Z to see, not something a subagent or the orchestrator resolves by picking a reading. +- A scope question the plan did not anticipate (for example, whether to add back a tool or view type the plan explicitly named out of scope). + ## What A1 must never do - Edit files diff --git a/.claude/skills/skill-editor/SKILL.md b/.claude/skills/skill-editor/SKILL.md index d6a395c..6256848 100644 --- a/.claude/skills/skill-editor/SKILL.md +++ b/.claude/skills/skill-editor/SKILL.md @@ -14,6 +14,10 @@ Before touching any file: - If a WP is mid-loop (developer has delivered; reviewer has not finished): defer until the loop closes. - Write the DL log entry **first**, status `PENDING`, with the intended change in one sentence and the current text of the section being changed captured verbatim (revert record). +**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. The pre-edit DL entry is still written first, and the revert record may point to the commit that precedes the first edit (`git show :`) instead of pasting the text. This is a one-off Z override, not a change to file authority, and it ends with the pass. + +**Generated regions are exempt from this gate.** Text between `` and `` markers is generated from the glossary by `uv run python -m glossary render`. It is changed only by changing the glossary, which is itself logged and confirmed by Z. Never edit it by hand. + ## Step 2 — Blast-radius assessment | Question | If yes | @@ -34,6 +38,7 @@ Before touching any file: - Re-read the modified section and the two adjacent sections. - Confirm no adjacent rule is accidentally weakened or contradicted. +- Confirm no skill you touched contradicts AGENTS.md Part 1 or a confirmed glossary definition (`uv run python -m glossary check`; search the skill for the terms you changed). When a skill and Part 1 disagree, Part 1 governs, and the skill is the thing to fix. - Update the DL entry to `COMPLETE` with a one-sentence summary of what changed and why. ## Revert protocol diff --git a/.claude/skills/sysml-diagrams/SKILL.md b/.claude/skills/sysml-diagrams/SKILL.md index e4cef6d..650df39 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 | Official SysML pilot `TREE` → SVG | Familiar definition/usage shapes, compartments, and composition notation. | -| How do parts connect through ports? Interconnection view | Model query → generated SysMLD intent → SysMLD SVG | Explicit port placement and orthogonal routing support readable interface views. The generated intent is a plotting input. | -| What happens next? Action-flow view | OpenSysML → PlantUML → SVG | Uses the existing model tool and produces familiar action nodes and control flow. | -| How does behavior change with events? State-transition view | Official SysML pilot `STATE` → SVG | Familiar state notation, transitions, and behavioral compartments. | -| Who sends what, in what order? Sequence view | OpenSysML sequence query → DOT → Graphviz SVG | White background, relationship-consistent rendering, no Mermaid dependency. Fallback: PlantUML if `opensysml -render-form dot` unsupported for sequences (confirmed at WP-1 and documented below). | +| 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. | +| 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. Where dedicated port boxes (not just labeled edges) matter pedagogically, sysml-toolkit is the alternative — it drew real port names correctly on every real fixture tested. | +| 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). | | 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. 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. +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). ## Make a figure recipe @@ -39,30 +39,30 @@ Keep model content and presentation settings distinct. A small configuration obj Prefer the renderer’s direct `.sysml` input. Where projection is useful, generate only the data needed for the view, retaining model IDs or qualified names. Compute simple projections in the notebook; introduce a shared helper when the same operation is repeated. -PlantUML, Mermaid, DOT, and SysMLD intent/layout files are generated build products. Preserve them when useful for inspection; regenerate them after model changes. Do not separately maintain their engineering relationships. Store presentation configuration as the authored asset. For a simple direct export, a command and its arguments are sufficient configuration. +DOT and the `render_interconnection()` intent dict are generated build products. Preserve them when useful for inspection; regenerate them after model changes. Do not separately maintain their engineering relationships. Store presentation configuration as the authored asset. For a simple direct export, a command and its arguments are sufficient configuration. -SysMLD needs an explicit projection: generate its nodes, ports, and edges from the model. Its reference-name check supplements the projection checks; the latter establish that displayed endpoints actually correspond to the selected model relationships. See the interconnection recipe for the required mapping. +The interconnection view needs an explicit projection: generate its nodes, ports, and edges from the model via `build_interconnection_intent()`. Checking that displayed endpoints actually correspond to the selected model relationships is a projection check, done the same way as any other view's — not a reference-name check from a third-party tool (the tutorial does not use SysMLD). See the interconnection recipe for the required mapping. ## Tailor like a scientific figure Start with one question and a small scope. Try orientation, label wrapping, and spacing before adding individual positions. Split an overloaded diagram into coordinated views when that explains the system better. Additional layout code is justified by readability, not by making every figure use identical geometry. -Use white backgrounds, readable typography, restrained color, and consistent names. Distinguish definition, usage, containment, connection, control flow, and dependency visually. **Never use Mermaid** — DOT is the default for sequence and relationship diagrams; SysMLD is first-class for interconnection. Prefer SVG for publication and notebook display. +Use white backgrounds, readable typography, restrained color, and consistent names. Distinguish definition, usage, containment, connection, control flow, and dependency visually. **Never use Mermaid** — DOT/Graphviz is the default for structure, sequence, and relationship diagrams; `render_interconnection()` (in-house, DOT-based) is first-class for interconnection. Prefer SVG for publication and notebook display. -## Diagram pipeline decisions (updated SA-9) +## Diagram pipeline decisions (updated after the real-fixture diagram study, 2026-09-29) -- **DOT/Graphviz** — default for sequence and relationship diagrams -- **SysMLD** — first-class for port-level interconnection; model-to-intent exporter built in WP-4 by A2 -- **PlantUML** — action flow only (via `opensysml -render-form plantuml`) +- **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 - **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 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) -**WP-1 probe result (2026-09-25): opensysml v0.9.0 has no native DOT or render-form CLI.** `sysml-grpc` is a gRPC server with no render flags; the Python API has `model.render_document()` for document queries only. There is no `-render-form dot` or equivalent. +**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. -**Confirmed approach:** DOT is generated directly from `model.query()` output in Python (via `src/toaster/render.py::model_to_dot()`). The function queries all elements, emits PartDefinition nodes and PartUsage composition/typing edges, then passes the DOT string to `render_dot()` → Graphviz `dot -Tsvg`. - -For action flow: PlantUML remains the target (WP-4 implementation). -For interconnection: SysMLD intent dict built from `model.query()` + `model.to_api_json()` (WP-4). +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 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. ## Check meaning and appearance diff --git a/.claude/skills/sysml-v2-toaster-model/SKILL.md b/.claude/skills/sysml-v2-toaster-model/SKILL.md index 1c57621..761ca7e 100644 --- a/.claude/skills/sysml-v2-toaster-model/SKILL.md +++ b/.claude/skills/sysml-v2-toaster-model/SKILL.md @@ -22,9 +22,12 @@ description: SysML v2 construct subset for the toaster tutorial — confirmed co | 11 | `allocate X to Y` | Ch5 | probed 2026-09-25 | | 12 | `flow X.port to Y.port` | Ch5 | probed 2026-09-25 | | 13 | `state` + entry/then/sub-states + `transition ... accept ... then ...` | Ch7 | probe.sysml lines 42–51 | +| 14 | `verification def` + `subject` + `objective { verify ... }` | Ch3 nb4 | probed 2026-09-25: ok=True | 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. + ## Ch9–10: analysis operations (not new constructs) | # | Operation | Introduced | API | @@ -45,7 +48,7 @@ Each chapter has a corresponding cumulative model file in `models/`: |---|---| | `models/ch01-cumulative.sysml` | Constructs 1–4 (abstract part def, part def, specialization, composition) | | `models/ch02-cumulative.sysml` | + constructs 5–6 (attribute override, requirement def) | -| `models/ch03-cumulative.sysml` | + constructs 7–8 (requirement usage + assert satisfy, calc def) | +| `models/ch03-cumulative.sysml` | + constructs 7–8, 14 (requirement usage + assert satisfy, calc def, verification def) | | `models/ch04-cumulative.sysml` | + constructs 9–10 (action def, item def) | | `models/ch05-cumulative.sysml` | + constructs 11–12 (allocate, flow) | | `models/ch06-cumulative.sysml` | Same constructs as Ch5, second-level decomposition added | @@ -71,3 +74,118 @@ Each chapter has a corresponding cumulative model file in `models/`: - **Fallback rule:** If a construct fails to parse, try the simplest legal alternative first. If none exists, escalate to the orchestrator — do not add complexity. **Ground truth:** `tests/fixtures/probe.sysml` — all confirmed constructs present and verified. + +## ISQ/SI unit typing — confirmed in opensysml v0.9.0 + +All model files (ch01–ch08) use ISQ physical types for the two primary measurement attributes and the `DeliveredEnergy` calc def parameters. Probe date: 2026-09-25. + +### Confirmed working syntax + +```sysml +private import ScalarValues::*; +private import SI::*; +private import ISQ::*; + +// Attribute with default (overridable): use `default =` form +attribute power : ISQ::PowerValue default = 800.0 [SI::W]; +attribute cycleTime : ISQ::DurationValue default = 120.0 [SI::s]; + +// Attribute override in a usage +attribute :>> cycleTime = 200.0 [SI::s]; + +// Constraint with unit-annotated threshold +require constraint { toaster.cycleTime <= 180.0 [SI::s] } + +// calc def with mixed ISQ + Real (efficiency is dimensionless — must stay Real) +calc def DeliveredEnergy { + in power : ISQ::PowerValue; + in duration : ISQ::DurationValue; + in efficiency : Real; + return : ISQ::EnergyValue = power * duration * efficiency; +} +``` + +### Critical: `=` vs `default =` + +- `attribute x : ISQ::DurationValue = 120.0 [SI::s]` — creates a **fixed** binding; cannot override in a usage. **Do not use this form.** +- `attribute x : ISQ::DurationValue default = 120.0 [SI::s]` — creates a default; can override with `:>>`. **Use this form.** + +### model.eval() with ISQ types + +When `calc def` parameters are ISQ-typed, `model.eval()` requires unit-annotated literals: + +```python +result = model.eval("ToasterDemo::DeliveredEnergy(800.0 [SI::W], 120.0 [SI::s], 0.7)") +# Returns Quantity, not float +result.magnitude # → 67200.0 (numeric value in SI base units) +result.unit.text # → 'SI::J' +``` + +`float(result)` fails — always use `.magnitude` to extract the numeric value. + +### Attributes left as Real + +`resistance` (ohms, in ResistanceCoil) and `gauge` (AWG, in PowerWire) remain `Real`. These are structural placeholders in the ch06 second-level decomposition; they are not physical quantities in the simulation scope. + +## Editor API authoring gaps (confirmed impl gaps, not spec gaps) + +Verified 2026-09-25 against SysML v2 spec (formal/2026-03-02), OMG API (formal/2026-03-04), +and opensysml edit.py. All five constructs parse correctly; the gap is in `Editor.add_member()`'s +gRPC authoring allowlist only. + +| # | Construct | Chapter | Upstream issue | Workaround | +|---|---|---|---|---| +| D-004 | `abstract part def` | Ch1 | OpenSysML#595 | `conn.load_from_content()` | +| D-005 | `attribute :>>` redefinition | Ch2 | OpenSysML#596 | `conn.load_from_content()` | +| D-006 | `require constraint { ... }` | Ch2 | OpenSysML#597 | `conn.load_from_content()` | +| D-007 | `assert satisfy R by P` | Ch3 | OpenSysML#598 | `conn.load_from_content()` | +| D-008 | `allocate X to Y` | Ch5 | OpenSysML#599 | `conn.load_from_content()` | +| D-009 | `flow X.port to Y.port` | Ch5 | OpenSysML#601 | `conn.load_from_content()` | +| D-010 | `state` + sub-states + transitions | Ch7 | OpenSysML#602 | `conn.load_from_content()` | +| D-011 | `attribute` with `default =` modifier | Ch1 | OpenSysML#603 | `conn.load_from_content()` | +| D-012 | `calc def` body (inputs + return expr) | Ch3 | OpenSysML#604 | `conn.load_from_content()` | +| D-013 | `action def` body (params + sequencing) | Ch4 | OpenSysML#605 | `conn.load_from_content()` | + +**Rule for notebook cells with gap constructs:** Use `conn.load_from_content(source, strict=False)` +to load a cumulative model string containing the gap construct. The parse/eval/execute paths work +correctly. Do not attempt `editor.add_member()` for these kinds — it will raise `IllegalMemberKindError`. + +**Implication for declarative notebook architecture:** The 5 gap constructs must be added to the +cumulative SysML string and loaded as text rather than constructed via the Editor API. The Editor +API is used for the constructs it supports (~8 kinds); the remaining 5 are demonstrated via the +`conn.load_from_content()` round-trip, which still shows the seam (definition, loading, result) +clearly — addressed behaviorally in the notebook's seam cell, never by naming Tall's three worlds +(AGENTS.md 1.10; see `toaster-recipe`'s "Tall's three worlds" section, corrected DL-050). + +## Construction cell patterns — all 13 notebooks use SysML strings + +All 13 construction notebooks use SysML string fragments (Editor API gaps — see DEFERRED.md). +Pattern A (Editor API) is deferred until the API reaches full spec coverage (D-004 through D-013, +confirmed during Phase 0 and Phase 2 pilot 2026-09-25). + +**Code is factored as if we had the API calls.** One fragment variable per element = one future +`editor.add_*()` call. When the API matures, swap each string for the call; structure stays the same. + +### Construction cell table + +| Construct | Chapter/Notebook | Known gap issue | +|---|---|---| +| `abstract part def` | Ch1/nb01 | toaster#9 / OpenSysML#595 | +| `part def` + `attribute` (with `default =`) | Ch1/nb02 | toaster#16 / OpenSysML#603 | +| `:>` specialization | Ch1/nb03 | (none — specializes= works via API; stays string for consistency) | +| `part` usage (composition) | Ch1/nb04 | (none — add_part works; stays string for consistency) | +| `requirement def` + `require constraint` | Ch2/nb01 | toaster#11 / OpenSysML#597 | +| `attribute :>>` override | Ch2/nb02 | toaster#10 / OpenSysML#596 | +| `requirement` usage + `assert satisfy` | Ch3/nb01 | toaster#12 / OpenSysML#598 | +| `calc def` with body (inputs + return) | Ch3/nb02 | toaster#17 / OpenSysML#604 | +| `action def` with body | Ch4/nb01 | toaster#18 / OpenSysML#605 | +| `item def` | Ch4/nb02 | (none — add_item_def works; stays string for consistency) | +| `allocate` | Ch5/nb02 | toaster#13 / OpenSysML#599 | +| `flow` | Ch5/nb03 | toaster#14 / OpenSysML#601 | +| `state def` (full machine w/ sub-states + transitions) | Ch7/nb02 | toaster#15 / OpenSysML#602 | + +### TOASTER_INCREMENT convention + +`TOASTER_INCREMENT` = the **new declarations for this notebook only** (assembled from fragment +variables at the end of the construction zone). It is NOT the full cumulative model. +`check_construction.py` validates it by loading it in a minimal package context. diff --git a/.claude/skills/toaster-recipe/SKILL.md b/.claude/skills/toaster-recipe/SKILL.md index 0f28e12..b7f7e3b 100644 --- a/.claude/skills/toaster-recipe/SKILL.md +++ b/.claude/skills/toaster-recipe/SKILL.md @@ -1,6 +1,6 @@ --- name: toaster-recipe -description: Sub-notebook 7-cell template, chapter index/conclusion structure, Tall three worlds requirement, and A6 review checklist. +description: Sub-notebook 7-cell template, chapter index/conclusion structure, the seam cell (Tall's three worlds as a builder-facing lens only, never named to learners — AGENTS.md 1.10), and A6 review checklist. --- # Toaster Recipe @@ -9,38 +9,122 @@ description: Sub-notebook 7-cell template, chapter index/conclusion structure, T The 7 cells below are the **required skeleton**. Additional markdown+code pairs may be inserted between cells 2–4 whenever a new operation needs narration or a code cell would otherwise do two conceptual things. A6 reviews for skeleton completeness by content type, not by cell index. +**Pacing rule (binding, found by direct human review of Ch1/Ch2: every construction-introducing notebook built so far violated this).** Two code cells are never adjacent without a markdown cell between them, unless they are literally two halves of one inseparable operation (a single fragment's declaration and its own `print`, for example). The model-increment cell (declare, assemble, load), the negative control, and any demonstration cell are three different things happening. Each transition between them needs its own sentence saying what just happened and what comes next, even when each individual cell is otherwise correct on its own. A reader should never see two code cells back to back and have to infer the connection alone. This applies retroactively: it is why Chapter 1 and Chapter 2 need a narration-only retrofit. + | Skeleton slot | Type | Constraint | |---|---|---| | **Concept** | Markdown | Exactly one sentence: "This notebook introduces X; after running it you can Y." | | **Context** | Markdown | One paragraph locating this notebook in the chapter arc. One link to prior notebook if model state carries over. | -| **Model load** | Code | Reads model from file, displays it, then loads it (see pattern below). `assert model.ok`. | +| **Model increment** | Code | Two-phase. (1) Declare the increment: Pattern A (Editor API, returns full model) or Pattern B (SysML string fragment for gap constructs). Assign to `TOASTER_INCREMENT`; print immediately as reflection. Only in construct-introducing notebooks — see scope table in `decisions/declarative-construction-plan.md`. (2) Load full chapter cumulative from `models/chXX-cumulative.sysml`; `assert model.ok`. | | **Negative control** | Code + Markdown | Short bad_source string. `bad = conn.load_from_content(bad_source, strict=False)`. `assert not bad.ok`. Markdown: one sentence naming the error type and pointing to the diagnostic. | | **Demonstration** | Code + Markdown | One key operation per code cell. If two things happen, split into two cells each with its own narration markdown. | -| **Tall seam** | Markdown | Exactly one sentence naming all three worlds. | +| **Seam** | Markdown | Exactly one sentence, addressing in behavior that the written construct, the tool that loaded it, and the rendered result are three distinct things the reader has just watched connect. Never names Tall or "the three worlds" (AGENTS.md 1.10) — see "Tall's three worlds" below. | | **Exercise pointer** | Markdown | One sentence: "Try the chapter exercise in `exercises/ch{N}/exercise.ipynb`: [one-line description]." No embedded code. | -### Cell 2 — model load pattern (required) +### Construction zone — model increment pattern (construct-introducing notebooks only) + +All 13 construction notebooks use SysML string fragments (Editor API gaps — see DEFERRED.md +D-004 through D-010 and skill `sysml-v2-toaster-model` for the full gap list). + +**Structural rule: code factored as if we had the API calls.** +One named fragment variable per element = one future `editor.add_*()` call. +When the Editor API matures, replace each string with the corresponding call. + +The construction zone replaces the single cell-02 with a sequence of code+markdown pairs: + +``` +[code] fragment variable declared + printed ← mirrors one editor.add_*() call +[markdown] narration for that element +[code] next fragment variable + printed ← mirrors next editor.add_*() call +[markdown] narration +... +[code] TOASTER_INCREMENT assembled + printed ← reflection + cumulative model loaded; assert model.ok ← for subsequent cells +``` + +**Fragment size rule:** ≤5 lines per fragment variable (ideally 1–3). If longer, split further. + +**Single-element example (Ch1/nb01 — abstract part def):** ```python from pathlib import Path +import opensysml +from toaster.report import format_diagnostics + conn = opensysml.connect(version="v0.9.0") -source = Path("../../models/ch07-cumulative.sysml").read_text() -print(source) + +# abstract modifier not yet supported — toaster#9 / OpenSysML#595 +# spec: SysML v2 formal/2026-03-02 §7.3.3 (PartDefinition — AbstractClassifier) +TOASTING_SYSTEM_DEF = """\ +abstract part def ToastingSystem { + doc /* Any system that converts electrical energy into thermal energy + for food preparation. */ +} +""" +print(TOASTING_SYSTEM_DEF) + +TOASTER_INCREMENT = TOASTING_SYSTEM_DEF +source = Path("../../models/ch01-cumulative.sysml").read_text() model = conn.load_from_content(source, strict=False) -assert model.ok +assert model.ok, f"Model failed: {format_diagnostics(model.diagnostics)}" ``` -- Path is relative from the notebook file to the repo `models/` directory. -- `print(source)` makes the model visible in output without embedding it in the cell. -- No inline SysML strings longer than ~10 lines. The negative-control `bad_source` is exempt — it is deliberately minimal by design. -- The model file is authored by A3 and must exist before A4 can finalize this cell. +**Multi-element example (Ch1/nb02 — part def + attributes, spread across cells):** + +```python +# Cell: part def shell +# editor.add_part_def(owner='ToasterDemo', name='Heater') when API ships +HEATER_DEF = "part def Heater {" +print(HEATER_DEF) +``` +```python +# Cell: power attribute +# editor.add_attribute(..., name='power', ..., default='800.0 [SI::W]') when API ships +# default = modifier not yet supported — toaster#16 / OpenSysML#603 +POWER_ATTR = " attribute power : ISQ::PowerValue default = 800.0 [SI::W];" +print(POWER_ATTR) +``` +```python +# Cell: assembly + reflection + cumulative load +TOASTER_INCREMENT = f"{HEATER_DEF}\n{POWER_ATTR}\n ...\n}}" +print(TOASTER_INCREMENT) -## Tall's three worlds +source = Path("../../models/ch01-cumulative.sysml").read_text() +model = conn.load_from_content(source, strict=False) +assert model.ok, f"Model failed: {format_diagnostics(model.diagnostics)}" +``` -- **A-F (axiomatic formalism):** the SysML model file at `models/chXX-cumulative.sysml` -- **O-S (operational symbolism):** `conn.load_from_content(...)` loads and indexes it; downstream API calls in demo cells execute operations on it -- **E (conceptual embodiment):** the output rendered below the demo cell (figure, table, or diagnostic) -- **Seam cell:** names all three worlds explicitly in one sentence; identified by content type, not cell index +**Notes:** +- `TOASTER_INCREMENT` = new declarations introduced by this notebook only (not the full model). +- It is assembled from the named fragment variables and printed as the reflection. +- 13 notebooks have construction cells; judgment, depth, navigation, analysis, param-sweep do not. +- The cumulative model file is authored by A3 and must exist before A4 finalizes the assembly cell. + +## Tall's three worlds (builder-facing lens; corrected DL-015/DL-050) + +Tall's three worlds — axiomatic formalism (A-F), operational symbolism (O-S), conceptual +embodiment (E) — is the lens *this recipe's author* uses to design the seam cell. It is not +learner-facing vocabulary and it never appears, spelled out or abbreviated, in a notebook, `index.md` +or `conclusion.md` (AGENTS.md 1.10, both clauses). Two prior drafts of this skill required the +seam cell to *name* the labels — DL-050's dry run confirmed this is a live violation, found +independently by two simulated learners and caught by neither's naming lint (the labels don't +contain the words "Tall" or "three worlds", so `tall-named`'s regex missed them; it was widened in +the same contract that fixed this text). + +For the author's own reference, mapping this recipe's constructs onto the lens: + +- **A-F:** the SysML model file at `models/chXX-cumulative.sysml`, or the printed fragment string + in a construction-zone cell. +- **O-S:** `conn.load_from_content(...)` (or `editor.apply()`) loads and indexes it; downstream API + calls in demo cells execute operations on it. +- **E:** the output rendered below the demo cell (figure, table, or diagnostic). + +**Seam cell (what the learner actually reads):** one sentence that lets a reader who has never +heard of Tall still notice the three things connect — e.g. "the definition printed above loaded +without error, and `model.find()` confirms it's now part of the model, shown by the symbol printed +below" — never a sentence built around naming the categories themselves. `user-testing`'s +simulated-learner checklist judges this behaviorally (does removing any label still leave the +connection legible?), which is exactly the test a lint rule can't run. ## Chapter index.md — 6-element recipe @@ -62,6 +146,22 @@ No executable cells. Pure navigation and framing. No executable cells. +**"What comes next" is a forward claim about a chapter that has not been re-derived yet, and it +goes stale the moment the next chapter's own re-derivation changes what it actually contains.** +Found live 2026-09-27: Chapter 2's conclusion.md named specific constructs for Chapter 3 +(`calc def`, a specific `assert satisfy` idiom) that Chapter 3's own audit findings may not +match once that chapter is rebuilt. Standing rule (`decisions/next-passes.md`): re-checking the +*previous* chapter's "What comes next" against what actually got built is a required step of +*every* chapter's own re-derivation contract, not an optional cleanup pass done only when +someone happens to reread it. + +## Judgment record notebooks + +A notebook that builds a `ReviewRecord` (an `asserted_context`, `asserted_inference` or +`asserted_solution` judgment) uses `toaster-review-protocol`'s own construction-zone pattern for +it, not one dense call: name each group of fields, narrate what it's for, print it, then assemble. +The size limits below are relaxed for this content (see that skill for the exact grouping and why). + ## Size limits (A6 review criteria) - Prose: ≤600 words across markdown cells @@ -74,14 +174,28 @@ Identify required cells by content type, not by cell index — additional narrat - [ ] **Concept statement present:** exactly one sentence starting "This notebook introduces" - [ ] **Context cell present:** one paragraph with link to prior notebook (where applicable) -- [ ] **Model load cell present:** reads from `models/chXX-cumulative.sysml` via `Path(...).read_text()`; no inline SysML string longer than ~10 lines (bad_source exempt); `print(source)` before `load_from_content`; `assert model.ok` +- [ ] **Model increment cell present (construct-introducing notebooks only):** two-phase — (1) `TOASTER_INCREMENT` assigned and printed as reflection (Pattern A: `str(editor.apply())`; Pattern B: SysML fragment string); (2) full cumulative loaded from `models/chXX-cumulative.sysml`; `assert model.ok`. Judgment/depth/navigation/analysis notebooks: cell-02 loads cumulative only, no TOASTER_INCREMENT. - [ ] **Negative control present:** short bad_source inline; `assert not bad.ok`; markdown names the error type - [ ] **Demo cell(s) present:** one key operation per code cell; each code cell followed by markdown narration -- [ ] **Tall seam present:** exactly one sentence naming A-F (model file), O-S (API call), and E (rendered output) +- [ ] **Seam present:** exactly one sentence addressing, in behavior, that the definition, the loading tool, and the printed result are three distinct things the reader just watched connect — never naming Tall, "the three worlds", A-F, O-S or E - [ ] **Exercise pointer present:** markdown only; one sentence pointing to `exercises/ch{N}/exercise.ipynb` - [ ] ≤600 words prose; ≤50 lines code - [ ] One new construct/operation (or DEPTH annotation for Ch6) +## Tall's three worlds — construction cell update (author's lens only, see above) + +The A-F → O-S seam is now visible in cell-02 of construct-introducing notebooks, for the author's +own design purposes only: + +- **A-F:** the SysML declaration produced by the construction call or written as a string +- **O-S:** `editor.apply()` (Pattern A) or `conn.load_from_content()` (Pattern B) executes it +- **E:** `TOASTER_INCREMENT` printed as the reflection — the engineer sees the validated canonical SysML + +The seam cell (slot 5) addresses the connection behaviorally, as above — for a Pattern A notebook +that means pointing at what `editor.add_*()` produced and what running it validated; for Pattern B, +at the `TOASTER_INCREMENT` string and what loading it validated. Neither the labels above nor "Tall" +nor "three worlds" appear in the sentence itself. + ## What A4 must never do - Write prose that explains how Python works diff --git a/.claude/skills/toaster-review-protocol/SKILL.md b/.claude/skills/toaster-review-protocol/SKILL.md index a30e510..9bf5a47 100644 --- a/.claude/skills/toaster-review-protocol/SKILL.md +++ b/.claude/skills/toaster-review-protocol/SKILL.md @@ -45,6 +45,65 @@ record = ReviewRecord( ) ``` +## Why a judgment record is its own notebook content, not an aside + +The model is computable: `model.eval(...)` and the conformance checks tell you whether a claim +holds. That is not the same as the model being interpretable — knowing a claim evaluates True or +False does not by itself tell a reader whether the claim was the right one to check, whether enough +was checked to trust it, or what would have to be true for the check to be wrong. A `ReviewRecord` +is where that second layer lives: it states, in the reader's terms, what appropriateness, +sufficiency and trustworthiness look like for this specific claim (Hawkins 2011 §§3.1-3.4). A +notebook that builds one is teaching that layer as directly as a construction-zone cell teaches a +SysML construct, and deserves the same narrated, one-idea-at-a-time treatment, not a single +dense call that a reader skims past to get to the printed validation result. + +## Judgment record construction zone + +Build a `ReviewRecord` the same way a construction-zone notebook builds a model fragment: name each +group of fields, narrate what it's for, print it, then assemble. Group by the question each part of +Hawkins' taxonomy is answering, not by the dataclass's field order: + +``` +[markdown] narration: what is being claimed, and about what +[code] claim = "..." + model_ref = "..." +[markdown] narration: what standard the claim is checked against (appropriateness) +[code] scope = "..." + criteria = "..." +[markdown] narration: what's being taken as given +[code] premises = [...] + assumption_refs = [...] +[markdown] narration: what supports the claim, and how (sufficiency) +[code] evidence_refs = [...] + rationale = "..." +[markdown] narration: what could be wrong, and what's still open (trustworthiness) — + counterevidence and residual_uncertainties are never blank; a record that + hides its own weak points is not more trustworthy, it is less checkable +[code] counterevidence = "..." + residual_uncertainties = "..." +[markdown] narration: assembling the record from the named parts above +[code] record = ReviewRecord(identifier=..., kind=..., claim=claim, model_ref=model_ref, + content_hash=hash_content(source), scope=scope, criteria=criteria, + premises=premises, assumption_refs=assumption_refs, + evidence_refs=evidence_refs, rationale=rationale, + counterevidence=counterevidence, + residual_uncertainties=residual_uncertainties, + disposition="pending", dependency_freshness="current", + engineering_conclusion=..., record_kind="worked_example") + errors = validate_record(record) + print(f"Validation errors: {errors}") +``` + +Five groups, five narration cells, matching the model-fragment construction zone's pacing rule (no +two code cells adjacent). Each `print`ed group is the record's own reflection, the same role a +printed `TOASTER_INCREMENT` plays for a model fragment. + +**Size limit:** `toaster-recipe`'s ≤600 words / ≤50 lines budget is sized for a notebook whose main +content is one model construct. A notebook whose construct is a judgment record may exceed it — the +fields Hawkins' taxonomy requires are the content, not overhead around it — provided the words spent +are the record's own claim, criteria, evidence, rationale and challenge, not restated narration +about the tutorial's own process. + ## Two evidence paths | Path | Use when | Call | diff --git a/.claude/skills/tutorial-glossary/SKILL.md b/.claude/skills/tutorial-glossary/SKILL.md new file mode 100644 index 0000000..cf90268 --- /dev/null +++ b/.claude/skills/tutorial-glossary/SKILL.md @@ -0,0 +1,60 @@ +--- +name: tutorial-glossary +description: How to use and extend the glossary knowledge graph (sources, terms, definition edges, kinds, the tutorial-definition view, confirmation and refinement rules, CLI, limits). Look a term up before defining it. +--- + +# Tutorial glossary + +`glossary/` is the local source of truth for definitions. It is a **bipartite graph**: nodes are **sources** and **terms**, and a **definition is an edge**, because one term can be defined a little differently by different texts. Keep sources few (N about 10) and terms modest (M about 50, load-bearing only); definitions can grow as N times M, so add an edge only when it is needed. `glossary/README.md` has the file layout. + +## Look up before you define or use a term + +``` +uv run python -m glossary lookup mechanism # every edge: source, locator, quote, status, refines/differsFrom +uv run python -m glossary tutorial mechanism # what the tutorial uses: idea, formal semantics, story, own refinement +uv run python -m glossary compare logical-architecture +uv run python -m glossary terms | sources | where sysml | stats +uv run python -m glossary sparql lookup_all --json # named or inline SPARQL, deterministic order +uv run python -m glossary lint [--baseline FILE] # vocabulary rules over learner content (rules in glossary/lint_rules.toml) +``` + +Every command takes `--json`. Rule: if a term is in the glossary, use its tutorial definition and cite the term id (`term-mop`). If it is not and the work depends on it, propose an edge (below); do not invent a definition in prose. + +## The four kinds of source + +Sources are not ranked against each other. Each supplies a **kind** of definition, and the kinds complement each other. + +| `gl:kind` | Sources | Role | +|---|---|---| +| `conceptual` (idea) | SEBoK, Hawkins, Åström and Murray, Sutton and Barto | What the concept means | +| `formal` | SysML v2 language spec, API spec, KerML | Checkable semantics | +| `didactic` (story) | Douglas | Analogy and example | +| `bridge` | This tutorial | Our refinements | + +`tutorial TERM` returns the best confirmed edge of each kind (`queries/tutorial_definitions.rq`). `gl:rank` orders sources of the *same* kind only; `gl:preferred` breaks a tie between edges of the same source (for example SEBoK's two senses of *behavior*). `check` fails on an ambiguity within a kind. The one-line gloss used in AGENTS.md and skills comes from the bridge edge if there is one, else the idea, else the formal semantics, else the story. + +## Rules + +1. **Only a human confirms.** Agents propose (`gl:status gl:proposed`). Only Z sets `gl:confirmed` and `gl:confirmedBy`. `check` rejects an agent name as `confirmedBy`. A proposed edge shows in `tutorial --proposed` (a preview) but never in the confirmed view or a rendered gloss. +2. **Canonical first; refine only where needed.** A tutorial edge (`src-tutorial`) may `gl:refines` another edge to narrow or clarify it, and only the tutorial source may. It must not contradict its parents. `gl:differsFrom` (a departure from the same term's edge) needs `gl:approvedBy` and `gl:approvalNote`; the only approved one is *logical architecture* versus SEBoK (DL-015). +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. + +## Adding a term, source or edge + +- **Term:** add a node to `glossary/terms/terms.ttl` (`glid:term-`, `gl:label`, `gl:loadBearing true` only if a Foundations paragraph or skill relies on it). A term with no edge is an orphan and fails `check`. +- **Source:** add to `glossary/sources/sources.ttl` with `gl:kind`, `gl:rank`, and either a file (`gl:sha256`, `gl:localPath` under the gitignored `glossary/sources/local/`) or a non-file source (`gl:url` and `gl:retrievedOn`, or `gl:commit`). Keep N small; prefer an edge in an existing source. +- **Edge:** add to `glossary/definitions/.ttl` as `glid:def---` with `gl:source`, `gl:term`, `gl:text`, `gl:locator`, `gl:status gl:proposed`, and `gl:quote` plus `gl:pdfPage` for files. +- Write Turtle through the `glossary.graph.save_graph` helper (canonical, byte-deterministic), not by hand, or `check` will report drift. Then run `check`. On Z's machine also run `verify-sources`. +- Tell the ACE (or Z) what you proposed; they triage and Z confirms. Log it in `decisions/log.md` if it changes a confirmed definition (only Z can change one). + +## Worktree and CI safety + +`check` passes in a fresh worktree or CI where the gitignored source PDFs are absent: it verifies hashes and quotes only for files that are present and warns for absent ones. `verify-sources` is the strict check and needs the originals in `glossary/sources/local/`. + +## Limits + +- Not a general knowledge base: load-bearing terms only, and the density D/(N x M) should stay low (`stats`). +- `lookup` is text and provenance; it does not judge whether prose uses a term correctly. That check belongs to reviewers, who use `lookup` instead of memory. diff --git a/.claude/skills/tutorial-style-guide/SKILL.md b/.claude/skills/tutorial-style-guide/SKILL.md index 54c49a7..0465800 100644 --- a/.claude/skills/tutorial-style-guide/SKILL.md +++ b/.claude/skills/tutorial-style-guide/SKILL.md @@ -11,13 +11,16 @@ Load this skill alongside domain skills. It does not replace them. - Active voice. Never "it can be seen that" or "it is worth noting." Say the thing. - Sentences ≤20 words as the default ceiling. Split longer ones. -- No em-dashes. Use parentheses (short aside) or a colon: for an elaboration, or a new sentence. +- **No em-dashes, anywhere, in any learner-facing file — including code-cell comments.** Not for asides, not for emphasis, not for a dramatic pause. Use a period, a comma, a colon for an elaboration, or parentheses for a short aside. **In a YAML file (`myst.yml`), quote any title that uses a colon this way** (`title: "Chapter 1: System and Purpose"`); an unquoted colon inside a YAML scalar is a parse error, not a style choice. Mechanically enforced, with a real gap: `tall-named`'s neighbor rule `no-em-dash` in `glossary/lint_rules.toml` flags every hit (`uv run python -m glossary lint`), but the lint only scans markdown cells and `.md` files (`glossary/lint.py`'s own documented scope) — it does not see code-cell comments at all. Found by direct human review 2026-09-27: 35 em-dashes in Chapters 1-2's markdown alone, in a rule already written down here and never checked, plus 3 more hiding in code-cell spec-citation comments that the lint cannot see regardless. Until the lint's scope is widened to cover code cells, grep for the literal character (`grep -rn $'—' `) across an entire notebook, not just its markdown, before calling prose done. +- **No metanarration: text about the act of teaching or writing, instead of the subject matter itself.** Textbook register states facts about the model and the method directly; it does not comment on itself. Banned patterns, all found in this tutorial's own output before this pass: "Let's explore/dive into/unpack X," "Now we'll turn to X," "This is where it gets interesting," "As you can see above," "It's worth noting that," "Here's the key insight," any sentence whose subject is "this notebook/section/tutorial" doing something to the reader rather than the subject matter doing something in the model. Write "The requirement constrains cycle time" not "In this section, we'll look at how the requirement constrains cycle time." - No hedging when the claim is established: "the model shows" not "the model seems to suggest." - Present tense for model facts: "the toaster has three parts." Past tense for actions already taken: "we added a requirement." - Oxford comma. - Glossary terms introduced once; used without definition thereafter. -**What A4 must never do:** Restate what the code just did. If `model.ok` is True and printed, don't write "as we can see, the model loaded successfully." +**What A4 must never do:** Restate what the code just did. If `model.ok` is True and printed, don't write "as we can see, the model loaded successfully" (also metanarration, doubly banned). + +**What A6 must check, mechanically, not by impression:** run `uv run python -m glossary lint` and read every `no-em-dash` hit before approving prose; grep the diff for the metanarration patterns above. A reviewer who read the prose and "didn't notice" an em-dash is not evidence there are none. ## Diagram aesthetics (A7) @@ -51,20 +54,56 @@ Every major operation gets its own dedicated markdown cell. This is not optional - A code cell whose output needs interpretation is followed by a markdown cell interpreting it. Do not leave output to speak for itself. - If a demo involves two distinct steps (e.g., define a sympy expression, then lambdify it), those are two code cells each with its own narration — not one cell with a comment. -**A6 test:** scan each code cell. If it does more than one conceptual thing OR if its output has no adjacent markdown explanation, flag it. +**A6 test, mechanical, not "scan and see if anything jumps out":** for every notebook in the diff, list the cell types in order (`code`/`markdown`) and check for two `code` cells in a row with no `markdown` between them. Found by direct human review 2026-09-27, not by any prior automated or human check: every one of Chapter 1 and Chapter 2's seven construction-introducing notebooks had at least one such run (`toaster-recipe`'s own pacing rule, and Chapter 1/2's retrofit). A quick check, worth running every time: + +```python +import json +nb = json.load(open("path/to/notebook.ipynb")) +seq = [c["cell_type"] for c in nb["cells"]] +print("".join("C" if t == "code" else "M" for t in seq)) +``` + +Any run of two or more consecutive `C`s is a finding, unless the contract explicitly names that pair as one inseparable operation. ## Structural consistency (A4, A6) - Cell 0 (concept statement): exactly one sentence. No exceptions. -- Cell 5 (Tall seam): exactly one sentence naming all three worlds. No exceptions. +- Cell 5 (seam): exactly one sentence addressing the seam (definition, loading tool, rendered result) in behavior. No exceptions, and no naming Tall, "the three worlds", A-F, O-S or E (AGENTS.md 1.10; corrected DL-050) — see `toaster-recipe`'s "Tall's three worlds" section. - Cell 6 (exercise pointer): exactly one sentence. Markdown only. - Chapter `conclusion.md`: exactly four items (three paragraphs + exercise reference). Not three, not five. - `index.md` six recipe elements appear in stated order. No reordering. +## Construction cells (construct-introducing notebooks only) + +**Rule: code factored as if we had the API calls we wanted.** +One fragment variable per element = one future `editor.add_*()` call. When the Editor API +gains full spec coverage, each string fragment is replaced by the corresponding call; the +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 + directly above the string, e.g.: + ```python + # abstract modifier not yet supported — toaster#9 / OpenSysML#595 + # spec: SysML v2 formal/2026-03-02 §7.3.3 + TOASTING_SYSTEM_DEF = "abstract part def ToastingSystem;" + ``` +- `TOASTER_INCREMENT` is assembled from the fragment variables in the final cell of the + construction zone; it equals the **new declarations for this notebook only** (not the full + cumulative model). Print it as the reflection. +- The cumulative load (`conn.load_from_content(ch0X-cumulative.sysml)`) happens in the same + final cell, after printing `TOASTER_INCREMENT`. +- `conn.close()` belongs at the end of the last code cell in the notebook (cell-04 or later), + never in the construction zone. +- Judgment, depth, navigation, analysis, and param-sweep notebooks have no construction zone + and do not assign `TOASTER_INCREMENT`. + ## What every agent loading this skill must never do -- Write a Tall seam that names only two worlds. -- Write a Tall seam that says "the source string in cell 2" — A-F is the model file `models/chXX-cumulative.sysml`, not the inline string. +- Write a seam sentence that names Tall, "the three worlds", or their abbreviations (A-F, O-S, E) — those are the author's own design lens, never learner-facing (AGENTS.md 1.10). +- Write a seam sentence vague enough that a reader could not point to which printed thing is the definition, which is the loading step, and which is the result — e.g. "the source string in cell 2" is not specific enough; name the actual model file or fragment, `models/chXX-cumulative.sysml`, not the inline string. - Use Mermaid for any diagram. - Use em-dashes in prose. - Write a figure caption longer than two sentences. diff --git a/.claude/skills/tutorial-supporting-pages/SKILL.md b/.claude/skills/tutorial-supporting-pages/SKILL.md index 6b3f8b6..d9f50dd 100644 --- a/.claude/skills/tutorial-supporting-pages/SKILL.md +++ b/.claude/skills/tutorial-supporting-pages/SKILL.md @@ -11,7 +11,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` | SysML v2 terms introduced in the tutorial | +| `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/reproducibility.md` | Closing reproducibility statement (populated from build manifest) | | `docs/contributor.md` | Maintainer guide (4 update scenarios) | diff --git a/.claude/skills/user-testing/SKILL.md b/.claude/skills/user-testing/SKILL.md index 1679008..e519f24 100644 --- a/.claude/skills/user-testing/SKILL.md +++ b/.claude/skills/user-testing/SKILL.md @@ -5,27 +5,27 @@ description: Simulated learner protocol for chapter checkpoint tests — persona # Simulated User Testing -**Loaded by:** A9 Simulated Learner (all activities), A8 ACE (synthesis and grounded self-test) +**Loaded by:** `simulated-learner` (all activities), `ace` (synthesis and grounded self-test). Spawned by the orchestrator, per the Pass 2 operating model (`decisions/next-passes.md` §2): the orchestrator launches one `simulated-learner` agent per persona and collects their reports; the ACE receives the compiled reports for synthesis, not the raw spawn. ## Purpose -At each chapter checkpoint, ACE spawns two or three A9 agents with different personas. Each agent reads and executes the chapter as a learner, then reports findings using the fixed format below. ACE runs one brief test of its own (one notebook) before synthesizing, so the decision is grounded rather than purely delegated. +At each chapter checkpoint, the orchestrator spawns two or three `simulated-learner` agents with different personas. Each agent reads and executes the chapter as a learner, then reports findings using the fixed format below. The ACE runs one brief test of its own (one notebook) before synthesizing, so the decision is grounded rather than purely delegated. -The bar is: **good enough to proceed to the next WP.** The question is not perfection — it is whether a learner could make meaningful progress through this content as written. +The bar is: **good enough to proceed to the next chapter.** The question is not perfection — it is whether a learner could make meaningful progress through this content as written. -## Personas +## Personas and model assignment -ACE chooses two or three from this list per checkpoint, ensuring coverage of novice and practitioner perspectives: +The orchestrator chooses two or three from this list per checkpoint, ensuring coverage of novice and practitioner perspectives, and launches each with an **explicit model override** — a simulated novice should not have more capability than the learner it stands for (`decisions/next-passes.md` §3): -| Persona | Background | Focus | -|---|---|---| -| **Novice** | Python-literate; no prior SysML or MBSE | Clarity of concept statements, negative-control diagnostics, whether prose assumes unstated context | -| **SE Practitioner** | Systems engineering background; no SysML v2 | Correctness of SE concepts, whether model choices are defensible, Tall seam clarity | -| **Returning Learner** | Completed prior chapters; starting this one fresh | Whether index.md sets up correctly, whether the cumulative model is self-contained, exercise pointer utility | +| Persona | Model | Background | Focus | +|---|---|---|---| +| **Novice** | Haiku 4.5 | Python-literate; no prior SysML or MBSE | Clarity of concept statements, negative-control diagnostics, whether prose assumes unstated context | +| **SE Practitioner** | Sonnet 5 | Systems engineering background; no SysML v2 | Correctness of SE concepts, whether model choices are defensible, whether the Tall seam (below) is addressed | +| **Returning Learner** | Sonnet 5 | Completed prior chapters; starting this one fresh | Whether index.md sets up correctly, whether the cumulative model is self-contained, exercise pointer utility | One agent per persona. No two agents with identical persona in one checkpoint run. -## Execution checklist (A9 must run these in order) +## Execution checklist (`simulated-learner` must run these in order) For each sub-notebook in the assigned chapter(s): @@ -35,7 +35,7 @@ For each sub-notebook in the assigned chapter(s): 4. **Cell 2 (execute)** — run the model-loading code. Record: `model.ok`, any diagnostic output. 5. **Cell 3 (execute)** — run the negative control. Record: `bad.ok` (must be False), printed diagnostic message. 6. **Cell 4 (execute)** — run the demonstration. Record: output produced; note if it matches what cell 0 promised. -7. **Cell 5** — read the Tall seam. Does it name all three worlds (SysML text, OpenSysML execution, visible output)? +7. **Cell 5** — read the Tall seam. AGENTS.md 1.10 binds that learner content **never names** Tall or "the three worlds" (the `tall-named` lint rule, `glossary/lint_rules.toml`, DL-028, already enforces the never-name half in CI). Your job is the half a lint rule cannot judge: does the cell **address the seam in behavior** — is it clear, without naming the lens, that the SysML text, the tool that loads and runs it, and the rendered/printed result are three distinct things the reader has just seen connect? Record which of the three you could each point to concretely from what the cell actually showed, and whether a reader who had not been told there were "three worlds" would still notice the seam. 8. **Cell 6** — read the exercise pointer. Is it one sentence? Does it describe what the exercise asks? 9. **Read conclusion.md** — three paragraphs (what was built / what this establishes / what comes next) plus exercise reference? @@ -66,7 +66,7 @@ NARRATIVE OBSERVATIONS (top 3, each quoting exact text): STRUCTURAL CHECKS: - Cell 0 one sentence: [yes/no] -- Cell 5 names three worlds: [yes/no] +- Cell 5 addresses the seam without naming it: [yes/no] — [which of the three you could point to; if no, what's missing] - Cell 6 one sentence: [yes/no] - conclusion.md three paragraphs + exercise reference: [yes/no] @@ -77,18 +77,18 @@ Maximum 400 words per report. ## ACE synthesis protocol -After receiving all A9 reports: +After receiving the compiled `simulated-learner` reports from the orchestrator: 1. **Run own test** — pick one notebook from the chapter, run all cells, read the narrative. One fresh observation. 2. **Triage** — for each NEEDS-FIX, classify: blocking (prevents understanding), minor (friction but learner can continue), cosmetic (wording preference). -3. **Fix blocking issues** — fix them inline; log each as a DL entry (Path: Handled by ACE — user-test fix). Do not fix minor or cosmetic without Z's direction. -4. **Decide** — if zero blocking issues remain: **CHECKPOINT PASS**. Log DL entry; proceed to next WP. If blocking issues remain after fix: escalate to Z. +3. **Rule or return, never edit.** The ACE does not edit repository files (`ace-protocol`, current and binding): for each blocking issue, the ACE either **rules** what the fix should be (if a framework, principle or heuristic determines it) or **escalates** to Z, and returns that to the orchestrator, which dispatches a builder/author-role work contract to make the change and a reviewer to confirm it — the same pipeline as any other fix. Log each ruling or escalation as a DL entry (Path: Handled by ACE, or Escalated to Z — user-test finding). Do not decide minor or cosmetic items without Z's direction. +4. **Decide** — if zero blocking issues remain (after the dispatched fixes are confirmed in): **CHECKPOINT PASS**. Log the DL entry; proceed to the next chapter. If blocking issues remain: escalate to Z. ## What counts as blocking - A cell that does not execute (Python error, not a deliberate negative control) - A concept statement longer than one sentence or missing entirely -- A Tall seam that does not name all three worlds +- A Tall seam that names Tall or "the three worlds" (already caught by the `tall-named` lint rule in CI; a `simulated-learner` finding of this kind is a lint escape and should also be reported as such) — or one that avoids naming them but does not address the seam in behavior either (the judgment this protocol exists to make) - A negative control where `bad.ok == True` (the assert would fail at runtime) - Cumulative model from prior chapter omitted or broken diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 89d9fb7..111e4ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: # Step 2: Run pytest (unit + smoke) - name: Run tests - run: uv run pytest tests/ -v --tb=short + 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 diff --git a/.gitignore b/.gitignore index 75e9695..4d469c4 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,13 @@ htmlcov/ # Editor .vscode/ .idea/ + +# glossary: copyrighted source originals (registered by hash in glossary/sources/sources.ttl) +glossary/sources/local/* +!glossary/sources/local/.gitkeep + +# Chapter notebooks' own scratch companion files (written and unlinked at run time; a repo- +# relative, portable location, not the system tempdir, so committed notebook output stays +# reproducible across machines: see chapters/ch08-checking/02-violation-witness.ipynb) +chapters/*/companion-check-scratch/ +exercises/*/companion-check-scratch/ diff --git a/AGENTS.md b/AGENTS.md index d4ca565..df85a68 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,23 +1,212 @@ # AGENTS.md — Toaster Multi-Agent Build Contract -This file is the binding contract for all agents operating on the Open-MBEE/toaster repository. Every agent must read it before taking any action. +This file is the binding contract for everyone who works on the Open-MBEE/toaster repository. It has two parts. + +- **Part 1, Foundations**, says what the tutorial teaches, which sources define its terms, how the three architecture layers differ, and how models are built and queried. It is role-agnostic and stable. It governs wherever Part 2 conflicts with it. +- **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 **legacy, pending rebuild** in Pass 4 (see `decisions/next-passes.md`). Treat Part 2 as the current authority matrix for anything a rebuilt role does not cover. + +A cold session should reach working alignment from `CLAUDE.md`, this Part 1, the skills it lists, and the glossary CLI. Nothing here depends on conversation history. + +--- + +# Part 1 — Foundations + +## 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: + +1. **SysML v2 is declarative, not procedural.** Its defining technical analogy is a database language: we build a model, then query and analyze it to check that it says what we intend. +2. **Functional (what), logical (how), physical (where)**, and the three boundaries between them (§1.5). +3. **Executable specifications** as the way to declare intended behavior. +4. **Recursive decomposition**, adding detail so that a design can be validated against intended behavior. +5. **Model checking and simulation are complementary**: formal properties on one side; scenarios, trajectories and analysis of results on the other. +6. **Sites of engineering judgment and the evidence base behind them.** Judgment is never eliminated (§1.6). +7. **Coverage and traceability** in service of the accountable engineer's sign-off. +8. **Explicit and implicit construction** balance a complete model against a readable narrative (§1.7). +9. **Diagrams are purposeful, judged views of the model** (§1.7). + +The conceptual (stakeholder) layer above the functional layer and the procurement layer below the physical layer are out of scope by design. Only the boundaries between functional, logical and physical are taught. + +## 1.2 Sources, and what kind of definition each supplies + +The sources are not rivals. Each supplies a different **kind** of definition, and the kinds fit together: + +| Kind | Source | Supplies | +|---|---|---| +| Idea (conceptual) | SEBoK v2.14; Hawkins et al. 2011 (judgment taxonomy); Åström and Murray and Sutton and Barto (for *mechanism* and *policy* only) | What the concept means and why it matters; generic and informal | +| Formal semantics | The OMG specs: SysML v2.0 language, API and Services v1.0, KerML 1.1 Beta 2 | Checkable semantics; the language we execute | +| 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. + +**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". + +## 1.3 The glossary is the source of truth for terms + +- Before defining or using a load-bearing term, look it up: `uv run python -m glossary lookup TERM` (all edges), `compare TERM`, and `tutorial TERM` (the idea, formal semantics and story that the tutorial uses, plus its own refinement if any). +- Definitions change only through the graph (`glossary/`), validated by `uv run python -m glossary check`. Agents propose; only Z confirms a definition. +- One-line glosses in this file sit between `` markers and are **generated** by `uv run python -m glossary render`. Do not edit them by hand. +- Use `uv run python -m glossary` for the command list. See the `tutorial-glossary` skill. + +## 1.4 Two languages, one loop + +**SysML v2 is declarative** and is the authoritative source of semantics: canonical semantics come from the specs; user-defined semantics live in the model (attribute types and units, `calc def` relations, MoE and MoP metadata, `doc`). **Scientific Python is the complementary procedural language.** It analyzes: simulation and sweeps, figures, and queries of the model. It never defines what the model means. A number produced in Python without a model-defined unit and relation is not evidence. + +The engineer's job is to align the model to their intent through **loops of construction and analysis of what was constructed**. Every chapter is one turn of that loop, and a negative control shows that the loop can detect a mismatch. Simulations produce the evidence base; judgments about whether requirements are satisfied rest on that evidence and point at it. They do not replace it. + +## 1.5 The three layers + +Intended behavior, stated solution-independently: functions with typed flows, the phenomena relations among them, and the MoEs. + +Prescribed mechanisms and policies carried by logical components, plus the interfaces between them; MoP thresholds are derived here. + +Concrete parts that realize the logical components and confer values; each must fit the logical interfaces and meet the derived thresholds. + +| Layer | Answers | Stated as | Measure | SysML v2 idiom | +|---|---|---|---|---| +| Functional | What | *Intents*: required behavior, with typed flows and the relations among phenomena (an energy **balance** inequality, which respects conservation without assuming perfect efficiency) | **MoE** | `action def` with typed in and out flows; calc or constraint for phenomena relations; behavioral `requirement def` | +| Logical | How | *Prescriptions* (mechanisms, policies, interfaces), plus the derived intents (MoP thresholds) they must meet | **MoP** | `abstract part def` with `perform action x : ActionDef`; `port def`, `interface def`, flows; constraints stating the principle; `allocate`; derived requirements | +| Physical | Where | *Prescriptions* (parts, values), plus the assessed results | **TPM** | concrete `part def` specializing the abstract logical part def; attribute values (assessed values are TPMs) | + +Key terms (glossed from the glossary): + +- **Mechanism.** A prescribed, comparatively deterministic input-to-output relation: a modeling decision grounded in engineering practice, a law we use to reason about behavior. Not itself the behavior. +- **Policy.** Decision guidance that selects inputs given the state, typically to close the loop under uncertainty; designed given the available mechanisms. +- **Logical component.** The prescribed carrier of a mechanism, with its interfaces; modeled here as an abstract part definition that performs an action. +- **Selection among alternatives.** Choosing among alternative mechanisms by trade study against the derived measures. +- **MoE.** A measure of stakeholder satisfaction with the outcome: a measurable attribute with a unit and a means of collecting data (for the toaster, how evenly the bread is toasted). Stated at the functional layer. +- **MoP.** An engineering measure of performance: a measurable attribute with a unit and a means of collecting data (for the toaster, power efficiency). It characterizes a requirement, which also needs a threshold. Typically logical. +- **TPM.** The value assessed on a design element by analysis or simulation: the evidence against a MoP threshold. +- **Allocation.** Assigning functions to logical components, and components to parts: SEBoK's idea, SysML v2's allocate, Douglas's grouping. + +**MoE → MoP → TPM is a derivation chain.** A MoE says what acceptance looks like; a MoP is a performance measure whose threshold is derived so that the MoE can be satisfied; a TPM is the value actually assessed on a design element. Each can be stated on any element as decomposition proceeds, and reasoned over from parts through interconnections to higher-order parts. In the SysML spec they are only metadata tags on attributes (§9.3.4), and neither SEBoK nor the spec ties them to layers, so the layer emphasis is a tutorial refinement. A MoP characterizes a requirement but does not make one: the requirement needs a threshold and a means of checking it. **Whether a measure is a MoE or a MoP is a modeling judgment for the case at hand**, recorded with its justification (who cares, and does it measure acceptance or engineering performance). How long toast takes could be either, and a hard case is a good place to show a judgment call. + +**The system of interest is the subject the layers describe, not a layer.** Its purpose statement is functional, its parts and arrangement are logical, its realized parts are physical; a bare top-level part def that only names the whole is the named subject. Classify the pieces. + +**A verification case is not itself a layer element.** A `verification def` (and the checks it runs) is the analysis half of the construct-and-analyze loop. Classify it by the layer of what it tests and by its tier (language, or staged project conformance); its verdict is evidence, and TPMs are the values it assesses. + +**Allocation is not realization.** `allocate` assigns functions (and requirements, budgets) to elements. A concrete part def *specializes* the abstract logical part def to realize it. Usage-level allocation of a logical component to a part is optional. + +**Constraints, split by solution-independence.** A constraint that holds for any solution (energy conservation) frames the problem and stays functional. A constraint that exists only because of a chosen mechanism or interface (Joule heating, I^2 R, as applied to a coil; outlet-to-plug compatibility; a derived MoP threshold) is logical. Physical laws such as Joule heating are mechanisms: modeling decisions grounded in established engineering practice, the laws we reason with. Stated for a chosen component, they are logical; a law that holds for any solution stays functional. + +**Numbers.** A MoP's definition and threshold are requirements at the layer that states them. What a specific part has, or is estimated to have, is the TPM. Sizing choices (fuel volume, tong length) appear only when a physical part is chosen. + +**Boundary tests** + +- *Functional to logical (substitution test).* If a pop-up toaster and tongs-with-a-blowtorch would both satisfy the statement, it is functional. If it commits to a mechanism, it is logical. +- *Logical to physical.* If any part built to the stated interface and derived thresholds satisfies it, it is logical. It is physical when a specific part def is named and its values are chosen. +- *Conceptual to functional.* Would the stakeholder recognize it as a need? Do not invent functions they have not asked for. +- *Prescribed versus emergent.* Is it something the design chooses (an element, relationship, principle or parameter) or something expected to result from those choices (a behavior or performance)? Choices are stated in the model. Results are derived by analysis and checked against intent, and a result must never be entered as if it were a choice. A cycle time set as an attribute default and then "verified" against its threshold is a prescription tested against a threshold, not emergent behavior. + +**Connectivity differs by layer.** Functional connectivity is behavioral dependency (you cannot apply heat without an energy source). Logical connectivity is interface compatibility: an outlet feeds a pop-up toaster, a fuel tank feeds a blowtorch, and the arrangement is settled before sizing. + +## 1.6 Prescribed versus emergent, and judgment + +A design can only *prescribe* elements, relationships and principles. The emergent outcome of a system in use; not prescribed but derived by analysis or simulation and judged against intent. The aim is not to eliminate emergence but to make desirable emergence likely. So the model states intents and prescriptions, and behavior is derived and checked, never asserted. + +Emergence: Properties or behaviors that arise at the level of the whole and cannot be attributed to any one component. SEBoK distinguishes simple, weak and strong emergence. SEBoK's three kinds map onto the layers by how a value is obtained: + +- **Simple** emergence (computable from well-understood parts and relations, such as a mass roll-up) is typical of the logical layer. Its MoPs are derived by composing relations symbolically, and the numbers arrive when a physical candidate supplies part values. +- **Weak** emergence (needs simulation, modeling or experiment) is typical of the functional layer's intents, such as "toasted to the user's liking". Stability straddles both: an analytic form that can be model checked, plus simulated trajectories and failure modes. +- **Strong** emergence (unanticipated; seen only in integration, test or operation) belongs to no layer. It is what sign-off judges. + +These are tendencies, not rules. The stable distinction is *computed versus explored*. + +**Judgment is never eliminated.** Prescribing does not guarantee, weak emergence is explored and not settled, and strong emergence cannot be anticipated, so real design rests on assumptions and on partly subjective calls. What makes them rigorous is an evidence base and explicit justification, which Hawkins's judgment taxonomy structures. Completely mitigating all assurance deficits is not normally achievable, so a judgment on when they can be tolerated is necessary, assessed by expert judgment of likelihood and severity. Any knowledge gap that prohibits total confidence. Engineers are not trying to remove judgment calls; they are experts at making contextually appropriate, evidence-informed ones. No tutorial text may describe a passing check as proof, imply that everything is reducible to what can be computed, or record a disposition as "accepted" (SA-7 stands). A judgment record's `counterevidence` and `residual_uncertainties` fields are load-bearing for this reason. + +## 1.7 Building the model and showing it + +**Explicit and implicit construction.** The model needs more parts than a learner should build by hand, so two kinds of construction coexist. **Explicit** constructions are walked through in the notebook. **Implicit** ones are coded in other Python files that the notebook only imports. The model is complete by the end, with only explicit construction on the page. + +- 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). + +**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. + +## 1.8 Recursion and stopping rule + +Decompose until every leaf is a concrete component def that **performs** its specified behavior, **connects** through its specified interfaces, and has **verification evidence**. At each level the three boundaries in §1.5 apply again. Completeness is auditable: at every level, account for every input and output. + +## 1.9 Querying, and tracking gaps + +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. +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`). + +**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. + +**Probe before you assert.** A construct is described as working only after it has been run. The skills mark constructs as tested or untested. + +## 1.10 Builder-facing lenses (never in learner content) + +Some habits of thought guide how we build and validate the tutorial and are not part of what it teaches. + +- **Tall's three worlds** (education and cognitive science) stay behind the scenes. Learner content never names them. Evaluation checks that the seam between model text, the tool that loads it, and the rendered result is addressed; that is an emergent behavioral requirement, not prescribed text. +- **Optimization and control.** Read the layers as objective (functional: what is good, what is good enough), design space (logical: typed, unit-bearing slots plus equality and inequality constraints, no solution values), and candidate (physical: a concrete point, checked for feasibility against the logical layer and for utility against the functional layer). Ask of any element which of the three it reads as. +- **Generalized dynamical systems.** A mechanism is a state-update relation, a policy selects inputs given the state, and we design policies from the mechanisms available. This informs Z's thinking and is not a source. + +Learner-facing vocabulary from these lenses is allowed only where it makes a term easier to learn, and never load-bearing. + +## 1.11 How alignment changes + +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. Shared domain context +# Part 2 — Roster and authority (partially rebuilt; content archetypes legacy, pending Pass 4) + +Roles rebuilt in Pass 2 live in `.claude/agents/` (`orchestrator`, `layer-auditor`, `builder`, `reviewer`, `ace`); where a role file exists it governs that role's duties, model and authority, and the matching legacy row below is superseded. Mapping, so this supersession is explicit rather than inferred: + +| Pass 2 role file | Supersedes | Notes | +|---|---|---| +| `orchestrator` | A1 Orchestrator | A1 was READ ONLY; the rebuilt role also integrates subagent commits (`decisions/next-passes.md` §2 operating model; the "read only, never edits files" line below is the superseded text). | +| `builder` | A2 Builder | File-authority matrix (§2 below) still names A2's blast zone for content work; a builder's own work contract (`decisions/work-contract-template.md`) narrows it per task. | +| `ace` | A8 ACE | A8's file authority (`decisions/log.md`, `.claude/skills/**/*.md`) still applies; `ace-protocol` is the current decision framework. | +| `layer-auditor` | *(none — new role)* | Did not exist as an archetype; audits chapters against `architecture-layers` (`decisions/audits/ch0N-layer-audit.md`). | +| `reviewer` | *(none — new role; overlaps A5/A6's intent)* | A5 (Technical) and A6 (Didactic) reviewer were both READ ONLY archetypes with no model assignment; `reviewer` generalizes that duty with the independent-model rule (`decisions/task-states.md`). A5/A6 remain legacy names for content-specific review until Pass 4 gives them their own role files, if it does. | +| `simulated-learner` (Pass 3) | A9 Simulated Learner | A9 had no model assignment; the rebuilt role pins per persona (Haiku 4.5 Novice, Sonnet 5 otherwise — `.claude/skills/user-testing/SKILL.md`), and its checklist and blocking criteria were corrected to match Part 1 §1.10 (the Tall never-name rule), which A9's old checklist directly contradicted. | + +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 rebuilt role replaced it. Where it conflicts with Part 1, Part 1 governs. Role ids (A1-A10) belong to this legacy roster only. -Every agent on this project knows Brian Douglas's *Systems Engineering Part 3: The Benefits of Functional Architectures* (MathWorks, 2020) cold. +## 1. Shared domain context (story source: Douglas) + +Every agent on this project knows Parts 3 and 4 of Brian Douglas's *Systems Engineering: Managing System Complexity* series (MathWorks MATLAB Tech Talks, 2020) cold. Both parts use a domestic toaster as the worked example; together they establish the engineering ground truth this tutorial re-implements in SysML v2 and Python. + +### Part 3 — The Benefits of Functional Architectures (Oct 15, 2020, 14:24) **Entry model.** Bread (input) → `toast bread` (function) → toast (output). **First decomposition.** Three child functions: load/position bread, apply thermal energy, remove toast. -**Full decomposition.** Approximately 15 verb-noun functions covering: heat conversion, heat transfer, heat regulation, energy conversion, control signals, crumb management, bread handling, sensory feedback, and the interfaces connecting them. +**Full decomposition.** Approximately 15 verb-noun functions covering: heat conversion, heat transfer, heat regulation, energy conversion, control signals, crumb management, bread handling, and sensory feedback. **Function anatomy.** A function has three parts: inputs (material, energy, or signals), the process, and outputs. Functions describe WHAT, not HOW. They are implementation-agnostic. **Auditing completeness.** Functional completeness is auditable: at every decomposition level you must be able to account for every input and every output. Any unaccounted flow is a gap. +### Part 4 — An Introduction to Requirements (Oct 28, 2020, 15:05) + +**Requirement anatomy.** Every requirement has three parts: a description of the need, a rationale for why it is valid, and a verification method. A requirement without all three is incomplete. + +**Verification method types.** Inspection, analysis, test, and demonstration. These four types classify how compliance will be checked and determine what evidence counts as meeting the requirement. + +**Requirement types.** Functional ("shall convert electrical energy to thermal energy"), performance ("capable of up to 100 W conversion"), constraint ("mass less than 5 kg"), environmental, human factors, reliability, safety. The toaster illustrates each type. + +**Requirement hierarchy.** Requirements cascade from stakeholder needs down to components. The toaster examples span from "must fit on a kitchen countertop" (system level) through spring specifications at the component level. Parent requirements decompose into child requirements; every child must be traceable to a parent. + +**Verification vs. validation.** Verification: does the design comply with the requirement? Validation: does the requirement trace to a real stakeholder need? Both are needed. + +**Connection to Part 3.** The functional architecture from Part 3 is the structure requirements attach to. A functional requirement is a claim about a function; a performance requirement quantifies an output flow. + --- ## 2. File authority matrix @@ -33,6 +222,7 @@ Every agent on this project knows Brian Douglas's *Systems Engineering Part 3: T | A1 Orchestrator | READ ONLY | All source files | | A8 ACE | `decisions/log.md`, `.claude/skills/**/*.md` | All source files (chapters, models, tests, CI, docs) | | A9 Simulated Learner | READ ONLY | All source files | +| A10 Systems Architect | Narration markdown cells in `chapters/ch01-*/` and `chapters/ch04-*/` (functional architecture framing); SysML source cells in `chapters/ch02-requirements/` (requirement anatomy); SysML source cells in `chapters/ch03-measures/04-verification-case.ipynb`; `models/ch02-cumulative.sysml`, `models/ch03-cumulative.sysml` | Calculation defs, action defs, state machines, test files, CI config, `docs/` pages | --- @@ -47,6 +237,18 @@ Every agent on this project knows Brian Douglas's *Systems Engineering Part 3: T --- +## 3b. A10 Systems Architect + +**Purpose:** Authors functional architecture narrative (verb-noun convention throughout), requirement definitions with full 3-part anatomy, and verification case specifications. Makes validation judgments over behavioral requirements. + +**Layer framing:** the three-layer framing (what, how, where; prescriptions versus derived results) is defined in Part 1, section 1.5. A10 narrates the layers in that order and uses verb-noun convention for functions. The earlier wording here, which defined the logical layer as partitioning plus interfaces, is superseded (DL-015; the verbatim old text is recorded there). + +**Skills loaded:** `sysml-v2-toaster-model`, `toaster-recipe`, `tutorial-style-guide`, `toaster-review-protocol` + +**Coordination:** When A4 writes functional architecture narration (Ch1, Ch4), A10 reviews for verb-noun compliance and functional-first framing before A6 didactic review. A10 does not write physical architecture narrative. + +**What A10 must never do:** Invent verification method kinds not in the SysML v2 spec; write `verify X` where X is a requirement def (it must be a usage); narrate physical implementation choices as functional requirements. + ## 4. Escalation chain A1 routes to A8. A8 handles, escalates to Z, or returns to A1. A1 never contacts Z directly. diff --git a/CLAUDE.md b/CLAUDE.md index 22e7882..2b8fac9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,18 +1,44 @@ -Read AGENTS.md first — it defines the file authority matrix, editing rules, -and escalation triggers. The short version: each agent role edits only its -assigned files, changes are committed after each logical unit, and reviewers -produce reports rather than edits. +Read AGENTS.md first. Part 1 (Foundations) says what this tutorial teaches, which +sources define its terms, how the functional, logical and physical layers differ, +and how models are built and queried. Part 2 is the legacy roster and file +authority matrix (each role edits only its assigned files; changes are committed +after each logical unit; reviewers produce reports rather than edits). Where they +conflict, Part 1 governs. + +Read order for a cold session: AGENTS.md Part 1, then the skills below that your +task touches (start with `architecture-layers` and `ace-protocol`), then the +glossary CLI for any term you are about to define or use: + + uv run python -m glossary lookup TERM # every definition, by source, with locators + uv run python -m glossary tutorial TERM # the idea, formal semantics and story we use + uv run python -m glossary check # must pass before you commit glossary or gloss changes + +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. + +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`. Skills (.claude/skills/ directory): +- architecture-layers — what / how / where boundary tests, spec idioms, per-layer audit checklist, source map +- opensysml-query — the three query surfaces, tested recipes, what does not work and the workarounds +- tutorial-glossary — using and extending the glossary knowledge graph - opensysml-api — opensysml v0.9.0 interface (A2, A5) - sysml-v2-toaster-model — SysML v2 subset + model conventions (A3) -- toaster-recipe — sub-notebook template + Tall three worlds (A4, A6) +- toaster-recipe — sub-notebook template (A4, A6) - toaster-review-protocol — Hawkins judgment record fields (A3) - sysml-diagrams — diagram pipelines + quality gates; DOT/SysMLD preferred (A2, A7) - orchestrator-protocol — WBS contract, loop rules, escalation triggers (A1) -- ace-protocol — decision framework, Z's patterns, brief format (A8) +- ace-protocol — the ACE: triage layer between the team and Z; decision framework, Z's patterns, brief format (A8) - myst-publication — CI pipeline, MyST config, GitHub Pages (A2) - tutorial-supporting-pages — docs/ pages structure and authoring rules (A4) - skill-editor — pre-edit gate, minimal-change rule, revert protocol (A8 only) - tutorial-style-guide — prose style, diagram aesthetics, code style (A3, A4, A6, A7) - user-testing — simulated learner protocol, personas, report format, ACE synthesis (A8, A9) + +Skills not yet updated for the Foundations (toaster-recipe, sysml-v2-toaster-model, +tutorial-style-guide, sysml-diagrams, orchestrator-protocol, user-testing) may +still carry the earlier framing; see decisions/next-passes.md. When a skill and +AGENTS.md Part 1 disagree, Part 1 governs. diff --git a/DEFERRED.md b/DEFERRED.md index 35265e0..ee55b4d 100644 --- a/DEFERRED.md +++ b/DEFERRED.md @@ -13,6 +13,8 @@ Notebook cells call that function; the workaround does not appear in notebook co **Upstream issue:** Open-MBEE/OpenSysML#590 **Toaster issue:** Open-MBEE/toaster#1 +**Update (Pass 1, 2026-09-26):** `get_satisfy_relationships()` now reads `.content` and is tested (`tests/test_query.py`); the wider visibility gap, including unnamed connectors and metadata, is D-015. + ## D-002: Custom theme / CSS for site SA-5 sets default book-theme, no custom CSS, for the first release. @@ -21,3 +23,870 @@ maintenance burden unrelated to learning outcomes in v0.1. **Resolution:** After v0.1 ships, design a MyST theme extension or custom CSS override. **Toaster issue:** Open-MBEE/toaster#2 + +## D-003: efficiency typed via MeasurementReferences::DimensionOneValue, not ISQ::DimensionOneValue + +`ISQ::DimensionOneValue` is not defined in opensysml v0.9.0 (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. + +The comment `// D-003` on the import line in each model file marks the workaround sites. + +**Resolution:** When `ISQ::DimensionOneValue` and `SI::one` ship in opensysml: +- Remove `private import MeasurementReferences::*;` from ch03–ch08 models. +- Change `in efficiency : DimensionOneValue;` → `in efficiency : ISQ::DimensionOneValue;`. +- Update `sysml-v2-toaster-model` skill ISQ section. +**Upstream issue:** Open-MBEE/OpenSysML#594 +**Toaster issue:** Open-MBEE/toaster#8 + +## D-004: Editor API does not support `abstract part def` authoring + +`Editor.add_member()` has no way to set the `abstract` modifier on a newly created `PartDefinition`. +The construct is defined in the SysML v2 spec and accepted by the parser; the gap is in the +gRPC authoring allowlist only. Affects Ch1/nb01. + +**Workaround:** Load `abstract part def` via `conn.load_from_content(source, strict=False)`. +**Resolution:** Add `abstract` modifier support to `Editor.add_part_def()` or `add_member()`. +**Upstream issue:** Open-MBEE/OpenSysML#595 +**Toaster issue:** Open-MBEE/toaster#9 + +## D-005: Editor API does not support anonymous attribute redefinition (`:>>`) + +`Editor.add_member()` cannot produce an anonymous `:>>` attribute redefinition. +The construct is defined in KerML spec §8.3.7 and accepted by the parser; the gap is in the +gRPC authoring allowlist. `attribute :>> cycleTime = 200.0 [SI::s]` must be loaded as notation. +Affects Ch2/nb02. + +**Workaround:** Load `:>>` redefinitions via `conn.load_from_content(source, strict=False)`. +**Resolution:** Add anonymous redefinition path to the authoring API. +**Upstream issue:** Open-MBEE/OpenSysML#596 +**Toaster issue:** Open-MBEE/toaster#10 + +## D-006: Editor API does not support `require constraint` (RequirementConstraintMembership) + +`Editor.add_member()` rejects `"require constraint"` as an illegal kind. +The construct is defined in SysML v2 spec formal/2026-03-02 §7.19 and accepted by the parser. +Affects Ch2/nb01. + +**Workaround:** Load requirement defs including constraint bodies via `conn.load_from_content()`. +**Resolution:** Add `"require constraint"` or `add_require_constraint()` to the authoring API. +**Upstream issue:** Open-MBEE/OpenSysML#597 +**Toaster issue:** Open-MBEE/toaster#11 + +## D-007: Editor API does not support `assert satisfy` (SatisfyRequirementUsage authoring) + +`Editor.add_member()` rejects `"assert satisfy"` as an illegal kind. +The construct is defined in SysML v2 spec formal/2026-03-02 §7.19 and accepted by the parser. +Note: combined with D-001, this construct has two distinct gaps: it cannot be added via authoring, +and it is not correctly returned by the OMG API query endpoint. Affects Ch3/nb01. + +**Workaround:** Load `assert satisfy` declarations via `conn.load_from_content()`. +**Resolution:** Add `"satisfy"` / `"assert satisfy"` to the authoring allowlist. +**Upstream issue:** Open-MBEE/OpenSysML#598 +**Toaster issue:** Open-MBEE/toaster#12 + +## D-008: Editor API does not support `allocate` (AllocationUsage authoring) + +`Editor.add_member()` rejects `"allocate"` as an illegal kind. +The construct is defined in SysML v2 spec formal/2026-03-02 §7.21 and accepted by the parser. +`generate.py` explicitly lists `allocation` as a "known skipped behavioral kind". +Affects Ch5/nb02. + +**Workaround:** Load `allocate X to Y` declarations via `conn.load_from_content()`. +**Resolution:** Add `"allocate"` to the authoring allowlist and `add_allocate()` helper. +**Upstream issue:** Open-MBEE/OpenSysML#599 +**Toaster issue:** Open-MBEE/toaster#13 + +## D-009: Editor API does not support `flow` (ConnectionUsage / flow connection authoring) + +`Editor.add_member()` rejects `"flow"` as an illegal kind (probed 2026-09-25). +`flow X.port to Y.port` connections are defined in SysML v2 spec formal/2026-03-02 §7.20 +and accepted by the parser. Affects Ch5/nb03. + +Note: `editor.add_member()` signature does not include `source`/`target` parameters either; +passing them raises a `TypeError`. The kind guard fires first when using just `kind="flow"`. + +**Workaround:** Load flow connection declarations via `conn.load_from_content()`. +**Resolution:** Add `"flow"` / `"flow connection"` to the authoring allowlist and `add_flow()` helper. +**Upstream issue:** Open-MBEE/OpenSysML#601 +**Toaster issue:** Open-MBEE/toaster#14 + +## D-010: Editor API does not support `state usage` or `transition usage` authoring + +`Editor.add_member()` rejects `"state usage"` and `"transition"` as illegal kinds (probed 2026-09-25). +A bare `state def Cycle;` can be added via `editor.add_member(kind='state def', name='Cycle')`, +but adding sub-states (state usages) and transition usages (with accept/then) is not supported. +Full state machines with sub-states and transitions require Pattern B. Affects Ch7/nb02. + +**Workaround:** Load the full state machine declaration via `conn.load_from_content()`. +**Resolution:** Add `"state usage"` and `"transition"` to the authoring allowlist. +**Upstream issue:** Open-MBEE/OpenSysML#602 +**Toaster issue:** Open-MBEE/toaster#15 + +## D-011: Editor API `add_attribute` produces fixed binding, not `default =` modifier + +`editor.add_attribute(owner, name, type=..., value=...)` produces `attribute x : T = v` +(a fixed binding that cannot be overridden) instead of `attribute x : T default = v` +(a default value that can be overridden with `:>>`). The `default` keyword is defined in +KerML formal/2026-03-02 §8.4.1 (FeatureValue) and accepted by the parser. Affects Ch1/nb02 +and all notebooks that introduce attributes with default values. + +**Workaround:** Write `attribute x : T default = v;` as a SysML string fragment and load via +`conn.load_from_content()`. +**Resolution:** Add a `default` boolean parameter to `add_attribute()` so that `default=True` +produces the `default =` form. +**Spec:** KerML formal/2026-03-02 §8.4.1 — FeatureValue (default keyword) +**Upstream issue:** Open-MBEE/OpenSysML#603 +**Toaster issue:** Open-MBEE/toaster#16 + +## D-012: Editor API `add_calc_def` produces bare declaration only (no inputs, no return expression) + +`editor.add_calc_def(owner, name)` produces `calc def X;` with no `in` parameters and no +`return` expression. The `add_member` kwargs (`type`, `multiplicity`, `value`, `specializes`) +do not map onto calc def body constructs; passing unsupported kwargs raises `TypeError`. +A bare calc def cannot be evaluated with `model.eval()`. Affects Ch3/nb02. + +**Workaround:** Write the full calc def body as a SysML string fragment and load via +`conn.load_from_content()`. +**Resolution:** Add `inputs` (list of `(name, type)` tuples) and `return_expression` parameters +to `add_calc_def()`. +**Spec:** SysML v2 formal/2026-03-02 §7.16 — CalculationDefinition, CalcDefBodyPart +**Upstream issue:** Open-MBEE/OpenSysML#604 +**Toaster issue:** Open-MBEE/toaster#17 + +## D-013: Editor API `add_action_def` produces bare declaration only (no params, sequencing, or nested actions) + +`editor.add_member(kind='action def', name=...)` produces `action def X;` with no `in`/`out` +parameters, no `first`/`then` sequencing, and no nested `action` usages. Passing unsupported +kwargs raises `TypeError`. Both action def body constructs and succession usages are spec-defined. +Affects Ch4/nb01. + +**Workaround:** Write the full action def body as a SysML string fragment and load via +`conn.load_from_content()`. +**Resolution:** Add typed helpers `add_action_def()` / `add_action()` (or extend `add_member`) +with support for `in`/`out` parameters, nested action usages, and `first`/`then` sequencing. +**Spec:** SysML v2 formal/2026-03-02 §7.15 (ActionDefinition), §7.20 (SuccessionAsUsage) +**Upstream issue:** Open-MBEE/OpenSysML#605 +**Toaster issue:** Open-MBEE/toaster#18 + +## D-004: VerificationMethodKind metadata not supported + +The spec-defined way to annotate the method kind of a verification case is: +```sysml +#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`. + +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`). + +**Resolution:** When upstream adds metadata parsing, replace the doc comment workaround +with the formal `#verificationMethod` annotation and remove the gap comment. +**Spec:** SysML v2 formal/2026-03-02 §7.24 Table 22 (Verification Methods Compartment) +**Upstream issue:** Open-MBEE/OpenSysML#608 +**Toaster issue:** Open-MBEE/toaster#19 + +## 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 +`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 +is treated as a **staged project conformance check** (AGENTS.md 1.9), not as a language-conformance bug. +Probe record: `decisions/probes.md`. Affects the interface chapters (the chapter that first declares a connection). + +**Workaround:** `toaster.query.port_type_mismatches(model)` (tested; recipe 5 in `opensysml-query`), applied from the +chapter and section where the connection is declared complete, with a negative control; reported open before then. +**Resolution:** SysML 7.12.1 defines when connected ports *conform* but no rule found requires a tool to reject a non-conforming connection; file only a feature request (see `decisions/gap-issue-drafts.md`). +**Upstream issue:** none; Z ruled this an internal clarification, no issue to file (2026-09-26) +**Toaster issue:** none + +## 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()`; +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, +7.2.2). The repository workaround is one module, `src/toaster/query.py` (`ApiIndex`), tested in `tests/test_query.py`. +Convention adopted: name allocations, connections and flows in the model. + +Related: OpenSysML#590 (closed 2026-09-26). A maintainer said anonymous elements (satisfy, connect, bind, allocate) are currently skipped and that a fix would ship in the next nightly (`nightly-20260926`). Not verified against the nightly; v0.9.0 still shows the behavior. No new issue is to be filed for this (draft 1 held). + +**Workaround:** `toaster.query` helpers; JSON route for satisfy, metadata and unnamed connectors. +**Resolution:** When `model.query()` exposes all elements, change `ApiIndex` only. +**Upstream issue:** filed 2026-09-27, [OpenSysML#643](https://github.com/Open-MBEE/OpenSysML/issues/643) +**Toaster issue:** not filed + +## D-016: Editor API does not support `perform action` authoring (gap G5) + +`Editor.add_member()` has no kind for `perform action x : ActionDef` (SysML v2 formal/2026-03-02 7.17.6), the +construct that records which logical component is responsible for a function. Loading it as notation works. +Sibling of D-008 (`allocate`), D-009 (`flow`) and D-010 (state). + +**Workaround:** Load `perform action` declarations via `conn.load_from_content(source, strict=False)` (Pattern B). +**Resolution:** Add `"perform"` support to the authoring allowlist. Re-confirmed 2026-09-27 against the current Editor: `add_member(kind="perform action", ...)` only queues the operation; `apply()` raises `IllegalMemberKindError`. +**Upstream issue:** filed 2026-09-27, [OpenSysML#644](https://github.com/Open-MBEE/OpenSysML/issues/644) +**Toaster issue:** not filed + +## D-017: `import` across separately loaded sources does not resolve in OpenSysML (gap G7) + +A chapter source that imports an implicit part's package fails with `unresolved reference`, both through +`conn.load_from_content` and `conn.load(path)` from the same directory; concatenating the sources into one load +works. sysml-toolkit v0.9.1 resolves the same imports across files (`sysmlv2 check base.sysml chapter.sysml`, +`Session.from_files`), so the capability exists elsewhere in the ecosystem. Needed for explicit and implicit +construction (AGENTS.md 1.7). + +**Workaround:** assemble by concatenation: join the SysML text yielded by the implicit modules and the chapter's +explicit increment into one string and load once. Concatenation loses which source an element came from, so give +implicit parts their own package (or a metadata marker) to keep provenance queryable. +**Resolution:** Check the spec's package-import and the API's project and commit model for the multi-resource +resolution it requires; file only what the spec requires. Re-test when OpenSysML changes. +**Upstream issue:** filed 2026-09-27, [OpenSysML#645](https://github.com/Open-MBEE/OpenSysML/issues/645) (filed as a question about intended multi-resource loading, not a bug claim — the spec requirement was never established) +**Toaster issue:** not filed + +## D-018: sysml-toolkit summary mode is not reachable from the CLI or Python (v0.9.1) + +The v0.9.1 changelog adds summary mode for large tree graphs (collapsed containers with hidden counts, member and note +limits). It is `VizOptions::summary` in the Rust `sysmlv2-viz` crate and the WebAssembly controls only; +`sysmlv2 viz` and `Session.to_plantuml` have no such option (probed 2026-09-26, `decisions/probes.md`). Collapsing +implicit parts in notebook diagrams is therefore not available through the toolkit's CLI or Python API. + +**Workaround:** choose the `element` root, the view and the filtered model slice per figure (AGENTS.md 1.7). +**Resolution:** Re-check after the next toolkit release, or request a CLI and Python option. +**Upstream issue:** filed 2026-09-27, [sysml-toolkit#5](https://github.com/Open-MBEE/sysml-toolkit/issues/5) +**Toaster issue:** not filed + +## 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. + +**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`. +**Upstream issue:** filed 2026-09-27, [OpenSysML#646](https://github.com/Open-MBEE/OpenSysML/issues/646) (cites and distinguishes from [OpenSysML#95](https://github.com/Open-MBEE/OpenSysML/issues/95) per the re-verification above) +**Toaster issue:** not filed + +## 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. + +**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. +**Upstream issue:** filed 2026-09-27, [OpenSysML#647](https://github.com/Open-MBEE/OpenSysML/issues/647) and [sysml-toolkit#6](https://github.com/Open-MBEE/sysml-toolkit/issues/6) +**Toaster issue:** not filed + +## D-021: A false `assert satisfy` is accepted + +`assert satisfy timely by slow` loads with no diagnostic while the constraint evaluates False (`slow.cycleTime` 200 s against 180 s), and the same holds for `weak` in Ch6 to Ch8 (400 W against 600 W). This is not language conformance (parse, name resolution and typing pass): it is a staged project check, "satisfaction claims evaluated" (DL-039). `assert not satisfy` parses (`isNegated: true`) and can express a deliberate failing branch. Found by the Ch3 audit (F-3), confirmed and extended by a spot review. + +**Workaround:** none yet; a staged check with `slow` as its negative control is to be built. +**Resolution:** none upstream is expected (a semantic check); the tutorial owns it. +**Upstream issue:** none +**Toaster issue:** not filed + +## D-022: `scripts/check_construction.py --check` now correctly fails on ch04 (predecessor-containment check, PASS2-010) + +`scripts/check_construction.py` gained a predecessor-containment check (`check_predecessor_containment`, wired into `check_chapter`): for each `chNN-cumulative.sysml` where N > 1, every NAMED element present in `ch(N-1)-cumulative.sysml` (by qualified name and `@type`, via `toaster.query.ApiIndex` over the API-JSON export) must still be present in `chNN-cumulative.sysml`. Identity is restricted to NAMED elements (`_named_elements`): an unnamed member such as a `doc` has no stable qualified name to compare across two separately-edited fixtures, so it is out of scope for this check by design — a known, separate blind spot, not an oversight. It correctly reports ch04 as a failure: `models/ch04-cumulative.sysml` silently drops `ToasterDemo::TimelyToastTest` (the whole `VerificationCaseDefinition`, including its `toaster` subject reference — both NAMED) that is present in `models/ch03-cumulative.sysml` — the pre-existing defect recorded as F-5 in `decisions/audits/ch04-layer-audit.md`. The same F-5 finding's drop of `TimelyToast`'s doc/rationale is UNNAMED and is not, and cannot be, caught by this check. `scripts/check_construction.py --check` (default, no `--chapter`) now exits 1 where it previously exited 0, because this new check surfaces a real, already-existing fixture defect the old checks (fragment parses, cumulative loads) could not see. This is the correct, desired output of the new check, not a regression in the check or a fixture change: no model file was edited to make it pass. Confirmed clean for the other adjacent pairs the checker can reach through `check_chapter`'s default chapter set (ch01->ch02, ch02->ch03, ch04->ch05, ch06->ch07 — ch07 is itself a key in `CONSTRUCTION_NOTEBOOKS`, so ch06->ch07 already runs under the bare default, not only via `--chapter`) and via `--chapter=N` for the two pairs the bare default cannot reach, because chapters 6 and 8 are not keys in `CONSTRUCTION_NOTEBOOKS` (ch05->ch06, ch07->ch08) — see `tests/test_predecessor_containment.py`. + +**Workaround:** none; this is not a tool gap, it is the check doing its job. Pass 4 (per the contract; see `decisions/pass4-backlog.md`) is expected to fix `models/ch04-cumulative.sysml` (restore `TimelyToast`'s rationale `doc` and `TimelyToastTest`) so the check passes again — do not silence or work around the failure before then. +**Resolution:** fix `models/ch04-cumulative.sysml` in a later pass; re-run `scripts/check_construction.py --check --chapter=4`. +**Upstream issue:** none (not a tool gap) +**Toaster issue:** not filed + +## D-023: OpenSysML does not resolve state-machine transition trigger names + +`accept ` in a transition usage is kept in the API-JSON export only as a string (`sysx:trigger`), never resolved against a declared element. A reference to an undefined name, or a typo of a defined name, loads with `ok=True` and no diagnostic; a typo'd trigger silently never fires at execution. sysml-toolkit v0.9.1 does resolve these names and warns on broken references. Found by the Ch7 audit (`decisions/audits/ch07-layer-audit.md` F-4), confirmed by independent spot review (four sub-claims reproduced on scratch models, plus confirmed the real Ch7 fixture's own triggers all resolve correctly today). A guard now exists (PASS4-000-B): `unresolved-transition-trigger` in `GAP_RULES`, `src/toaster/conformance.py` (`_unresolved_transition_trigger`), picked up automatically by `language_gap_findings`. + +It flags an `accept` trigger (`sysx:triggerKeyword == "accept"`) whose payload name resolves to no declared element in scope. `sysx:trigger` is not always a plain name: it also covers a qualified name (`Outer::Start`), a named payload (`s : Start`, resolved by the part after the colon; `s :> sig`, a subsetting payload, resolved the same way), a time trigger (`accept after ` or `accept at `) and a change trigger (`accept when `) — the latter two are expressions, not names, and are skipped rather than flagged, as is an untriggered (unconditional) transition. The payload type can be any kind with a `declaredName` (an `item def`, `part def`, `port def`, `attribute def`, `enum def`, or an existing usage referenced by name), not only an `item def` — this also means an unqualified match is not restricted to type-like kinds at all (an unqualified trigger that happens to spell a state's own name would resolve too; an intentional, reviewed leniency, not a functional gap). + +Scope (corrected in a second review round; the first round's same-package-only ruling was wrong, verified empirically before ruling again, not assumed: the real ch07 fixture itself wildcard-imports four external packages, so that scoping would have silently stopped checking exactly the fixture this guard exists to protect): + +An unqualified name is resolved in three steps, in this order: + +1. **Exact match.** Resolved against the declared name of *any* element anywhere in the loaded model, full stop, with no package or import modeling at all. This correctly handles a same-document, different-package reference through a wildcard or member import, and a nested/outer-package reference. +2. **Similarity match (typo detection).** If step 1 finds nothing, `difflib.get_close_matches` (Python stdlib, no new dependency) is tried against every declared name in the model, at a cutoff of 0.8. A close match is **flagged** as a plausible typo of a real local name — this is D-023's own headline case: `accept Strat` for a locally-declared `Start` (ratio 0.8) is exactly this. +3. **External-import fallback.** Only if steps 1 and 2 both find nothing does an unresolvable import in the document matter: if present, the name might be a member of it and is skipped, not flagged (the export gives no reliable, formatting-independent way to name an unresolved import's target to check further). With no such import either, the name is flagged. + +**Round 3 fix, replacing an earlier round-2 mistake:** round 2's version skipped an unresolved unqualified name whenever the document had *any* unresolvable import, with no similarity check — i.e., step 3 with no step 2 in between. The reviewer found this defeats the rule's entire purpose: every real chapter model imports at least one external library (`ScalarValues`, `SI`, `ISQ`, `MeasurementReferences`), so that blanket rule silently skipped unqualified-name checking in every real chapter, including a plain `accept Strat` typo of a locally-declared `Start` — confirmed directly: replacing `Start` with `Strat` in the real ch07 and ch08 fixtures gave **zero** findings under round 2's rule. Step 2 (added this round) fixes it: a name that closely resembles something declared right here is flagged before the import fallback is even consulted, regardless of what else the document imports. This is now a permanent regression test (`test_unresolved_transition_trigger_real_fixture_typo_is_flagged`, parametrized over ch07 and ch08, using the real fixture files with the same one-word substitution) — it is what caught the bug and must keep catching a regression of it. A second dedicated test (`test_unresolved_transition_trigger_local_typo_still_flagged_with_unrelated_import`) pins the exact combination that broke: a genuine local typo, in a document that also has an unrelated external import. + +The 0.8 similarity cutoff was chosen empirically against the real ch07 fixture's own 48 declared names, not picked arbitrarily: every typo form tried (`Strat`/`Start` 0.800, `Cancle`/`Cancel` 0.833, `Finsh`/`Finish` 0.909, and others) scores at or above 0.8, while the closest of 22 plausible standard-library member names tried (`Boolean`, `Vector`, `PowerValue`, ...) against that same vocabulary is `Vector` vs. the locally-declared part `ejector` at 0.769 — below 0.8, so it correctly falls through to the import-fallback step rather than being wrongly flagged. This is a fit to one real fixture's vocabulary, not a proof for all possible names; a future chapter could in principle need the cutoff re-tuned if its own vocabulary produces a false match near this boundary. + +A qualified name is unaffected by this round's change: it is resolved by an exact match against every element's qualified name anywhere in the model, or a suffix match (so a legitimate relative qualification, e.g. `Inner::Start` when the full path is `P::Inner::Start`, also resolves). If neither matches, the qualified name's own top-level segment decides whether it is judged at all: a segment that names a real local package (any nesting depth) means the reference is judged and, if still unmatched, flagged as genuinely broken; a segment that does not name a local package but is a recognized standard-library package name (`ScalarValues`, `SI`, `ISQ`, `MeasurementReferences`, `Time` — the ones this repo's own models import, plus `Time`; not exhaustive of the OMG library) is treated as a probable external reference the rule cannot verify — skipped, not flagged. A segment matching neither is flagged: nothing backs reading it as external. + +The round-2 final ruling on a genuine no-import cross-package reference stands unchanged this round, and is unaffected by the step-2/step-3 fix above: it is resolved at step 1, the same flat, package-blind exact match that resolves the legitimate with-import case, since step 1 never checks for an import at all. An unqualified name declared only in a different package, with no import at all bringing it into scope, is **not flagged** — a deliberate, accepted false negative (a real resolver would need the missing import for the reference to actually be legitimate; this guard cannot tell the two cases apart), disproportionate to fix with real import-graph resolution for a defect absent from every real fixture (`test_unresolved_transition_trigger_no_import_cross_package_not_flagged` still pins this down). + +**Workaround:** `unresolved-transition-trigger` (see above) — now added to `language_gap_findings` in `src/toaster/conformance.py`. +**Resolution:** upstream fix (resolve triggers like `perform`/`allocate` targets are resolved); or a tutorial-supplied guard per DL-039's pattern. +**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. + +## 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. + +**Superseded by D-025** (below): Z decided the subprocess call is acceptable, wrapped in a utility function with an intent-to-deprecate record. + +## D-025: `toaster.modelcheck` wraps `sysmlv2 verify --solve` via subprocess, intended for deprecation + +sysml-toolkit's Python binding (`sysmlv2.Session`) has no `verify`/`solve` method (checked directly: not in `dir(Session)`); only the Rust CLI (`sysmlv2 verify --solve`) proves a constraint holds for all values of an unbound feature via Z3. `verify` also has no `--format json` (unlike `check`/`lint`), so the wrapper parses the CLI's stable text output. Per Z's ruling (2026-09-27, decisions/log.md DL-046): the subprocess call is accepted, wrapped in `src/toaster/modelcheck.py` so a chapter notebook sees only a clean Python function, never a shell-out, following the repo's standard gap-tracking pattern (patch, document, intend to delete once upstream supports it natively). + +**Workaround:** `toaster.modelcheck.verify_holds(...)` shells out to the `sysmlv2` binary and parses its text output into a `Verdict`-shaped result. +**Resolution:** delete the wrapper and call a Python method directly once EITHER (a) sysml-toolkit's Python binding gains a `verify`/`solve` method, or (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). **Watch specifically:** the PyPI name `sysmlv2` is already reserved by sysml-toolkit's own maintaining organization (confirmed 2026-09-27), currently holding a placeholder release, not the real package. Once that placeholder is replaced with the actual binding, check it for a `verify`/`solve` method first, before checking anywhere else. +**Provenance:** the `sysmlv2` binary this wrapper calls was built locally from `Open-MBEE/sysml-toolkit` commit `af839f0d22723772676e509213c65756d1e08ef2` (one commit past the tagged `v0.9.1` release, 2026-09-20). A learner following `docs/setup.md` instead downloads the current tagged release's pre-built binary, not this exact commit; the two have not been diffed against each other. +**Upstream issue:** not filed; not blocking (the workaround is sufficient and intended to be short-lived, not a missing-capability report) +**Toaster issue:** not filed +**CI note (PASS2-012 F7):** `tests/test_modelcheck.py` is skipped in CI — the `sysmlv2` binary is a local build artifact (`~/Documents/GitHub/sysml-toolkit/target/release/sysmlv2`), not something CI builds or installs, so the whole file is guarded by a `pytest.mark.skipif` on the binary's presence rather than run there. + +## 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 + +**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 +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 +the second keeps the model evaluable. + +Found building Chapter 4's own re-derivation (PASS4-004), corrected across five +rounds of independent review re-probing (Opus 5.5): nesting `ApplyHeat` as an actual +step of `ToastBread` (`action def ApplyHeat { in bread : Bread; in energy : +ISQ::EnergyValue; in duration : ISQ::DurationValue; ... }`, kept as typed, valueless +functional input slots per DL-030/DL-031's rulings) made `model.eval()` fail on +*any* attribute of a `Toaster` part usage that transitively owns or performs that +action graph, not only on expressions that touch `ApplyHeat` itself. +`ToasterDemo::slow.cycleTime` (a directly-overridden literal, `200.0 [SI::s]`, with +no relation to `ApplyHeat`, `energy` or `duration` at all) raised `unbound +parameter: action ApplyHeat: input parameter energy is bound by no argument` (and +`bread`, before it was bound to `ToastBread::bread` per Q2's ruling), and so did +`ToasterDemo::timely(ToasterDemo::slow)` (the expression +`src/toaster/conformance.py::satisfaction_claims_evaluated` and Chapter 3's own +notebooks both use). + +**The precise trigger, isolated in two separate experiments.** + +*Experiment 1 (which construction reaches the parameter at all).* Six variants, +each varying only how `ApplyHeat` is referenced, all tested on the same single +parameter (`in bread : Bread;`, left as declared, no multiplicity written), against +the same minimal model (`Toaster :> ToastingSystem { perform action toastBread : +ToastBread { action applyHeat : ; } }`, `slow.cycleTime` evaluated): + +| Variant | Result | +|---|---| +| `action def ApplyHeat { out toast : Toast; ... }` (only `out` parameters, no `in` at all) | evaluates cleanly | +| `action applyHeat : ApplyHeat;` (sequenced with `first`/`then`) | fails | +| `action applyHeat : ApplyHeat;` (bare ownership, no succession, no `perform`) | fails identically | +| `ref action applyHeat : ApplyHeat;` | fails | +| `abstract action def ApplyHeat { ... }` | fails | +| `action applyHeat : ApplyHeat[0..*];` (multiplicity on the *usage*, not the parameter) | fails | + +So the trigger is not "any owned action" (only-`out` is fine), not the +`perform`/succession machinery (`ref action`, bare ownership and `perform action` +all fail the same way), and not merely "any reference to a separate definition" +(the only-`out` variant is such a reference too, and it is fine). It is specifically +an unbound `in` parameter, reached through a nested step, that matters — which +motivates Experiment 2. + +*Experiment 2 (what about the parameter's declared multiplicity matters).* With the +nesting held fixed at the failing shape above, only `energy`'s declared multiplicity +was varied, one value at a time, each tested in isolation (no other unresolved `in` +parameter present in that run): + +| Declared multiplicity on `energy` | Result | +|---|---| +| none written (the bare, implicit form) | fails | +| `[1..1]` (explicit) | fails | +| `[1]` | fails | +| `[1..*]` | fails | +| `[2..*]` | fails | +| `[0..*]` (explicit, spec-identical to the implicit default — see below) | evaluates cleanly | +| `[0..1]` | evaluates cleanly | +| `[0..2]` | evaluates cleanly | +| `[*]` | evaluates cleanly | + +**The mechanism this evidence actually supports:** OpenSysML 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 +kind keyword, which a bare `in` parameter is not (§7.6.4: "a reference usage is a +usage that is declared without any kind keyword"). This matches the tool's own +`model.find(...).kind` reporting `attributeUsage` for these parameters even though +the API-JSON export types them `ReferenceUsage` (a second, smaller inconsistency, +kept below as corroborating evidence). Having applied that wrong `[1..1]`-shaped +default, the tool then raises whenever a nested step's parameter has an effective +lower bound of 1 or more and is left unbound — which is why every multiplicity with +lower bound ≥ 1 (bare, `[1..1]`, `[1]`, `[1..*]`, `[2..*]`) fails identically, and +every multiplicity with lower bound 0 (`[0..*]`, `[0..1]`, `[0..2]`, `[*]`) +evaluates cleanly. This is a coherent, if wrong, rule — not, as an earlier draft of +this entry claimed, a tool that "keys on whether a multiplicity token is present in +the text" regardless of what it means: that reading is contradicted by explicit +`[1..1]` failing exactly like the bare form. + +The spec's own default for `bread`/`energy`/`duration`, none of which carries a kind +keyword, is the general, unbounded `[0..*]` (KerML 1.1 Beta 2 agrees, calling this +"the usual default"), not the `[1..1]` the tool applies. Writing `[0..*]` out +explicitly states nothing the bare declaration did not already mean per §7.6.3/ +§7.6.4 — it is spec-identical to the implicit default — and it evaluates cleanly. +That is the headline finding: an implicit and an explicit-but-spec-identical +declaration should behave the same under any coherent reading of the spec, and in +this tool they do not. + +**Reproducing the applied fix precisely.** `ApplyHeat` as built has *three* +unbound-by-default `in` parameters (`bread`, `energy`, `duration`), not one — a +reader who changes only one of them (say, `energy`'s multiplicity) on the full, +real `ApplyHeat` and expects `slow.cycleTime` to evaluate will still see the +failure, now naming whichever of the other two parameters is still unresolved +(`bread`, then `duration`, in declaration order). This is expected, not a +contradiction of Experiment 2 above (which isolates one parameter at a time in a +model with no other unresolved `in` parameter) or of the applied fix (which +resolves all three: `bread` by reference-binding to `ToastBread::bread`, per Q2's +ruling, and `energy`/`duration` by explicit `[0..*]`). All three must be resolved, +by whichever means, before the model is fully evaluable again. + +**Internal inconsistency (further evidence this is a tool defect, not a +deliberate rule):** `ToastBread`'s own top-level `in bread : Bread;` has the +identical shape — no declared multiplicity, unbound — and the tool tolerates it +fine: `slow.cycleTime` evaluates cleanly when `ToastBread`'s body is `first start; +then done;` with no nested reference to a separate action definition at all. Only +the *nested* case — one level deeper, where the unbound parameter belongs to a +definition reached through another action usage rather than being the directly +performed action's own parameter — triggers the failure. Per KerML 1.1 Beta 2 +§9.2.8.2.6 (`FeatureReadEvaluation`), a feature read's result is scoped to "the +values of `accessedFeature` of `onOccurrence`" — nothing in the read semantics +singles out a *nested* unbound feature for different treatment than a top-level +one, so this asymmetry is not something this citation explains. + +A second, smaller inconsistency corroborates the first: the tool's own account of +what kind of feature these parameters are does not agree with itself. +`model.find("ToasterDemo::ApplyHeat::bread").kind` (and the same for `energy`, +`duration`) reports `attributeUsage`, while the same feature's API-JSON export +types it `ReferenceUsage` (confirmed directly, both checked against the real +fixture). Whichever is correct, the tool's two own surfaces for asking "what kind +of feature is this" disagree with each other, on the very parameters this gap is +about. + +`[1..1]` (which would be the spec-accurate way to state that +`bread`/`energy`/`duration` mean exactly one value, not yet known, rather than +`[0..*]`'s "any number, including none") fails identically to the bare form, per +Experiment 2 above — expected under the mechanism this entry now gives, since +`[1..1]` has lower bound 1, same as the tool's own wrong default. Whether to write +`[1..1]` everywhere it is spec-accurate across the tutorial, trading the tool's +current bug (which the model does not need to work around, since `[0..*]` already +does) for stating each parameter's true intended cardinality, is a broader, separate +question than this gap, spanning every chapter's action and calc parameters, not +just Chapter 4's; logged separately (`decisions/next-passes.md` item 11). + +**`[0..1]` is a real technical workaround, considered and rejected; `[0..*]` is the +applied fix.** `[0..1]` does avoid the failure (confirmed above), but it is **not +used**: it narrows the multiplicity below the spec's own `[0..*]` default and +asserts `bread`/`energy`/`duration` are genuinely optional (zero-or-one) inputs to +`ApplyHeat`, neither of which is true — the action needs all three to mean +anything; they are simply not yet bound to a value at this stage of decomposition. +`[0..*]`, by contrast, is not a rejected workaround: `models/ch04-cumulative.sysml` +now declares `in energy : ISQ::EnergyValue[0..*]` and `in duration : +ISQ::DurationValue[0..*]` (`bread` stays unannotated, already bound to +`ToastBread::bread` per Q2). This is **the applied fix**, not a documented +alternative, because writing it states nothing the bare declaration did not already +mean per §7.6.3/§7.6.4: DL-030/DL-031's requirement (typed, unit-bearing, no value) +is completely unaffected, the parameter is exactly as valueless and exactly as +"not yet bound" as before, and the model's claim about `energy`/`duration` has not +changed at all. Verified: `model.ok == True`; `slow.cycleTime` and +`timely(slow)` both evaluate normally again (`slow.cycleTime` returns `200 [SI::s]`, +`timely(slow)` returns `False`, matching Chapter 3's own established result); the +balance constraint (`assert constraint balance { delivered >= 0.0 [SI::J] and loss +>= 0.0 [SI::J] and delivered + loss <= energy }`) still evaluates correctly against +`energy[0..*]`, holding for a plausible split and failing for both an overdrawn and +a negative-loss one. + +**Side effect worth noting:** under explicit `[0..*]`, the tool also now accepts a +*multi-valued* `energy` (more than one bound value), which the balance constraint's +`<= energy` cannot evaluate (reports a type mismatch between a quantity and a +sequence). Nothing in the tutorial ever supplies more than one value, so this has +no practical effect here, but it shows `[0..*]` only really makes sense for these +parameters because exactly one value is what every actual use assumes — reinforcing +that `[1..1]` is the spec-accurate statement of intent (`decisions/next-passes.md` +item 11), even though `[0..*]` is what the tool currently requires. + +**Workaround:** the applied fix above (explicit `[0..*]` on `energy` and +`duration`) is spec-neutral and needs no separate workaround language: +`src/toaster/conformance.py::satisfaction_claims_evaluated` now reports Chapter 4's +`slow` claim exactly as it reports Chapter 3's (`passed`, no findings; +`tests/test_conformance.py:: +test_satisfaction_claims_evaluated_scheduled_reports_no_findings_on_ch04`). +**Resolution:** upstream fix so an implicit and an explicit-but-identical +multiplicity are treated the same (the headline finding above), or documentation +explaining why they are not; separately, resolving the `model.find(...).kind` vs +API-JSON `@type` disagreement noted above. +**Upstream issue:** not filed — Draft 10 (`decisions/gap-issue-drafts.md`), citing +the exact reproduction, isolation table and spec citations above, is drafted and +held for Z's review. +**Toaster issue:** not filed + +**PASS4-007 note.** Chapter 7's state machine hits this exact gap. `Cycle`'s +`heating` state was first tried with `do action applyHeat : ApplyHeat;`, invoking +`ApplyHeat` directly, the same action `HeatingSystem` performs. `execute_state` +then raised `ExecutionError: state machine execution failed: do action in state +heating: unbound parameter: action ApplyHeat: input parameter bread is bound by +no argument`: `ApplyHeat`'s own `bread` input (deliberately left bare, per Q2's +ruling, since it is always reference-bound wherever `ApplyHeat` is actually +invoked, e.g. `ToastBread::applyHeat { in bread = ToastBread::bread; }`) has no +value at this level of decomposition, and `Cycle` has no bread instance to bind +it to. Confirmed this is genuine execution, not a load-time artifact: temporarily +breaking `GenerateHeat`'s own already-fixed `[0..*]` multiplicity on `energyIn` +reproduces the identical failure shape (`unbound parameter: action GenerateHeat: +input parameter energyIn is bound by no argument`), proving the do action really +executes whatever it names. `GenerateHeat` was used instead +(`do action generateHeat : GenerateHeat;`): its own input is already `[0..*]` +(this entry's own applied fix), so it stays executable with no value bound. This +is not itself a new instance of D-026 (the unbound `bread` failure is correct +tool behavior given a genuinely unresolved required input, not the eager-eval bug +this entry documents), but it depends directly on D-026's applied fix to work at +all, so it is recorded here rather than as a separate entry. + +## D-027: a second declaration reopening an existing namespace member's name loads with warnings, then crashes `to_api_json()` + +**Found:** PASS4-005 (Chapter 5 re-derivation), while probing whether a definition +could be extended across two separate declarations sharing one name (a pattern +briefly considered, then not used, for spreading `HeatingSystem`'s construction +across two notebooks). Independently reproduced by the reviewer, who caught that +an earlier draft of this repro was missing the import `Real` needs and so +actually failed with `ok=False` (`unresolved: Real`), a different error than the +one this entry documents; the corrected repro below was re-verified directly. + +**Observed.** `package P { private import ScalarValues::*; part def X; part def +X { attribute a : Real; } }` (two owned members of the same package sharing the +name `X`) loads with `model.ok == True` and two `severity='warning'`, +`code='name-conflict'` diagnostics ("Duplicate of other owned member name"), +one per declaration. `model.find("P::X")` returns a single resolved symbol. +Calling `model.to_api_json()` on the same loaded model raises `ConversionError: +cannot convert the duplicate declaration of "X" at :L:C: a name +identifies an element in the graph, so two members of one namespace cannot +share it`, not a diagnostic on the model itself. + +**Why this matters for the tutorial.** Every helper this repo uses for anything +beyond `model.query()`/`model.find()` (`toaster.query.ApiIndex` and everything +built on it: `find_connectors`, `find_allocations`, `perform_relationships`, +`port_type_mismatches`, `build_interconnection_intent`, and +`scripts/check_construction.py`'s own predecessor-containment check) goes +through `to_api_json()`. A model that loads cleanly by every check that reads +`model.ok` or iterates `model.query()` can still be silently unusable by every +one of those helpers, with the actual cause (a name collision loudly warned +about at load time) two calls removed from the crash site. + +**Not yet resolved which of two readings is correct:** (a) `to_api_json()` +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 +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 +directly (only the diagnostic message and the observed behavior); this entry +does not claim a specific spec section, unlike D-019/D-020. + +**Workaround:** none needed in shipped content; PASS4-005 designed around the +pattern entirely rather than using it (every construction-zone fragment that +extends an earlier notebook's type restates it completely, rather than +reopening it). Flagged here so a future builder does not reach for the +"reopen to add a member" idiom expecting it to be safe. +**Resolution:** none attempted; needs Z's read on which of the two framings +above is the actual bug, before filing an upstream report. +**Upstream issue:** not filed — Draft 11 (`decisions/gap-issue-drafts.md`), +citing the exact reproduction above and naming the two unresolved framings, is +drafted and held for Z's review. +**Toaster issue:** not filed + +## D-028: `model.execute_state`'s `performer` argument has no effect on the result + +**Found:** PASS4-007 (Chapter 7 re-derivation, round 3 review), while checking a +notebook claim that naming a specific usage (e.g. `ToasterDemo::nominal`) as +`performer` demonstrates that `Toaster` inherits and executes the state machine +`ToastingSystem` exhibits. Independently reproduced by the orchestrator directly +against the real, committed `models/ch07-cumulative.sysml` before this entry was +written, not just taken from the reviewer's report. + +**Observed.** `model.execute_state("ToasterDemo::Cycle", events=["Start","Finish"])` +returns the identical `{"states_visited": [...], "final_context": {}, "final_time": +0.0}` regardless of `performer`: no argument at all, `ToasterDemo::nominal` (a real +`Toaster` usage that inherits `cycle`), `ToasterDemo::rated` (a `ResistanceCoil` +usage that exhibits nothing at all), and `ToasterDemo::Bread` (an `item def`, not +even a part) all give the same trace. Only a `performer` name that resolves to no +symbol at all changes anything (`ExecutionError: symbol not found`). The tool does +not check that the named performer actually exhibits the state being executed, and +does not vary the trace by what it is given. + +**Why this matters for the tutorial.** `execute_state` runs a state def's own +transition table in isolation; it is not, as written, a way to demonstrate that a +particular usage inherits and can execute an exhibited state machine through +specialization. That inheritance is a fact about the model's structure (checkable +via `model.find`, e.g. `Toaster::cycle` resolving to `None` the same way +`Toaster::toastBread` does, both inherited from `ToastingSystem` and not +redeclared), not something the execution trace itself shows. + +**Workaround:** none needed in shipped content; Chapter 7's own re-derivation +(`chapters/ch07-execution/02-state-traces.ipynb`) states the distinction directly +rather than claiming the trace demonstrates inheritance. +**Resolution:** none attempted; would need `execute_state` to validate that +`performer` (when given) actually exhibits the named state, and ideally to be +usable at all as a way to execute a state machine through a specific realizing +usage rather than only through the state def's own qualified name. +**Upstream issue:** not filed; not blocking (a documentation/API-surface gap, not +a load-time or evaluation-correctness defect). +**Toaster issue:** not filed + +## D-029: `toaster.modelcheck.verify_holds`'s line parser cannot read a `verify --solve` verdict for an `assert satisfy`/`assert not satisfy` declaration + +**Found:** PASS4-008 (Chapter 8 re-derivation), while probing whether `verify_holds` +could run directly against the real, committed `models/ch08-cumulative.sysml` +(which carries Chapter 3's and Chapter 6's `assert satisfy`/`assert not satisfy` +declarations forward from Chapter 7) rather than a small companion file. + +**Observed.** For an ordinary `constraint`/`assert constraint`, the CLI's verdict +line is `:: (): [ ()]`, which +`toaster/modelcheck.py`'s `_LINE_RE` already parses. For a constraint that is also +the subject of an `assert satisfy`/`assert not satisfy` declaration, the real CLI +instead prints one extra verdict line per such declaration, with the kind +parenthetical widened to `(, satisfies )` and no separate reason +parenthetical, e.g. (all three lines reproduced verbatim from a real run against +`models/ch08-cumulative.sysml`): + + :83:30 (ConstraintUsage, satisfies ToasterDemo::timely): VIOLATED + :184:30 (ConstraintUsage, satisfies ToasterDemo::heatGenerationReq): satisfied + :184:30 (ConstraintUsage, satisfies ToasterDemo::heatGenerationReq): VIOLATED + +**Correction (round 2 review, PASS4-008):** the first draft of this entry claimed +the CLI also prints a `not satisfies ` variant for a negative +declaration (`assert not satisfy ... by ...`). Checked directly against the real +CLI and found false: every satisfy/not-satisfy declaration prints the identical +`satisfies ` wording (there is no `not satisfies` form at all), and +the two are disambiguated only by the verdict word, which reports whether the +requirement's own constraint holds for that subject, not whether the surrounding +`assert`/`assert not` declaration's own polarity was upheld. The line 83 example +above is `slow`'s `assert not satisfy timely by slow;`: `VIOLATED` means the +constraint `toaster.cycleTime <= 180.0` is false for `slow` (cycleTime 200), which +is exactly what the negated declaration correctly asserts should happen. The two +line-184 examples are `rated`'s `assert satisfy heatGenerationReq by rated;` +(`satisfied`: `heatGen.power >= 600.0` is true, power 800) and `weak`'s +`assert not satisfy heatGenerationReq by weak;` (`VIOLATED`: the same constraint +body is false for `weak`, power 400, again the outcome the negated declaration +correctly asserts): the same source line and column because both usages check the +same `require constraint` body text, against different subjects. + +`_LINE_RE` matches `(?P\w+)` only, so the comma and the trailing +`satisfies ...` text do not match, and `verify_holds` raises +`ModelCheckError(f"could not parse verdict line {line!r}")` on any file containing +such a declaration, real CLI output that is well-formed, not a CLI error. + +**Why this matters for the tutorial.** `models/ch08-cumulative.sysml` (like +`ch03-cumulative.sysml` onward) carries `assert satisfy`/`assert not satisfy` +declarations forward from Chapter 3 and Chapter 6, so `verify_holds` cannot be run +directly against the real, committed cumulative fixture at all right now, only +against a file that carries no such declaration. + +**Workaround:** `chapters/ch08-checking/02-violation-witness.ipynb` runs +`verify_holds` against a small companion file assembled in the notebook itself (not +committed to `models/`), restating only `deliveredEnergyBoundedBySupply` and the +two usages it needs, which carries no `assert satisfy` declaration and so never +hits this parser gap. The construct itself is real, committed content in +`models/ch08-cumulative.sysml` (introduced in +`chapters/ch08-checking/01-invariant-def.ipynb`); only the file handed to +`verify_holds` is a restatement, and the notebook says so. See D-030 and D-031 for +the separate, deeper reason this construct is a hand-restated lemma rather than a +solver-checked reference to `HeatGenerator`'s own `efficiencyBounded` and +`deliveredEnergy`, which is a real limit of this toolchain, not only a parser gap. + +**Confirmed to also apply to `models/ch10-cumulative.sysml`** (found during the +Chapter 10 tie-remediation work, `decisions/log.md` DL-071/DL-072): this file +carries the same `assert satisfy`/`assert not satisfy` declarations forward, so +`verify_holds` cannot run against it directly either. The gap is not scoped to any +one constraint: `_LINE_RE`'s own failure fires on the FIRST `satisfies +`-annotated verdict line anywhere in the file (in practice, +`timely`'s own auto-generated required-constraint line, since it appears earliest), +before ever reaching whichever constraint a caller actually wanted to check (e.g. +Chapter 10's own `energyConservationReq`'s required constraint `c`). So a targeted +look-up of one specific, unrelated constraint's own verdict is not enough to avoid +this gap — any `assert satisfy` anywhere earlier in the same file trips it first. +The workaround used in Chapter 10's own notebook: call the real `sysmlv2 verify +--solve` CLI directly via `subprocess` and read its raw output, rather than going +through `toaster.modelcheck.verify_holds`'s own line parser at all. +**Resolution:** widen `_LINE_RE` (or add a second pattern) to accept an optional +`, satisfies ` segment inside the kind parenthetical (no `not` +variant exists, per the correction above), verified against the real CLI's exact +text before shipping the fix, per the module's own requirement that every case be +checked against a real run. **Caution for whoever fixes this:** a naive fix that +only widens the regex, without also teaching `holds()`'s own status precedence +about the satisfy/not-satisfy distinction, would make `holds()` read the real +model's own *correct* negative claims (`assert not satisfy timely by slow`, +`assert not satisfy heatGenerationReq by weak`, both `VIOLATED` verdicts exactly +as intended) as if they were failures of the model, since `holds()`'s +violated-beats-everything precedence has no way to know a `VIOLATED` verdict on a +`not satisfy` declaration is the correct, desired outcome. Fixing the parser alone +is not enough; the caller-facing semantics need the same care `satisfaction_claims_evaluated` +already gives this distinction (its own `is_negated` handling in `src/toaster/conformance.py`). +**Upstream issue:** not filed; not applicable (this is this repository's own +wrapper, not a claim about the `sysmlv2` CLI, which is behaving correctly). +**Toaster issue:** not filed; not blocking (the companion-file workaround is +sufficient for this chapter; `src/toaster/modelcheck.py` is outside this +contract's blast zone). + +**Note (round 2 review, PASS4-008): a `satisfied` verdict can come from interval +propagation alone, not necessarily Z3.** `verify --solve` runs propagation first +and only hands Z3 whatever propagation left undecided (per the CLI's own `--help` +text), so a verdict's `reason` can read `(propagation: holds for all values in the +narrowed ranges)` with no `z3:` text at all, even though the summary line and the +top-level status are identical to a genuinely solver-proved `satisfied`. Confirmed +directly against the real committed `models/ch08-cumulative.sysml`: +`efficiencyBounded` itself (`0.0 <= efficiency and efficiency <= 1.0`, a bound with +no other feature to relate) is reported `satisfied (propagation: holds for all +values in the narrowed ranges)`, never invoking Z3 at all, while +`deliveredEnergyBoundedBySupply` (an implication over three unbound features) is +reported `satisfied (z3: holds for all values of unbound features)`. Anyone +checking "was this actually proved by the solver, not merely by range narrowing" +should read the verdict's `reason` text, not just its `status`; `ConstraintVerdict` +carries both, and `chapters/ch08-checking/02-violation-witness.ipynb` asserts on +the reason text for exactly this purpose. + +## D-030: two independently-declared `assert constraint`s are never composed by `verify --solve`, whether sibling or inherited + +**Found:** PASS4-008 round 2 review (Opus 5.5), confirming that +`deliveredEnergyBoundedBySupply` (Chapter 8's new construct) is not actually +solver-linked to `HeatGenerator`'s own `efficiencyBounded` and `deliveredEnergy`, +despite the chapter's first-round prose claiming it proves the entailment those +two already imply. Independently reproduced by the builder with two further +constructed probes before writing this entry. + +**Observed.** `verify --solve` checks each `assert constraint` (or `constraint`) +body entirely on its own: nothing in the tool treats an already-declared sibling +or inherited constraint as an assumed-true hypothesis available to a different +constraint's own body, even when both are members of the exact same part def. +Three constructed reproductions, all giving `undecided` with a genuine Z3 witness +(not a parse error, not a trivial fold): + +1. **Sibling, same def, bare (non-dotted) feature references:** a second + `assert constraint` added directly inside `HeatGenerator` alongside + `efficiencyBounded`, referencing the bare `power`/`efficiency` features (no + `heatGenCheck.` qualification) plus a new local `duration`-typed attribute, with + no restated bound in its own antecedent: + `undecided (result is indeterminate over unbound features) (z3: satisfiable, + e.g. power = 0 [W], checkDuration = -1 [s], efficiency = 2)`. `efficiencyBounded` + itself still reports `satisfied` alongside it, unaffected, and does nothing to + constrain the second constraint's own check. +2. **Inherited via specialization:** the same second constraint moved onto a new + subtype `HeatGeneratorCheck :> HeatGenerator`, so it inherits `efficiencyBounded` + through specialization rather than sibling membership: identical `undecided` + verdict and witness. +3. **Confirms the point directly on the real committed model:** loosening + `efficiencyBounded`'s own literal bound in `models/ch08-cumulative.sysml` from + `<= 1.0` to `<= 1.5`, or doubling `deliveredEnergy`'s own definition from + `power * duration * efficiency` to `power * duration * efficiency * 2.0`, leaves + `deliveredEnergyBoundedBySupply`'s verdict unchanged (`satisfied`) either way, + because the construct's own antecedent restates its own copy of the bound and + its own copy of the arithmetic rather than referencing either original element. + +**Why this matters for the tutorial.** Any assert constraint meant to state "given +some other already-declared constraint holds, prove this" must restate that other +constraint's own hypothesis inline (which is legitimate and is what +`tests/test_modelcheck.py`'s own `TIMELY_TOAST` fixture already does); it cannot +rely on inheritance or same-scope membership to import the other constraint's +truth automatically. A property phrased this way is therefore only ever a +standalone lemma of the same shape as the original elements, never a solver- +checked reference to them, and re-checking it after either original element +changes is a manual, not automatic, step. + +**Workaround:** `deliveredEnergyBoundedBySupply`'s own doc comment, and Chapter 8's +prose (`chapters/ch08-checking/01-invariant-def.ipynb`, +`02-violation-witness.ipynb`, `index.md`, `conclusion.md`), state this limit +plainly rather than claiming a link the toolchain cannot check. +**Resolution:** none attempted; would need `verify --solve` (or a successor tool) +to treat already-proved sibling or inherited constraints as background axioms +when checking a new one, a nontrivial solver-integration feature, not a parsing +fix. +**Upstream issue:** not filed; a real capability gap in `sysml-toolkit`'s +`verify --solve`, worth raising once this pattern recurs enough to justify asking +for it, not this contract's call to file alone. +**Toaster issue:** not filed + +## D-031: a chained calc/function invocation inside an `assert constraint` is not in Z3's solvable fragment + +**Found:** PASS4-008 round 2 review (Opus 5.5), same probe session as D-030; +independently reproduced by the builder. + +**Observed.** `assert constraint c { ... heatGenCheck.deliveredEnergy(power, duration) <= power * duration ... }` +(calling a `calc` through a usage's own dotted path, rather than restating the +calc's body inline) gives: +`undecided (result is indeterminate over unbound features; z3: not in the +solvable fragment: chained function references)`, regardless of what the calc's +own definition actually computes (confirmed alongside D-030's probe 3: the verdict +does not change even when the calc's own definition is edited). + +**Why this matters for the tutorial.** A property that needs to reason about what +a `calc` actually computes cannot invoke the calc from inside an `assert +constraint` and expect Z3 to reason through the call; the calc's own body must be +restated inline in the constraint (exactly what `deliveredEnergyBoundedBySupply` +does), which is why the tutorial's new construct is a hand-restated lemma rather +than a call into `deliveredEnergy` itself. + +**Workaround:** none needed; the chapter's own construct never attempts a chained +calc invocation, and its prose says why. +**Resolution:** none attempted; would need `verify --solve`'s Z3 encoding to +inline or symbolically expand a calc invocation, a solver-integration feature. +**Upstream issue:** not filed, for the same reason as D-030. +**Toaster issue:** not filed + +## 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 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. + +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`. + +**Status: GUARDED.** D-019 and D-020 ALSO already have their own guards (`allocate-between-definitions` and `part-typed-only-by-item-def`, both predating this whole effort) — an earlier version of this entry and of DL-059 wrongly said they "remain open tool holes with no guard"; corrected 2026-09-29 (DL-059 ADDENDUM). The tutorial's own model (`models/ch01-cumulative.sysml` through `ch10-cumulative.sysml`, no ch09 fixture) is confirmed clean under the now four-times-hardened rule pair (this rule plus its now-extended sibling `allocate-between-definitions`); a regression back into the non-conformant idiom is now caught, with the algorithm bugs fixed and re-validated against the pilot across ~50 targeted probe fixtures (Tasks 5, 6 and 7 combined). 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. After four independent review rounds each finding and fixing real bugs, and Task 7 fixing the last two named ones, further open-ended hardening is deliberately NOT being pursued (DL-059 ADDENDUM 3's own stopping rationale): this is a well-validated best-effort scoping check for an always-on tutorial guard rule, not a claim of formal completeness. + +**Workaround:** `allocate-connector-end-accessibility` (Task 4, hardened Task 5, hardened again Task 6, hardened again Task 7) together with the now-extended `allocate-between-definitions` (D-019), an always-on language-gap `GapRule` pair, each with its own negative control, following D-019/D-020's own shape. +**Resolution:** upstream fix in both OpenSysML and sysml-toolkit; re-test with `scripts/probes`. +**Upstream issue:** not yet filed (drafted upstream bug reports for both tools remain open follow-up work, `decisions/next-passes.md` item 23(c)). +**Toaster issue:** not filed + +## 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. + +**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. +**Upstream issue:** not yet filed. +**Toaster issue:** not filed + +## 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. + +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. + +**Workaround:** don't use `filter` as a bare feature, part, or definition name anywhere in this tutorial's own model content; the Chapter 5 exercise renamed its own `filter` part to `filterUnit` instead of escaping it. No language-gap `GapRule` guard has been built for this (unlike D-019/D-020/D-032, which have their own always-on `GapRule`s) — this is a narrower, purely lexical collision (a fixed, small set of reserved words) rather than a structural/semantic non-conformance pattern, so a full guard rule was judged disproportionate; a simple reserved-word check against the model's own declared names would be a lighter-weight alternative if this recurs. +**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); re-test once available. +**Upstream issue:** not yet filed. +**Toaster issue:** not filed + +## 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. + +**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. +**Upstream issue:** not yet filed. +**Toaster issue:** not filed + +## 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. + +**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. +**Upstream issue:** not yet filed. +**Toaster issue:** not filed diff --git a/README.md b/README.md index 0b3be85..fbd4357 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. -**Published site:** https://open-mbee.github.io/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. 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). Engineering judgment records follow Hawkins et al. 2011 §§3.1–3.4. diff --git a/chapters/ch01-system-purpose/01-abstract-def.ipynb b/chapters/ch01-system-purpose/01-abstract-def.ipynb index d173633..7760c6f 100644 --- a/chapters/ch01-system-purpose/01-abstract-def.ipynb +++ b/chapters/ch01-system-purpose/01-abstract-def.ipynb @@ -1,111 +1,188 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - }, - "title": "Ch1 nb1 \u2014 abstract part def" - }, - "cells": [ - { - "cell_type": "markdown", - "id": "cell-00", - "metadata": {}, - "source": [ - "## abstract part def\n", - "\n", - "This notebook introduces `abstract part def`; after running it you can declare a top-level concept that no part can directly instantiate." - ] - }, - { - "cell_type": "markdown", - "id": "cell-01", - "metadata": {}, - "source": [ - "Chapter 1 builds the structural model of a toaster from first principles. This first notebook declares the system concept: `ToastingSystem`. Subsequent notebooks add component types, specialization, and composition." - ] - }, - { - "cell_type": "code", - "id": "cell-02", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from pathlib import Path\n", - "import opensysml\n", - "from toaster.report import format_diagnostics\n", - "\n", - "conn = opensysml.connect(version=\"v0.9.0\")\n", - "source = Path(\"../../models/ch01-cumulative.sysml\").read_text()\n", - "print(source)\n", - "model = conn.load_from_content(source, strict=False)\n", - "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" - ] - }, - { - "cell_type": "markdown", - "id": "cell-03", - "metadata": {}, - "source": [ - "The `ch01-cumulative.sysml` file declares the four foundational part definitions: `ToastingSystem` (abstract system concept with a doc annotation), `Heater` (with a `power : Real` attribute), `HeatingSystem` and `ControlSystem` (specializations via `:>`), and `Toaster` (composed from `heating` and `control` parts). These constructs form the structural skeleton that every later chapter extends." - ] - }, - { - "cell_type": "code", - "id": "cell-04", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# Negative control: doc requires /* */ delimiters, not a string literal.\n", - "bad_source = \"\"\"\n", - "package Bad {\n", - " abstract part def ToastingSystem {\n", - " doc \"a plain string is not valid doc syntax\";\n", - " }\n", - "}\n", - "\"\"\"\n", - "bad = conn.load_from_content(bad_source, strict=False)\n", - "assert not bad.ok\n", - "print(\"Expected error:\", bad.diagnostics[0].message)" - ] - }, - { - "cell_type": "code", - "id": "cell-05", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "sym = model.find(\"ToasterDemo::ToastingSystem\")\n", - "assert sym is not None\n", - "print(f\"kind : {sym.kind}\")\n", - "print(f\"id : {sym.id}\")\n", - "conn.close()" - ] - }, - { - "cell_type": "markdown", - "id": "cell-06", - "metadata": {}, - "source": [ - "`abstract part def ToastingSystem` is the A-F construct; OpenSysML parses and indexes it (O-S); `model.find()` returns the symbol, confirming the definition is reachable (E)." - ] - }, - { - "cell_type": "markdown", - "id": "cell-07", - "metadata": {}, - "source": [ - "Try the chapter exercise in `exercises/ch01/exercise.ipynb`: declare an abstract part def for a coffee maker and verify it loads." - ] - } - ] -} \ No newline at end of file + "nbformat": 4, + "nbformat_minor": 5, + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + }, + "title": "Ch1 nb1: abstract part def" + }, + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": [ + "## abstract part def\n", + "\n", + "This notebook introduces `abstract part def` together with the flow-typed `action def` it performs; after running it you can state a system's purpose as an executable functional construct, not a comment." + ] + }, + { + "cell_type": "markdown", + "id": "cell-01", + "metadata": {}, + "source": [ + "Chapter 1 builds the model of a toaster's purpose and structure from first principles. This first notebook declares what the whole toasting system does: transform bread into toast, stated as typed input and output flows through an action the system performs. Subsequent notebooks add component types, specialization, and composition." + ] + }, + { + "cell_type": "code", + "id": "cell-02", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": [ + "from pathlib import Path\n", + "import opensysml\n", + "from toaster.report import format_diagnostics\n", + "\n", + "conn = opensysml.connect(version=\"v0.9.0\")\n", + "\n", + "# item def : a typed flow, carried in or out by the action def below\n", + "# spec: SysML v2 formal/2026-03-02 §8.3.10.2 (ItemDefinition)\n", + "BREAD_DEF = \"item def Bread;\"\n", + "print(BREAD_DEF)\n", + "TOAST_DEF = \"item def Toast;\"\n", + "print(TOAST_DEF)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-03", + "metadata": {}, + "source": [ + "`Bread` and `Toast` are item definitions: typed flows with no mechanism, ready to be the `in` and `out` parameters of the action below." + ] + }, + { + "cell_type": "code", + "id": "cell-04", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "# action def : typed in/out flows state the purpose without committing to a mechanism\n# spec: SysML v2 formal/2026-03-02 §7.17.2 (ActionDefinition)\nTOASTBREAD_DEF = \"\"\"\\\naction def ToastBread {\n doc /* Transform bread into toast acceptable to its user. */\n in bread : Bread;\n out toast : Toast;\n}\n\"\"\"\nprint(TOASTBREAD_DEF)" + }, + { + "cell_type": "markdown", + "id": "cell-05", + "metadata": {}, + "source": [ + "`ToastBread` states the purpose functionally: bread in, toast out. The `doc` keeps the acceptance language (\"acceptable to its user\") as the seed of a measure of effectiveness Chapter 3 builds; the action names no mechanism and no part." + ] + }, + { + "cell_type": "code", + "id": "cell-06", + "metadata": {}, + "outputs": [], + "execution_count": null, + "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", + "# 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", + "abstract part def ToastingSystem {\n", + " perform action toastBread : ToastBread;\n", + "}\n", + "\"\"\"\n", + "print(TOASTING_SYSTEM_DEF)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-06b", + "metadata": {}, + "source": [ + "`ToastingSystem` performs `ToastBread`: the abstract subject carries out the stated purpose without committing to a mechanism or a concrete part." + ] + }, + { + "cell_type": "code", + "id": "cell-07", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "TOASTER_INCREMENT = f\"{BREAD_DEF}\\n{TOAST_DEF}\\n{TOASTBREAD_DEF}\\n{TOASTING_SYSTEM_DEF}\"\nprint(TOASTER_INCREMENT)\nsource = Path(\"../../models/ch01-cumulative.sysml\").read_text()\nmodel = conn.load_from_content(source, strict=False)\nassert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" + }, + { + "cell_type": "markdown", + "id": "cell-07b", + "metadata": {}, + "source": [ + "The assembled increment loads against the cumulative model with no diagnostics: `assert model.ok` passes silently, confirming all four declarations resolve together. The next cell checks what happens when one does not." + ] + }, + { + "cell_type": "code", + "id": "cell-08", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "# Negative control: doc requires /* */ delimiters, not a string literal.\nbad_source = \"\"\"\npackage Bad {\n action def ToastBread {\n doc \"a plain string is not valid doc syntax\";\n }\n}\n\"\"\"\nbad = conn.load_from_content(bad_source, strict=False)\nassert not bad.ok\nprint(\"Expected error:\", bad.diagnostics[0].message)" + }, + { + "cell_type": "markdown", + "id": "cell-09", + "metadata": {}, + "source": [ + "A `doc` written as a quoted string rather than a `/* */` comment block is a syntax error, reported in `bad.diagnostics[0]`." + ] + }, + { + "cell_type": "code", + "id": "cell-10", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "sym = model.find(\"ToasterDemo::ToastingSystem\")\nassert sym is not None\nprint(f\"kind : {sym.kind}\")\nprint(f\"id : {sym.id}\")" + }, + { + "cell_type": "markdown", + "id": "cell-11", + "metadata": {}, + "source": [ + "`model.find()` resolves `ToastingSystem` by its qualified name, confirming the abstract subject is indexed." + ] + }, + { + "cell_type": "code", + "id": "cell-12", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "action_sym = model.find(\"ToasterDemo::ToastBread\")\nassert action_sym is not None\nprint(f\"kind : {action_sym.kind}\")\nprint(f\"id : {action_sym.id}\")\nconn.close()" + }, + { + "cell_type": "markdown", + "id": "cell-13", + "metadata": {}, + "source": [ + "The same lookup on `ToastBread` confirms the performed action is indexed too, separately from the part def that performs it." + ] + }, + { + "cell_type": "markdown", + "id": "cell-14", + "metadata": {}, + "source": [ + "The `ToastBread` and `ToastingSystem` declarations printed above loaded without error, and the two `model.find()` calls above confirm each is now part of the model, shown by the kind and id printed below each." + ] + }, + { + "cell_type": "markdown", + "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." + ] + } + ] +} diff --git a/chapters/ch01-system-purpose/02-part-def.ipynb b/chapters/ch01-system-purpose/02-part-def.ipynb index b4ef997..fa6dee3 100644 --- a/chapters/ch01-system-purpose/02-part-def.ipynb +++ b/chapters/ch01-system-purpose/02-part-def.ipynb @@ -1,120 +1,131 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - }, - "title": "Ch1 nb2 \u2014 part def and attributes" + "nbformat": 4, + "nbformat_minor": 5, + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" }, - "cells": [ - { - "cell_type": "markdown", - "id": "cell-00", - "metadata": {}, - "source": [ - "## part def and attributes\n", - "\n", - "This notebook introduces `part def` with typed attributes; after running it you can define component types with numeric parameters." - ] - }, - { - "cell_type": "markdown", - "id": "cell-01", - "metadata": {}, - "source": [ - "The previous notebook established `ToastingSystem` as the abstract system concept. This notebook introduces concrete component definitions: `Heater` carries a `power` attribute, and `HeatingSystem` and `ControlSystem` are named as distinct subsystem types, with no hierarchy yet." - ] - }, - { - "cell_type": "code", - "id": "cell-02", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from pathlib import Path\n", - "import opensysml\n", - "from toaster.report import format_diagnostics\n", - "\n", - "conn = opensysml.connect(version=\"v0.9.0\")\n", - "source = Path(\"../../models/ch01-cumulative.sysml\").read_text()\n", - "print(source)\n", - "model = conn.load_from_content(source, strict=False)\n", - "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" - ] - }, - { - "cell_type": "markdown", - "id": "cell-03", - "metadata": {}, - "source": [ - "The `ch01-cumulative.sysml` file declares the four foundational part definitions: `ToastingSystem` (abstract system concept with a doc annotation), `Heater` (with a `power : Real` attribute), `HeatingSystem` and `ControlSystem` (specializations via `:>`), and `Toaster` (composed from `heating` and `control` parts). These constructs form the structural skeleton that every later chapter extends." - ] - }, - { - "cell_type": "code", - "id": "cell-04", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# Negative control: attribute type must resolve to a known classifier.\n", - "# Referencing an undefined type causes an \"unresolved reference\" error.\n", - "bad_source = \"\"\"\n", - "package Bad {\n", - " part def Heater {\n", - " attribute power : UnknownType default = 800.0;\n", - " }\n", - "}\n", - "\"\"\"\n", - "bad = conn.load_from_content(bad_source, strict=False)\n", - "assert not bad.ok\n", - "print(\"Expected error:\", bad.diagnostics[0].message)" - ] - }, - { - "cell_type": "code", - "id": "cell-05", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "heater = model.find(\"ToasterDemo::Heater\")\n", - "assert heater is not None\n", - "attrs = heater.attributes()\n", - "print(f\"Heater attributes ({len(attrs)}):\")\n", - "for a in attrs:\n", - " print(f\" {a.id}\")\n", - "\n", - "print()\n", - "for e in model.query():\n", - " d = e.as_dict()\n", - " if d[\"@type\"] == \"PartDefinition\":\n", - " print(f\"PartDefinition: {d['qualifiedName']}\")\n", - "conn.close()" - ] - }, - { - "cell_type": "markdown", - "id": "cell-06", - "metadata": {}, - "source": [ - "`part def Heater { attribute power : Real default = 800.0; }` is the A-F declaration; OpenSysML resolves `Real` from the imported library and stores the attribute (O-S); `heater.attributes()` returns the attribute symbol (E)." - ] - }, - { - "cell_type": "markdown", - "id": "cell-07", - "metadata": {}, - "source": [ - "Try the chapter exercise in `exercises/ch01/exercise.ipynb`: define a `BrewUnit` part def with a `brewTemp` attribute and verify it loads." - ] - } - ] -} \ No newline at end of file + "language_info": { + "name": "python" + }, + "title": "Ch1 nb2: part def" + }, + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": [ + "## part def\n", + "\n", + "This notebook introduces `part def`; after running it you can declare concrete component types with no content of their own yet." + ] + }, + { + "cell_type": "markdown", + "id": "cell-01", + "metadata": {}, + "source": [ + "The previous notebook established `ToastingSystem` as the abstract system concept, together with the action it performs. This notebook introduces two concrete component types with no attributes or hierarchy yet: `HeatingSystem` and `ControlSystem`. They stay bare placeholders in this chapter; later chapters give them mechanisms, policies and interfaces." + ] + }, + { + "cell_type": "code", + "id": "cell-02", + "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_part_def(owner='ToasterDemo', name='HeatingSystem') when API ships\nHEATING_SYS_DEF = \"part def HeatingSystem;\"\nprint(HEATING_SYS_DEF)" + }, + { + "cell_type": "markdown", + "id": "cell-03", + "metadata": {}, + "source": [ + "`HeatingSystem` is a concrete part definition, declared bare (terminated with `;`, no body). Unlike `abstract part def`, it can be instantiated directly, but nothing yet says what it does." + ] + }, + { + "cell_type": "code", + "id": "cell-04", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "# editor.add_part_def(owner='ToasterDemo', name='ControlSystem') when API ships\nCONTROL_SYS_DEF = \"part def ControlSystem;\"\nprint(CONTROL_SYS_DEF)" + }, + { + "cell_type": "markdown", + "id": "cell-05", + "metadata": {}, + "source": [ + "`ControlSystem` takes the same bare form. Two part definitions can exist side by side with no relationship yet; the next notebook relates the whole they compose into to `ToastingSystem` by specialization." + ] + }, + { + "cell_type": "code", + "id": "cell-06", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "TOASTER_INCREMENT = f\"{HEATING_SYS_DEF}\\n{CONTROL_SYS_DEF}\"\nprint(TOASTER_INCREMENT)\nsource = Path(\"../../models/ch01-cumulative.sysml\").read_text()\nmodel = conn.load_from_content(source, strict=False)\nassert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" + }, + { + "cell_type": "markdown", + "id": "cell-06b", + "metadata": {}, + "source": [ + "Both part definitions load with no diagnostics. The next cell checks what a bare `part def` looks like when its terminating syntax is missing." + ] + }, + { + "cell_type": "code", + "id": "cell-07", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "# Negative control: a bare part def declaration must end with a semicolon (or a body).\nbad_source = \"\"\"\npackage Bad {\n part def HeatingSystem\n}\n\"\"\"\nbad = conn.load_from_content(bad_source, strict=False)\nassert not bad.ok\nprint(\"Expected error:\", bad.diagnostics[0].message)" + }, + { + "cell_type": "markdown", + "id": "cell-08", + "metadata": {}, + "source": [ + "Omitting the terminating `;` (or a body) after a bare `part def` is a syntax error, reported in `bad.diagnostics[0]`." + ] + }, + { + "cell_type": "code", + "id": "cell-09", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "hs = model.find(\"ToasterDemo::HeatingSystem\")\nassert hs is not None\nprint(f\"kind : {hs.kind}\")\nprint(f\"id : {hs.id}\")\n\nprint()\nfor e in model.query():\n d = e.as_dict()\n if d[\"@type\"] == \"PartDefinition\":\n print(f\"PartDefinition: {d['qualifiedName']}\")\nconn.close()" + }, + { + "cell_type": "markdown", + "id": "cell-10", + "metadata": {}, + "source": [ + "`model.find()` resolves `HeatingSystem`, and the `PartDefinition` query above lists every named part def in the model so far, `ControlSystem` included." + ] + }, + { + "cell_type": "markdown", + "id": "cell-11", + "metadata": {}, + "source": [ + "The two bare part def declarations printed above loaded without error, and the `PartDefinition` query above lists both names, confirming each is now part of the model." + ] + }, + { + "cell_type": "markdown", + "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." + ] + } + ] +} diff --git a/chapters/ch01-system-purpose/03-specialization.ipynb b/chapters/ch01-system-purpose/03-specialization.ipynb index ef7a582..e9edd7d 100644 --- a/chapters/ch01-system-purpose/03-specialization.ipynb +++ b/chapters/ch01-system-purpose/03-specialization.ipynb @@ -1,116 +1,115 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - }, - "title": "Ch1 nb3 \u2014 specialization" + "nbformat": 4, + "nbformat_minor": 5, + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" }, - "cells": [ - { - "cell_type": "markdown", - "id": "cell-00", - "metadata": {}, - "source": [ - "## specialization\n", - "\n", - "This notebook introduces `:>` specialization; after running it you can declare that one part type is a kind of another." - ] - }, - { - "cell_type": "markdown", - "id": "cell-01", - "metadata": {}, - "source": [ - "The previous notebook defined `HeatingSystem` and `ControlSystem` as standalone types. This notebook makes them specializations of `ToastingSystem`, establishing that both are toasting-system components. The model now has a three-level type hierarchy: abstract concept \u2192 specialized type \u2192 (composition to follow in the next notebook)." - ] - }, - { - "cell_type": "code", - "id": "cell-02", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from pathlib import Path\n", - "import opensysml\n", - "from toaster.report import format_diagnostics\n", - "\n", - "conn = opensysml.connect(version=\"v0.9.0\")\n", - "source = Path(\"../../models/ch01-cumulative.sysml\").read_text()\n", - "print(source)\n", - "model = conn.load_from_content(source, strict=False)\n", - "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" - ] - }, - { - "cell_type": "markdown", - "id": "cell-03", - "metadata": {}, - "source": [ - "The `ch01-cumulative.sysml` file declares the four foundational part definitions: `ToastingSystem` (abstract system concept with a doc annotation), `Heater` (with a `power : Real` attribute), `HeatingSystem` and `ControlSystem` (specializations via `:>`), and `Toaster` (composed from `heating` and `control` parts). These constructs form the structural skeleton that every later chapter extends." - ] - }, - { - "cell_type": "code", - "id": "cell-04", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# Negative control: the supertype must exist in the same package or be imported.\n", - "# Specializing an undefined type raises \"unresolved reference\".\n", - "bad_source = \"\"\"\n", - "package Bad {\n", - " part def HeatingSystem :> UndefinedBase;\n", - "}\n", - "\"\"\"\n", - "bad = conn.load_from_content(bad_source, strict=False)\n", - "assert not bad.ok\n", - "print(\"Expected error:\", bad.diagnostics[0].message)" - ] - }, - { - "cell_type": "code", - "id": "cell-05", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "hs = model.find(\"ToasterDemo::HeatingSystem\")\n", - "assert hs is not None\n", - "specs = hs.specializations\n", - "print(f\"HeatingSystem specializations ({len(specs)}):\")\n", - "for s in specs:\n", - " print(f\" {s.kind}: {s.declared} -> {s.target_id}\")\n", - "\n", - "cs = model.find(\"ToasterDemo::ControlSystem\")\n", - "print()\n", - "print(f\"ControlSystem specializes: {cs.specializations[0].target_id}\")\n", - "conn.close()" - ] - }, - { - "cell_type": "markdown", - "id": "cell-06", - "metadata": {}, - "source": [ - "`part def HeatingSystem :> ToastingSystem` is the A-F specialization; OpenSysML resolves the supertype reference and records the relationship (O-S); `hs.specializations` returns the target id (E)." - ] - }, - { - "cell_type": "markdown", - "id": "cell-07", - "metadata": {}, - "source": [ - "Try the chapter exercise in `exercises/ch01/exercise.ipynb`: define a `HeatExchanger` that specializes `BrewUnit` and confirm the specialization records correctly." - ] - } - ] -} \ No newline at end of file + "language_info": { + "name": "python" + }, + "title": "Ch1 nb3: specialization" + }, + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": [ + "## specialization\n", + "\n", + "This notebook introduces `:>` specialization; after running it you can declare that the whole is a kind of the concept that names it." + ] + }, + { + "cell_type": "markdown", + "id": "cell-01", + "metadata": {}, + "source": [ + "The previous notebook declared `HeatingSystem` and `ControlSystem` as standalone types with no supertype. This notebook declares `Toaster`, the actual whole, as a specialization of `ToastingSystem`: the system that carries out the purpose is a kind of the concept that names it, not the other way around. `Toaster`'s body (its cycle-time slot and its subsystem parts) is completed in the next notebook." + ] + }, + { + "cell_type": "code", + "id": "cell-02", + "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_part_def(owner='ToasterDemo', name='Toaster', specializes=['ToastingSystem']) when API ships\nTOASTER_SPEC_DEF = \"part def Toaster :> ToastingSystem;\"\nprint(TOASTER_SPEC_DEF)" + }, + { + "cell_type": "markdown", + "id": "cell-03", + "metadata": {}, + "source": [ + "`:>` declares that every `Toaster` is a kind of `ToastingSystem`, inheriting the purpose action `ToastBread` it performs. The supertype must be in scope: `ToastingSystem` was declared in notebook 01 and is present in the cumulative model." + ] + }, + { + "cell_type": "code", + "id": "cell-04", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "TOASTER_INCREMENT = TOASTER_SPEC_DEF\nprint(TOASTER_INCREMENT)\nsource = Path(\"../../models/ch01-cumulative.sysml\").read_text()\nmodel = conn.load_from_content(source, strict=False)\nassert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" + }, + { + "cell_type": "markdown", + "id": "cell-04b", + "metadata": {}, + "source": [ + "`Toaster :> ToastingSystem` loads with no diagnostics. The next cell checks what happens when the named supertype does not exist." + ] + }, + { + "cell_type": "code", + "id": "cell-05", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "# Negative control: the supertype must exist in the same package or be imported.\nbad_source = \"\"\"\npackage Bad {\n part def Toaster :> UndefinedBase;\n}\n\"\"\"\nbad = conn.load_from_content(bad_source, strict=False)\nassert not bad.ok\nprint(\"Expected error:\", bad.diagnostics[0].message)" + }, + { + "cell_type": "markdown", + "id": "cell-06", + "metadata": {}, + "source": [ + "Specializing an undefined type raises an unresolved-reference error, reported in `bad.diagnostics[0]`." + ] + }, + { + "cell_type": "code", + "id": "cell-07", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "toaster = model.find(\"ToasterDemo::Toaster\")\nassert toaster is not None\nspecs = toaster.specializations\nprint(f\"Toaster specializations ({len(specs)}):\")\nfor s in specs:\n print(f\" {s.kind}: {s.declared} -> {s.target_id}\")\nconn.close()" + }, + { + "cell_type": "markdown", + "id": "cell-08", + "metadata": {}, + "source": [ + "`toaster.specializations` reports the target above, confirming `Toaster :> ToastingSystem` is recorded, not just parsed." + ] + }, + { + "cell_type": "markdown", + "id": "cell-09", + "metadata": {}, + "source": [ + "`part def Toaster :> ToastingSystem;` printed above loaded without error, and `toaster.specializations` shows the target reported below, confirming the relationship is now part of the model." + ] + }, + { + "cell_type": "markdown", + "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." + ] + } + ] +} diff --git a/chapters/ch01-system-purpose/04-composition.ipynb b/chapters/ch01-system-purpose/04-composition.ipynb index eb6f59b..ed5049d 100644 --- a/chapters/ch01-system-purpose/04-composition.ipynb +++ b/chapters/ch01-system-purpose/04-composition.ipynb @@ -1,120 +1,147 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - }, - "title": "Ch1 nb4 \u2014 composition" - }, - "cells": [ - { - "cell_type": "markdown", - "id": "cell-00", - "metadata": {}, - "source": [ - "## composition\n", - "\n", - "This notebook introduces `part` usage (composition); after running it you can declare that a system definition owns named instances of its subsystem types." - ] - }, - { - "cell_type": "markdown", - "id": "cell-01", - "metadata": {}, - "source": [ - "The previous notebook established that `HeatingSystem` and `ControlSystem` are specializations of `ToastingSystem`. This notebook composes them into a `Toaster`: a system that owns a heating part and a control part. The model is now structurally complete for Chapter 1." - ] - }, - { - "cell_type": "code", - "id": "cell-02", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from pathlib import Path\n", - "import opensysml\n", - "from toaster.report import format_diagnostics\n", - "\n", - "conn = opensysml.connect(version=\"v0.9.0\")\n", - "source = Path(\"../../models/ch01-cumulative.sysml\").read_text()\n", - "print(source)\n", - "model = conn.load_from_content(source, strict=False)\n", - "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" - ] - }, - { - "cell_type": "markdown", - "id": "cell-03", - "metadata": {}, - "source": [ - "The `ch01-cumulative.sysml` file declares the four foundational part definitions: `ToastingSystem` (abstract system concept with a doc annotation), `Heater` (with a `power : Real` attribute), `HeatingSystem` and `ControlSystem` (specializations via `:>`), and `Toaster` (composed from `heating` and `control` parts). These constructs form the structural skeleton that every later chapter extends." - ] - }, - { - "cell_type": "code", - "id": "cell-04", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# Negative control: a part usage must name a type that exists in the model.\n", - "# Composing an undefined type raises \"unresolved reference\".\n", - "bad_source = \"\"\"\n", - "package Bad {\n", - " part def Toaster {\n", - " part heating : UndefinedSubsystem;\n", - " }\n", - "}\n", - "\"\"\"\n", - "bad = conn.load_from_content(bad_source, strict=False)\n", - "assert not bad.ok\n", - "print(\"Expected error:\", bad.diagnostics[0].message)" - ] - }, - { - "cell_type": "code", - "id": "cell-05", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "toaster = model.find(\"ToasterDemo::Toaster\")\n", - "assert toaster is not None\n", - "\n", - "parts = toaster.parts()\n", - "attrs = toaster.attributes()\n", - "print(f\"Toaster parts ({len(parts)}):\")\n", - "for p in parts:\n", - " print(f\" {p.id}\")\n", - "print(f\"Toaster attributes ({len(attrs)}):\")\n", - "for a in attrs:\n", - " print(f\" {a.id}\")\n", - "\n", - "conn.close()" - ] - }, - { - "cell_type": "markdown", - "id": "cell-06", - "metadata": {}, - "source": [ - "`part def Toaster { part heating : HeatingSystem; part control : ControlSystem; }` is the A-F composition; OpenSysML resolves each part usage to its typed definition (O-S); `toaster.parts()` returns the two part symbols (E)." - ] - }, - { - "cell_type": "markdown", - "id": "cell-07", - "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()`." - ] - } - ] -} \ No newline at end of file + "nbformat": 4, + "nbformat_minor": 5, + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + }, + "title": "Ch1 nb4: composition" + }, + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": [ + "## composition\n", + "\n", + "This notebook introduces `part` usage (composition); after running it you can declare that a system definition owns named instances of its subsystem types." + ] + }, + { + "cell_type": "markdown", + "id": "cell-01", + "metadata": {}, + "source": [ + "The previous notebook declared that `Toaster` specializes `ToastingSystem`. This notebook completes `Toaster`'s declaration: a cycle-time slot with no value yet, and named parts for its heating and control subsystems. The model is now structurally complete for Chapter 1." + ] + }, + { + "cell_type": "code", + "id": "cell-02", + "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_part_def(owner='ToasterDemo', name='Toaster', specializes=['ToastingSystem']) when API ships\nTOASTER_DEF = \"part def Toaster :> ToastingSystem {\"\nprint(TOASTER_DEF)" + }, + { + "cell_type": "markdown", + "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." + ] + }, + { + "cell_type": "code", + "id": "cell-04", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "# editor.add_attribute(owner='ToasterDemo::Toaster', name='cycleTime', type='ISQ::DurationValue') when API ships\nCYCLE_TIME_ATTR = \" attribute cycleTime : ISQ::DurationValue;\"\nprint(CYCLE_TIME_ATTR)" + }, + { + "cell_type": "markdown", + "id": "cell-05", + "metadata": {}, + "source": [ + "`cycleTime` is a typed, unit-bearing slot with no value: how long a cycle actually takes is a result the design produces, derived later from the mechanism and the energy balance, not a number chosen here." + ] + }, + { + "cell_type": "code", + "id": "cell-06", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "# editor.add_part(owner='ToasterDemo::Toaster', name='heating', type='HeatingSystem') when API ships\nHEATING_PART = \" part heating : HeatingSystem;\"\nprint(HEATING_PART)\n# editor.add_part(owner='ToasterDemo::Toaster', name='control', type='ControlSystem') when API ships\nCONTROL_PART = \" part control : ControlSystem;\"\nprint(CONTROL_PART)" + }, + { + "cell_type": "markdown", + "id": "cell-07", + "metadata": {}, + "source": [ + "Each `part` usage declares that `Toaster` owns one instance of its type. `heating : HeatingSystem` means there is one heating subsystem per toaster: the heating subsystem exists inside the Toaster, not just pointed to by it. These are structural ownership relationships, not Python-style references." + ] + }, + { + "cell_type": "code", + "id": "cell-08", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "TOASTER_INCREMENT = f\"{TOASTER_DEF}\\n{CYCLE_TIME_ATTR}\\n{HEATING_PART}\\n{CONTROL_PART}\\n}}\"\nprint(TOASTER_INCREMENT)\nsource = Path(\"../../models/ch01-cumulative.sysml\").read_text()\nmodel = conn.load_from_content(source, strict=False)\nassert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" + }, + { + "cell_type": "markdown", + "id": "cell-08b", + "metadata": {}, + "source": [ + "The complete `Toaster` body loads with no diagnostics. The next cell checks what happens when a `part` usage names a type the model does not have." + ] + }, + { + "cell_type": "code", + "id": "cell-09", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "# Negative control: a part usage must name a type that exists in the model.\nbad_source = \"\"\"\npackage Bad {\n part def Toaster {\n part heating : UndefinedSubsystem;\n }\n}\n\"\"\"\nbad = conn.load_from_content(bad_source, strict=False)\nassert not bad.ok\nprint(\"Expected error:\", bad.diagnostics[0].message)" + }, + { + "cell_type": "markdown", + "id": "cell-10", + "metadata": {}, + "source": [ + "Composing an undefined type raises an unresolved-reference error, reported in `bad.diagnostics[0]`." + ] + }, + { + "cell_type": "code", + "id": "cell-11", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "toaster = model.find(\"ToasterDemo::Toaster\")\nassert toaster is not None\n\nparts = toaster.parts()\nattrs = toaster.attributes()\nprint(f\"Toaster parts ({len(parts)}):\")\nfor p in parts:\n print(f\" {p.id}\")\nprint(f\"Toaster attributes ({len(attrs)}):\")\nfor a in attrs:\n print(f\" {a.id}\")\n\nconn.close()" + }, + { + "cell_type": "markdown", + "id": "cell-12", + "metadata": {}, + "source": [ + "`toaster.parts()` returns the two part symbols and `toaster.attributes()` returns `cycleTime`, confirming the composition is now part of the model." + ] + }, + { + "cell_type": "markdown", + "id": "cell-13", + "metadata": {}, + "source": [ + "`part def Toaster :> ToastingSystem { ... }` printed above loaded without error, and `toaster.parts()` returns the two part symbols shown below, confirming the composition is now part of the model." + ] + }, + { + "cell_type": "markdown", + "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()`." + ] + } + ] +} diff --git a/chapters/ch01-system-purpose/conclusion.md b/chapters/ch01-system-purpose/conclusion.md index 4817b4a..9502c27 100644 --- a/chapters/ch01-system-purpose/conclusion.md +++ b/chapters/ch01-system-purpose/conclusion.md @@ -1,15 +1,15 @@ -# Chapter 1 — Conclusion +# Chapter 1: Conclusion ## What we built -The Chapter 1 model contains four component type definitions and one composed system. `ToastingSystem` is abstract and documents the system purpose. `Heater` carries a `power` attribute. `HeatingSystem` and `ControlSystem` specialize `ToastingSystem`, establishing them as kinds of toasting-system components. `Toaster` composes those two subsystems and carries a `cycleTime` attribute. After notebook 04, `model.find("ToasterDemo::Toaster").parts()` returns two symbols: `heating` and `control`. +The Chapter 1 model states the toaster's purpose and its logical composition. `ToastingSystem`, the abstract subject, performs `ToastBread`: an action with typed `Bread` in and `Toast` out flows, carrying the acceptance language ("acceptable to its user") as its `doc`. `HeatingSystem` and `ControlSystem` are concrete placeholders with no content of their own. `Toaster`, the actual whole, specializes `ToastingSystem` (inheriting the performed purpose) and composes those two subsystems as its `heating` and `control` parts; it also carries a `cycleTime` slot, typed but with no value. After notebook 04, `model.find("ToasterDemo::Toaster").parts()` returns two symbols: `heating` and `control`. ## What this establishes -The model answers Chapter 1's engineering question: a toaster is a system with two subsystems, both traceable to a common abstract concept. The structure is implementation-agnostic. It states what the system is made of, not how each part works. That separation — structure now, behavior later — is what makes the model a useful engineering artifact rather than a design sketch. +The model answers Chapter 1's engineering question: a toaster is the subject that performs the purpose of transforming bread into toast, composed of a heating subsystem and a control subsystem, neither of which yet commits to a mechanism, an interface, or a value. `cycleTime` stays an empty, unit-bearing slot rather than a chosen number, because how long a cycle actually takes is a result the design will produce, not a choice made here. That separation (purpose and arrangement now, mechanisms and values later) is what makes the model a useful engineering artifact rather than a design sketch. ## What comes next -Chapter 2 asks what the toaster must do. It introduces requirements, attribute overrides for design variants, and the first engineering judgment record. The model from Chapter 1 is the starting point. +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 four constructs. The problem is structurally similar to the toaster but uses a different domain: declare the abstract concept, add a component type with an attribute, specialize it, and compose it into a top-level system. +**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. diff --git a/chapters/ch01-system-purpose/index.md b/chapters/ch01-system-purpose/index.md index 4d31c92..9281ce0 100644 --- a/chapters/ch01-system-purpose/index.md +++ b/chapters/ch01-system-purpose/index.md @@ -1,40 +1,40 @@ -# Chapter 1 — System and Purpose +# Chapter 1: System and Purpose ## Purpose -Chapter 1 asks: how do we describe a system in SysML v2 before we know how to build it? After completing this chapter, the model contains four part definitions and one composed system definition. +Chapter 1 asks: how do we describe a system in SysML v2 before we know how it is built? After completing this chapter, the model states the toaster's purpose as a performed, flow-typed function, and composes the toaster's logical arrangement from two subsystem placeholders. ## Ingredients | Notebook | Construct | Concept | |---|---|---| -| [01 — abstract part def](01-abstract-def.ipynb) | `abstract part def` + `doc` | A system concept no part may directly instantiate | -| [02 — part def and attributes](02-part-def.ipynb) | `part def` + `attribute : Real default` | Heater with numeric attribute; HeatingSystem and ControlSystem added as bare stubs | -| [03 — specialization](03-specialization.ipynb) | `:>` specialization | Declaring that one type is a kind of another | -| [04 — composition](04-composition.ipynb) | `part` usage | A system that owns named instances of its subsystem types | +| [01: abstract part def](01-abstract-def.ipynb) | `abstract part def` + `perform` + `action def` + `item def` | The system's purpose stated as a performed, flow-typed function, not a comment | +| [02: part def](02-part-def.ipynb) | `part def` | Two concrete subsystem placeholders, no attributes or hierarchy yet | +| [03: specialization](03-specialization.ipynb) | `:>` specialization | Declaring that the whole is a kind of the concept that names it | +| [04: composition](04-composition.ipynb) | `part` usage | A system that owns named instances of its subsystem types, plus an unvalued cycle-time slot | ## Equipment -See [setup](../../docs/setup.md) to provision Python, Node, and the OpenSysML binary before running any notebook. +See [setup](../../docs/setup.md) to provision Python and the OpenSysML binary before running any notebook. ## Method -The four notebooks build the model in one direction: from the most abstract (the system concept) toward the most concrete (the assembled system). Each notebook adds exactly one SysML construct. The model in each notebook is the full cumulative model up to that point. +The four notebooks build the model of the toaster, the subject the tutorial's layers describe. Notebook 01 states the toaster's purpose functionally: `ToastingSystem`, the abstract subject, performs `ToastBread`, an action with typed `Bread` in and `Toast` out flows and the acceptance language as its `doc`. Notebooks 02 through 04 build the logical composition: two concrete subsystem placeholders (`HeatingSystem`, `ControlSystem`) with no content yet, the specialization that makes the concrete whole (`Toaster`) a kind of the subject it names, and the composition that gives `Toaster` a `heating` part and a `control` part. -By the end of notebook 04, `Toaster` owns a `HeatingSystem` part and a `ControlSystem` part, both of which specialize `ToastingSystem`. That structure is the starting point for Chapter 2. +By the end of notebook 04, `Toaster :> ToastingSystem` performs the toasting purpose and owns both subsystems. Neither subsystem carries a mechanism, an interface, or a value yet. That is later chapters' work, once a mechanism has been selected for each. ## Expected result The Ch1 cumulative model contains: -- `ToastingSystem` (abstract, with `doc`) -- `Heater` (with `power : Real default = 800.0`) -- `HeatingSystem :> ToastingSystem` -- `ControlSystem :> ToastingSystem` -- `Toaster` (with `cycleTime : Real default = 120.0`, composed of `heating` and `control`) +- `Bread`, `Toast` (`item def`, typed flows) +- `ToastBread` (`action def`, `in bread : Bread`, `out toast : Toast`, with the acceptance `doc`) +- `ToastingSystem` (abstract, performs `ToastBread`) +- `HeatingSystem`, `ControlSystem` (concrete, bare placeholders) +- `Toaster :> ToastingSystem` (with `cycleTime : ISQ::DurationValue`, no value yet; composed of `heating` and `control`) `model.find("ToasterDemo::Toaster").parts()` returns two part symbols after notebook 04. ## Experiment -The [chapter exercise](../../exercises/ch01/exercise.ipynb) asks you to model a coffee maker using the same four constructs. Work through it after completing all four notebooks. +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. diff --git a/chapters/ch02-requirements/01-requirement-def.ipynb b/chapters/ch02-requirements/01-requirement-def.ipynb index 37e2080..f3d6ecc 100644 --- a/chapters/ch02-requirements/01-requirement-def.ipynb +++ b/chapters/ch02-requirements/01-requirement-def.ipynb @@ -1,120 +1,178 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - }, - "title": "Ch2 nb1 \u2014 requirement def" - }, - "cells": [ - { - "cell_type": "markdown", - "id": "cell-00", - "metadata": {}, - "source": [ - "## requirement def\n", - "\n", - "This notebook introduces `requirement def`; after running it you can declare a formal requirement with a subject and a constraint expression." - ] - }, - { - "cell_type": "markdown", - "id": "cell-01", - "metadata": {}, - "source": [ - "Chapter 1 established the structure of the toaster: `Toaster` composes `HeatingSystem` and `ControlSystem`, which specialize `ToastingSystem`. This notebook adds the first requirement: the toaster must complete a cycle in at most 180 seconds. A requirement in SysML v2 has a subject (the part being required), a constraint body, and optionally a documentation comment." - ] - }, - { - "cell_type": "code", - "id": "cell-02", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from pathlib import Path\n", - "import opensysml\n", - "from toaster.report import format_diagnostics\n", - "\n", - "conn = opensysml.connect(version=\"v0.9.0\")\n", - "source = Path(\"../../models/ch02-cumulative.sysml\").read_text()\n", - "print(source)\n", - "model = conn.load_from_content(source, strict=False)\n", - "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" - ] - }, - { - "cell_type": "markdown", - "id": "cell-03", - "metadata": {}, - "source": [ - "The `ch02-cumulative.sysml` file adds the first requirements construct: `requirement def TimelyToast` constrains `cycleTime <= 180.0` seconds with a typed `subject` and `require constraint` body. Two candidate parts \u2014 `nominal` (default 120 s) and `slow` (overridden to 200 s) \u2014 are declared for comparison. The `assert satisfy` pattern comes in Chapter 3; for now the candidates exist without a recorded claim." - ] - }, - { - "cell_type": "code", - "id": "cell-04", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# Negative control: a constraint that references a non-existent attribute\n", - "# raises \"unresolved member\" at the point of use.\n", - "bad_source = \"\"\"\n", - "package Bad {\n", - " private import ScalarValues::*;\n", - " part def Toaster { attribute cycleTime : Real default = 120.0; }\n", - " requirement def BadReq {\n", - " subject t : Toaster;\n", - " require constraint { t.nonExistentAttr <= 180.0 }\n", - " }\n", - "}\n", - "\"\"\"\n", - "bad = conn.load_from_content(bad_source, strict=False)\n", - "assert not bad.ok\n", - "print(\"Expected error:\", bad.diagnostics[0].message)" - ] - }, - { - "cell_type": "code", - "id": "cell-05", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "req = model.find(\"ToasterDemo::TimelyToast\")\n", - "assert req is not None\n", - "print(f\"requirement kind: {req.kind}\")\n", - "print(f\"requirement id : {req.id}\")\n", - "\n", - "for e in model.query():\n", - " d = e.as_dict()\n", - " if d[\"@type\"] == \"RequirementDefinition\":\n", - " print(f\"RequirementDefinition: {d['qualifiedName']}\")\n", - "conn.close()" - ] - }, - { - "cell_type": "markdown", - "id": "cell-06", - "metadata": {}, - "source": [ - "`requirement def TimelyToast { subject toaster : Toaster; require constraint { toaster.cycleTime <= 180.0 } }` is the A-F declaration; OpenSysML parses the constraint and registers the requirement (O-S); `model.find()` returns the symbol and `model.query()` lists it as a RequirementDefinition (E)." - ] - }, - { - "cell_type": "markdown", - "id": "cell-07", - "metadata": {}, - "source": [ - "Try the chapter exercise in `exercises/ch02/exercise.ipynb`: declare a `TemperatureReq` that requires `brewTemp <= 96.0` and confirm it loads." - ] - } - ] + "nbformat": 4, + "nbformat_minor": 5, + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + }, + "title": "Ch2 nb1: requirement def" + }, + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": [ + "## requirement def\n", + "\n", + "This notebook introduces `requirement def`; after running it you can declare a formal requirement with a subject and a constraint expression." + ] + }, + { + "cell_type": "markdown", + "id": "cell-01", + "metadata": {}, + "source": [ + "Chapter 1 established the structure of the toaster: `Toaster` specializes `ToastingSystem` and composes `HeatingSystem` and `ControlSystem`. A complete requirement has three parts (inspired by Brian Douglas, Part 4): a description of the need, a rationale for why that need is valid, and a verification method. This notebook declares `TimelyToast` with description and rationale using the SysML v2 `doc` comment (§7.21.2); Chapter 3 adds the formal verification case (§7.24). Each code cell contains a commented-out `editor.add_*()` call showing the future Editor API equivalent; these are informational; run the cell as written." + ] + }, + { + "cell_type": "code", + "id": "cell-02", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": [ + "from pathlib import Path\n", + "import opensysml\n", + "from toaster.report import format_diagnostics\n", + "\n", + "conn = opensysml.connect(version=\"v0.9.0\")\n", + "\n", + "# editor.add_requirement_def(owner='ToasterDemo', name='TimelyToast', doc=..., subject_type='Toaster') when API ships\n", + "# spec: SysML v2 formal/2026-03-02 §7.21.2: doc gives the informal text (description + rationale)\n", + "TIMELY_TOAST_REQ = \"\"\"\\\n", + "requirement def TimelyToast {\n", + " doc /*\n", + " * The toaster shall complete a toasting cycle in at most 180 seconds.\n", + " * Rationale: kitchen workflows typically span 5-15 minutes; a cycle\n", + " * exceeding 3 minutes delays meal preparation and falls outside where\n", + " * and how a user prepares a meal.\n", + " */\n", + " subject toaster : Toaster;\n", + "\"\"\"\n", + "print(TIMELY_TOAST_REQ)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-03", + "metadata": {}, + "source": "`TimelyToast` is a requirement definition. The `doc` block is the informal text (§7.21.2): it combines the description of the need with the rationale for the 180-second threshold. The `subject toaster : Toaster` declaration names the part type this requirement is about." + }, + { + "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)", + "metadata": {}, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "5aa00cff", + "source": "The `require constraint` body contains the condition that must hold: `toaster.cycleTime <= 180.0 [SI::s]`. This is a logical proposition over a subject attribute; the constraint form is stated now, but nothing yet tests a specific usage against it. Chapter 3 adds `assert satisfy`, the mechanism that tests a specific usage against a requirement.", + "metadata": {} + }, + { + "cell_type": "code", + "id": "1899381d", + "source": "# editor.add_part(owner='ToasterDemo', name='nominal', type='Toaster') when API ships\nNOMINAL_PART = \"part nominal : Toaster;\"\nprint(NOMINAL_PART)", + "metadata": {}, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "b49faf2d", + "source": [ + "`nominal` is a package-level `part` usage: a `Toaster` with no attribute values set. `cycleTime` carries no default, since Chapter 1 removed it. It is a named usage of the subject, not a physical candidate: it adds nothing beyond `Toaster` itself. The next notebook introduces `slow`, a fixture built to fail the requirement's check: its cycle time will be a deliberately injected fault value, not a plausible design point." + ], + "metadata": {} + }, + { + "cell_type": "code", + "id": "e77ee323", + "source": "TOASTER_INCREMENT = f\"{TIMELY_TOAST_REQ}{CONSTRAINT_BODY}\\n}}\\n{NOMINAL_PART}\"\nprint(TOASTER_INCREMENT)\nsource = Path(\"../../models/ch02-cumulative.sysml\").read_text()\nmodel = conn.load_from_content(source, strict=False)\nassert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"", + "metadata": {}, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "cell-08b", + "metadata": {}, + "source": [ + "`TimelyToast` and `nominal` load with no diagnostics. The next cell checks what happens when a constraint references an attribute that does not exist." + ] + }, + { + "cell_type": "code", + "id": "cell-04", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": [ + "# Negative control: a constraint that references a non-existent attribute\n", + "# raises \"unresolved member\" at the point of use.\n", + "bad_source = \"\"\"\n", + "package Bad {\n", + " private import ScalarValues::*;\n", + " part def Toaster { attribute cycleTime : Real default = 120.0; }\n", + " requirement def BadReq {\n", + " subject t : Toaster;\n", + " require constraint { t.nonExistentAttr <= 180.0 }\n", + " }\n", + "}\n", + "\"\"\"\n", + "bad = conn.load_from_content(bad_source, strict=False)\n", + "assert not bad.ok\n", + "print(\"Expected error:\", bad.diagnostics[0].message)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-09b", + "metadata": {}, + "source": [ + "With the negative control confirmed, the next cell looks up `TimelyToast` in the loaded model directly." + ] + }, + { + "cell_type": "code", + "id": "cell-05", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": [ + "req = model.find(\"ToasterDemo::TimelyToast\")\n", + "assert req is not None\n", + "print(f\"requirement kind: {req.kind}\")\n", + "print(f\"requirement id : {req.id}\")\n", + "\n", + "for e in model.query():\n", + " d = e.as_dict()\n", + " if d[\"@type\"] == \"RequirementDefinition\":\n", + " print(f\"RequirementDefinition: {d['qualifiedName']}\")\n", + "conn.close()" + ] + }, + { + "cell_type": "markdown", + "id": "cell-06", + "metadata": {}, + "source": "The `requirement def TimelyToast { doc /* ... */ subject toaster : Toaster; require constraint { toaster.cycleTime <= 180.0 [SI::s] } }` printed above loaded without error, and `model.find()` returns its symbol while `model.query()` lists it as a `RequirementDefinition`, confirming it's now part of the model." + }, + { + "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." + } + ] } \ No newline at end of file diff --git a/chapters/ch02-requirements/02-assumptions.ipynb b/chapters/ch02-requirements/02-assumptions.ipynb index 22d8ee2..c04c29c 100644 --- a/chapters/ch02-requirements/02-assumptions.ipynb +++ b/chapters/ch02-requirements/02-assumptions.ipynb @@ -1,122 +1,145 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - }, - "title": "Ch2 nb2 \u2014 attribute override" + "nbformat": 4, + "nbformat_minor": 5, + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" }, - "cells": [ - { - "cell_type": "markdown", - "id": "cell-00", - "metadata": {}, - "source": [ - "## attribute override\n", - "\n", - "This notebook introduces `attribute :>>` override; after running it you can express named variants of a design by overriding inherited attribute values." - ] - }, - { - "cell_type": "markdown", - "id": "cell-01", - "metadata": {}, - "source": [ - "The previous notebook declared that the toaster must complete a cycle in at most 180 seconds. Before checking whether the design meets that requirement, we need to state the operating conditions we are designing for. This notebook introduces `attribute :>>` override: a part usage can redeclare an inherited attribute with a specific value, encoding the assumption being evaluated." - ] - }, - { - "cell_type": "code", - "id": "cell-02", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from pathlib import Path\n", - "import opensysml\n", - "from toaster.report import format_diagnostics\n", - "\n", - "conn = opensysml.connect(version=\"v0.9.0\")\n", - "source = Path(\"../../models/ch02-cumulative.sysml\").read_text()\n", - "print(source)\n", - "model = conn.load_from_content(source, strict=False)\n", - "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" - ] - }, - { - "cell_type": "markdown", - "id": "cell-03", - "metadata": {}, - "source": [ - "The `ch02-cumulative.sysml` file adds the first requirements construct: `requirement def TimelyToast` constrains `cycleTime <= 180.0` seconds with a typed `subject` and `require constraint` body. Two candidate parts \u2014 `nominal` (default 120 s) and `slow` (overridden to 200 s) \u2014 are declared for comparison. The `assert satisfy` pattern comes in Chapter 3; for now the candidates exist without a recorded claim." - ] - }, - { - "cell_type": "code", - "id": "cell-04", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# Negative control: :>> can only override an attribute that already\n", - "# exists in the inherited chain. Overriding a non-existent name fails.\n", - "bad_source = \"\"\"\n", - "package Bad {\n", - " private import ScalarValues::*;\n", - " part def Toaster { attribute cycleTime : Real default = 120.0; }\n", - " part slow : Toaster {\n", - " attribute :>> nonExistent = 200.0;\n", - " }\n", - "}\n", - "\"\"\"\n", - "bad = conn.load_from_content(bad_source, strict=False)\n", - "assert not bad.ok\n", - "print(\"Expected error:\", bad.diagnostics[0].message)" - ] - }, - { - "cell_type": "code", - "id": "cell-05", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "nominal = model.find(\"ToasterDemo::nominal\")\n", - "slow = model.find(\"ToasterDemo::slow\")\n", - "assert nominal is not None\n", - "assert slow is not None\n", - "\n", - "print(\"nominal:\", nominal.id, \"| kind:\", nominal.kind)\n", - "print(\"slow :\", slow.id, \"| kind:\", slow.kind)\n", - "\n", - "slow_attrs = slow.attributes()\n", - "print(f\"slow overridden attributes ({len(slow_attrs)}):\")\n", - "for a in slow_attrs:\n", - " print(f\" {a.id}\")\n", - "conn.close()" - ] - }, - { - "cell_type": "markdown", - "id": "cell-06", - "metadata": {}, - "source": [ - "`part slow : Toaster { attribute :>> cycleTime = 200.0; }` is the A-F override; OpenSysML resolves the redeclaration against the inherited attribute from `Toaster` (O-S); `slow.attributes()` returns the overridden symbol (E)." - ] - }, - { - "cell_type": "markdown", - "id": "cell-07", - "metadata": {}, - "source": [ - "Try the chapter exercise in `exercises/ch02/exercise.ipynb`: create a `weakBrew` variant of your `CoffeeMaker` with a lower `brewTemp` and confirm the override loads." - ] - } - ] + "language_info": { + "name": "python" + }, + "title": "Ch2 nb2: attribute override" + }, + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": "## attribute override\n\nThis notebook introduces `attribute :>>` override; after running it you can override an inherited attribute value on a named usage." + }, + { + "cell_type": "markdown", + "id": "cell-01", + "metadata": {}, + "source": "The previous notebook declared that the toaster must complete a cycle in at most 180 seconds, and added `nominal`, a usage of `Toaster` with no attribute values set. This notebook introduces `attribute :>>` override: a part usage can redeclare an inherited attribute with a specific value. We use it to build `slow`: not a design variant or an operating condition, but a fixture whose cycle time is a deliberately injected fault value, built to exercise the requirement's failing branch." + }, + { + "cell_type": "code", + "id": "cell-02", + "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_part(owner='ToasterDemo', name='slow', type='Toaster') when API ships\nSLOW_PART = \"part slow : Toaster {\"\nprint(SLOW_PART)" + }, + { + "cell_type": "markdown", + "id": "cell-03", + "metadata": {}, + "source": "`slow` is a named usage of `Toaster`, built to fail `TimelyToast`'s check. The open brace introduces a body where the inherited attribute will be overridden with a deliberately injected fault value." + }, + { + "cell_type": "code", + "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", + "# 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", + "print(CYCLE_OVERRIDE)" + ], + "metadata": {}, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "62882e06", + "source": "`attribute :>> cycleTime` redeclares the inherited `cycleTime` under `slow`. The `:>>` operator is a redefinition; it can only name an attribute that already exists in the type chain. The value it is bound to, `200.0 [SI::s]`, is a separate matter: writing `= value` with no `default` keyword gives a fixed binding, while `default = value` (which `Toaster::cycleTime` no longer has, since Chapter 1) would bind a value that a further usage could still override. `slow`'s 200 seconds is deliberately fixed and deliberately faulty: chosen to exceed `TimelyToast`'s 180-second bound, not to represent a plausible design point.", + "metadata": {} + }, + { + "cell_type": "code", + "id": "ec2c4402", + "source": "TOASTER_INCREMENT = f\"{SLOW_PART}\\n{CYCLE_OVERRIDE}\\n}}\"\nprint(TOASTER_INCREMENT)\nsource = Path(\"../../models/ch02-cumulative.sysml\").read_text()\nmodel = conn.load_from_content(source, strict=False)\nassert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"", + "metadata": {}, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "cell-06b", + "metadata": {}, + "source": [ + "`slow` loads with its overridden `cycleTime` and no diagnostics. The next cell checks what `:>>` does when there is no inherited attribute to override." + ] + }, + { + "cell_type": "code", + "id": "cell-04", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": [ + "# Negative control: :>> can only override an attribute that already\n", + "# exists in the inherited chain. Overriding a non-existent name fails.\n", + "bad_source = \"\"\"\n", + "package Bad {\n", + " private import ScalarValues::*;\n", + " part def Toaster { attribute cycleTime : Real default = 120.0; }\n", + " part slow : Toaster {\n", + " attribute :>> nonExistent = 200.0;\n", + " }\n", + "}\n", + "\"\"\"\n", + "bad = conn.load_from_content(bad_source, strict=False)\n", + "assert not bad.ok\n", + "print(\"Expected error:\", bad.diagnostics[0].message)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-07b", + "metadata": {}, + "source": [ + "With the negative control confirmed, the next cell looks up both `nominal` and `slow` in the loaded model." + ] + }, + { + "cell_type": "code", + "id": "cell-05", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": [ + "nominal = model.find(\"ToasterDemo::nominal\")\n", + "slow = model.find(\"ToasterDemo::slow\")\n", + "assert nominal is not None\n", + "assert slow is not None\n", + "\n", + "print(\"nominal:\", nominal.id, \"| kind:\", nominal.kind)\n", + "print(\"slow :\", slow.id, \"| kind:\", slow.kind)\n", + "\n", + "slow_attrs = slow.attributes()\n", + "print(f\"slow overridden attributes ({len(slow_attrs)}):\")\n", + "for a in slow_attrs:\n", + " print(f\" {a.id}\")\n", + "conn.close()" + ] + }, + { + "cell_type": "markdown", + "id": "cell-06", + "metadata": {}, + "source": "The `part slow : Toaster { attribute :>> cycleTime = 200.0 [SI::s]; }` printed above loaded without error, and `slow.attributes()` returns the overridden symbol shown above, confirming the redeclaration is now part of the model." + }, + { + "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." + } + ] } \ No newline at end of file diff --git a/chapters/ch02-requirements/03-judgment-context.ipynb b/chapters/ch02-requirements/03-judgment-context.ipynb index 848a86b..8fa2f11 100644 --- a/chapters/ch02-requirements/03-judgment-context.ipynb +++ b/chapters/ch02-requirements/03-judgment-context.ipynb @@ -1,137 +1,189 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - }, - "title": "Ch2 nb3 \u2014 asserted context" - }, - "cells": [ - { - "cell_type": "markdown", - "id": "cell-00", - "metadata": {}, - "source": [ - "## asserted context\n", - "\n", - "This notebook introduces the `asserted_context` judgment record; after running it you can declare and inspect the assumptions that frame an engineering requirement." - ] - }, - { - "cell_type": "markdown", - "id": "cell-01", - "metadata": {}, - "source": [ - "The model now has a requirement (`TimelyToast`) and two design variants (`nominal` and `slow`). Before asking whether either variant satisfies the requirement, we need to declare the context: what do we assume about the operating environment? An `asserted_context` record (Hawkins 2011 \u00a73.2) documents one such assumption. The context record does not claim the design is correct \u2014 it claims the assumption is appropriate for the evaluation we are about to perform." - ] - }, - { - "cell_type": "code", - "id": "cell-02", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from pathlib import Path\n", - "import opensysml\n", - "from toaster.report import format_diagnostics\n", - "\n", - "conn = opensysml.connect(version=\"v0.9.0\")\n", - "source = Path(\"../../models/ch02-cumulative.sysml\").read_text()\n", - "print(source)\n", - "model = conn.load_from_content(source, strict=False)\n", - "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" - ] - }, - { - "cell_type": "markdown", - "id": "cell-03", - "metadata": {}, - "source": [ - "The `ch02-cumulative.sysml` file adds the first requirements construct: `requirement def TimelyToast` constrains `cycleTime <= 180.0` seconds with a typed `subject` and `require constraint` body. Two candidate parts \u2014 `nominal` (default 120 s) and `slow` (overridden to 200 s) \u2014 are declared for comparison. The `assert satisfy` pattern comes in Chapter 3; for now the candidates exist without a recorded claim." - ] - }, - { - "cell_type": "code", - "id": "cell-04", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# Negative control: a constraint that references an attribute not in scope\n", - "# confirms that the model enforces referential integrity in constraints.\n", - "bad_source = \"\"\"\n", - "package Bad {\n", - " private import ScalarValues::*;\n", - " part def Toaster { attribute cycleTime : Real default = 120.0; }\n", - " requirement def BadReq {\n", - " subject t : Toaster;\n", - " require constraint { t.nonExistentAttr <= 180.0 }\n", - " }\n", - "}\n", - "\"\"\"\n", - "bad = conn.load_from_content(bad_source, strict=False)\n", - "assert not bad.ok\n", - "print(\"Expected error:\", bad.diagnostics[0].message)" - ] - }, - { - "cell_type": "code", - "id": "cell-05", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from toaster.evidence import ReviewRecord, hash_content, validate_record\n", - "\n", - "context_record = ReviewRecord(\n", - " identifier=\"AC-001\",\n", - " kind=\"asserted_context\",\n", - " claim=\"120 seconds is the nominal cycle time for standard sliced bread.\",\n", - " model_ref=\"ToasterDemo::nominal\",\n", - " content_hash=hash_content(source),\n", - " scope=\"ToasterDemo\",\n", - " criteria=\"attribute cycleTime : Real default = 120.0\",\n", - " premises=[],\n", - " assumption_refs=[],\n", - " evidence_refs=[\"ToasterDemo::Toaster::cycleTime default = 120.0\"],\n", - " rationale=\"120s is consistent with manufacturer guidance for domestic sliced bread.\",\n", - " counterevidence=\"Thick-cut and frozen bread may require 180-240s.\",\n", - " residual_uncertainties=\"User preference variation not modeled.\",\n", - " disposition=\"pending\",\n", - " dependency_freshness=\"current\",\n", - " engineering_conclusion=\"undetermined\",\n", - " record_kind=\"worked_example\",\n", - ")\n", - "\n", - "errors = validate_record(context_record)\n", - "print(f\"Record: {context_record.identifier} | kind: {context_record.kind}\")\n", - "print(f\"Claim: {context_record.claim}\")\n", - "print(f\"Validation errors: {errors}\")\n", - "conn.close()" - ] - }, - { - "cell_type": "markdown", - "id": "cell-06", - "metadata": {}, - "source": [ - "The Hawkins \u00a73.2 schema specifies what an `asserted_context` record must contain (A-F); filling and validating the `ReviewRecord` in Python enacts that schema (O-S); the printed record shows the claim, rationale, and counterevidence populated (E)." - ] - }, - { - "cell_type": "markdown", - "id": "cell-07", - "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." - ] - } - ] -} \ No newline at end of file + "nbformat": 4, + "nbformat_minor": 5, + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + }, + "title": "Ch2 nb3: asserted context" + }, + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": [ + "## asserted context\n", + "\n", + "This notebook introduces the `asserted_context` judgment record; after running it you can declare and inspect the assumptions that frame an engineering requirement." + ] + }, + { + "cell_type": "markdown", + "id": "cell-01", + "metadata": {}, + "source": [ + "The model now has a requirement (`TimelyToast`) and two named usages of `Toaster`: `nominal`, which carries no cycle-time value, and `slow`, built with a deliberately injected fault cycle time to exercise the requirement's failing branch. Before asking whether either satisfies the requirement, we need to declare the context: what do we assume about the operating environment? An `asserted_context` record (Hawkins 2011 §3.2) documents one such assumption. The context record does not claim the design is correct. It claims that the assumption used for the cycle-time estimate is appropriate, not that any evaluation has been performed." + ] + }, + { + "cell_type": "code", + "id": "cell-02", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": [ + "from pathlib import Path\n", + "import opensysml\n", + "from toaster.report import format_diagnostics\n", + "\n", + "conn = opensysml.connect(version=\"v0.9.0\")\n", + "source = Path(\"../../models/ch02-cumulative.sysml\").read_text()\n", + "print(source)\n", + "model = conn.load_from_content(source, strict=False)\n", + "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" + ] + }, + { + "cell_type": "markdown", + "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." + }, + { + "cell_type": "code", + "id": "cell-04", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": [ + "# Negative control: a constraint that references an attribute not in scope\n", + "# confirms that the model enforces referential integrity in constraints.\n", + "bad_source = \"\"\"\n", + "package Bad {\n", + " private import ScalarValues::*;\n", + " part def Toaster { attribute cycleTime : Real default = 120.0; }\n", + " requirement def BadReq {\n", + " subject t : Toaster;\n", + " require constraint { t.nonExistentAttr <= 180.0 }\n", + " }\n", + "}\n", + "\"\"\"\n", + "bad = conn.load_from_content(bad_source, strict=False)\n", + "assert not bad.ok\n", + "print(\"Expected error:\", bad.diagnostics[0].message)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-05", + "metadata": {}, + "source": [ + "With the model side confirmed, the next cell turns to the judgment side: recording the assumption behind the cycle-time estimate as a Hawkins-style `asserted_context` record." + ] + }, + { + "cell_type": "markdown", + "id": "cell-06", + "metadata": {}, + "source": "`AC-001` states the claim first: the placeholder cycle-time estimate this chapter uses, and the model element it's an assumption about." + }, + { + "cell_type": "code", + "id": "cell-07", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "from toaster.evidence import ReviewRecord, hash_content, validate_record\n\nclaim = (\"Approximately 120 seconds is assumed, for illustration only, as a plausible \"\n \"nominal cycle time for standard sliced bread, a placeholder pending the \"\n \"mechanism-and-energy-balance derivation a later chapter performs, not a value \"\n \"drawn from any real-world source.\")\nmodel_ref = (\"ToasterDemo::nominal\")\nprint(claim)" + }, + { + "cell_type": "markdown", + "id": "cell-08", + "metadata": {}, + "source": "`scope` names where in the model the assumption applies; `criteria` states the illustrative range the estimate must fall inside, so a reader can judge whether the claim is appropriate for what it's used for." + }, + { + "cell_type": "code", + "id": "cell-09", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "scope = (\"ToasterDemo\")\ncriteria = (\"This chapter adopts an illustrative placeholder range of 90-150 seconds for a \"\n \"toaster's nominal cycle time toasting standard sliced bread. The range is \"\n \"invented for this tutorial and is not drawn from any manufacturer data, \"\n \"measurement, or cited source.\")\nprint(criteria)" + }, + { + "cell_type": "markdown", + "id": "cell-10", + "metadata": {}, + "source": "`premises` is empty (the estimate isn't derived from anything else in this chapter); `assumption_refs` names the one assumption it rests on directly." + }, + { + "cell_type": "code", + "id": "cell-11", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "premises = ([])\nassumption_refs = ([\n \"A-CH02-1: illustrative placeholder cycle-time range (90-150s) for standard \"\n \"sliced bread, invented for this tutorial; no real-world source exists for it.\",\n ])\nprint(assumption_refs)" + }, + { + "cell_type": "markdown", + "id": "cell-12", + "metadata": {}, + "source": "`evidence_refs` states plainly that there is no real evidence behind this number, only the stated assumption; `rationale` explains why 120 seconds was picked within that range anyway." + }, + { + "cell_type": "code", + "id": "cell-13", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "evidence_refs = ([\n \"None: the value is a stated placeholder assumption (A-CH02-1), not evidence \"\n \"from a real source, and not the model's own declared value. \"\n \"Toaster::cycleTime carries no value in this chapter.\",\n ])\nrationale = (\"120 seconds sits within the illustrative placeholder range (A-CH02-1) and is \"\n \"used only as a stand-in estimate; it is not derived from any mechanism or \"\n \"energy-balance analysis in this chapter, and it is not backed by any \"\n \"real-world data.\")\nprint(rationale)" + }, + { + "cell_type": "markdown", + "id": "cell-14", + "metadata": {}, + "source": "The challenge: `counterevidence` names a real case the placeholder doesn't cover, and `residual_uncertainties` says what a reader must not conclude from this record (Hawkins' trustworthiness: naming the record's own limits, not hiding them)." + }, + { + "cell_type": "code", + "id": "cell-15", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "counterevidence = (\"Thick-cut and frozen bread may require 180-240s, which would exceed \"\n \"TimelyToast's 180-second bound. More fundamentally, the placeholder range \"\n \"itself is invented for this tutorial and has no real-world source, so it \"\n \"carries no evidentiary weight beyond illustrating the pattern.\")\nresidual_uncertainties = (\"User preference variation is not modeled. Because 120 seconds is an \"\n \"invented placeholder, not a derived or measured value, any comparison \"\n \"against the 180-second threshold is conditional on this assumption and must \"\n \"never be reported as a settled pass/fail verdict.\")\nprint(counterevidence)\nprint(residual_uncertainties)" + }, + { + "cell_type": "markdown", + "id": "cell-16", + "metadata": {}, + "source": "With every part named above, the context record assembles from them directly." + }, + { + "cell_type": "code", + "id": "cell-17", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "context_record = ReviewRecord(\n identifier=\"AC-001\",\n kind=\"asserted_context\",\n claim=claim,\n model_ref=model_ref,\n content_hash=hash_content(source),\n scope=scope,\n criteria=criteria,\n premises=premises,\n assumption_refs=assumption_refs,\n evidence_refs=evidence_refs,\n rationale=rationale,\n counterevidence=counterevidence,\n residual_uncertainties=residual_uncertainties,\n disposition=\"pending\",\n dependency_freshness=\"current\",\n engineering_conclusion=\"undetermined\",\n record_kind=\"worked_example\",\n)\n\nerrors = validate_record(context_record)\nprint(f\"Record: {context_record.identifier} | kind: {context_record.kind}\")\nprint(f\"Claim: {context_record.claim}\")\nprint(f\"Validation errors: {errors}\")\nconn.close()" + }, + { + "cell_type": "markdown", + "id": "cell-18", + "metadata": {}, + "source": "The Hawkins §3.2 schema fields were filled in above, and `validate_record` reports no errors, confirming the claim, rationale and counterevidence are populated and checked, not just printed." + }, + { + "cell_type": "markdown", + "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." + ] + } + ] +} diff --git a/chapters/ch02-requirements/conclusion.md b/chapters/ch02-requirements/conclusion.md index 39d6904..2e6830c 100644 --- a/chapters/ch02-requirements/conclusion.md +++ b/chapters/ch02-requirements/conclusion.md @@ -1,15 +1,15 @@ -# Chapter 2 — Conclusion +# Chapter 2: Conclusion ## What we built -The Chapter 2 model adds `TimelyToast`, a requirement definition that constrains `cycleTime` to at most 180 seconds for any `Toaster`. It also adds two named design variants: `nominal` (default 120 seconds) and `slow` (overridden to 200 seconds via `attribute :>>`). The Python side adds `context_record`, a `ReviewRecord` of kind `asserted_context` that declares the 120-second nominal condition as an assumption appropriate for evaluating the requirement. +The Chapter 2 model adds `TimelyToast`, a requirement definition whose constraint bounds its `Toaster` subject's `cycleTime` to at most 180 seconds. It also adds two named usages of `Toaster`, not design variants: `nominal`, whose `cycleTime` carries no value, and `slow`, a deliberately faulty fixture whose `cycleTime` is fixed at 200 seconds via `attribute :>>`. The Python side adds `context_record`, a `ReviewRecord` of kind `asserted_context` that records an illustrative placeholder estimate of nominal cycle time (about 120 seconds, explicitly labeled as invented for this tutorial, not drawn from any real-world source), used only as context pending the value a later chapter derives. ## What this establishes -The chapter answers its engineering question: we now have a formal requirement and two competing conditions to evaluate against it. One passes (120 seconds is within the 180-second bound), one fails (200 seconds is not). The context record makes the assumption explicit before any evaluation takes place. That ordering matters: a judgment about satisfaction is only meaningful when the context is stated. +The chapter answers its engineering question: we now have a formal requirement and two named usages built to exercise it. `nominal` carries no cycle-time value yet; `slow` is a deliberately faulty fixture whose fixed 200-second cycle time exceeds the 180-second bound, built to exercise the requirement's failing branch, not to represent a competing design. No analysis in this chapter derives a cycle time, so neither usage's relationship to the bound is reported as a settled pass/fail verdict; the context record's estimate for `nominal` is likewise conditional on its stated assumption, not a derived value. The context record makes the assumption explicit before any such comparison is made. That ordering matters: a judgment about satisfaction is only meaningful when the context is stated. ## What comes next -Chapter 3 introduces `requirement` usage (applying a requirement to a specific part) and `calc def` (defining a reusable calculation). It also introduces `assert satisfy ... by ...`, which connects a design variant to a requirement claim. +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`. diff --git a/chapters/ch02-requirements/index.md b/chapters/ch02-requirements/index.md index 634da7f..31a579c 100644 --- a/chapters/ch02-requirements/index.md +++ b/chapters/ch02-requirements/index.md @@ -1,16 +1,16 @@ -# Chapter 2 — Requirements and Assumptions +# Chapter 2: Requirements and Assumptions ## Purpose -Chapter 2 asks: what must the toaster do, and what do we assume about the conditions under which it operates? After completing this chapter, the model has a requirement definition, two named design variants, and the first engineering judgment record. +Chapter 2 asks: what must the toaster do, and what do we assume about the conditions under which it operates? After completing this chapter, the model has a requirement definition, two named usages of `Toaster`, and the first engineering judgment record. ## Ingredients | Notebook | Construct / operation | Concept | |---|---|---| -| [01 — requirement def](01-requirement-def.ipynb) | `requirement def` + `subject` + `require constraint` | A formal statement of what the system must satisfy | -| [02 — attribute override](02-assumptions.ipynb) | `attribute :>>` override | Named variants that redeclare an inherited attribute value | -| [03 — asserted context](03-judgment-context.ipynb) | `asserted_context` record | An assumption that frames the requirement evaluation | +| [01: requirement def](01-requirement-def.ipynb) | `requirement def` + `subject` + `require constraint` | A formal statement of what the system must satisfy | +| [02: attribute override](02-assumptions.ipynb) | `attribute :>>` override | A named usage that redeclares an inherited attribute with a deliberately faulty value | +| [03: asserted context](03-judgment-context.ipynb) | `asserted_context` record | An assumption underlying a cycle-time estimate, not an evaluation of the requirement | ## Equipment @@ -18,9 +18,9 @@ See [setup](../../docs/setup.md). Chapter 2 also uses `toaster.evidence.ReviewRe ## Method -Notebook 01 adds the requirement to the cumulative model from Chapter 1. Notebook 02 adds the `nominal` and `slow` variants by overriding `cycleTime`. Notebook 03 introduces the first judgment record: an `asserted_context` that declares the 120-second cycle assumption before we evaluate whether any variant satisfies the requirement. +Notebook 01 adds the requirement definition to the cumulative model from Chapter 1, along with `nominal`, a bare usage of `Toaster` with no attribute values set. Notebook 02 adds `slow`, overriding `cycleTime` with a deliberately injected fault value that exceeds the requirement's bound: a fixture for the requirement's failing branch, not a design variant or an operating condition. Notebook 03 introduces the first judgment record: an `asserted_context` that records an assumed estimate of nominal cycle time before any comparison against the requirement is reported. -The judgment record is the first example of Hawkins et al. (2011) §3.2 in the tutorial. It does not assert that the design is correct — it asserts that the assumption is appropriate for the context. +The judgment record is the first example of Hawkins et al. (2011) §3.2 in the tutorial. It does not assert that the design is correct. It asserts that the assumption is appropriate for the context. ## Expected result @@ -28,7 +28,8 @@ After notebook 03: - `TimelyToast` is a `RequirementDefinition` in the model - `nominal` and `slow` are `PartUsage` instances of `Toaster` -- `slow` has `cycleTime = 200.0` via `attribute :>>` +- `nominal`'s `cycleTime` carries no value (Chapter 1 removed the default) +- `slow` has `cycleTime = 200.0` via `attribute :>>`, a fixed, deliberately injected fault value - `context_record` is a Python `ReviewRecord` with `kind="asserted_context"` and `disposition="pending"` ## Experiment diff --git a/chapters/ch03-measures/01-moe-definition.ipynb b/chapters/ch03-measures/01-moe-definition.ipynb index 648eeb3..1ad8436 100644 --- a/chapters/ch03-measures/01-moe-definition.ipynb +++ b/chapters/ch03-measures/01-moe-definition.ipynb @@ -1,116 +1,307 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, - "cells": [ - { - "cell_type": "markdown", - "id": "cell-00", - "metadata": {}, - "source": [ - "## requirement usage\n", - "\n", - "This notebook introduces `requirement` usage and `assert satisfy ... by ...`; after running it you can apply a requirement definition to named design candidates and record which ones satisfy it." - ] - }, - { - "cell_type": "markdown", - "id": "cell-01", - "metadata": {}, - "source": [ - "Chapter 2 defined `TimelyToast` as a requirement definition with a `Toaster` subject. A requirement definition describes *what* must hold; a requirement usage applies it to actual candidates. This notebook adds `requirement timely : TimelyToast;` and two assert-satisfy claims \u2014 one for the `nominal` variant (120 s) and one for `slow` (200 s)." - ] - }, - { - "cell_type": "code", - "id": "cell-02", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from pathlib import Path\n", - "import opensysml\n", - "from toaster.report import format_diagnostics\n", - "\n", - "conn = opensysml.connect(version=\"v0.9.0\")\n", - "source = Path(\"../../models/ch03-cumulative.sysml\").read_text()\n", - "print(source)\n", - "model = conn.load_from_content(source, strict=False)\n", - "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" - ] - }, - { - "cell_type": "markdown", - "id": "cell-03", - "metadata": {}, - "source": [ - "The `ch03-cumulative.sysml` file adds the satisfy-assertion pattern: a `requirement` usage `timely` and `assert satisfy timely by nominal/slow` declarations inside an `evidence` block record which candidates are claimed to meet the requirement. It also adds `calc def DeliveredEnergy`, which computes `power * duration * efficiency` \u2014 the symbolic model that Chapter 7's parameter sweep binds to numpy." - ] - }, - { - "cell_type": "code", - "id": "cell-04", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# Negative control: assert satisfy against an undefined requirement reference\n", - "# raises \"unresolved reference\" at the assert-satisfy site.\n", - "bad_source = \"\"\"\n", - "package Bad {\n", - " private import ScalarValues::*;\n", - " part def Toaster { attribute cycleTime : Real default = 120.0; }\n", - " part nominal : Toaster;\n", - " part evidence { assert satisfy undefinedReq by nominal; }\n", - "}\n", - "\"\"\"\n", - "bad = conn.load_from_content(bad_source, strict=False)\n", - "assert not bad.ok\n", - "print(\"Expected error:\", bad.diagnostics[0].message)" - ] - }, - { - "cell_type": "code", - "id": "cell-05", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "req_usage = model.find(\"ToasterDemo::timely\")\n", - "assert req_usage is not None\n", - "print(f\"requirement usage kind: {req_usage.kind}\")\n", - "\n", - "for e in model.query():\n", - " d = e.as_dict()\n", - " if d.get(\"@type\") == \"RequirementUsage\":\n", - " print(f\"RequirementUsage: {d['qualifiedName']}\")\n", - "conn.close()" - ] - }, - { - "cell_type": "markdown", - "id": "cell-06", - "metadata": {}, - "source": [ - "`requirement timely : TimelyToast; part evidence { assert satisfy timely by nominal; assert satisfy timely by slow; }` is the A-F declaration; OpenSysML parses the satisfy relationships and registers them (O-S); `model.find()` returns the RequirementUsage symbol and `model.query()` lists it (E)." - ] - }, - { - "cell_type": "markdown", - "id": "cell-07", - "metadata": {}, - "source": [ - "Try the chapter exercise in `exercises/ch03/exercise.ipynb`: declare a `TemperatureReq` usage and assert satisfy for your `nominal` and `hot` coffee maker candidates." - ] - } - ] -} \ No newline at end of file + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": [ + "## requirement usage\n", + "\n", + "This notebook introduces `requirement` usage; after running it you can apply `TimelyToast` to a named element and record whether the measure it constrains is a measure of effectiveness or a measure of performance." + ] + }, + { + "cell_type": "markdown", + "id": "cell-01", + "metadata": {}, + "source": [ + "Chapter 2 defined `TimelyToast` as a requirement definition with a `Toaster` subject. A requirement definition describes what must hold; a requirement usage applies it. This notebook adds `requirement timely : TimelyToast;`, then records the judgment that decides what kind of measure `timely` is: a case-specific decision about who cares and whether the measure names acceptance or performance, not one read off a file name." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-02", + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "import opensysml\n", + "from toaster.report import format_diagnostics\n", + "\n", + "conn = opensysml.connect(version=\"v0.9.0\")\n", + "\n", + "# editor.add_requirement(owner='ToasterDemo', name='timely', type='TimelyToast') when API ships\n", + "TIMELY_USAGE = \"requirement timely : TimelyToast;\"\n", + "print(TIMELY_USAGE)\n", + "\n", + "TOASTER_INCREMENT = TIMELY_USAGE\n", + "source = Path(\"../../models/ch03-cumulative.sysml\").read_text()\n", + "model = conn.load_from_content(source, strict=False)\n", + "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" + ] + }, + { + "cell_type": "markdown", + "id": "cell-03", + "metadata": {}, + "source": [ + "The requirement usage loads without diagnostics. The next cell checks what happens when a requirement usage names a definition that does not exist." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-04", + "metadata": {}, + "outputs": [], + "source": [ + "# Negative control: a requirement usage referencing an undefined requirement\n", + "# definition raises \"unresolved reference\" at the usage site.\n", + "bad_source = \"\"\"\n", + "package Bad {\n", + " private import ScalarValues::*;\n", + " requirement timely_bad : UndefinedRequirement;\n", + "}\n", + "\"\"\"\n", + "bad = conn.load_from_content(bad_source, strict=False)\n", + "assert not bad.ok\n", + "print(\"Expected error:\", bad.diagnostics[0].message)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-05", + "metadata": {}, + "source": [ + "With the negative control confirmed, the next cell looks up the requirement usage in the loaded model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-06", + "metadata": {}, + "outputs": [], + "source": [ + "req_usage = model.find(\"ToasterDemo::timely\")\n", + "assert req_usage is not None\n", + "print(f\"requirement usage kind: {req_usage.kind}\")\n", + "\n", + "for e in model.query():\n", + " d = e.as_dict()\n", + " if d.get(\"@type\") == \"RequirementUsage\":\n", + " print(f\"RequirementUsage: {d['qualifiedName']}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-07", + "metadata": {}, + "source": [ + "`timely` now applies `TimelyToast`'s constraint. What remains open is whether the measure it constrains, toast time, is a measure of effectiveness (the user's acceptance) or a measure of performance (an engineering figure with a threshold derived from something else). That split is a modeling judgment, not a fact the model states. The record below states who cares and which framing the measure takes." + ] + }, + { + "cell_type": "markdown", + "id": "cell-08", + "metadata": {}, + "source": [ + "`AC-C03` states the claim first: what `timely` is being framed as, and which model element the framing applies to." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-09", + "metadata": {}, + "outputs": [], + "source": [ + "from toaster.evidence import ReviewRecord, validate_record, hash_content\n", + "\n", + "claim = (\"timely (TimelyToast) is framed as a measure of effectiveness: an \"\n", + " \"acceptance criterion for the user's kitchen workflow, not an \"\n", + " \"engineering performance figure derived from a lower-level measure.\")\n", + "model_ref = (\"ToasterDemo::timely\")\n", + "print(claim)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-10", + "metadata": {}, + "source": [ + "The claim needs a standard to be judged against. `scope` names where in the model this applies; `criteria` states, in plain terms, what would make the claim MoE versus MoP (Hawkins' appropriateness: is this the right frame for this measure?)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-11", + "metadata": {}, + "outputs": [], + "source": [ + "scope = (\"ToasterDemo\")\n", + "criteria = (\"MoE if the split names who cares and frames the measure as \"\n", + " \"acceptance; MoP if its threshold is derived from a stated MoE with a \"\n", + " \"means of checking (architecture-layers skill).\")\n", + "print(criteria)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-12", + "metadata": {}, + "source": [ + "Next, what the framing takes as given. `premises` is empty here (the framing does not rest on any prior derivation); `assumption_refs` names the one modeling judgment it does rest on." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-13", + "metadata": {}, + "outputs": [], + "source": [ + "premises = ([])\n", + "assumption_refs = ([\n", + " \"The MoE/MoP split for toast timing is a case-specific modeling \"\n", + " \"judgment, not a fixed rule.\",\n", + " ])\n", + "print(assumption_refs)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-14", + "metadata": {}, + "source": [ + "`evidence_refs` points at what actually supports the claim: the requirement's own rationale text, not a computed value (there is nothing to compute for a framing judgment). `rationale` is the argument connecting that evidence to the claim (Hawkins' sufficiency: is this evidence enough?)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-15", + "metadata": {}, + "outputs": [], + "source": [ + "evidence_refs = ([\n", + " \"ToasterDemo::TimelyToast doc: the rationale argues from kitchen \"\n", + " \"workflow timing, naming the user as who cares.\",\n", + " ])\n", + "rationale = (\"TimelyToast's rationale argues from the user's kitchen workflow, not \"\n", + " \"from a solution class or a lower-level performance figure: it names \"\n", + " \"who cares (the user) and frames the 180-second bound as part of what \"\n", + " \"the user accepts, not an engineering figure derived from another \"\n", + " \"measure.\")\n", + "print(rationale)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-16", + "metadata": {}, + "source": [ + "Finally, the challenge. A framing judgment is contestable by nature, so `counterevidence` states the strongest case for the other framing, and `residual_uncertainties` says plainly that this is not settled (Hawkins' trustworthiness: a record that hid this would be less trustworthy, not more)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-17", + "metadata": {}, + "outputs": [], + "source": [ + "counterevidence = (\"TimelyToastTest's doc checks the bound as a timed test at a stated \"\n", + " \"input condition ('nominal input power'), which reads like an \"\n", + " \"engineering performance test rather than an acceptance criterion. \"\n", + " \"Toast time could reasonably be framed either way.\")\n", + "residual_uncertainties = (\"This split is a contestable modeling judgment, not a settled fact. A \"\n", + " \"later chapter that derives cycle time from the mechanism and the \"\n", + " \"energy balance may instead introduce a genuine MoP threshold derived \"\n", + " \"from this MoE.\")\n", + "print(counterevidence)\n", + "print(residual_uncertainties)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-18", + "metadata": {}, + "source": [ + "With every part named above, the record assembles from them directly." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-19", + "metadata": {}, + "outputs": [], + "source": [ + "framing_record = ReviewRecord(\n", + " identifier=\"AC-C03\",\n", + " kind=\"asserted_context\",\n", + " claim=claim,\n", + " model_ref=model_ref,\n", + " content_hash=hash_content(source),\n", + " scope=scope,\n", + " criteria=criteria,\n", + " premises=premises,\n", + " assumption_refs=assumption_refs,\n", + " evidence_refs=evidence_refs,\n", + " rationale=rationale,\n", + " counterevidence=counterevidence,\n", + " residual_uncertainties=residual_uncertainties,\n", + " disposition=\"pending\",\n", + " dependency_freshness=\"current\",\n", + " engineering_conclusion=\"undetermined\",\n", + " record_kind=\"worked_example\",\n", + ")\n", + "\n", + "errors = validate_record(framing_record)\n", + "print(f\"Validation errors: {errors}\")\n", + "print(f\"Record kind: {framing_record.kind}\")\n", + "conn.close()" + ] + }, + { + "cell_type": "markdown", + "id": "cell-20", + "metadata": {}, + "source": [ + "`validate_record` returns no errors, confirming the framing judgment's required fields, including its own counterevidence, are present." + ] + }, + { + "cell_type": "markdown", + "id": "cell-21", + "metadata": {}, + "source": [ + "`requirement timely : TimelyToast;` printed above loaded without error, and `model.find()` returns its symbol, confirmed as a `RequirementUsage` by `model.query()` above." + ] + }, + { + "cell_type": "markdown", + "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." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + }, + "title": "Ch3 nb1: requirement usage" + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/chapters/ch03-measures/02-mop-candidate-eval.ipynb b/chapters/ch03-measures/02-mop-candidate-eval.ipynb index ca56620..30eecf3 100644 --- a/chapters/ch03-measures/02-mop-candidate-eval.ipynb +++ b/chapters/ch03-measures/02-mop-candidate-eval.ipynb @@ -1,113 +1,115 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - } + "nbformat": 4, + "nbformat_minor": 5, + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" }, - "cells": [ - { - "cell_type": "markdown", - "id": "cell-00", - "metadata": {}, - "source": [ - "## calc def\n", - "\n", - "This notebook introduces `calc def`; after running it you can define a named calculation with typed inputs and a return expression, and evaluate it against specific values." - ] - }, - { - "cell_type": "markdown", - "id": "cell-01", - "metadata": {}, - "source": [ - "The `timely` requirement usage from the previous notebook applies `TimelyToast` to the nominal and slow candidates. To reason about *why* the nominal candidate is appropriate, we need a quantity: the energy delivered during a toast cycle. `calc def` in SysML v2 declares a reusable calculation with typed inputs and a return expression. This notebook adds `DeliveredEnergy` to the model." - ] - }, - { - "cell_type": "code", - "id": "cell-02", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from pathlib import Path\n", - "import opensysml\n", - "from toaster.report import format_diagnostics\n", - "\n", - "conn = opensysml.connect(version=\"v0.9.0\")\n", - "source = Path(\"../../models/ch03-cumulative.sysml\").read_text()\n", - "print(source)\n", - "model = conn.load_from_content(source, strict=False)\n", - "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" - ] - }, - { - "cell_type": "markdown", - "id": "cell-03", - "metadata": {}, - "source": [ - "The `ch03-cumulative.sysml` file adds the satisfy-assertion pattern: a `requirement` usage `timely` and `assert satisfy timely by nominal/slow` declarations inside an `evidence` block record which candidates are claimed to meet the requirement. It also adds `calc def DeliveredEnergy`, which computes `power * duration * efficiency` \u2014 the symbolic model that Chapter 7's parameter sweep binds to numpy." - ] - }, - { - "cell_type": "code", - "id": "cell-04", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# Negative control: a calc def that references an undefined symbol in its\n", - "# return expression raises \"unresolved reference\" at that site.\n", - "bad_source = \"\"\"\n", - "package Bad {\n", - " private import ScalarValues::*;\n", - " calc def BadCalc {\n", - " in power : Real;\n", - " return : Real = power * undefinedEfficiency;\n", - " }\n", - "}\n", - "\"\"\"\n", - "bad = conn.load_from_content(bad_source, strict=False)\n", - "assert not bad.ok\n", - "print(\"Expected error:\", bad.diagnostics[0].message)" - ] - }, - { - "cell_type": "code", - "id": "cell-05", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "result = model.eval(\"ToasterDemo::DeliveredEnergy(800.0, 120.0, 0.7)\")\n", - "reference = 800.0 * 120.0 * 0.7 # 67200.0 J\n", - "assert abs(float(result) - reference) < 1.0, f\"Unexpected: {result}\"\n", - "print(f\"DeliveredEnergy(800 W, 120 s, \u03b7=0.7) = {float(result):.1f} J\")\n", - "conn.close()" - ] - }, - { - "cell_type": "markdown", - "id": "cell-06", - "metadata": {}, - "source": [ - "`calc def DeliveredEnergy { in power : Real; in duration : Real; in efficiency : Real; return : Real = power * duration * efficiency; }` is the A-F expression; OpenSysML evaluates it for the given arguments (O-S); `model.eval()` returns 67200.0 J, confirming the reference value (E)." - ] - }, - { - "cell_type": "markdown", - "id": "cell-07", - "metadata": {}, - "source": [ - "Try the chapter exercise in `exercises/ch03/exercise.ipynb`: add a `HeatLoss` calc def and verify it returns a lower effective energy for the same inputs." - ] - } - ] -} \ No newline at end of file + "language_info": { + "name": "python" + }, + "title": "Ch3 nb2: satisfaction claims" + }, + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": [ + "## satisfaction claims\n", + "\n", + "This notebook introduces the `assert satisfy` / `assert not satisfy` idiom; after running it you can record, inside a usage's own context, whether it meets a requirement, and evaluate that claim against the model." + ] + }, + { + "cell_type": "markdown", + "id": "cell-01", + "metadata": {}, + "source": [ + "The previous notebook applied `TimelyToast` to the model as `timely : TimelyToast`. `slow`, from Chapter 2, is a deliberately injected fault: `cycleTime` fixed at 200 seconds, built to fail `timely`'s 180-second bound. This notebook folds a negated satisfaction claim into `slow`'s own body and evaluates it against the model's own values." + ] + }, + { + "cell_type": "code", + "id": "cell-02", + "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)" + }, + { + "cell_type": "markdown", + "id": "cell-03", + "metadata": {}, + "source": [ + "The claim is folded into `slow`'s own body: `slow` names itself as the usage the claim is about, so no separate container is needed. `slow`'s full usage, restated with this new line, is what the cumulative model now carries." + ] + }, + { + "cell_type": "code", + "id": "cell-04", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "SLOW_WITH_CLAIM = (\n \"part slow : Toaster {\\n\"\n \" attribute :>> cycleTime = 200.0 [SI::s];\\n\"\n f\"{SLOW_NOT_SATISFY}\\n\"\n \"}\"\n)\nTOASTER_INCREMENT = SLOW_WITH_CLAIM\nprint(TOASTER_INCREMENT)\n\nsource = Path(\"../../models/ch03-cumulative.sysml\").read_text()\nmodel = conn.load_from_content(source, strict=False)\nassert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" + }, + { + "cell_type": "markdown", + "id": "cell-05", + "metadata": {}, + "source": [ + "The reopened `slow` usage loads without diagnostics, restating its Chapter 2 override with the injected-fault claim added. The next cell checks what happens when an `assert satisfy` names a usage that does not resolve." + ] + }, + { + "cell_type": "code", + "id": "cell-06", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "# Negative control: assert satisfy naming an undeclared usage raises\n# \"unresolved reference\" at the assert-satisfy site.\nbad_source = \"\"\"\npackage Bad {\n private import ScalarValues::*;\n private import SI::*;\n private import ISQ::*;\n part def Toaster { attribute cycleTime : ISQ::DurationValue; }\n requirement def TimelyToast {\n subject toaster : Toaster;\n require constraint { toaster.cycleTime <= 180.0 [SI::s] }\n }\n requirement timely : TimelyToast;\n part slow : Toaster {\n attribute :>> cycleTime = 200.0 [SI::s];\n assert not satisfy timely by undefinedUsage;\n }\n}\n\"\"\"\nbad = conn.load_from_content(bad_source, strict=False)\nassert not bad.ok\nprint(\"Expected error:\", bad.diagnostics[0].message)" + }, + { + "cell_type": "markdown", + "id": "cell-07", + "metadata": {}, + "source": [ + "With the negative control confirmed, the next cell evaluates the claim `slow` now carries against the model's own values." + ] + }, + { + "cell_type": "code", + "id": "cell-08", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "from toaster.query import satisfy_relationships\n\n# TimelyToastTest's verify timely; also exports as a SatisfyRequirementUsage,\n# with no subject: it names no usage and is not itself a claim about one.\nclaims = [c for c in satisfy_relationships(model) if c[\"subject\"]]\nassert len(claims) == 1\nclaim = claims[0]\nprint(f\"satisfy claim: {claim}\")\n\nexpression = f\"{claim['requirement']}({claim['subject']})\"\nholds = model.eval(expression)\nprint(f\"{expression} = {holds}\")\nconn.close()" + }, + { + "cell_type": "markdown", + "id": "cell-09", + "metadata": {}, + "source": [ + "`timely(slow)` evaluates to `False`: `slow`'s 200-second `cycleTime` does not meet the 180-second bound. The model's claim is `assert not satisfy`, so this result confirms the claim rather than contradicting it. This demonstrates the deliberately negated claim applied to an injected fault value: a real, evaluable claim about a fixture built to fail. It does not yet demonstrate a check catching a failure that traces back to a design choice, since deriving a cycle time from an actual mechanism is not yet possible; `slow`'s fixed value is the measured quantity itself, typed in directly, not a result computed from anything." + ] + }, + { + "cell_type": "markdown", + "id": "cell-10", + "metadata": {}, + "source": [ + "`assert not satisfy timely by slow;`, folded into `slow`'s own body and printed above, loaded without error; `model.eval()` confirms the negated claim holds against `slow`'s own values, shown by the `False` result above." + ] + }, + { + "cell_type": "markdown", + "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`: its `brewTemp` is unbound." + ] + } + ] +} diff --git a/chapters/ch03-measures/03-threshold-judgment.ipynb b/chapters/ch03-measures/03-threshold-judgment.ipynb index 51ee191..5ef1c3b 100644 --- a/chapters/ch03-measures/03-threshold-judgment.ipynb +++ b/chapters/ch03-measures/03-threshold-judgment.ipynb @@ -1,129 +1,167 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, - "cells": [ - { - "cell_type": "markdown", - "id": "cell-00", - "metadata": {}, - "source": [ - "## threshold judgment\n", - "\n", - "This notebook introduces `asserted_solution`; after running it you can record a judgment that a candidate design satisfies a requirement, following Hawkins \u00a73.3." - ] - }, - { - "cell_type": "markdown", - "id": "cell-01", - "metadata": {}, - "source": [ - "The previous two notebooks established the formal structure: a requirement usage (`timely`) applied to two candidates, and a calculation (`DeliveredEnergy`) that quantifies the nominal design. Before claiming the nominal design satisfies `TimelyToast`, we need to record why that claim is appropriate and what evidence supports it. That record is an `asserted_solution` \u2014 the third Hawkins judgment type, used when evidence directly supports a conclusion." - ] - }, - { - "cell_type": "code", - "id": "cell-02", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from pathlib import Path\n", - "import opensysml\n", - "from toaster.report import format_diagnostics\n", - "\n", - "conn = opensysml.connect(version=\"v0.9.0\")\n", - "source = Path(\"../../models/ch03-cumulative.sysml\").read_text()\n", - "print(source)\n", - "model = conn.load_from_content(source, strict=False)\n", - "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" - ] - }, - { - "cell_type": "markdown", - "id": "cell-03", - "metadata": {}, - "source": [ - "The `ch03-cumulative.sysml` file adds the satisfy-assertion pattern: a `requirement` usage `timely` and `assert satisfy timely by nominal/slow` declarations inside an `evidence` block record which candidates are claimed to meet the requirement. It also adds `calc def DeliveredEnergy`, which computes `power * duration * efficiency` \u2014 the symbolic model that Chapter 7's parameter sweep binds to numpy." - ] - }, - { - "cell_type": "code", - "id": "cell-04", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# Negative control: a requirement usage referencing an undefined requirement def\n", - "# raises \"unresolved reference\" at the usage site.\n", - "bad_source = \"\"\"\n", - "package Bad {\n", - " private import ScalarValues::*;\n", - " requirement timely_bad : UndefinedRequirement;\n", - "}\n", - "\"\"\"\n", - "bad = conn.load_from_content(bad_source, strict=False)\n", - "assert not bad.ok\n", - "print(\"Expected error:\", bad.diagnostics[0].message)" - ] - }, - { - "cell_type": "code", - "id": "cell-05", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "solution_record = ReviewRecord(\n", - " identifier=\"AS-C03\",\n", - " kind=\"asserted_solution\",\n", - " claim=\"The nominal design (cycleTime = 120 s) satisfies TimelyToast (cycleTime <= 180 s).\",\n", - " model_ref=\"ToasterDemo::nominal\",\n", - " content_hash=hash_content(source),\n", - " scope=\"ToasterDemo\",\n", - " criteria=\"TimelyToast: toaster.cycleTime <= 180.0\",\n", - " premises=[],\n", - " assumption_refs=[\"AC-001\"],\n", - " evidence_refs=[\"assert satisfy timely by nominal\"],\n", - " rationale=\"120 s < 180 s; the nominal variant is within the bound by a 60 s margin.\",\n", - " counterevidence=\"The slow variant (200 s) violates the bound. The nominal holds only for the default cycleTime.\",\n", - " residual_uncertainties=\"Thermal cycling effects on actual cycle duration are not modeled.\",\n", - " disposition=\"pending\",\n", - " dependency_freshness=\"current\",\n", - " engineering_conclusion=\"undetermined\",\n", - " record_kind=\"worked_example\",\n", - ")\n", - "\n", - "errors = validate_record(solution_record)\n", - "print(f\"Validation errors: {errors}\")\n", - "print(f\"Record kind: {solution_record.kind}\")\n", - "conn.close()" - ] - }, - { - "cell_type": "markdown", - "id": "cell-06", - "metadata": {}, - "source": [ - "The Hawkins \u00a73.3 schema specifies what an `asserted_solution` record must contain (A-F); filling and validating the `ReviewRecord` in Python enacts that schema (O-S); `validate_record()` returning `[]` confirms all required fields are present (E)." - ] - }, - { - "cell_type": "markdown", - "id": "cell-07", - "metadata": {}, - "source": [ - "Try the chapter exercise in `exercises/ch03/exercise.ipynb`: write an `asserted_solution` record for your `TemperatureReq` satisfaction claim." - ] - } - ] -} \ No newline at end of file + "nbformat": 4, + "nbformat_minor": 5, + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + }, + "title": "Ch3 nb3: threshold judgment" + }, + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": [ + "## threshold judgment\n", + "\n", + "This notebook introduces the `asserted_solution` judgment record; after running it you can record which satisfaction claims the model's evaluated values support, and which remain open." + ] + }, + { + "cell_type": "markdown", + "id": "cell-01", + "metadata": {}, + "source": [ + "The previous notebook evaluated `assert not satisfy timely by slow` and confirmed it holds. Before treating that evaluation as settled, we record the judgment as a Hawkins-style `asserted_solution`: what the evaluation supports, and what it does not yet decide about `nominal`." + ] + }, + { + "cell_type": "code", + "id": "cell-02", + "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\")\nsource = Path(\"../../models/ch03-cumulative.sysml\").read_text()\nprint(source)\nmodel = conn.load_from_content(source, strict=False)\nassert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" + }, + { + "cell_type": "markdown", + "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)." + ] + }, + { + "cell_type": "code", + "id": "cell-04", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "# Negative control: a require constraint referencing an attribute the\n# subject's type does not declare raises \"unresolved member\" at the point\n# of use.\nbad_source = \"\"\"\npackage Bad {\n private import ScalarValues::*;\n part def Toaster { attribute cycleTime : Real default = 120.0; }\n requirement def BadReq {\n subject t : Toaster;\n require constraint { t.notAnAttribute <= 180.0 }\n }\n}\n\"\"\"\nbad = conn.load_from_content(bad_source, strict=False)\nassert not bad.ok\nprint(\"Expected error:\", bad.diagnostics[0].message)" + }, + { + "cell_type": "markdown", + "id": "cell-05", + "metadata": {}, + "source": [ + "With the negative control confirmed, the next cell rebuilds the threshold judgment: what the evaluated claim on `slow` supports, and what remains unclaimed about `nominal`." + ] + }, + { + "cell_type": "markdown", + "id": "cell-06", + "metadata": {}, + "source": "The evaluation itself comes first: `model.eval` checks the negated claim on `slow` against the model's own values. `claim` states what that evaluation is being read as supporting, and `model_ref` names the requirement it concerns." + }, + { + "cell_type": "code", + "id": "cell-07", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "from toaster.evidence import ReviewRecord, validate_record, hash_content\n\nslow_holds = model.eval(\"ToasterDemo::timely(ToasterDemo::slow)\")\nprint(f\"ToasterDemo::timely(ToasterDemo::slow) = {slow_holds}\")\n\nclaim = (\"The negated claim on slow (assert not satisfy timely by slow) \"\n \"evaluates True: slow's cycleTime (200 s) fails timely's 180 s \"\n \"bound, as intended for the injected fault. No corresponding claim is made for nominal.\")\nmodel_ref = (\"ToasterDemo::timely\")\nprint(claim)" + }, + { + "cell_type": "markdown", + "id": "cell-08", + "metadata": {}, + "source": "`scope` and `criteria` state the standard the claim is judged against: exactly which evaluation, on which usage, counts as this claim holding." + }, + { + "cell_type": "code", + "id": "cell-09", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "scope = (\"ToasterDemo\")\ncriteria = (\"assert not satisfy timely by slow holds when \"\n \"ToasterDemo::timely(ToasterDemo::slow) evaluates False.\")\nprint(criteria)" + }, + { + "cell_type": "markdown", + "id": "cell-10", + "metadata": {}, + "source": "`premises` names what makes `slow` a legitimate fixture for this claim in the first place: a deliberately injected value, not a derived one." + }, + { + "cell_type": "code", + "id": "cell-11", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "premises = ([\n \"slow.cycleTime is fixed at 200.0 [SI::s], a deliberately injected \"\n \"fault value (Chapter 2), not a value derived from any mechanism.\",\n ])\nprint(premises)" + }, + { + "cell_type": "markdown", + "id": "cell-12", + "metadata": {}, + "source": "`AC-C03`'s framing judgment is the assumption this record rests on; `evidence_refs` points at the evaluation above, and `rationale` connects that evidence to the claim." + }, + { + "cell_type": "code", + "id": "cell-13", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "assumption_refs = ([\n \"AC-C03: timely is framed as a measure of effectiveness \"\n \"(notebook 01).\",\n ])\nevidence_refs = ([\n \"assert not satisfy timely by slow (ToasterDemo::slow::@1), \"\n \"evaluated by model.eval('ToasterDemo::timely(ToasterDemo::slow)') \"\n \"= False, above.\",\n ])\nrationale = (\"slow's fixed cycleTime exceeds the bound, and the model's own \"\n \"evaluation confirms the negated claim holds. This demonstrates \"\n \"the deliberately negated satisfaction claim applied to an \"\n \"injected fault value: a real, evaluable claim about a fixture \"\n \"built to fail, not a check that traces a failure back to a \"\n \"design choice, since deriving a cycle time from an actual \"\n \"mechanism is not yet possible. Toaster::cycleTime carries no \"\n \"default value, so nominal.cycleTime has no value and \"\n \"ToasterDemo::timely(ToasterDemo::nominal) cannot be evaluated \"\n \"at all; claiming nominal satisfies timely would assert a \"\n \"result that was never computed.\")\nprint(rationale)" + }, + { + "cell_type": "markdown", + "id": "cell-14", + "metadata": {}, + "source": "The challenge: `counterevidence` states plainly what this result does not demonstrate, and `residual_uncertainties` says what stays genuinely open about `nominal` (Hawkins' trustworthiness again: an evaluated True is not the same as a settled question)." + }, + { + "cell_type": "code", + "id": "cell-15", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "counterevidence = (\"This only demonstrates that a chosen fault value fails. It does \"\n \"not demonstrate that a derived cycle time can pass; that \"\n \"demonstration needs a chapter that derives cycle time from the \"\n \"mechanism and the energy balance.\")\nresidual_uncertainties = (\"Whether timely is best framed as a measure of effectiveness or a \"\n \"measure of performance stays a contestable judgment (AC-C03, \"\n \"notebook 01), independent of this result. nominal's status under \"\n \"timely is genuinely open, not merely deferred.\")\nprint(counterevidence)\nprint(residual_uncertainties)" + }, + { + "cell_type": "markdown", + "id": "cell-16", + "metadata": {}, + "source": "With every part named above, the solution record assembles from them directly." + }, + { + "cell_type": "code", + "id": "cell-17", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "solution_record = ReviewRecord(\n identifier=\"AS-C03\",\n kind=\"asserted_solution\",\n claim=claim,\n model_ref=model_ref,\n content_hash=hash_content(source),\n scope=scope,\n criteria=criteria,\n premises=premises,\n assumption_refs=assumption_refs,\n evidence_refs=evidence_refs,\n rationale=rationale,\n counterevidence=counterevidence,\n residual_uncertainties=residual_uncertainties,\n disposition=\"pending\",\n dependency_freshness=\"current\",\n engineering_conclusion=\"supported\",\n record_kind=\"worked_example\",\n)\n\nerrors = validate_record(solution_record)\nprint(f\"Validation errors: {errors}\")\nprint(f\"Record kind: {solution_record.kind}\")\nconn.close()" + }, + { + "cell_type": "markdown", + "id": "cell-18", + "metadata": {}, + "source": [ + "The Hawkins §3.3 schema fields were filled in above, and `validate_record` reports no errors, confirming the claim, rationale and counterevidence are populated and checked, not just printed." + ] + }, + { + "cell_type": "markdown", + "id": "cell-19", + "metadata": {}, + "source": [ + "Try the chapter exercise in `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 new file mode 100644 index 0000000..ab42421 --- /dev/null +++ b/chapters/ch03-measures/04-verification-case.ipynb @@ -0,0 +1,137 @@ +{ + "nbformat": 4, + "nbformat_minor": 5, + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + }, + "title": "Ch3 nb4: verification def" + }, + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": "## verification def\n\nThis notebook introduces `verification def`; after running it you can declare a named verification case that specifies how a requirement will be checked." + }, + { + "cell_type": "markdown", + "id": "cell-01", + "metadata": {}, + "source": "Notebooks 01\u201303 of this chapter established the requirement usage `timely : TimelyToast`, a satisfaction claim demonstrating the requirement's failing branch (slow), and a threshold judgment record. A complete requirement has three parts (Part 4): description, rationale, and verification method. The first two are in `TimelyToast`'s `doc` comment (Chapter 2, \u00a77.21.2). `verification def` in SysML v2 (\u00a77.24) closes the anatomy by declaring how the requirement will be verified: the subject under test, and an objective that names the requirement usage to verify." + }, + { + "cell_type": "code", + "id": "cell-02", + "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_verification_def(owner='ToasterDemo', name='TimelyToastTest', doc=..., subject_type='Toaster') when API ships\n# spec: SysML v2 formal/2026-03-02 \u00a77.24.2 (VerificationCaseDefinition)\nVERIF_DEF_OPEN = \"verification def TimelyToastTest {\"\nprint(VERIF_DEF_OPEN)" + }, + { + "cell_type": "markdown", + "id": "cell-03", + "metadata": {}, + "source": "`verification def TimelyToastTest` declares a reusable verification case for the toasting cycle requirement. Like `requirement def`, a `verification def` takes a subject (the system under test) and a body specifying what must be determined." + }, + { + "cell_type": "code", + "id": "a1b2c3d4", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "# editor.set_doc(owner='ToasterDemo::TimelyToastTest', text=...) when API ships\n# spec: SysML v2 formal/2026-03-02 \u00a77.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 \u00a77.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 \u00a77.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 (\u00a77.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)." + }, + { + "cell_type": "code", + "id": "i9j0k1l2", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "# editor.set_subject(owner='ToasterDemo::TimelyToastTest', subject_type='Toaster') when API ships\nSUBJECT_DECL = \" subject toaster : Toaster;\"\nprint(SUBJECT_DECL)" + }, + { + "cell_type": "markdown", + "id": "m3n4o5p6", + "metadata": {}, + "source": "The `subject toaster : Toaster` declaration names the entity under test: any `Toaster` instance placed in this verification role." + }, + { + "cell_type": "code", + "id": "q7r8s9t0", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "# editor.set_objective(owner='ToasterDemo::TimelyToastTest', verify=['timely']) when API ships\n# spec: SysML v2 formal/2026-03-02 \u00a77.24.2: objective declares what requirements are verified\nOBJECTIVE_BODY = \"\"\" objective {\n verify timely;\n }\"\"\"\nprint(OBJECTIVE_BODY)" + }, + { + "cell_type": "markdown", + "id": "u1v2w3x4", + "metadata": {}, + "source": "The `objective` block names the requirement usage being verified: `timely : TimelyToast` from notebook 01. `verify timely` is a shorthand satisfy constraint (\u00a77.24.2) that asserts the objective is met when `timely` is determined to hold for the subject. `verify` takes a requirement **usage**, not a definition: `verify TimelyToast` would fail because `TimelyToast` is a `requirementDef`, not a usage." + }, + { + "cell_type": "code", + "id": "y5z6a7b8", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "TOASTER_INCREMENT = f\"{VERIF_DEF_OPEN}\\n{DOC_COMMENT}\\n{SUBJECT_DECL}\\n{OBJECTIVE_BODY}\\n}}\"\nprint(TOASTER_INCREMENT)\nsource = Path(\"../../models/ch03-cumulative.sysml\").read_text()\nmodel = conn.load_from_content(source, strict=False)\nassert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" + }, + { + "cell_type": "markdown", + "id": "bridge-04a", + "metadata": {}, + "source": [ + "The verification case loads without diagnostics. The next cell checks what happens when its objective names a requirement definition instead of a usage." + ] + }, + { + "cell_type": "code", + "id": "cell-04", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "# Negative control: verify referencing a requirement def (not a usage) raises a type error.\nbad_source = \"\"\"\npackage Bad {\n private import ScalarValues::*;\n part def Toaster { attribute cycleTime : Real default = 120.0; }\n requirement def TimelyToast {\n subject toaster : Toaster;\n require constraint { toaster.cycleTime <= 180.0 }\n }\n verification def BadCheck {\n subject toaster : Toaster;\n objective {\n verify TimelyToast; // error: must be a requirement usage, not a def\n }\n }\n}\n\"\"\"\nbad = conn.load_from_content(bad_source, strict=False)\nassert not bad.ok, \"Expected parse/semantic error for verify-on-def\"\nprint(\"Expected error:\", bad.diagnostics[0].message)" + }, + { + "cell_type": "markdown", + "id": "bridge-04b", + "metadata": {}, + "source": [ + "With the negative control confirmed, the next cell looks up the verification case in the loaded model." + ] + }, + { + "cell_type": "code", + "id": "cell-05", + "metadata": {}, + "outputs": [], + "execution_count": null, + "source": "vd = model.find(\"ToasterDemo::TimelyToastTest\")\nassert vd is not None\nprint(f\"verification kind : {vd.kind}\")\nprint(f\"verification id : {vd.id}\")\n\nfor e in model.query():\n d = e.as_dict()\n if d[\"@type\"] == \"VerificationCaseDefinition\":\n print(f\"VerificationCaseDefinition: {d['qualifiedName']}\")\nconn.close()" + }, + { + "cell_type": "markdown", + "id": "cell-06", + "metadata": {}, + "source": "`verification def TimelyToastTest { doc /* ... */ subject toaster : Toaster; objective { verify timely; } }` printed above loaded without error, and `model.find()` returns its symbol, confirmed as a `VerificationCaseDefinition` by `model.query()` above." + }, + { + "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." + } + ] +} diff --git a/chapters/ch03-measures/conclusion.md b/chapters/ch03-measures/conclusion.md index b609045..1c0644c 100644 --- a/chapters/ch03-measures/conclusion.md +++ b/chapters/ch03-measures/conclusion.md @@ -1,15 +1,15 @@ -# Chapter 3 — Conclusion +# Chapter 3: Conclusion ## What we built -The Chapter 3 model applies the `TimelyToast` requirement to the nominal and slow candidates via a `requirement timely : TimelyToast` usage and explicit `assert satisfy` claims. It also adds `DeliveredEnergy`, a calc def that computes thermal energy as `power * duration * efficiency`. The Python side adds `AS-C03`, an `asserted_solution` ReviewRecord that records the argument: 120 s is within the 180 s bound by a 60 s margin, and the slow variant at 200 s violates it. +The Chapter 3 model applies `TimelyToast` to the model as a `requirement timely : TimelyToast` usage. `slow`, Chapter 2's deliberately injected fault (`cycleTime` fixed at 200 s), now carries `assert not satisfy timely by slow`, folded into its own body and evaluated against the model's own values: it holds, confirming the negated claim. `TimelyToastTest`, a `verification def` with an `objective { verify timely; }`, declares how the requirement would be checked, and is never run in this chapter. On the Python side, `AC-C03` records the judgment that `timely` is framed as a measure of effectiveness, and `AS-C03` records what the evaluated claim on `slow` supports. ## What this establishes -The chapter answers its engineering question: the model now records *which* candidate satisfies the requirement and *why* that judgment holds. The assert-satisfy claims are formal; the ReviewRecord makes the reasoning visible and auditable. That pairing — formal claim plus recorded argument — is what distinguishes an engineering judgment from an assertion. +The chapter answers its engineering question: the model now records and evaluates a satisfaction claim against a requirement, using `slow` as the requirement's demonstrated failing branch. It does not yet claim that `nominal` satisfies `TimelyToast`: `Toaster.cycleTime` carries no value absent a mechanism-and-energy-balance derivation, so `nominal`'s status stays genuinely open rather than asserted from an unset default. That restraint, an evaluated claim on `slow`, no claim on `nominal`, and a verification case that states how the requirement will eventually be checked, is what distinguishes an engineering judgment from an assertion. ## What comes next -Chapter 4 asks how the system performs its function step by step. It introduces `action def` for functional decomposition, `item def` for typed flows, and the first `asserted_inference` record — the judgment that a chain of child claims supports a parent claim. +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, assert satisfaction for the nominal and hot candidates, and write an `asserted_solution` record for the nominal claim. Use the same pattern as `timely` and `AS-C03`. +**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`. diff --git a/chapters/ch03-measures/index.md b/chapters/ch03-measures/index.md index fbdabc9..87fb67d 100644 --- a/chapters/ch03-measures/index.md +++ b/chapters/ch03-measures/index.md @@ -1,35 +1,36 @@ -# Chapter 3 — Measures of Success +# Chapter 3: Measures of Success ## Purpose -Chapter 3 asks: how do we verify that a candidate design satisfies a requirement? After completing this chapter, the model contains a requirement usage (`timely`) applied to the nominal and slow candidates, a named calculation (`DeliveredEnergy`), and a Python judgment record that claims the nominal design satisfies the requirement. +Chapter 3 asks: how do we record and check a satisfaction claim against a requirement? After completing this chapter, the model contains a requirement usage (`timely`), a satisfaction claim folded into the failing usage's own context (`slow`), and a verification case (`TimelyToastTest`) declaring how the requirement will be checked. `nominal`'s satisfaction of `timely` is not yet claimed: `Toaster.cycleTime` has no value until a later chapter derives one. ## Ingredients | Notebook | Construct | Concept | |---|---|---| -| [01 — requirement usage](01-moe-definition.ipynb) | `requirement` usage + `assert satisfy ... by ...` | Applying a requirement definition to named design candidates | -| [02 — calc def](02-mop-candidate-eval.ipynb) | `calc def` with `in` / `return : Real = expr` | A named, reusable calculation with typed inputs and a return expression | -| [03 — threshold judgment](03-threshold-judgment.ipynb) | `asserted_solution` ReviewRecord | A judgment record claiming that evidence directly supports a conclusion | +| [01: requirement usage](01-moe-definition.ipynb) | `requirement` usage | Applying a requirement definition to the model, and recording whether the measure it constrains is effectiveness or performance | +| [02: satisfaction claims](02-mop-candidate-eval.ipynb) | `assert satisfy` / `assert not satisfy` | Recording, inside a usage's own context, whether it meets a requirement, and evaluating the claim | +| [03: threshold judgment](03-threshold-judgment.ipynb) | `asserted_solution` ReviewRecord | A judgment record stating what an evaluated claim supports, and what remains open | +| [04: verification def](04-verification-case.ipynb) | `verification def` + `objective { verify ... }` | A formal verification case specifying how a requirement will be checked | ## Equipment -See [setup](../../docs/setup.md) to provision Python, Node, and the OpenSysML binary before running any notebook. +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. ## Method -Notebook 01 applies the `TimelyToast` requirement definition from Chapter 2 to the nominal and slow candidates. The model now carries explicit `assert satisfy` claims for both. Notebook 02 adds `DeliveredEnergy`, a calc def that computes the thermal energy delivered in one cycle — the quantitative basis for evaluating the nominal design. Notebook 03 does not add a new SysML construct; instead it introduces the first `asserted_solution` judgment record, recording the argument that the nominal candidate satisfies the requirement. +Notebook 01 applies the `TimelyToast` requirement definition from Chapter 2 to the model as `timely : TimelyToast`, then records the modeling judgment behind it: whether toast time is a measure of effectiveness (the user's acceptance) or a measure of performance (an engineering figure), a case-specific decision this chapter justifies rather than assumes. Notebook 02 folds a satisfaction claim into `slow`'s own body, `assert not satisfy timely by slow`, and evaluates it against the model's own values. Notebook 03 writes the chapter's `asserted_solution` judgment record, stating what the evaluated claim supports and what it does not decide about `nominal`. Notebook 04 closes the three-part requirement anatomy (description, rationale, verification method) by adding `TimelyToastTest`: a `verification def` (§7.24) that declares the subject under test and an objective naming `timely` as the requirement to verify. ## Expected result The Ch3 cumulative model contains everything from Ch1-2, plus: -- `requirement timely : TimelyToast;` — the requirement usage -- `part evidence { assert satisfy timely by nominal; assert satisfy timely by slow; }` — satisfaction claims for both candidates -- `calc def DeliveredEnergy { in power : Real; in duration : Real; in efficiency : Real; return : Real = power * duration * efficiency; }` — the delivered-energy calculation +- `requirement timely : TimelyToast;`, the requirement usage +- `part slow : Toaster { attribute :>> cycleTime = 200.0 [SI::s]; assert not satisfy timely by slow; }`, the negated satisfaction claim folded into the failing usage's own context +- `verification def TimelyToastTest { doc /* ... */ subject toaster : Toaster; objective { verify timely; } }`, the verification case (§7.24) -The Python side carries an `asserted_solution` ReviewRecord (`AS-C03`) with populated `rationale`, `counterevidence`, and `evidence_refs`. +The Python side carries an `asserted_context` ReviewRecord (`AC-C03`) recording the MoE/MoP framing judgment, and an `asserted_solution` ReviewRecord (`AS-C03`) recording what the evaluated claim on `slow` supports and what remains open for `nominal`. ## Experiment -The [chapter exercise](../../exercises/ch03/exercise.ipynb) asks you to add a requirement usage and an asserted_solution record to your coffee maker model. Work through it after completing all three notebooks. +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. diff --git a/chapters/ch04-functional-decomp/01-action-def-ffbd.ipynb b/chapters/ch04-functional-decomp/01-action-def-ffbd.ipynb index decef48..25abb54 100644 --- a/chapters/ch04-functional-decomp/01-action-def-ffbd.ipynb +++ b/chapters/ch04-functional-decomp/01-action-def-ffbd.ipynb @@ -1,116 +1,283 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, - "cells": [ - { - "cell_type": "markdown", - "id": "cell-00", - "metadata": {}, - "source": [ - "## action def\n", - "\n", - "This notebook introduces `action def`; after running it you can declare a named action with typed inputs and outputs and an ordered sequence of sub-actions." - ] - }, - { - "cell_type": "markdown", - "id": "cell-01", - "metadata": {}, - "source": [ - "The Chapter 3 model expresses *what* the toaster must accomplish (the requirement) and *how much* energy it delivers (the calculation). Chapter 4 adds the functional layer: *how* the system transforms inputs into outputs step by step. `action def` in SysML v2 declares a named behavior with `in`/`out` parameters, a `first`/`then` sequence, and nested `action` steps. This notebook adds `ApplyHeat` to the model." - ] - }, - { - "cell_type": "code", - "id": "cell-02", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from pathlib import Path\n", - "import opensysml\n", - "from toaster.report import format_diagnostics\n", - "\n", - "conn = opensysml.connect(version=\"v0.9.0\")\n", - "source = Path(\"../../models/ch04-cumulative.sysml\").read_text()\n", - "print(source)\n", - "model = conn.load_from_content(source, strict=False)\n", - "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" - ] - }, - { - "cell_type": "markdown", - "id": "cell-03", - "metadata": {}, - "source": [ - "The `ch04-cumulative.sysml` file adds `action def ApplyHeat` with sequential steps (`first start; then calculate; then done`) and a nested `calculate` action that calls `DeliveredEnergy`. Three `item def` types \u2014 `Start`, `Finish`, `Cancel` \u2014 declare typed items for structural use; they appear as part types in `BreadHandling` in Chapter 5, not as references inside `ApplyHeat` itself. This is the functional decomposition of the heating operation: inputs, process, and outputs all named." - ] - }, - { - "cell_type": "code", - "id": "cell-04", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# Negative control: an action def that references an undefined calculation\n", - "# in an assign statement raises \"unresolved reference\" at that site.\n", - "bad_source = \"\"\"\n", - "package Bad {\n", - " private import ScalarValues::*;\n", - " action def BadAction {\n", - " in power : Real;\n", - " out energy : Real;\n", - " first start;\n", - " then action step { assign energy := UndefinedCalc(power); }\n", - " then done;\n", - " }\n", - "}\n", - "\"\"\"\n", - "bad = conn.load_from_content(bad_source, strict=False)\n", - "assert not bad.ok\n", - "print(\"Expected error:\", bad.diagnostics[0].message)" - ] - }, - { - "cell_type": "code", - "id": "cell-05", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "action = model.find(\"ToasterDemo::ApplyHeat\")\n", - "assert action is not None\n", - "print(f\"action kind: {action.kind}\")\n", - "print(f\"action id : {action.id}\")\n", - "conn.close()" - ] - }, - { - "cell_type": "markdown", - "id": "cell-06", - "metadata": {}, - "source": [ - "`action def ApplyHeat { in power : Real; ... first start; then action calculate { ... } then done; }` is the A-F declaration; OpenSysML parses the sequence and sub-action assignments (O-S); `model.find()` returns the ActionDefinition symbol (E)." - ] - }, - { - "cell_type": "markdown", - "id": "cell-07", - "metadata": {}, - "source": [ - "Try the chapter exercise in `exercises/ch04/exercise.ipynb`: declare a `Brew` action def for your coffee maker with `waterTemp` and `duration` inputs, a `first`/`then` sequence, and an assign step using `HeatRate`." - ] - } - ] -} \ No newline at end of file + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": [ + "## action def\n", + "\n", + "This notebook introduces `action def`; after running it you can declare a named action with typed flows, a phenomena constraint, and nest it as a step of another action." + ] + }, + { + "cell_type": "markdown", + "id": "cell-01", + "metadata": {}, + "source": [ + "The Chapter 3 model expresses what the toaster must accomplish and evaluates a satisfaction claim against it, but says nothing about the steps by which the system does it. Chapter 4 adds the functional layer: named actions with typed flows and the relations that constrain them, composed into an actual decomposition of `ToastBread` from Chapter 1. This notebook adds `ApplyHeat`, an `action def` for the heating step, and nests it inside `ToastBread` as its first sub-action." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-02", + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "import opensysml\n", + "from toaster.report import format_diagnostics\n", + "\n", + "conn = opensysml.connect(version=\"v0.9.0\")\n", + "\n", + "# editor.add_action_def(owner='ToasterDemo', name='ApplyHeat') when API ships\n", + "ACTION_DEF_OPEN = \"action def ApplyHeat {\"\n", + "print(ACTION_DEF_OPEN)\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-03", + "metadata": {}, + "source": [ + "`action def ApplyHeat` opens the new action. Its parameters follow: first the inputs, then the outputs." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-04", + "metadata": {}, + "outputs": [], + "source": [ + "# params argument of add_action_def not yet supported, toaster#18 / OpenSysML#605\n", + "# spec: SysML v2 formal/2026-03-02 section 7.15 (ActionDefinition)\n", + "IN_PARAMS = \"\"\"\\\n", + " in bread : Bread;\n", + " in energy : ISQ::EnergyValue[0..*];\n", + " in duration : ISQ::DurationValue[0..*];\"\"\"\n", + "print(IN_PARAMS)\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-05", + "metadata": {}, + "source": [ + "Three typed inputs: `bread`, the material Chapter 1 already defines; `energy`, the energy supplied to the action; and `duration`, a signal from a control function stating how long to apply heat. No control function is modeled in this chapter, so `energy` and `duration` are declared, typed, and left unconnected to any value; `duration`'s own `doc` states its denotation directly. Both carry an explicit `[0..*]` multiplicity, spelled out rather than left implicit; the next few cells build the rest of `ApplyHeat` before explaining why." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-06", + "metadata": {}, + "outputs": [], + "source": [ + "OUT_PARAMS = \"\"\"\\\n", + " out toast : Toast;\n", + " out delivered : ISQ::EnergyValue;\n", + " out loss : ISQ::EnergyValue;\"\"\"\n", + "print(OUT_PARAMS)\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-07", + "metadata": {}, + "source": [ + "Three typed outputs: `toast`, the material Chapter 1 already defines; `delivered`, the energy actually delivered to the bread; and `loss`, the energy that is not. `delivered` and `loss` together are the two quantities the balance constraint bounds against `energy`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-08", + "metadata": {}, + "outputs": [], + "source": [ + "# spec: SysML v2 formal/2026-03-02 section 7.20.1 (AssertConstraintUsage)\n", + "BALANCE_CONSTRAINT = \"\"\"\\\n", + " assert constraint balance {\n", + " delivered >= 0.0 [SI::J] and loss >= 0.0 [SI::J] and delivered + loss <= energy\n", + " }\"\"\"\n", + "print(BALANCE_CONSTRAINT)\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-09", + "metadata": {}, + "source": [ + "The constraint relates the three energy flows without choosing a mechanism: delivered energy and loss are each non-negative, and together they can never exceed the energy supplied. Asserting the constraint, rather than leaving it a plain constraint usage that SysML only allows to hold sometimes (7.20.1), is what makes that bound actually required of every usage of `ApplyHeat`, not merely checkable case by case. No specific efficiency is assumed and no conversion formula is stated; a chosen heating solution characterizes its own efficiency later, once one exists." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-10", + "metadata": {}, + "outputs": [], + "source": [ + "APPLY_HEAT_DEF = f\"{ACTION_DEF_OPEN}\\n{IN_PARAMS}\\n{OUT_PARAMS}\\n{BALANCE_CONSTRAINT}\\n}}\"\n", + "print(APPLY_HEAT_DEF)\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-11", + "metadata": {}, + "source": [ + "`ApplyHeat` states typed flows and an asserted phenomena relation, but it is still a free-floating action, not a step of anything. The next fragment nests it inside `ToastBread`, the action Chapter 1 declared with no body, and binds `applyHeat`'s own `bread` input to `ToastBread`'s `bread`: the one flow actually available at this level. `energy` stays unbound; no energy source exists anywhere in the model yet." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-12", + "metadata": {}, + "outputs": [], + "source": [ + "# ToastBread (Chapter 1) declared bread/toast but no body; this fragment adds one.\n", + "TOASTBREAD_SEQUENCE = \"\"\"\\\n", + " first start;\n", + " then action applyHeat : ApplyHeat {\n", + " in bread = ToastBread::bread;\n", + " }\n", + " then done;\"\"\"\n", + "print(TOASTBREAD_SEQUENCE)\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-13", + "metadata": {}, + "source": [ + "`ToastBread`'s own doc and its `bread`/`toast` parameters are restated unchanged; the sequence is new, and it wires the one flow this level actually has. The full increment for this notebook is `ApplyHeat`'s declaration together with `ToastBread`'s reopened body." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-14", + "metadata": {}, + "outputs": [], + "source": [ + "TOASTBREAD_REOPENED = (\n", + " \"action def ToastBread {\\n\"\n", + " \" doc /* Transform bread into toast acceptable to its user. */\\n\"\n", + " \" in bread : Bread;\\n\"\n", + " \" out toast : Toast;\\n\"\n", + " f\"{TOASTBREAD_SEQUENCE}\\n\"\n", + " \"}\"\n", + ")\n", + "TOASTER_INCREMENT = f\"{APPLY_HEAT_DEF}\\n{TOASTBREAD_REOPENED}\"\n", + "print(TOASTER_INCREMENT)\n", + "\n", + "source = Path(\"../../models/ch04-cumulative.sysml\").read_text()\n", + "model = conn.load_from_content(source, strict=False)\n", + "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"\n" + ] + }, + { + "cell_type": "markdown", + "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`." + ] + }, + { + "cell_type": "markdown", + "id": "cell-16", + "metadata": {}, + "source": [ + "The next cell checks what happens when a nested step names an action definition that does not exist." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-17", + "metadata": {}, + "outputs": [], + "source": [ + "# Negative control: a nested step naming an undefined action definition\n", + "# raises \"unresolved reference\" at the reference site.\n", + "bad_source = \"\"\"\n", + "package Bad {\n", + " action def BadParent {\n", + " first start;\n", + " then action child : UndefinedAction;\n", + " then done;\n", + " }\n", + "}\n", + "\"\"\"\n", + "bad = conn.load_from_content(bad_source, strict=False)\n", + "assert not bad.ok\n", + "print(\"Expected error:\", bad.diagnostics[0].message)\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-18", + "metadata": {}, + "source": [ + "With the negative control confirmed, the next cell looks up `ApplyHeat` and confirms it resolves as a nested step of `ToastBread`, with its balance constraint present as a named member, and confirms the model stays fully evaluable: `slow`'s `cycleTime`, unrelated to `ApplyHeat`, still evaluates to the 200-second value Chapter 2 gave it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-19", + "metadata": {}, + "outputs": [], + "source": [ + "action = model.find(\"ToasterDemo::ApplyHeat\")\n", + "assert action is not None\n", + "print(f\"action kind: {action.kind}\")\n", + "\n", + "step = model.find(\"ToasterDemo::ToastBread::applyHeat\")\n", + "assert step is not None\n", + "print(f\"nested step kind: {step.kind}\")\n", + "\n", + "balance = model.find(\"ToasterDemo::ApplyHeat::balance\")\n", + "assert balance is not None\n", + "print(f\"balance constraint kind: {balance.kind}\")\n", + "\n", + "slow_cycle_time = model.eval(\"ToasterDemo::slow.cycleTime\")\n", + "print(f\"slow.cycleTime: {slow_cycle_time}\")\n", + "conn.close()\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-20", + "metadata": {}, + "source": [ + "`action def ApplyHeat { ... assert constraint balance { ... } }` and `ToastBread`'s reopened body, printed above, loaded without error, and `model.find()` resolves `ApplyHeat` as `ToastBread`'s own step and its balance constraint by name, shown by the kinds printed above. `model.find(...).kind` reports the general constraint kind, not the asserted subtype: the printed `constraintUsage` is not a contradiction of `assert constraint`, just a coarser label than the model's own declaration. `slow.cycleTime` evaluating to 200 seconds confirms the explicit `[0..*]` above keeps the whole model evaluable, not just `ApplyHeat` itself." + ] + }, + { + "cell_type": "markdown", + "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`." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/chapters/ch04-functional-decomp/02-heating-refinement.ipynb b/chapters/ch04-functional-decomp/02-heating-refinement.ipynb index c54472e..7db3ebf 100644 --- a/chapters/ch04-functional-decomp/02-heating-refinement.ipynb +++ b/chapters/ch04-functional-decomp/02-heating-refinement.ipynb @@ -1,109 +1,189 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - } + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": [ + "## item def\n", + "\n", + "This notebook introduces `item def`; after running it you can declare named signal types and state, in the model itself, what each one denotes." + ] }, - "cells": [ - { - "cell_type": "markdown", - "id": "cell-00", - "metadata": {}, - "source": [ - "## item def\n", - "\n", - "This notebook introduces `item def`; after running it you can declare named item types that flow between actions, giving the action sequence a typed material vocabulary." - ] - }, - { - "cell_type": "markdown", - "id": "cell-01", - "metadata": {}, - "source": [ - "The `ApplyHeat` action from the previous notebook transforms energy \u2014 but what physical things move through the toaster? `item def` in SysML v2 names the typed flows: the bread entering, the toast exiting, and the signal that cancels the cycle. Items are not parts (they do not own sub-structure); they are the typed goods that actions produce and consume. This notebook adds `Start`, `Finish`, and `Cancel` item definitions." - ] - }, - { - "cell_type": "code", - "id": "cell-02", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from pathlib import Path\n", - "import opensysml\n", - "from toaster.report import format_diagnostics\n", - "\n", - "conn = opensysml.connect(version=\"v0.9.0\")\n", - "source = Path(\"../../models/ch04-cumulative.sysml\").read_text()\n", - "print(source)\n", - "model = conn.load_from_content(source, strict=False)\n", - "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" - ] - }, - { - "cell_type": "markdown", - "id": "cell-03", - "metadata": {}, - "source": [ - "The `ch04-cumulative.sysml` file adds `action def ApplyHeat` with sequential steps (`first start; then calculate; then done`) and a nested `calculate` action that calls `DeliveredEnergy`. Three `item def` types \u2014 `Start`, `Finish`, `Cancel` \u2014 declare typed items for structural use; they appear as part types in `BreadHandling` in Chapter 5, not as references inside `ApplyHeat` itself. This is the functional decomposition of the heating operation: inputs, process, and outputs all named." - ] - }, - { - "cell_type": "code", - "id": "cell-04", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# Negative control: an item def that specializes an undefined type\n", - "# raises \"unresolved reference\" at the specialization site.\n", - "bad_source = \"\"\"\n", - "package Bad {\n", - " item def BadItem :> UndefinedBase;\n", - "}\n", - "\"\"\"\n", - "bad = conn.load_from_content(bad_source, strict=False)\n", - "assert not bad.ok\n", - "print(\"Expected error:\", bad.diagnostics[0].message)" - ] - }, - { - "cell_type": "code", - "id": "cell-05", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "for name in (\"ToasterDemo::Start\", \"ToasterDemo::Finish\", \"ToasterDemo::Cancel\"):\n", - " sym = model.find(name)\n", - " assert sym is not None, f\"Not found: {name}\"\n", - " print(f\"{sym.id}: kind={sym.kind}\")\n", - "conn.close()" - ] - }, - { - "cell_type": "markdown", - "id": "cell-06", - "metadata": {}, - "source": [ - "`item def Start; item def Finish; item def Cancel;` (A-F) are parsed and registered by OpenSysML (O-S); `model.find()` retrieves each item symbol with its kind (E)." - ] - }, - { - "cell_type": "markdown", - "id": "cell-07", - "metadata": {}, - "source": [ - "Try the chapter exercise in `exercises/ch04/exercise.ipynb`: add `item def CoffeeGrounds` and `item def BrewedCoffee` to your coffee maker model and confirm both are findable." - ] - } - ] -} \ No newline at end of file + { + "cell_type": "markdown", + "id": "cell-01", + "metadata": {}, + "source": [ + "The previous notebook nested `ApplyHeat` inside `ToastBread` using the material flows Chapter 1 already defines (`Bread`, `Toast`) and the energy flows this chapter adds. The toasting cycle also has three named nouns Chapter 1 leaves undefined: a start, a finish, and a cancel request. This notebook declares them as item definitions and states, with each one's own `doc`, that they denote signals: cycle start, cycle finish, cancel request, not the bread or toast themselves." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-02", + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "import opensysml\n", + "from toaster.report import format_diagnostics\n", + "\n", + "conn = opensysml.connect(version=\"v0.9.0\")\n", + "\n", + "# editor.add_item_def(owner='ToasterDemo', name='Start') when API ships\n", + "START_DEF = \"\"\"\\\n", + "item def Start {\n", + " doc /* Signal marking the start of a toasting cycle, not the bread itself. */\n", + "}\"\"\"\n", + "print(START_DEF)\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-03", + "metadata": {}, + "source": [ + "`Start` names a signal, not the bread entering the toaster: that material flow is already `ToastBread::bread`. The `doc` states this plainly so the model, not the surrounding prose, is the authority on what `Start` denotes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-04", + "metadata": {}, + "outputs": [], + "source": [ + "# editor.add_item_def(owner='ToasterDemo', name='Finish') when API ships\n", + "FINISH_DEF = \"\"\"\\\n", + "item def Finish {\n", + " doc /* Signal marking the finish of a toasting cycle, not the toast itself. */\n", + "}\"\"\"\n", + "print(FINISH_DEF)\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-05", + "metadata": {}, + "source": [ + "`Finish` names the paired signal for the cycle's end, distinct from `ToastBread::toast`, the toast itself." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-06", + "metadata": {}, + "outputs": [], + "source": [ + "# editor.add_item_def(owner='ToasterDemo', name='Cancel') when API ships\n", + "CANCEL_DEF = \"\"\"\\\n", + "item def Cancel {\n", + " doc /* Signal requesting cancellation of an in-progress toasting cycle. */\n", + "}\"\"\"\n", + "print(CANCEL_DEF)\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-07", + "metadata": {}, + "source": [ + "`Cancel` is the third signal: a request to stop an in-progress cycle. None of the three is wired as an accepted or produced item of `ApplyHeat` in this chapter; each states its own meaning so a later chapter that does wire them has a fixed denotation to wire to, not an undecided one." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-08", + "metadata": {}, + "outputs": [], + "source": [ + "TOASTER_INCREMENT = f\"{START_DEF}\\n{FINISH_DEF}\\n{CANCEL_DEF}\"\n", + "print(TOASTER_INCREMENT)\n", + "\n", + "source = Path(\"../../models/ch04-cumulative.sysml\").read_text()\n", + "model = conn.load_from_content(source, strict=False)\n", + "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-09", + "metadata": {}, + "source": [ + "The three item definitions load without diagnostics. The next cell checks what happens when an item definition specializes a type that does not exist." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-10", + "metadata": {}, + "outputs": [], + "source": [ + "# Negative control: an item def that specializes an undefined type\n", + "# raises \"unresolved reference\" at the specialization site.\n", + "bad_source = \"\"\"\n", + "package Bad {\n", + " item def BadItem :> UndefinedBase;\n", + "}\n", + "\"\"\"\n", + "bad = conn.load_from_content(bad_source, strict=False)\n", + "assert not bad.ok\n", + "print(\"Expected error:\", bad.diagnostics[0].message)\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-11", + "metadata": {}, + "source": [ + "With the negative control confirmed, the next cell confirms all three item definitions, each carrying its own denotation as a `doc`, resolve in the loaded model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-12", + "metadata": {}, + "outputs": [], + "source": [ + "for name in (\"ToasterDemo::Start\", \"ToasterDemo::Finish\", \"ToasterDemo::Cancel\"):\n", + " sym = model.find(name)\n", + " assert sym is not None, f\"Not found: {name}\"\n", + " print(f\"{sym.id}: kind={sym.kind}\")\n", + "conn.close()\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-13", + "metadata": {}, + "source": [ + "`item def Start { doc ... } item def Finish { doc ... } item def Cancel { doc ... }`, printed above, loaded without error, and `model.find()` resolves each one, with each kind confirmed above." + ] + }, + { + "cell_type": "markdown", + "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." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/chapters/ch04-functional-decomp/03-completeness-check.ipynb b/chapters/ch04-functional-decomp/03-completeness-check.ipynb index 4296bdc..1185311 100644 --- a/chapters/ch04-functional-decomp/03-completeness-check.ipynb +++ b/chapters/ch04-functional-decomp/03-completeness-check.ipynb @@ -1,135 +1,387 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, - "cells": [ - { - "cell_type": "markdown", - "id": "cell-00", - "metadata": {}, - "source": [ - "## completeness check\n", - "\n", - "This notebook introduces `asserted_inference`; after running it you can record a functional-completeness judgment that ties child claims (the action-level evidence) to a parent claim (the functional architecture is complete), following Hawkins \u00a73.1." - ] - }, - { - "cell_type": "markdown", - "id": "cell-01", - "metadata": {}, - "source": [ - "The Chapter 4 model now has a functional decomposition: `ApplyHeat` sequences power input through a calculation to an energy output, with item types naming the flows. The question is whether that decomposition is complete \u2014 does every input flow contribute to an output? An `asserted_inference` (Hawkins \u00a73.1) records this judgment: a parent claim supported by child claims, rather than directly by evidence. This is the first use of the inference record type." - ] - }, - { - "cell_type": "code", - "id": "cell-02", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from pathlib import Path\n", - "import opensysml\n", - "from toaster.report import format_diagnostics\n", - "\n", - "conn = opensysml.connect(version=\"v0.9.0\")\n", - "source = Path(\"../../models/ch04-cumulative.sysml\").read_text()\n", - "print(source)\n", - "model = conn.load_from_content(source, strict=False)\n", - "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" - ] - }, - { - "cell_type": "markdown", - "id": "cell-03", - "metadata": {}, - "source": [ - "The `ch04-cumulative.sysml` file adds `action def ApplyHeat` with sequential steps (`first start; then calculate; then done`) and a nested `calculate` action that calls `DeliveredEnergy`. Three `item def` types \u2014 `Start`, `Finish`, `Cancel` \u2014 declare typed items for structural use; they appear as part types in `BreadHandling` in Chapter 5, not as references inside `ApplyHeat` itself. This is the functional decomposition of the heating operation: inputs, process, and outputs all named." - ] - }, - { - "cell_type": "code", - "id": "cell-04", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# Negative control: an action def that assigns to an out parameter via\n", - "# an undefined action definition raises \"unresolved reference\".\n", - "bad_source = \"\"\"\n", - "package Bad {\n", - " private import ScalarValues::*;\n", - " action def BadDecomp {\n", - " in x : Real;\n", - " out y : Real;\n", - " first start;\n", - " then action step : UndefinedActionDef;\n", - " then done;\n", - " }\n", - "}\n", - "\"\"\"\n", - "bad = conn.load_from_content(bad_source, strict=False)\n", - "assert not bad.ok\n", - "print(\"Expected error:\", bad.diagnostics[0].message)" - ] - }, - { - "cell_type": "code", - "id": "cell-05", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "inference_record = ReviewRecord(\n", - " identifier=\"AI-C04\",\n", - " kind=\"asserted_inference\",\n", - " claim=\"The ApplyHeat action decomposition is functionally complete: all inputs are consumed and the output is assigned.\",\n", - " model_ref=\"ToasterDemo::ApplyHeat\",\n", - " content_hash=hash_content(source),\n", - " scope=\"ToasterDemo\",\n", - " criteria=\"Every in parameter feeds at least one sub-action; the out parameter is assigned before done.\",\n", - " premises=[\"AS-C03\"],\n", - " assumption_refs=[],\n", - " evidence_refs=[\"action calculate { assign energy := DeliveredEnergy(power, duration, efficiency); }\"],\n", - " rationale=\"The calculate action consumes all three in parameters (power, duration, efficiency) and assigns the out parameter (energy). No input is unrouted.\",\n", - " counterevidence=\"The model does not capture heat loss or warm-up transients \u2014 those flows are absent from this decomposition.\",\n", - " residual_uncertainties=\"Temporal ordering via first/then is syntactic; actual execution semantics are not checked by this model alone.\",\n", - " disposition=\"pending\",\n", - " dependency_freshness=\"current\",\n", - " engineering_conclusion=\"undetermined\",\n", - " record_kind=\"worked_example\",\n", - ")\n", - "\n", - "errors = validate_record(inference_record)\n", - "print(f\"Validation errors: {errors}\")\n", - "print(f\"Record kind: {inference_record.kind}\")\n", - "conn.close()" - ] - }, - { - "cell_type": "markdown", - "id": "cell-06", - "metadata": {}, - "source": [ - "The Hawkins \u00a73.1 schema specifies what an `asserted_inference` record must contain, including a non-empty `premises` list (A-F); filling and validating the `ReviewRecord` in Python enacts that schema (O-S); `validate_record()` returning `[]` confirms all required fields are present (E)." - ] - }, - { - "cell_type": "markdown", - "id": "cell-07", - "metadata": {}, - "source": [ - "Try the chapter exercise in `exercises/ch04/exercise.ipynb`: write an `asserted_inference` record claiming that the `BrewUnit` action decomposition accounts for all inputs and outputs." - ] - } - ] -} \ No newline at end of file + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": [ + "## completeness check\n", + "\n", + "This notebook introduces `asserted_inference`; after running it you can record a functional-completeness judgment tying child claims to a parent claim, following Hawkins §3.1." + ] + }, + { + "cell_type": "markdown", + "id": "cell-01", + "metadata": {}, + "source": [ + "The Chapter 4 model now nests `ApplyHeat` inside `ToastBread`, with typed flows for bread and energy and a balance constraint bounding delivered energy and loss by the energy supplied. The question this notebook asks is narrower than whether the toaster's functional architecture is complete: it is whether the flows this one worked example names are accounted for, honestly scoped to what one action out of Douglas's roughly fifteen verb-noun functions can show." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-02", + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "import opensysml\n", + "from toaster.evidence import ReviewRecord, hash_content, validate_record\n", + "from toaster.report import format_diagnostics\n", + "\n", + "conn = opensysml.connect(version=\"v0.9.0\")\n", + "source = Path(\"../../models/ch04-cumulative.sysml\").read_text()\n", + "print(source)\n", + "model = conn.load_from_content(source, strict=False)\n", + "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-03", + "metadata": {}, + "source": [ + "The cumulative model prints above: `ApplyHeat` with its balance constraint, nested inside `ToastBread`, and the three signal item definitions from the previous notebook. The next cell checks what happens when a constraint references a feature the action definition does not declare." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-04", + "metadata": {}, + "outputs": [], + "source": [ + "# Negative control: an asserted constraint referencing a feature the action\n", + "# definition does not declare raises \"unresolved reference\".\n", + "bad_source = \"\"\"\n", + "package Bad {\n", + " private import ScalarValues::*;\n", + " action def BadHeat {\n", + " in energy : Real;\n", + " out delivered : Real;\n", + " out loss : Real;\n", + " assert constraint balance { delivered + loss <= notAFeature }\n", + " }\n", + "}\n", + "\"\"\"\n", + "bad = conn.load_from_content(bad_source, strict=False)\n", + "assert not bad.ok\n", + "print(\"Expected error:\", bad.diagnostics[0].message)\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-05", + "metadata": {}, + "source": [ + "With the negative control confirmed, the next cells build `AI-C04`: an `asserted_inference` record about `ApplyHeat`'s own flow accounting, following the construction zone Hawkins' taxonomy uses (claim, frame, premises, evidence, challenge, assemble)." + ] + }, + { + "cell_type": "markdown", + "id": "cell-06", + "metadata": {}, + "source": [ + "The claim first: what is being claimed, and about which model element." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-07", + "metadata": {}, + "outputs": [], + "source": [ + "claim = (\"ApplyHeat's typed flows account for bread, energy and duration in, and \"\n", + " \"toast, delivered energy and loss out; the asserted balance constraint \"\n", + " \"(delivered and loss each non-negative, their sum bounded by energy) is a \"\n", + " \"real, evaluable relation, not merely declared syntax. ApplyHeat is a step \"\n", + " \"of ToastBread, its bread input bound to ToastBread's own bread.\")\n", + "model_ref = \"ToasterDemo::ApplyHeat\"\n", + "print(claim)\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-08", + "metadata": {}, + "source": [ + "The standard the claim is checked against: what would make this flow accounting complete, at the scope this one worked example claims (Hawkins' appropriateness)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-09", + "metadata": {}, + "outputs": [], + "source": [ + "scope = \"ToasterDemo::ApplyHeat, nested inside ToasterDemo::ToastBread\"\n", + "criteria = (\"Every declared flow (bread, energy, duration in; toast, delivered, loss \"\n", + " \"out) is named and typed, and the asserted balance constraint (delivered \"\n", + " \"and loss each non-negative, their sum bounded by energy) evaluates against \"\n", + " \"concrete values, holding or failing as conservation requires. This is not \"\n", + " \"the criterion for the toaster's full functional architecture (about \"\n", + " \"fifteen verb-noun functions, index.md); it is the criterion for this one \"\n", + " \"worked example.\")\n", + "print(criteria)\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-10", + "metadata": {}, + "source": [ + "What the claim takes as given: the prior chapter's satisfaction judgment, and one modeling choice this chapter itself makes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-11", + "metadata": {}, + "outputs": [], + "source": [ + "premises = [\"AS-C03\"]\n", + "assumption_refs = [\n", + " \"duration is declared as a typed input but plays no role in the balance \"\n", + " \"constraint: this chapter does not derive delivered energy from duration, \"\n", + " \"so duration's own denotation as a signal from a control function is stated \"\n", + " \"but not yet connected to any computation.\",\n", + "]\n", + "print(assumption_refs)\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-12", + "metadata": {}, + "source": [ + "Sufficiency needs real evidence too for notebook 01's fix: writing `energy` and `duration` with an explicit `[0..*]` keeps the whole model evaluable, not just `ApplyHeat` in isolation. The next cell checks this directly against the model already loaded above." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-13", + "metadata": {}, + "outputs": [], + "source": [ + "slow_cycle_time = model.eval(\"ToasterDemo::slow.cycleTime\")\n", + "print(f\"slow.cycleTime: {slow_cycle_time}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-14", + "metadata": {}, + "source": [ + "`slow.cycleTime` evaluates to the 200-second value Chapter 2 gave it, unrelated to `ApplyHeat` entirely: the explicit `[0..*]` keeps the model fully evaluable, exactly the claim `evidence_refs` cites below." + ] + }, + { + "cell_type": "markdown", + "id": "cell-15", + "metadata": {}, + "source": [ + "Sufficiency also needs evidence for the balance constraint itself, not just a description of what it would do. The next cell builds three usages of `ApplyHeat`: one plausible, one that overdraws the energy budget, and one with a negative loss, in a small model that mirrors `ApplyHeat`'s own declaration rather than the toaster's own model, since `nominal` and `slow` carry no concrete `energy`, `delivered` or `loss` values to check the constraint against." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-16", + "metadata": {}, + "outputs": [], + "source": [ + "PROBE_SOURCE = \"\"\"\n", + "package Probe {\n", + " private import ScalarValues::*;\n", + " private import SI::*;\n", + " private import ISQ::*;\n", + " item def Bread;\n", + " item def Toast;\n", + " action def ApplyHeat {\n", + " in bread : Bread;\n", + " in energy : ISQ::EnergyValue[0..*];\n", + " in duration : ISQ::DurationValue[0..*];\n", + " out toast : Toast;\n", + " out delivered : ISQ::EnergyValue;\n", + " out loss : ISQ::EnergyValue;\n", + " assert constraint balance {\n", + " delivered >= 0.0 [SI::J] and loss >= 0.0 [SI::J] and delivered + loss <= energy\n", + " }\n", + " }\n", + " action plausibleHeat : ApplyHeat {\n", + " :>> energy = 1000.0 [SI::J];\n", + " :>> delivered = 700.0 [SI::J];\n", + " :>> loss = 200.0 [SI::J];\n", + " }\n", + " action implausibleHeat : ApplyHeat {\n", + " :>> energy = 1000.0 [SI::J];\n", + " :>> delivered = 900.0 [SI::J];\n", + " :>> loss = 300.0 [SI::J];\n", + " }\n", + " action negativeLossHeat : ApplyHeat {\n", + " :>> energy = 1000.0 [SI::J];\n", + " :>> delivered = 1500.0 [SI::J];\n", + " :>> loss = -600.0 [SI::J];\n", + " }\n", + "}\n", + "\"\"\"\n", + "probe_model = conn.load_from_content(PROBE_SOURCE, strict=False)\n", + "assert probe_model.ok\n", + "\n", + "probe_verdicts = {}\n", + "for name in (\"Probe::plausibleHeat\", \"Probe::implausibleHeat\", \"Probe::negativeLossHeat\"):\n", + " probe_verdicts[name] = probe_model.verify_constraint(\n", + " \"Probe::ApplyHeat::balance\", subject=name\n", + " )\n", + " print(f\"{name}: {probe_verdicts[name]}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-17", + "metadata": {}, + "source": [ + "The constraint holds for the plausible usage and fails for both faulty ones: the implausible split that overdraws the energy budget, and the negative-loss usage that satisfies the sum bound alone only by letting `loss` go negative. Asserting non-negativity alongside the sum bound is what catches this second case; `evidence_refs` and `rationale` cite all three results directly." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-18", + "metadata": {}, + "outputs": [], + "source": [ + "evidence_refs = [\n", + " f\"ToasterDemo::slow.cycleTime: {slow_cycle_time}, evaluated directly against \"\n", + " \"the loaded model above.\",\n", + " f\"balance constraint probe: {probe_verdicts['Probe::plausibleHeat']}\",\n", + " f\"balance constraint probe: {probe_verdicts['Probe::implausibleHeat']}\",\n", + " f\"balance constraint probe: {probe_verdicts['Probe::negativeLossHeat']}\",\n", + " \"ToasterDemo::ToastBread::applyHeat: ApplyHeat resolves as a nested action \"\n", + " \"usage with its bread input bound to ToastBread::bread, confirmed by \"\n", + " \"model.find() in the previous notebook.\",\n", + "]\n", + "rationale = (\"Every parameter ApplyHeat declares is named in the claim above, and the \"\n", + " \"explicit [0..*] on energy and duration keeps the whole model evaluable: \"\n", + " \"slow.cycleTime, unrelated to ApplyHeat, evaluates cleanly above. The \"\n", + " \"asserted balance constraint is not merely stated either: the probe above \"\n", + " \"shows it holds for values consistent with conservation and fails for both \"\n", + " \"an overdrawn split and a negative-loss split, so the non-negativity bounds \"\n", + " \"are doing real work, not just the sum bound. ApplyHeat is reachable from \"\n", + " \"ToastBread's own sequence, so it is a step of a decomposition, not a \"\n", + " \"definition nothing composes.\")\n", + "print(rationale)\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-19", + "metadata": {}, + "source": [ + "The challenge: what this does not demonstrate, and what stays open (Hawkins' trustworthiness)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-20", + "metadata": {}, + "outputs": [], + "source": [ + "counterevidence = (\"bread is wired from ToastBread's own bread parameter, but energy \"\n", + " \"is not: no energy source exists anywhere in the model yet, so leaving it \"\n", + " \"unbound is honest, not an omission. duration is declared but unconnected \"\n", + " \"to the balance constraint or to any other computation in this chapter, so \"\n", + " \"it does not yet participate in the flow accounting it claims to name. \"\n", + " \"ApplyHeat is the only function modeled; Douglas's functional architecture \"\n", + " \"names roughly fifteen, so this is a complete accounting for one worked \"\n", + " \"example, not a complete functional decomposition of the toaster. The \"\n", + " \"probe above checks a small model mirroring ApplyHeat's declaration, not \"\n", + " \"nominal or slow themselves, which give ApplyHeat no concrete energy \"\n", + " \"value.\")\n", + "residual_uncertainties = (\"Start, Finish and Cancel are declared as signals (their own \"\n", + " \"doc states this) but are not wired as accepted or produced items of \"\n", + " \"ApplyHeat in this chapter; whether they should be is left open for \"\n", + " \"whichever chapter models the cycle's event handling. efficiency and a \"\n", + " \"characterized conversion belong to a logical component this chapter does \"\n", + " \"not build; this record makes no claim about them.\")\n", + "print(counterevidence)\n", + "print(residual_uncertainties)\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-21", + "metadata": {}, + "source": [ + "With every part named above, the record assembles from them directly." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-22", + "metadata": {}, + "outputs": [], + "source": [ + "inference_record = ReviewRecord(\n", + " identifier=\"AI-C04\",\n", + " kind=\"asserted_inference\",\n", + " claim=claim,\n", + " model_ref=model_ref,\n", + " content_hash=hash_content(source),\n", + " scope=scope,\n", + " criteria=criteria,\n", + " premises=premises,\n", + " assumption_refs=assumption_refs,\n", + " evidence_refs=evidence_refs,\n", + " rationale=rationale,\n", + " counterevidence=counterevidence,\n", + " residual_uncertainties=residual_uncertainties,\n", + " disposition=\"pending\",\n", + " dependency_freshness=\"current\",\n", + " engineering_conclusion=\"supported\",\n", + " record_kind=\"worked_example\",\n", + ")\n", + "\n", + "errors = validate_record(inference_record)\n", + "print(f\"Validation errors: {errors}\")\n", + "print(f\"Record kind: {inference_record.kind}\")\n", + "conn.close()\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-23", + "metadata": {}, + "source": [ + "The Hawkins §3.1 schema fields named above are what the assembled record satisfies, and `validate_record` returning an empty list confirms the required fields, including a non-empty `premises`, are present, not merely printed." + ] + }, + { + "cell_type": "markdown", + "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`." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/chapters/ch04-functional-decomp/conclusion.md b/chapters/ch04-functional-decomp/conclusion.md index c8f54bc..c1749a0 100644 --- a/chapters/ch04-functional-decomp/conclusion.md +++ b/chapters/ch04-functional-decomp/conclusion.md @@ -1,15 +1,15 @@ -# Chapter 4 — Conclusion +# Chapter 4: Conclusion ## What we built -The Chapter 4 model adds three constructs to the cumulative model. `ApplyHeat` is an action definition that sequences three inputs (power, duration, efficiency) through a `calculate` sub-action that assigns the output (`energy`) via `DeliveredEnergy`. `Start`, `Finish`, and `Cancel` are item definitions naming the typed flows that move through the cycle. The Python side adds `AI-C04`, an `asserted_inference` ReviewRecord that claims the decomposition is complete, 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 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. ## What this establishes -The chapter answers its engineering question: the toaster now has a formal functional layer. `ApplyHeat` states what the system *does* — sequence inputs through a calculation to produce an output — without committing to how the hardware achieves it. The inference record makes the completeness argument visible: every input reaches at least one sub-action, and the output is assigned. That argument rests on a prior solution claim, which is why `premises` references `AS-C03`. +The chapter answers its engineering question: the toaster now has one functional step, correctly typed and correctly placed. `ApplyHeat` states what the system *does*, bread and energy in, toast and accounted-for energy out, without committing to how the hardware achieves it. The balance constraint is a real, evaluable, asserted relation, not a conversion formula: it holds or fails against concrete values, catching both an overdrawn energy split and a negative-loss split, and no specific efficiency is assumed. `ApplyHeat` is reachable as `ToastBread`'s own step, which is what makes this a decomposition rather than an isolated action, and the model stays fully evaluable: `slow.cycleTime`, unrelated to `ApplyHeat`, evaluates the same 200 seconds Chapter 2 gave it. The inference record states the scope honestly: this is a flow accounting for the one function modeled, not for the toaster's full functional architecture of roughly fifteen verb-noun functions. ## What comes next -Chapter 5 asks how functions are allocated to parts and how interfaces between parts are defined. It introduces `allocate` for assignment relationships and `flow` for item flows between parts. +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 an `EjectToast` action for the bread-removal path and write an `asserted_inference` record claiming the eject sequence is complete. Use the same pattern as `ApplyHeat` and `AI-C04`. +**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`. diff --git a/chapters/ch04-functional-decomp/index.md b/chapters/ch04-functional-decomp/index.md index 882ce66..efe216c 100644 --- a/chapters/ch04-functional-decomp/index.md +++ b/chapters/ch04-functional-decomp/index.md @@ -1,34 +1,35 @@ -# Chapter 4 — Functional Decomposition +# Chapter 4: Functional Decomposition ## Purpose -Chapter 4 asks: how do we describe the sequence of functional steps that transforms inputs into outputs? After completing this chapter, the model contains an `action def` with typed `in`/`out` parameters and an ordered sequence of sub-actions, item definitions naming the flows between actions, and a Python judgment record claiming the decomposition is functionally complete. +Chapter 4 asks: how do we describe one functional step and make it an actual step of a larger function's decomposition? A step needs typed flows in and out and a phenomena relation among them. After completing this chapter, the model contains an `action def` (`ApplyHeat`) with typed `in`/`out` parameters for bread, energy and duration, an asserted balance-inequality constraint requiring delivered energy and loss to each be non-negative and together bounded by the energy supplied, and `ApplyHeat` nested as a step of `ToastBread` from Chapter 1. It also contains three item definitions naming the cycle's signals, and a Python judgment record claiming the flows this worked example names are accounted for. ## Ingredients | Notebook | Construct | Concept | |---|---|---| -| [01 — action def](01-action-def-ffbd.ipynb) | `action def` with `in`/`out`, `first`/`then`, nested `action` | A named behavior with ordered sub-actions and typed parameter assignments | -| [02 — item def](02-heating-refinement.ipynb) | `item def` | Typed goods that flow between actions | -| [03 — completeness check](03-completeness-check.ipynb) | `asserted_inference` ReviewRecord | A judgment record claiming child claims support a parent claim | +| [01: action def](01-action-def-ffbd.ipynb) | `action def` with `in`/`out`, a balance constraint, nested as a step of another action | A named behavior with typed flows, a phenomena relation, and its place in a decomposition | +| [02: item def](02-heating-refinement.ipynb) | `item def` | Named signals, each stating its own denotation | +| [03: completeness check](03-completeness-check.ipynb) | `asserted_inference` ReviewRecord | A judgment record claiming child claims support a parent claim | ## Equipment -See [setup](../../docs/setup.md) to provision Python, Node, and the OpenSysML binary before running any notebook. +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. ## Method -Notebook 01 adds `ApplyHeat`, an action definition that sequences power input through `DeliveredEnergy` to an energy output using `first`/`then` and a nested assign step. Notebook 02 adds `Start`, `Finish`, and `Cancel` — three item definitions that name the typed flows entering and leaving the cycle. Notebook 03 introduces the first `asserted_inference` record: a parent claim (the decomposition is complete) supported by a child claim (the calculate sub-action accounts for all parameters). +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). ## Expected result The Ch4 cumulative model contains everything from Ch1-3, plus: -- `action def ApplyHeat { in power : Real; in duration : Real; in efficiency : Real; out energy : Real; first start; then action calculate { assign energy := DeliveredEnergy(power, duration, efficiency); } then done; }` -- `item def Start; item def Finish; item def Cancel;` +- `action def ApplyHeat { in bread : Bread; in energy : ISQ::EnergyValue[0..*]; in duration : ISQ::DurationValue[0..*]; out toast : Toast; out delivered : ISQ::EnergyValue; out loss : ISQ::EnergyValue; assert constraint balance { delivered >= 0.0 [SI::J] and loss >= 0.0 [SI::J] and delivered + loss <= energy } }` +- `ToastBread`'s body (Chapter 1 declared none): `first start; then action applyHeat : ApplyHeat { in bread = ToastBread::bread; } then done;` +- `item def Start { doc ... } item def Finish { doc ... } item def Cancel { doc ... }`, each `doc` stating the signal it names The Python side carries an `asserted_inference` ReviewRecord (`AI-C04`) with a non-empty `premises` list referencing `AS-C03`. ## Experiment -The [chapter exercise](../../exercises/ch04/exercise.ipynb) asks you to define an `EjectToast` action and write an `asserted_inference` record for your coffee maker. Work through it after completing all three notebooks. +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. diff --git a/chapters/ch05-architecture/01-concept-selection.ipynb b/chapters/ch05-architecture/01-concept-selection.ipynb index 070bec0..f8a023d 100644 --- a/chapters/ch05-architecture/01-concept-selection.ipynb +++ b/chapters/ch05-architecture/01-concept-selection.ipynb @@ -1,118 +1,122 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - } + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": [ + "## model navigation\n", + "\n", + "This notebook introduces `model.find()` and `model.get()` for navigating model elements by qualified name; after running it you can inspect any named element in the cumulative model directly." + ] }, - "cells": [ - { - "cell_type": "markdown", - "id": "cell-00", - "metadata": {}, - "source": [ - "This notebook introduces `model.find()` and `model.get()` for navigating model elements by qualified name; after running it you can inspect any named element in the cumulative model without knowing its position in the query result list." - ] - }, - { - "cell_type": "markdown", - "id": "cell-01", - "metadata": {}, - "source": [ - "Chapter 5 shifts from defining the model to inspecting and extending it. The cumulative model has fifteen named elements spanning five kinds: part definitions, part usages, a requirement, a calc def, an action def, and item definitions. Navigating by position is fragile; navigating by qualified name (`package::element`) is stable across additions.\n", - "\n", - "`model.find(name)` returns a `Symbol` or `None` for a short or fully-qualified name. `model.get(fqn)` returns a `Symbol` and raises if the name is absent. Together they provide the navigation layer Ch5 and Ch6 build on." - ] - }, - { - "cell_type": "code", - "id": "cell-02", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from pathlib import Path\n", - "import opensysml\n", - "from toaster.report import format_diagnostics\n", - "\n", - "conn = opensysml.connect(version=\"v0.9.0\")\n", - "source = Path(\"../../models/ch05-cumulative.sysml\").read_text()\n", - "print(source)\n", - "model = conn.load_from_content(source, strict=False)\n", - "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" - ] - }, - { - "cell_type": "markdown", - "id": "cell-03", - "metadata": {}, - "source": [ - "The `ch05-cumulative.sysml` file adds two architectural constructs: `allocate ApplyHeat to HeatingSystem` records the functional-to-physical assignment, and a `BreadHandling` subsystem with `flow loader.bread to ejector.bread` expresses the item flow at the port level. These connect the functional layer (actions) to the structural layer (parts)." - ] - }, - { - "cell_type": "code", - "id": "cell-04", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# Negative control: specializing from an undefined type raises \"unresolved reference\".\n", - "# find/get can only navigate elements that parsed successfully \u2014 this confirms\n", - "# the model must be valid before navigation is meaningful.\n", - "bad_source = \"\"\"\n", - "package BadNav {\n", - " private import ScalarValues::*;\n", - " part def Probe :> UndefinedBase;\n", - "}\n", - "\"\"\"\n", - "bad = conn.load_from_content(bad_source, strict=False)\n", - "assert not bad.ok\n", - "print(f\"Neg control diagnostics: {bad.diagnostics[0].message!r}\")" - ] - }, - { - "cell_type": "code", - "id": "cell-05", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# Navigate to the Heater part definition\n", - "heater = model.find(\"ToasterDemo::Heater\")\n", - "print(f\"find result: id={heater.id!r}, kind={heater.kind!r}\")\n", - "\n", - "# Navigate to the DeliveredEnergy calc def by FQN\n", - "energy_calc = model.get(\"ToasterDemo::DeliveredEnergy\")\n", - "print(f\"get result: id={energy_calc.id!r}, kind={energy_calc.kind!r}\")\n", - "\n", - "# model.find returns None for unknown names (no exception)\n", - "missing = model.find(\"ToasterDemo::Nonexistent\")\n", - "assert missing is None\n", - "print(f\"missing element: {missing}\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-06", - "metadata": {}, - "source": [ - "The qualified-name addressing scheme in SysML v2 (A-F) is traversed by `model.find()` and `model.get()` in OpenSysML (O-S); the symbol's `id` and `kind` are printed, confirming the element is present and correctly typed (E)." - ] - }, - { - "cell_type": "markdown", - "id": "cell-07", - "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." - ] - } - ] -} \ No newline at end of file + { + "cell_type": "markdown", + "id": "cell-01", + "metadata": {}, + "source": [ + "Chapter 5 shifts from defining the model to inspecting and extending it. The cumulative model now carries dozens of named elements: part definitions, part usages, ports, an allocation, a requirement, and item definitions. Navigating by position in a query result is fragile; navigating by qualified name (`package::element`) is stable as the model grows.\n", + "\n", + "`model.find(name)` returns a `Symbol` or `None` for a short or fully-qualified name. `model.get(fqn)` returns a `Symbol` and raises if the name is absent. Together they are the navigation layer the rest of this chapter, and Chapter 6, build on." + ] + }, + { + "cell_type": "code", + "id": "cell-02", + "metadata": {}, + "source": [ + "from pathlib import Path\n", + "import opensysml\n", + "from toaster.report import format_diagnostics\n", + "\n", + "conn = opensysml.connect(version=\"v0.9.0\")\n", + "source = Path(\"../../models/ch05-cumulative.sysml\").read_text()\n", + "print(source)\n", + "model = conn.load_from_content(source, strict=False)\n", + "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "cell-03", + "metadata": {}, + "source": [ + "`model.find()` and `model.get()` only resolve elements that parsed. The negative control below loads a model with an unresolved specialization and confirms it fails before navigation is attempted." + ] + }, + { + "cell_type": "code", + "id": "cell-04", + "metadata": {}, + "source": [ + "bad_source = \"\"\"\n", + "package BadNav {\n", + " private import ScalarValues::*;\n", + " part def Probe :> UndefinedBase;\n", + "}\n", + "\"\"\"\n", + "bad = conn.load_from_content(bad_source, strict=False)\n", + "assert not bad.ok\n", + "print(f\"Neg control diagnostics: {bad.diagnostics[0].message!r}\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "cell-05", + "metadata": {}, + "source": [ + "The diagnostic reports an unresolved reference: `UndefinedBase` is not a name in scope, so the model fails to load before any navigation can run." + ] + }, + { + "cell_type": "code", + "id": "cell-06", + "metadata": {}, + "source": [ + "heating = model.find(\"ToasterDemo::HeatingSystem\")\n", + "print(f\"find result: id={heating.id!r}, kind={heating.kind!r}\")\n", + "\n", + "apply_heat = model.get(\"ToasterDemo::ApplyHeat\")\n", + "print(f\"get result: id={apply_heat.id!r}, kind={apply_heat.kind!r}\")\n", + "\n", + "missing = model.find(\"ToasterDemo::Nonexistent\")\n", + "assert missing is None\n", + "print(f\"missing element: {missing}\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "cell-07", + "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." + ] + }, + { + "cell_type": "markdown", + "id": "cell-08", + "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." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/chapters/ch05-architecture/02-allocate.ipynb b/chapters/ch05-architecture/02-allocate.ipynb index b473dae..c3a29a1 100644 --- a/chapters/ch05-architecture/02-allocate.ipynb +++ b/chapters/ch05-architecture/02-allocate.ipynb @@ -1,119 +1,180 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - } + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": [ + "## allocate\n", + "\n", + "This notebook introduces `allocate`, the SysML v2 relationship that assigns a behavioral element to a logical component; after running it you can express which component performs which function." + ] }, - "cells": [ - { - "cell_type": "markdown", - "id": "cell-00", - "metadata": {}, - "source": [ - "This notebook introduces `allocate`, the SysML v2 relationship that assigns a behavioral element to a structural part; after running it you can express which hardware component is responsible for which function." - ] - }, - { - "cell_type": "markdown", - "id": "cell-01", - "metadata": {}, - "source": [ - "The cumulative model has an `ApplyHeat` action definition (Ch4) and a `HeatingSystem` part definition (Ch1). They are related by design intent but not yet formally connected. `allocate` makes that connection explicit: it states that the heating subsystem is the structural locus of the heating action.\n", - "\n", - "`allocate X to Y` creates an `AllocationUsage` element. OpenSysML stores it in the model graph; `model.to_api_json()` exposes it alongside `FlowUsage` elements with connector endpoints." - ] - }, - { - "cell_type": "code", - "id": "cell-02", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from pathlib import Path\n", - "import opensysml\n", - "from toaster.report import format_diagnostics\n", - "\n", - "conn = opensysml.connect(version=\"v0.9.0\")\n", - "source = Path(\"../../models/ch05-cumulative.sysml\").read_text()\n", - "print(source)\n", - "model = conn.load_from_content(source, strict=False)\n", - "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" - ] - }, - { - "cell_type": "markdown", - "id": "cell-03", - "metadata": {}, - "source": [ - "The `ch05-cumulative.sysml` file adds two architectural constructs: `allocate ApplyHeat to HeatingSystem` records the functional-to-physical assignment, and a `BreadHandling` subsystem with `flow loader.bread to ejector.bread` expresses the item flow at the port level. These connect the functional layer (actions) to the structural layer (parts)." - ] - }, - { - "cell_type": "code", - "id": "cell-04", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# Negative control: allocating an undefined symbol raises \"unresolved reference\".\n", - "# Both the source and target of allocate must be defined in scope.\n", - "bad_source = \"\"\"\n", - "package BadAlloc {\n", - " allocate UndefinedAction to HeatingSystem;\n", - "}\n", - "\"\"\"\n", - "bad = conn.load_from_content(bad_source, strict=False)\n", - "assert not bad.ok\n", - "print(f\"Neg control diagnostics: {bad.diagnostics[0].message!r}\")" - ] - }, - { - "cell_type": "code", - "id": "cell-05", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "import json, warnings\n", - "\n", - "# AllocationUsage elements are in the JSON export (not in model.query())\n", - "with warnings.catch_warnings():\n", - " warnings.simplefilter(\"ignore\")\n", - " data = json.loads(model.to_api_json().content)\n", - "\n", - "by_id = {e[\"@id\"]: e for e in data if \"@id\" in e}\n", - "for elem in data:\n", - " if elem.get(\"@type\") == \"AllocationUsage\":\n", - " ends = elem.get(\"connectorEnd\", [])\n", - " if len(ends) == 2:\n", - " src = by_id.get(ends[0][\"@id\"], {}).get(\"sysx:sourceText\", \"?\")\n", - " tgt = by_id.get(ends[1][\"@id\"], {}).get(\"sysx:sourceText\", \"?\")\n", - " print(f\"allocate {src!r} to {tgt!r}\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-06", - "metadata": {}, - "source": [ - "The `allocate` relationship in SysML v2 (A-F) is parsed and stored in the OpenSysML element graph (O-S); querying the JSON export and reading `sysx:sourceText` from each connector endpoint reveals the assignment as `'ApplyHeat'` \u2192 `'HeatingSystem'` (E)." - ] - }, - { - "cell_type": "markdown", - "id": "cell-07", - "metadata": {}, - "source": [ - "Try the chapter exercise in `exercises/ch05/exercise.ipynb`: add an `allocate` statement assigning your `Brew` action to a `BrewUnit` part, then confirm the allocation appears in the JSON export." - ] - } - ] -} \ No newline at end of file + { + "cell_type": "markdown", + "id": "cell-01", + "metadata": {}, + "source": [ + "Chapter 4 nested `ApplyHeat` inside `ToastBread` as the usage `applyHeat`, reachable from `Toaster`'s own inherited `toastBread` action (Chapter 1's `perform action toastBread : ToastBread` on `ToastingSystem`). Chapter 1 also gave `Toaster` a `heating` part typed by `HeatingSystem`. `allocate` connects these two: the component that performs the function, named and pointed at directly, rather than left as an unstated intent." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-02", + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "import opensysml\n", + "from toaster.report import format_diagnostics\n", + "\n", + "conn = opensysml.connect(version=\"v0.9.0\")\n", + "\n", + "HEATING_SYSTEM_DEF = \"\"\"\\\n", + "abstract part def HeatingSystem {\n", + " perform action applyHeat : ApplyHeat;\n", + "}\"\"\"\n", + "print(HEATING_SYSTEM_DEF)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-03", + "metadata": {}, + "source": [ + "`HeatingSystem` becomes an abstract logical component that performs `ApplyHeat`: the `perform` relationship states, in the model, which component carries the function. This is the logical-component idiom this tutorial uses: an abstract part def that performs an action, not yet realized by any concrete part." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-04", + "metadata": {}, + "outputs": [], + "source": [ + "# allocate not yet supported by the Editor API: toaster#13 / OpenSysML#599\n", + "# spec: SysML v2 formal/2026-03-02 §7.15.2 (AllocationUsage)\n", + "TOASTER_WITH_ALLOCATION = \"\"\"\\\n", + "part def Toaster :> ToastingSystem {\n", + " attribute cycleTime : ISQ::DurationValue;\n", + " part heating : HeatingSystem;\n", + " part control : ControlSystem;\n", + " allocation heatAllocation allocate toastBread.applyHeat to heating;\n", + "}\"\"\"\n", + "print(TOASTER_WITH_ALLOCATION)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-05", + "metadata": {}, + "source": [ + "The allocation is named (`heatAllocation`) so `model.query()` can find it directly, and it is written as a member of `Toaster` itself, alongside `heating`: both ends are features the allocation can reach because it lives in the same body — `toastBread`, the action `Toaster` inherits from `ToastingSystem`, and `heating`, the part declared right beside it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-06", + "metadata": {}, + "outputs": [], + "source": [ + "TOASTER_INCREMENT = f\"{HEATING_SYSTEM_DEF}\\n{TOASTER_WITH_ALLOCATION}\"\n", + "print(TOASTER_INCREMENT)\n", + "\n", + "source = Path(\"../../models/ch05-cumulative.sysml\").read_text()\n", + "model = conn.load_from_content(source, strict=False)\n", + "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" + ] + }, + { + "cell_type": "markdown", + "id": "cell-07", + "metadata": {}, + "source": [ + "Both ends of an allocation must resolve to an existing usage. The negative control below allocates from a step that was never declared." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-08", + "metadata": {}, + "outputs": [], + "source": [ + "bad_source = \"\"\"\n", + "package BadAlloc {\n", + " private import ScalarValues::*;\n", + " action def ApplyHeat;\n", + " action def ToastBread { action applyHeat : ApplyHeat; }\n", + " part def HeatingSystem;\n", + " part def Toaster { part heating : HeatingSystem; }\n", + " allocation badAlloc allocate ToastBread::undefinedStep to Toaster::heating;\n", + "}\n", + "\"\"\"\n", + "bad = conn.load_from_content(bad_source, strict=False)\n", + "assert not bad.ok\n", + "print(f\"Neg control diagnostics: {bad.diagnostics[0].message!r}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-09", + "metadata": {}, + "source": [ + "The diagnostic reports an unresolved reference: `ToastBread::undefinedStep` was never declared, so the allocation's source usage does not exist." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-10", + "metadata": {}, + "outputs": [], + "source": [ + "from toaster.query import find_allocations, perform_relationships\n", + "\n", + "allocations = find_allocations(model)\n", + "print(f\"Allocations: {allocations}\")\n", + "print(f\"Perform relationships: {perform_relationships(model)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-11", + "metadata": {}, + "source": [ + "`find_allocations` shows `heatAllocation`'s real ends: the first is the feature chain `toastBread.applyHeat` — `toastBread`, declared on `ToastingSystem` and inherited by `Toaster`, then `applyHeat`, nested inside the `ToastBread` action — and the second is `Toaster::heating`, a usage, not `HeatingSystem` the definition. `perform_relationships` shows `HeatingSystem` (the definition `Toaster::heating` is typed by) performing `ApplyHeat`: the allocation targets the usage; the performer relationship is stated on its type. Usage-level allocation is exactly this distinction, not a detail to blur past." + ] + }, + { + "cell_type": "markdown", + "id": "cell-12", + "metadata": {}, + "source": [ + "The definitions printed above loaded without error, and the query results confirm the allocation's own ends, not just that a component of the right type performs the function." + ] + }, + { + "cell_type": "markdown", + "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." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/chapters/ch05-architecture/03-interfaces.ipynb b/chapters/ch05-architecture/03-interfaces.ipynb index 5260d0a..8954716 100644 --- a/chapters/ch05-architecture/03-interfaces.ipynb +++ b/chapters/ch05-architecture/03-interfaces.ipynb @@ -1,123 +1,257 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, - "cells": [ - { - "cell_type": "markdown", - "id": "cell-00", - "metadata": {}, - "source": [ - "This notebook introduces `flow`, the SysML v2 construct for declaring item flows between parts; after running it you can model the material or signal interfaces in a structural decomposition and render an interconnection diagram." - ] - }, - { - "cell_type": "markdown", - "id": "cell-01", - "metadata": {}, - "source": [ - "`allocate` (Ch5 nb02) shows which part performs a function. `flow` shows what passes between parts at runtime. A `flow X.port to Y.port` statement creates a `FlowUsage` element connecting two `PartUsage` members by their item ports.\n", - "\n", - "This notebook adds a `BreadHandling` assembly with a `BreadLoader` and `BreadEjector`, connected by the bread item flow. It then uses `build_interconnection_intent()` and `render_sysmld()` to produce an interconnection SVG." - ] - }, - { - "cell_type": "code", - "id": "cell-02", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from pathlib import Path\n", - "import opensysml\n", - "from toaster.report import format_diagnostics\n", - "\n", - "conn = opensysml.connect(version=\"v0.9.0\")\n", - "source = Path(\"../../models/ch05-cumulative.sysml\").read_text()\n", - "print(source)\n", - "model = conn.load_from_content(source, strict=False)\n", - "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" - ] - }, - { - "cell_type": "markdown", - "id": "cell-03", - "metadata": {}, - "source": [ - "The `ch05-cumulative.sysml` file adds two architectural constructs: `allocate ApplyHeat to HeatingSystem` records the functional-to-physical assignment, and a `BreadHandling` subsystem with `flow loader.bread to ejector.bread` expresses the item flow at the port level. These connect the functional layer (actions) to the structural layer (parts)." - ] - }, - { - "cell_type": "code", - "id": "cell-04", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# Negative control: a flow referencing a part usage that does not exist in the assembly\n", - "# raises \"unresolved reference\" for the undefined dotted path.\n", - "bad_source = \"\"\"\n", - "package BadFlow {\n", - " private import ScalarValues::*;\n", - " item def Bread;\n", - " part def Loader { part loaf : Bread; }\n", - " part def Assembly {\n", - " part loader : Loader;\n", - " flow loader.loaf to undefined_ejector.loaf;\n", - " }\n", - "}\n", - "\"\"\"\n", - "bad = conn.load_from_content(bad_source, strict=False)\n", - "assert not bad.ok\n", - "print(f\"Neg control diagnostics: {bad.diagnostics[0].message!r}\")" - ] - }, - { - "cell_type": "code", - "id": "cell-05", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from toaster.render import build_interconnection_intent, render_sysmld\n", - "from pathlib import Path\n", - "import tempfile, os\n", - "\n", - "# Build the interconnection intent for BreadHandling\n", - "intent = build_interconnection_intent(model, \"ToasterDemo::BreadHandling\")\n", - "print(f\"Parts: {[p['name'] for p in intent['parts']]}\")\n", - "print(f\"Flows: {intent['flows']}\")\n", - "\n", - "# Render to SVG\n", - "out_path = Path(tempfile.mkdtemp()) / \"bread_handling.svg\"\n", - "render_sysmld(intent, out_path)\n", - "print(f\"SVG written: {out_path} ({os.path.getsize(out_path)} bytes)\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-06", - "metadata": {}, - "source": [ - "The `flow` relationship in SysML v2 (A-F) is parsed and stored in OpenSysML's element graph (O-S); `build_interconnection_intent()` extracts the endpoint paths via `sysx:sourceText` and `render_sysmld()` produces an SVG showing the `loader` \u2192 `ejector` item flow (E)." - ] - }, - { - "cell_type": "markdown", - "id": "cell-07", - "metadata": {}, - "source": [ - "Try the chapter exercise in `exercises/ch05/exercise.ipynb`: add a `CoffeeFlow` part with a `pump` and a `filter`, declare a `flow pump.water to filter.water`, build the interconnection intent, and confirm the flow endpoint paths appear correctly." - ] - } - ] -} \ No newline at end of file + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": [ + "## port and interface\n", + "\n", + "This notebook introduces `port def` and `interface`, a connection whose ends are all ports; after running it you can declare a real, port-typed connection between two logical components and see it rendered as a diagram." + ] + }, + { + "cell_type": "markdown", + "id": "cell-01", + "metadata": {}, + "source": [ + "`HeatingSystem` (Ch5 nb02) and `ControlSystem` (Ch1) are logical components with no connection point yet. `ApplyHeat::duration` (Ch4) is declared but not bound to anything: this notebook gives the two components a typed connection point for a duration signal, showing where it would flow once something produces it, without binding `ApplyHeat::duration` itself. A port gives each component that connection point, and an interface between two ports states which two connection points are joined." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-02", + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "import opensysml\n", + "from toaster.report import format_diagnostics\n", + "\n", + "conn = opensysml.connect(version=\"v0.9.0\")\n", + "\n", + "DURATION_PORT_DEF = \"\"\"\\\n", + "port def DurationPort {\n", + " out duration : ISQ::DurationValue[0..*];\n", + "}\"\"\"\n", + "print(DURATION_PORT_DEF)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-03", + "metadata": {}, + "source": [ + "`DurationPort` carries a value of the same type `ApplyHeat` already declares for its `duration` input, not that value itself: the port gives that kind of signal a place to enter the component, not yet a source." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-04", + "metadata": {}, + "outputs": [], + "source": [ + "HEATING_SYSTEM_PORT = \"\"\"\\\n", + "abstract part def HeatingSystem {\n", + " perform action applyHeat : ApplyHeat;\n", + " port durationIn : ~DurationPort;\n", + "}\"\"\"\n", + "print(HEATING_SYSTEM_PORT)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-05", + "metadata": {}, + "source": [ + "`~DurationPort` is the conjugate of `DurationPort`: `durationIn` receives what a `DurationPort` sends, the SysML v2 idiom for matching a port to its interface partner." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-06", + "metadata": {}, + "outputs": [], + "source": [ + "CONTROL_SYSTEM_PORT = \"\"\"\\\n", + "part def ControlSystem {\n", + " port durationOut : DurationPort;\n", + "}\"\"\"\n", + "print(CONTROL_SYSTEM_PORT)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-07", + "metadata": {}, + "source": [ + "`ControlSystem` gets the matching, unconjugated port: `durationIn` and `durationOut` declare the same underlying port type, which is what makes them a type-compatible pair." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-08", + "metadata": {}, + "outputs": [], + "source": [ + "TOASTER_WITH_INTERFACE = \"\"\"\\\n", + "part def Toaster :> ToastingSystem {\n", + " attribute cycleTime : ISQ::DurationValue;\n", + " part heating : HeatingSystem;\n", + " part control : ControlSystem;\n", + " interface durationInterface connect control.durationOut to heating.durationIn;\n", + " allocation heatAllocation allocate toastBread.applyHeat to heating;\n", + "}\"\"\"\n", + "print(TOASTER_WITH_INTERFACE)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-09", + "metadata": {}, + "source": [ + "The interface connects the two ports by their owning parts' names, `control` and `heating`, both already composed inside `Toaster`. Naming it (`durationInterface`) makes it visible to `model.query()`, the same convention nb02's allocation already follows. `Toaster`'s body carries nb02's `heatAllocation` forward here too, alongside the new interface: restating a member from the previous notebook, the way this chapter's convention already restates `cycleTime`, `heating` and `control` in every notebook that touches `Toaster`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-10", + "metadata": {}, + "outputs": [], + "source": [ + "TOASTER_INCREMENT = (\n", + " f\"{DURATION_PORT_DEF}\\n{HEATING_SYSTEM_PORT}\\n\"\n", + " f\"{CONTROL_SYSTEM_PORT}\\n{TOASTER_WITH_INTERFACE}\"\n", + ")\n", + "print(TOASTER_INCREMENT)\n", + "\n", + "source = Path(\"../../models/ch05-cumulative.sysml\").read_text()\n", + "model = conn.load_from_content(source, strict=False)\n", + "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" + ] + }, + { + "cell_type": "markdown", + "id": "cell-11", + "metadata": {}, + "source": [ + "An interface's ends must resolve to real features. The negative control below connects to a part that was never composed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-12", + "metadata": {}, + "outputs": [], + "source": [ + "bad_source = \"\"\"\n", + "package BadInterface {\n", + " private import ScalarValues::*;\n", + " private import SI::*;\n", + " private import ISQ::*;\n", + " port def P { out x : ISQ::DurationValue[0..*]; }\n", + " part def A { port p : P; }\n", + " part def B {\n", + " part a : A;\n", + " interface iface connect a.p to undefined_receiver.p;\n", + " }\n", + "}\n", + "\"\"\"\n", + "bad = conn.load_from_content(bad_source, strict=False)\n", + "assert not bad.ok\n", + "print(f\"Neg control diagnostics: {bad.diagnostics[0].message!r}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-13", + "metadata": {}, + "source": [ + "The diagnostic reports an unresolved reference: `undefined_receiver` was never composed inside `B`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-14", + "metadata": {}, + "outputs": [], + "source": [ + "from toaster.query import port_type_mismatches\n", + "\n", + "mismatches = port_type_mismatches(model)\n", + "print(f\"Port type mismatches: {mismatches}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-15", + "metadata": {}, + "source": [ + "An empty list means `durationIn` and `durationOut` declare related types (equal, or one specializing the other): the check compares declared port types, not whether the conjugation itself is the correct one for this interface." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-16", + "metadata": {}, + "outputs": [], + "source": [ + "from toaster.render import build_interconnection_intent, render_interconnection\n", + "from IPython.display import SVG\n", + "\n", + "intent = build_interconnection_intent(model, \"ToasterDemo::Toaster\")\n", + "print(f\"Parts: {[p['name'] for p in intent['parts']]}\")\n", + "print(f\"Connections: {intent['flows']}\")\n", + "\n", + "out_path = Path(\"../../figures/ch05-interconnection.svg\")\n", + "out_path.parent.mkdir(exist_ok=True)\n", + "render_interconnection(intent, out_path)\n", + "SVG(filename=str(out_path))" + ] + }, + { + "cell_type": "markdown", + "id": "cell-17", + "metadata": {}, + "source": [ + "The diagram shows the `control` and `heating` parts of `Toaster` connected by the `durationInterface` port connection. It supports the conclusion that `ControlSystem` and `HeatingSystem` are now joined through a single, type-checked connection point, not a claim about what flows through `ApplyHeat` itself." + ] + }, + { + "cell_type": "markdown", + "id": "cell-18", + "metadata": {}, + "source": [ + "The port and interface definitions printed above loaded without error, and the diagram rendered directly from the loaded model shows the same `control`-to-`heating` connection." + ] + }, + { + "cell_type": "markdown", + "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." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/chapters/ch05-architecture/conclusion.md b/chapters/ch05-architecture/conclusion.md index aef977f..de0eb86 100644 --- a/chapters/ch05-architecture/conclusion.md +++ b/chapters/ch05-architecture/conclusion.md @@ -1,15 +1,15 @@ -# Chapter 5 — Conclusion +# Chapter 5: Conclusion ## What we built -The Chapter 5 model adds three constructs to the cumulative model. `model.find()` and `model.get()` are now the standard navigation layer: notebook 01 confirms they return a `Symbol` for any known qualified name and `None` (rather than an exception) for an unknown name. `allocate ApplyHeat to HeatingSystem` creates an `AllocationUsage` element, visible in the JSON export via `sysx:sourceText` on its connector endpoints. `BreadHandling` introduces the `flow` construct, connecting `loader.bread` to `ejector.bread` as a `FlowUsage` element. The `build_interconnection_intent()` and `render_sysmld()` functions extract those endpoints and render an SVG. +The Chapter 5 model adds three constructs to the cumulative model. `model.find()` and `model.get()` are now the standard navigation layer: notebook 01 confirms they return a `Symbol` for any known qualified name and `None` (rather than an exception) for an unknown name. `HeatingSystem` becomes an abstract logical component: `abstract part def HeatingSystem { perform action applyHeat : ApplyHeat; }`, giving it real content instead of an empty name. `allocation heatAllocation allocate toastBread.applyHeat to heating;`, written as a member of `Toaster` itself, is a named, usage-level `AllocationUsage`, visible directly through `model.query()`. `DurationPort` types a new port on `ControlSystem` and its conjugate `~DurationPort` types a new port on `HeatingSystem`; `interface durationInterface connect control.durationOut to heating.durationIn;` inside `Toaster` is what joins them: a named, port-typed interface, a connection whose ends are both ports. It states where the duration signal `ApplyHeat` declared in Chapter 4 would flow, once something produces it; `ApplyHeat::duration` itself is not yet bound to this port. The `build_interconnection_intent()` and `render_interconnection()` functions extract that connection and render it as an SVG, displayed directly in notebook 03. ## What this establishes -The chapter answers its engineering question: the toaster model now has formal allocation and interface declarations. The `allocate` statement makes explicit what was implicit — that `HeatingSystem` realizes `ApplyHeat`. The `flow` statement makes the bread-handling interface visible: the loader transfers a `Start`-typed item to the ejector, and the ejector handles `Finish`. The interconnection SVG confirms that the structural connectivity is readable and matches the model. +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. ## What comes next -Chapter 6 asks how deep the decomposition should go. It applies the same structural constructs from Chapter 1 one level down — decomposing `HeatingSystem` into its component parts — and records a stopping judgment that ties the child-level evidence back to the parent claims. +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 add a `CoffeeFlow` assembly with a `pump` and a `filter`, declare a flow between them, build the interconnection intent, and confirm the endpoint paths appear correctly in the intent dict. +**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. diff --git a/chapters/ch05-architecture/index.md b/chapters/ch05-architecture/index.md index 50b2e2f..899d984 100644 --- a/chapters/ch05-architecture/index.md +++ b/chapters/ch05-architecture/index.md @@ -1,18 +1,18 @@ -# Chapter 5 — Architecture +# Chapter 5: Architecture and Allocation ## Purpose -This chapter asks: which structural parts perform which functions, and what flows between them? +This chapter asks: which logical component performs which function, and how are components connected? -After completing this chapter, the cumulative model has two new relationship types — `allocate` for function-to-structure assignment and `flow` for item interfaces — and you can inspect any model element by qualified name using `model.find()` and `model.get()`. +After completing this chapter, the cumulative model has a named, usage-level `allocate` connecting `ApplyHeat` to the logical component that performs it, and a real port-typed interface between `ControlSystem` and `HeatingSystem`. You can also inspect any model element by qualified name using `model.find()` and `model.get()`. ## Ingredients | Notebook | Concept | |---|---| -| [01 — Concept Selection](01-concept-selection.ipynb) | Navigate model elements by qualified name using `model.find()` and `model.get(fqn)`. | -| [02 — Allocate](02-allocate.ipynb) | Assign a behavioral element to a structural part using `allocate X to Y`. | -| [03 — Interfaces](03-interfaces.ipynb) | Declare item flows between parts using `flow X.port to Y.port`; render an interconnection SVG. | +| [01: Model Navigation](01-concept-selection.ipynb) | Navigate model elements by qualified name using `model.find()` and `model.get(fqn)`. | +| [02: Allocate](02-allocate.ipynb) | Make `HeatingSystem` an abstract logical component that performs `ApplyHeat`, and assign it a named, usage-level allocation. | +| [03: Interfaces](03-interfaces.ipynb) | Declare a port-typed interface between `ControlSystem` and `HeatingSystem` and render the interconnection diagram. | ## Equipment @@ -22,14 +22,14 @@ See [docs/setup.md](../../docs/setup.md) for environment setup. No chapter-speci The chapter begins with navigation: before adding new relationships, you need to locate elements reliably. Notebook 01 shows how qualified names anchor every subsequent operation in this chapter and in Chapter 6. -Notebook 02 introduces `allocate`, which answers the question "which part is responsible for which function?" by creating a formal assignment between `ApplyHeat` and `HeatingSystem`. +Notebook 02 introduces `allocate`, which answers the question "which component is responsible for which function?" `HeatingSystem` becomes an abstract logical component that performs `ApplyHeat` (Chapter 4's function, nested inside `ToastBread`), and a named allocation usage connects the two directly. -Notebook 03 introduces `flow`, which answers "what passes between parts?" by declaring a bread item flow between `BreadLoader` and `BreadEjector` in a new `BreadHandling` assembly. It closes with `build_interconnection_intent()` and `render_sysmld()`, which extract the flow endpoints from the model's JSON export and render an SVG interconnection diagram. +Notebook 03 introduces `port def` and `interface`, which answer "what connection point does each component expose, and how are they joined?" `HeatingSystem` and `ControlSystem` each get a port, joined by a named interface showing where the `duration` signal `ApplyHeat` has declared since Chapter 4 would flow, once something produces it. It closes with `build_interconnection_intent()` and `render_interconnection()`, which extract the connection from the model and render it as a displayed SVG diagram. ## Expected result -After running all three notebooks, the cumulative model contains the complete Ch1–Ch5 model including `allocate ApplyHeat to HeatingSystem`, three new part definitions (`BreadLoader`, `BreadEjector`, `BreadHandling`), and a `flow loader.bread to ejector.bread` declaration. `build_interconnection_intent(model, "ToasterDemo::BreadHandling")` returns a dict with two parts and one flow, and `render_sysmld()` produces a valid SVG. +After running all three notebooks, the cumulative model contains the complete Ch1-Ch5 model including `abstract part def HeatingSystem` (performing `ApplyHeat` through a `perform` relationship, no supertype), a `DurationPort` typing a new port on each of `ControlSystem` and `HeatingSystem`, and, inside `Toaster`, both `interface durationInterface connect control.durationOut to heating.durationIn;` and `allocation heatAllocation allocate toastBread.applyHeat to heating;`, joining `control` to `heating` and allocating `applyHeat` to it. `build_interconnection_intent(model, "ToasterDemo::Toaster")` returns a dict with two parts and one connection, and the rendered interconnection diagram is visible in notebook 03's own output. ## Experiment -Try the [Chapter 5 exercise](../../exercises/ch05/exercise.ipynb): add a `CoffeeFlow` assembly with `pump` and `filter` parts, declare a flow between them, and render the interconnection diagram. +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. diff --git a/chapters/ch06-recursive-decomp/01-subsystem-requirements.ipynb b/chapters/ch06-recursive-decomp/01-subsystem-requirements.ipynb index c6b01fe..8673f11 100644 --- a/chapters/ch06-recursive-decomp/01-subsystem-requirements.ipynb +++ b/chapters/ch06-recursive-decomp/01-subsystem-requirements.ipynb @@ -1,117 +1,452 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, - "cells": [ + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": [ + "## level-2 function and logical carrier\n", + "\n", + "This notebook introduces `GenerateHeat`, a level-2 function nested inside `ApplyHeat`, and `HeatGenerator`, the abstract logical carrier that performs it and exposes an energy port; after running it you can see the nest-and-carry pattern that gave `ApplyHeat` its own logical carrier recur one level deeper." + ] + }, + { + "cell_type": "markdown", + "id": "cell-01", + "metadata": {}, + "source": [ + "Chapter 5 gave `ApplyHeat` a logical carrier, `HeatingSystem`, that performs it and exposes a port for a duration signal. `HeatingSystem`'s own `applyHeat` step has an inner behavior with no function of its own yet: generating heat from the energy supplied. This notebook nests `GenerateHeat` inside `ApplyHeat`, the same way Chapter 4 nested `ApplyHeat` inside `ToastBread`, then gives it a logical carrier of its own, `HeatGenerator`, one level below `HeatingSystem`." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "cell-02", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-29T20:53:45.341114Z", + "iopub.status.busy": "2026-09-29T20:53:45.340915Z", + "iopub.status.idle": "2026-09-29T20:53:45.557080Z", + "shell.execute_reply": "2026-09-29T20:53:45.556633Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-00", - "metadata": {}, - "source": [ - "This notebook applies requirement def and attribute override to the heating subsystem; after running it you can see how the same two constructs from Chapter 2 recur at the second level of decomposition." - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "action def GenerateHeat {\n", + " in energyIn : ISQ::EnergyValue[0..*];\n", + " out heatOut : ISQ::EnergyValue;\n", + "}\n" + ] + } + ], + "source": [ + "from pathlib import Path\n", + "import opensysml\n", + "from toaster.report import format_diagnostics\n", + "\n", + "conn = opensysml.connect(version=\"v0.9.0\")\n", + "\n", + "GENERATE_HEAT_DEF = \"\"\"\\\n", + "action def GenerateHeat {\n", + " in energyIn : ISQ::EnergyValue[0..*];\n", + " out heatOut : ISQ::EnergyValue;\n", + "}\"\"\"\n", + "print(GENERATE_HEAT_DEF)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-03", + "metadata": {}, + "source": [ + "`GenerateHeat` states typed flows only: an energy input and a thermal energy output, with no mechanism and no energy form committed. Any device that turns some supplied energy into heat satisfies it, the same substitution test `ApplyHeat` itself passes: a resistive coil and a gas flame both take some energy input and deliver heat." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "cell-04", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-29T20:53:45.558654Z", + "iopub.status.busy": "2026-09-29T20:53:45.558468Z", + "iopub.status.idle": "2026-09-29T20:53:45.560687Z", + "shell.execute_reply": "2026-09-29T20:53:45.560362Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-01", - "metadata": {}, - "source": [ - "Chapter 2 introduced requirement def and attribute override at the top-level `Toaster`. Chapter 6 applies the same pattern one level down: the `Heater` part definition now has its own requirement (`HeatingReq`) and a variant with an overridden `power` attribute.\n", - "\n", - "This is the pedagogical core of recursive decomposition: the pattern does not change as you go deeper. Each level has a formal specification, candidate variants, and satisfaction claims." - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "port def EnergyPort {\n", + " out energy : ISQ::EnergyValue[0..*];\n", + "}\n" + ] + } + ], + "source": [ + "ENERGY_PORT_DEF = \"\"\"\\\n", + "port def EnergyPort {\n", + " out energy : ISQ::EnergyValue[0..*];\n", + "}\"\"\"\n", + "print(ENERGY_PORT_DEF)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-05", + "metadata": {}, + "source": [ + "`EnergyPort` carries the same energy quantity `GenerateHeat` already declares, not a bound value: a place for it to enter a component, the same role `DurationPort` plays for the duration signal (Chapter 5)." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "cell-06", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-29T20:53:45.561865Z", + "iopub.status.busy": "2026-09-29T20:53:45.561778Z", + "iopub.status.idle": "2026-09-29T20:53:45.563728Z", + "shell.execute_reply": "2026-09-29T20:53:45.563442Z" + } + }, + "outputs": [ { - "cell_type": "code", - "id": "cell-02", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from pathlib import Path\n", - "import opensysml\n", - "from toaster.report import format_diagnostics\n", - "\n", - "conn = opensysml.connect(version=\"v0.9.0\")\n", - "source = Path(\"../../models/ch06-cumulative.sysml\").read_text()\n", - "print(source)\n", - "model = conn.load_from_content(source, strict=False)\n", - "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "action def ApplyHeat {\n", + " in bread : Bread;\n", + " in energy : ISQ::EnergyValue[0..*];\n", + " in duration : ISQ::DurationValue[0..*];\n", + " out toast : Toast;\n", + " out delivered : ISQ::EnergyValue;\n", + " out loss : ISQ::EnergyValue;\n", + " assert constraint balance {\n", + " delivered >= 0.0 [SI::J] and loss >= 0.0 [SI::J] and delivered + loss <= energy\n", + " }\n", + " first start;\n", + " then action generateHeat : GenerateHeat {\n", + " in energyIn = ApplyHeat::energy;\n", + " }\n", + " then done;\n", + "}\n" + ] + } + ], + "source": [ + "# ApplyHeat's flows and balance constraint are unchanged from Chapter 4; printed here\n", + "# in full because a nested step cannot be added to an already-declared action in a\n", + "# separate statement.\n", + "APPLY_HEAT_INCREMENT = \"\"\"\\\n", + "action def ApplyHeat {\n", + " in bread : Bread;\n", + " in energy : ISQ::EnergyValue[0..*];\n", + " in duration : ISQ::DurationValue[0..*];\n", + " out toast : Toast;\n", + " out delivered : ISQ::EnergyValue;\n", + " out loss : ISQ::EnergyValue;\n", + " assert constraint balance {\n", + " delivered >= 0.0 [SI::J] and loss >= 0.0 [SI::J] and delivered + loss <= energy\n", + " }\n", + " first start;\n", + " then action generateHeat : GenerateHeat {\n", + " in energyIn = ApplyHeat::energy;\n", + " }\n", + " then done;\n", + "}\"\"\"\n", + "print(APPLY_HEAT_INCREMENT)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-07", + "metadata": {}, + "source": [ + "`generateHeat` nests inside `ApplyHeat` exactly the way `applyHeat` nests inside `ToastBread`: a named step, bound to the outer action's own input. `ApplyHeat` now has an inner behavior with a function of its own." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "cell-08", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-29T20:53:45.564864Z", + "iopub.status.busy": "2026-09-29T20:53:45.564787Z", + "iopub.status.idle": "2026-09-29T20:53:45.566546Z", + "shell.execute_reply": "2026-09-29T20:53:45.566257Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-03", - "metadata": {}, - "source": [ - "The `ch06-cumulative.sysml` file adds a second-level requirement: `HeatingReq` constrains `heater.power >= 600.0` W on the `Heater` sub-component. A `HeatingAssembly` decomposition adds `ResistanceCoil` and `PowerWire` sub-parts. Two candidate heaters \u2014 `efficient` (800 W) and `weak` (400 W) \u2014 exercise the new requirement using the same satisfy-assertion pattern from Chapter 3, applied one level down in the hierarchy." - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "abstract part def HeatGenerator {\n", + " perform action generateHeat : GenerateHeat;\n", + " port energyIn : ~EnergyPort;\n", + " attribute power : ISQ::PowerValue;\n", + "}\n" + ] + } + ], + "source": [ + "HEAT_GENERATOR_DEF = \"\"\"\\\n", + "abstract part def HeatGenerator {\n", + " perform action generateHeat : GenerateHeat;\n", + " port energyIn : ~EnergyPort;\n", + " attribute power : ISQ::PowerValue;\n", + "}\"\"\"\n", + "print(HEAT_GENERATOR_DEF)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-09", + "metadata": {}, + "source": [ + "`HeatGenerator` is named for the function it carries, not for a mechanism: no concrete part specializes it yet, so no selection among alternatives has been made. `power` is a typed slot with no value, the same design-space idiom `Toaster::cycleTime` uses: a performance measure a concrete realization will bind, not a value this carrier chooses. `energyIn` is declared and typed but no interface connects it to a producer: a genuine gap, not yet filled, the same kind of incompleteness Chapter 5 closed for `duration` by building `durationIn`/`durationOut` and connecting them. No such connection is built for `energyIn` in this chapter." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "cell-10", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-29T20:53:45.567672Z", + "iopub.status.busy": "2026-09-29T20:53:45.567602Z", + "iopub.status.idle": "2026-09-29T20:53:45.569412Z", + "shell.execute_reply": "2026-09-29T20:53:45.569145Z" + } + }, + "outputs": [ { - "cell_type": "code", - "id": "cell-04", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# Negative control: overriding an attribute that does not exist in the parent type\n", - "# raises \"unresolved reference\" \u2014 the override target must name a declared attribute.\n", - "bad_source = \"\"\"\n", - "package BadSubsys {\n", - " private import ScalarValues::*;\n", - " part def Heater { attribute power : Real default = 800.0; }\n", - " part def BadVariant :> Heater {\n", - " attribute :>> nonExistentAttr = 500.0;\n", - " }\n", - "}\n", - "\"\"\"\n", - "bad = conn.load_from_content(bad_source, strict=False)\n", - "assert not bad.ok\n", - "print(f\"Neg control diagnostics: {bad.diagnostics[0].message!r}\")" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "part def HeatingAssembly :> HeatingSystem {\n", + " part heatGen : HeatGenerator;\n", + " allocation heatGenAllocation allocate applyHeat.generateHeat to heatGen;\n", + "}\n" + ] + } + ], + "source": [ + "HEATING_ASSEMBLY_DEF = \"\"\"\\\n", + "part def HeatingAssembly :> HeatingSystem {\n", + " part heatGen : HeatGenerator;\n", + " allocation heatGenAllocation allocate applyHeat.generateHeat to heatGen;\n", + "}\"\"\"\n", + "print(HEATING_ASSEMBLY_DEF)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-11", + "metadata": {}, + "source": [ + "`HeatingAssembly` specializes `HeatingSystem` and composes `heatGen`, giving the heating subsystem real internal structure that traces to a function for the first time. `heatGenAllocation` is nested inside `HeatingAssembly` itself rather than stated at package level: it points at `applyHeat.generateHeat` (the nested step, reached through the `applyHeat` usage `HeatingAssembly` inherits from `HeatingSystem`) and at `heatGen` (the usage typed by the carrier that now performs it), and neither end is an accessible feature of the other outside `HeatingAssembly`'s own context, so the allocation has to be a member here to resolve." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "cell-14", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-29T20:53:45.570626Z", + "iopub.status.busy": "2026-09-29T20:53:45.570562Z", + "iopub.status.idle": "2026-09-29T20:53:45.585571Z", + "shell.execute_reply": "2026-09-29T20:53:45.585087Z" + } + }, + "outputs": [ { - "cell_type": "code", - "id": "cell-05", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# Navigate to the subsystem requirement using find/get (Ch5 nav op)\n", - "heating_req = model.find(\"ToasterDemo::HeatingReq\")\n", - "print(f\"HeatingReq: id={heating_req.id!r}, kind={heating_req.kind!r}\")\n", - "\n", - "efficient = model.find(\"ToasterDemo::efficient\")\n", - "print(f\"efficient variant: id={efficient.id!r}, kind={efficient.kind!r}\")\n", - "\n", - "weak = model.find(\"ToasterDemo::weak\")\n", - "print(f\"weak variant: id={weak.id!r}, kind={weak.kind!r}\")" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "action def GenerateHeat {\n", + " in energyIn : ISQ::EnergyValue[0..*];\n", + " out heatOut : ISQ::EnergyValue;\n", + "}\n", + "port def EnergyPort {\n", + " out energy : ISQ::EnergyValue[0..*];\n", + "}\n", + "action def ApplyHeat {\n", + " in bread : Bread;\n", + " in energy : ISQ::EnergyValue[0..*];\n", + " in duration : ISQ::DurationValue[0..*];\n", + " out toast : Toast;\n", + " out delivered : ISQ::EnergyValue;\n", + " out loss : ISQ::EnergyValue;\n", + " assert constraint balance {\n", + " delivered >= 0.0 [SI::J] and loss >= 0.0 [SI::J] and delivered + loss <= energy\n", + " }\n", + " first start;\n", + " then action generateHeat : GenerateHeat {\n", + " in energyIn = ApplyHeat::energy;\n", + " }\n", + " then done;\n", + "}\n", + "abstract part def HeatGenerator {\n", + " perform action generateHeat : GenerateHeat;\n", + " port energyIn : ~EnergyPort;\n", + " attribute power : ISQ::PowerValue;\n", + "}\n", + "part def HeatingAssembly :> HeatingSystem {\n", + " part heatGen : HeatGenerator;\n", + " allocation heatGenAllocation allocate applyHeat.generateHeat to heatGen;\n", + "}\n" + ] + } + ], + "source": [ + "TOASTER_INCREMENT = (\n", + " f\"{GENERATE_HEAT_DEF}\\n{ENERGY_PORT_DEF}\\n{APPLY_HEAT_INCREMENT}\\n\"\n", + " f\"{HEAT_GENERATOR_DEF}\\n{HEATING_ASSEMBLY_DEF}\"\n", + ")\n", + "print(TOASTER_INCREMENT)\n", + "\n", + "source = Path(\"../../models/ch06-cumulative.sysml\").read_text()\n", + "model = conn.load_from_content(source, strict=False)\n", + "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" + ] + }, + { + "cell_type": "markdown", + "id": "cell-15", + "metadata": {}, + "source": [ + "An allocation's target must resolve to a real usage. The negative control below allocates to a slot that was never composed." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "cell-16", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-29T20:53:45.586725Z", + "iopub.status.busy": "2026-09-29T20:53:45.586644Z", + "iopub.status.idle": "2026-09-29T20:53:45.599007Z", + "shell.execute_reply": "2026-09-29T20:53:45.598594Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-06", - "metadata": {}, - "source": [ - "The SysML v2 requirement def and attribute override constructs (A-F) are applied to the `Heater` subsystem and parsed by OpenSysML (O-S); `model.find()` confirms the subsystem requirement and both variants are present as named model elements (E)." - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "Neg control diagnostics: 'unresolved reference: HeatingAssembly::undefinedSlot'\n" + ] + } + ], + "source": [ + "bad_source = \"\"\"\n", + "package BadAlloc {\n", + " private import ScalarValues::*;\n", + " action def GenerateHeat;\n", + " action def ApplyHeat { action generateHeat : GenerateHeat; }\n", + " abstract part def HeatGenerator;\n", + " part def HeatingAssembly { part heatGen : HeatGenerator; }\n", + " allocation badAlloc allocate ApplyHeat::generateHeat to HeatingAssembly::undefinedSlot;\n", + "}\n", + "\"\"\"\n", + "bad = conn.load_from_content(bad_source, strict=False)\n", + "assert not bad.ok\n", + "print(f\"Neg control diagnostics: {bad.diagnostics[0].message!r}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-17", + "metadata": {}, + "source": [ + "The diagnostic reports an unresolved reference: `HeatingAssembly::undefinedSlot` was never composed, so the allocation's target does not exist." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "cell-18", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-29T20:53:45.600092Z", + "iopub.status.busy": "2026-09-29T20:53:45.600020Z", + "iopub.status.idle": "2026-09-29T20:53:45.712562Z", + "shell.execute_reply": "2026-09-29T20:53:45.712153Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-07", - "metadata": {}, - "source": [ - "Try the chapter exercise in `exercises/ch06/exercise.ipynb`: add a `BrewReq` requirement for your coffee maker's `BrewUnit`, create an `overTemp` variant with an overridden `waterTemp` attribute, and confirm both the requirement and the variant appear with `model.find()`." - ] - } - ] -} \ No newline at end of file + "name": "stdout", + "output_type": "stream", + "text": [ + "Allocations: [{'id': 'ToasterDemo::Toaster::heatAllocation', 'type': 'AllocationUsage', 'ends': [['ToasterDemo::ToastingSystem::toastBread', 'ToasterDemo::ToastBread::applyHeat'], ['ToasterDemo::Toaster::heating']]}, {'id': 'ToasterDemo::HeatingAssembly::heatGenAllocation', 'type': 'AllocationUsage', 'ends': [['ToasterDemo::HeatingSystem::applyHeat', 'ToasterDemo::ApplyHeat::generateHeat'], ['ToasterDemo::HeatingAssembly::heatGen']]}]\n", + "Perform relationships: [{'performer': 'ToasterDemo::ToastingSystem', 'action': 'ToasterDemo::ToastBread'}, {'performer': 'ToasterDemo::HeatingSystem', 'action': 'ToasterDemo::ApplyHeat'}, {'performer': 'ToasterDemo::HeatGenerator', 'action': 'ToasterDemo::GenerateHeat'}]\n" + ] + } + ], + "source": [ + "from toaster.query import find_allocations, perform_relationships\n", + "\n", + "allocations = find_allocations(model)\n", + "print(f\"Allocations: {allocations}\")\n", + "print(f\"Perform relationships: {perform_relationships(model)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-19", + "metadata": {}, + "source": [ + "`find_allocations` now shows `heatGenAllocation` alongside Chapter 5's `heatAllocation`, qualified as `HeatingAssembly::heatGenAllocation` since it is now a member of `HeatingAssembly` rather than the package. Its source end is the two-segment chain `['HeatingSystem::applyHeat', 'ApplyHeat::generateHeat']`, reflecting the feature path actually walked (the inherited `applyHeat` usage, then its own nested `generateHeat` step); its target end is `['HeatingAssembly::heatGen']`, a usage, not `HeatGenerator` the definition. `perform_relationships` shows `HeatGenerator` performing `GenerateHeat`, the same performer-and-allocation split Chapter 5 established for `HeatingSystem` and `ApplyHeat`, one level deeper." + ] + }, + { + "cell_type": "markdown", + "id": "cell-20", + "metadata": {}, + "source": [ + "The definitions printed above loaded without error, and the query results confirm the new allocation's own ends, the same way Chapter 5 confirmed `heatAllocation`'s." + ] + }, + { + "cell_type": "markdown", + "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." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/chapters/ch06-recursive-decomp/02-second-level.ipynb b/chapters/ch06-recursive-decomp/02-second-level.ipynb index 54e556b..8d03321 100644 --- a/chapters/ch06-recursive-decomp/02-second-level.ipynb +++ b/chapters/ch06-recursive-decomp/02-second-level.ipynb @@ -1,117 +1,979 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, - "cells": [ - { - "cell_type": "markdown", - "id": "cell-00", - "metadata": {}, - "source": [ - "This notebook decomposes `HeatingSystem` into its component parts using the same four structural constructs introduced in Chapter 1; after running it you can see the same abstract-def, part-def, specialization, and composition pattern applied one level down." - ] - }, - { - "cell_type": "markdown", - "id": "cell-01", - "metadata": {}, - "source": [ - "Chapter 1 built the toaster's top-level structure: an abstract base, concrete types, specialization, and composition. Chapter 6 applies those four constructs to decompose `HeatingSystem` into a `ResistanceCoil` and a `PowerWire`, both specializations of `HeatingElement`.\n", - "\n", - "A `HeatingAssembly` part definition composes them \u2014 it specializes `HeatingSystem` and owns both subparts. `model.query()` can then return the full set of part definitions at this level." - ] - }, - { - "cell_type": "code", - "id": "cell-02", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from pathlib import Path\n", - "import opensysml\n", - "from toaster.report import format_diagnostics\n", - "\n", - "conn = opensysml.connect(version=\"v0.9.0\")\n", - "source = Path(\"../../models/ch06-cumulative.sysml\").read_text()\n", - "print(source)\n", - "model = conn.load_from_content(source, strict=False)\n", - "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" - ] - }, - { - "cell_type": "markdown", - "id": "cell-03", - "metadata": {}, - "source": [ - "The `ch06-cumulative.sysml` file adds a second-level requirement: `HeatingReq` constrains `heater.power >= 600.0` W on the `Heater` sub-component. A `HeatingAssembly` decomposition adds `ResistanceCoil` and `PowerWire` sub-parts. Two candidate heaters \u2014 `efficient` (800 W) and `weak` (400 W) \u2014 exercise the new requirement using the same satisfy-assertion pattern from Chapter 3, applied one level down in the hierarchy." - ] - }, - { - "cell_type": "code", - "id": "cell-04", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# Negative control: composing a part typed by an undefined type raises \"unresolved reference\".\n", - "# Composition requires the type to be declared \u2014 the same rule applies at every level.\n", - "bad_source = \"\"\"\n", - "package BadSecond {\n", - " private import ScalarValues::*;\n", - " abstract part def HeatingElement;\n", - " part def ResistanceCoil :> HeatingElement;\n", - " part def HeatingAssembly {\n", - " part coil : ResistanceCoil;\n", - " part wire : UndefinedType;\n", - " }\n", - "}\n", - "\"\"\"\n", - "bad = conn.load_from_content(bad_source, strict=False)\n", - "assert not bad.ok\n", - "print(f\"Neg control diagnostics: {bad.diagnostics[0].message!r}\")" - ] - }, - { - "cell_type": "code", - "id": "cell-05", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# List all PartDefinition elements \u2014 should include the second-level types\n", - "part_defs = [e.as_dict() for e in model.query()\n", - " if e.as_dict().get(\"@type\") == \"PartDefinition\"]\n", - "for pd in part_defs:\n", - " name = pd.get(\"declaredName\") or pd.get(\"name\", \"?\")\n", - " abstract = pd.get(\"isAbstract\") == \"true\"\n", - " print(f\" {'abstract ' if abstract else ''}part def {name}\")" - ] - }, - { - "cell_type": "markdown", - "id": "cell-06", - "metadata": {}, - "source": [ - "The four structural constructs from Chapter 1 (A-F) are applied one level down in the model hierarchy and parsed by OpenSysML (O-S); `model.query()` returns all `PartDefinition` elements including the second-level `HeatingElement`, `ResistanceCoil`, `PowerWire`, and `HeatingAssembly` (E)." - ] - }, - { - "cell_type": "markdown", - "id": "cell-07", - "metadata": {}, - "source": [ - "Try the chapter exercise in `exercises/ch06/exercise.ipynb`: decompose `BrewUnit` into an `Impeller` and a `FilterBasket`, both specializations of a `BrewComponent` abstract part, and confirm all three appear in the `model.query()` result." - ] - } - ] -} \ No newline at end of file + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": [ + "## level-2 physical realization\n", + "\n", + "This notebook introduces `HeatGenerationReq`, the requirement a heat generator's rating is checked against, and `ResistanceCoil`, the concrete mechanism selected and built to satisfy it; after running it you can see a requirement stated on an abstract carrier, a mechanism selected for a real, recorded reason, and a physical part realizing that selection, checked on two real candidates." + ] + }, + { + "cell_type": "markdown", + "id": "cell-01", + "metadata": {}, + "source": [ + "The previous notebook left `HeatGenerator` abstract: a function, a port, and an unbound performance slot, with no mechanism chosen. This notebook states the requirement that slot is checked against first, then chooses and builds the mechanism that realizes it, in that order: the selection is argued from a stated engineering premise, not read off the name of a part built before the argument for it exists." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "cell-02", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T09:54:22.005032Z", + "iopub.status.busy": "2026-09-28T09:54:22.004761Z", + "iopub.status.idle": "2026-09-28T09:54:22.133442Z", + "shell.execute_reply": "2026-09-28T09:54:22.132934Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "requirement def HeatGenerationReq {\n", + " subject heatGen : HeatGenerator;\n", + " require constraint { heatGen.power >= 600.0 [SI::W] }\n", + "}\n", + "requirement heatGenerationReq : HeatGenerationReq;\n" + ] + } + ], + "source": [ + "from pathlib import Path\n", + "import opensysml\n", + "from toaster.report import format_diagnostics\n", + "\n", + "conn = opensysml.connect(version=\"v0.9.0\")\n", + "\n", + "HEAT_GENERATION_REQ_DEF = \"\"\"\\\n", + "requirement def HeatGenerationReq {\n", + " subject heatGen : HeatGenerator;\n", + " require constraint { heatGen.power >= 600.0 [SI::W] }\n", + "}\n", + "requirement heatGenerationReq : HeatGenerationReq;\"\"\"\n", + "print(HEAT_GENERATION_REQ_DEF)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-03", + "metadata": {}, + "source": [ + "`HeatGenerationReq`'s subject is `HeatGenerator`, the abstract carrier, not any concrete realization: any realization of the carrier is checked against the same threshold, the design-space form a logical requirement takes. Its 600 W bound is not yet derived from any stated measure of effectiveness: no supply and no coil exist together in this model to derive it from. What kind of measure this threshold states is recorded next, as `AC-C06`." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "cell-04", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T09:54:22.134976Z", + "iopub.status.busy": "2026-09-28T09:54:22.134798Z", + "iopub.status.idle": "2026-09-28T09:54:22.151822Z", + "shell.execute_reply": "2026-09-28T09:54:22.151360Z" + } + }, + "outputs": [], + "source": [ + "source = Path(\"../../models/ch06-cumulative.sysml\").read_text()\n", + "model = conn.load_from_content(source, strict=False)\n", + "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" + ] + }, + { + "cell_type": "markdown", + "id": "cell-05", + "metadata": {}, + "source": [ + "The cumulative model, loaded here so the judgment records below can cite real analysis directly instead of the model's own declaration. The next cells record `AC-C06`, following the construction zone Hawkins' taxonomy uses (claim, frame, premises, evidence, challenge, assemble)." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "cell-06", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T09:54:22.153684Z", + "iopub.status.busy": "2026-09-28T09:54:22.153549Z", + "iopub.status.idle": "2026-09-28T09:54:22.155937Z", + "shell.execute_reply": "2026-09-28T09:54:22.155536Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "heatGenerationReq (HeatGenerationReq) is framed as a measure of performance: an engineering rating on a chosen component, not a direct measure of the user's acceptance of the toast.\n" + ] + } + ], + "source": [ + "from toaster.evidence import ReviewRecord, validate_record, hash_content\n", + "\n", + "framing_claim = (\n", + " \"heatGenerationReq (HeatGenerationReq) is framed as a measure of performance: \"\n", + " \"an engineering rating on a chosen component, not a direct measure of the \"\n", + " \"user's acceptance of the toast.\"\n", + ")\n", + "framing_model_ref = \"ToasterDemo::heatGenerationReq\"\n", + "print(framing_claim)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-07", + "metadata": {}, + "source": [ + "The standard this framing is checked against, the same one Chapter 3's `AC-C03` used for toast timing." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "cell-08", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T09:54:22.157099Z", + "iopub.status.busy": "2026-09-28T09:54:22.157008Z", + "iopub.status.idle": "2026-09-28T09:54:22.158993Z", + "shell.execute_reply": "2026-09-28T09:54:22.158620Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MoE if the split names who cares and frames the measure as acceptance; MoP if its threshold is derived from a stated MoE with a means of checking (architecture-layers skill).\n" + ] + } + ], + "source": [ + "framing_scope = \"ToasterDemo\"\n", + "framing_criteria = (\n", + " \"MoE if the split names who cares and frames the measure as acceptance; MoP \"\n", + " \"if its threshold is derived from a stated MoE with a means of checking \"\n", + " \"(architecture-layers skill).\"\n", + ")\n", + "print(framing_criteria)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-09", + "metadata": {}, + "source": [ + "What the framing takes as given." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "cell-10", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T09:54:22.160232Z", + "iopub.status.busy": "2026-09-28T09:54:22.160103Z", + "iopub.status.idle": "2026-09-28T09:54:22.162400Z", + "shell.execute_reply": "2026-09-28T09:54:22.161903Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['The MoE/MoP split for a component rating is a case-specific modeling judgment, not a fixed rule (AC-C03 makes the same split for toast timing).']\n" + ] + } + ], + "source": [ + "framing_premises = []\n", + "framing_assumption_refs = [\n", + " \"The MoE/MoP split for a component rating is a case-specific modeling \"\n", + " \"judgment, not a fixed rule (AC-C03 makes the same split for toast timing).\"\n", + "]\n", + "print(framing_assumption_refs)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-11", + "metadata": {}, + "source": [ + "What supports the claim, and the argument connecting it." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "cell-12", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T09:54:22.163800Z", + "iopub.status.busy": "2026-09-28T09:54:22.163694Z", + "iopub.status.idle": "2026-09-28T09:54:22.165877Z", + "shell.execute_reply": "2026-09-28T09:54:22.165535Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "HeatGenerationReq's rationale argues from a component's own rating, not from what a user notices about the toast: it names no one who would reject a toaster on this figure alone, and a heat generator rated below 600 W could still make acceptable toast more slowly. Framing it as a MoP is honest about who cares (the engineer sizing a component) and what the threshold characterizes (a rating), not the user's acceptance.\n" + ] + } + ], + "source": [ + "framing_evidence_refs = [\n", + " \"ToasterDemo::HeatGenerationReq doc: the rationale states a component rating \"\n", + " \"threshold, naming no stakeholder acceptance criterion.\",\n", + "]\n", + "framing_rationale = (\n", + " \"HeatGenerationReq's rationale argues from a component's own rating, not \"\n", + " \"from what a user notices about the toast: it names no one who would reject \"\n", + " \"a toaster on this figure alone, and a heat generator rated below 600 W \"\n", + " \"could still make acceptable toast more slowly. Framing it as a MoP is \"\n", + " \"honest about who cares (the engineer sizing a component) and what the \"\n", + " \"threshold characterizes (a rating), not the user's acceptance.\"\n", + ")\n", + "print(framing_rationale)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-13", + "metadata": {}, + "source": [ + "The challenge: what stays open about this framing, and about the threshold itself." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "cell-14", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T09:54:22.167496Z", + "iopub.status.busy": "2026-09-28T09:54:22.167386Z", + "iopub.status.idle": "2026-09-28T09:54:22.169785Z", + "shell.execute_reply": "2026-09-28T09:54:22.169317Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "A user might notice a heat generator so weak that toasting takes too long, which links this rating back to timely (Chapter 3) indirectly. The link is not modeled: no relation connects power, resistance and cycle time yet, so treating power as purely engineering-internal is a simplification.\n", + "The 600 W threshold is not derived from timely or from any stated measure of effectiveness through the energy relation: no supply and no coil exist together in this model to derive it from. It is recorded as a free-standing engineering figure, honestly, not a fixed rule for every future chapter.\n" + ] + } + ], + "source": [ + "framing_counterevidence = (\n", + " \"A user might notice a heat generator so weak that toasting takes too long, \"\n", + " \"which links this rating back to timely (Chapter 3) indirectly. The link is \"\n", + " \"not modeled: no relation connects power, resistance and cycle time yet, so \"\n", + " \"treating power as purely engineering-internal is a simplification.\"\n", + ")\n", + "framing_residual_uncertainties = (\n", + " \"The 600 W threshold is not derived from timely or from any stated measure \"\n", + " \"of effectiveness through the energy relation: no supply and no coil exist \"\n", + " \"together in this model to derive it from. It is recorded as a \"\n", + " \"free-standing engineering figure, honestly, not a fixed rule for every \"\n", + " \"future chapter.\"\n", + ")\n", + "print(framing_counterevidence)\n", + "print(framing_residual_uncertainties)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-15", + "metadata": {}, + "source": [ + "With every part named above, the framing record assembles from them directly." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "cell-16", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T09:54:22.171003Z", + "iopub.status.busy": "2026-09-28T09:54:22.170912Z", + "iopub.status.idle": "2026-09-28T09:54:22.173284Z", + "shell.execute_reply": "2026-09-28T09:54:22.172958Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Validation errors: []\n" + ] + } + ], + "source": [ + "framing_record = ReviewRecord(\n", + " identifier=\"AC-C06\",\n", + " kind=\"asserted_context\",\n", + " claim=framing_claim,\n", + " model_ref=framing_model_ref,\n", + " content_hash=hash_content(source),\n", + " scope=framing_scope,\n", + " criteria=framing_criteria,\n", + " premises=framing_premises,\n", + " assumption_refs=framing_assumption_refs,\n", + " evidence_refs=framing_evidence_refs,\n", + " rationale=framing_rationale,\n", + " counterevidence=framing_counterevidence,\n", + " residual_uncertainties=framing_residual_uncertainties,\n", + " disposition=\"pending\",\n", + " dependency_freshness=\"current\",\n", + " engineering_conclusion=\"undetermined\",\n", + " record_kind=\"worked_example\",\n", + ")\n", + "\n", + "errors = validate_record(framing_record)\n", + "print(f\"Validation errors: {errors}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-17", + "metadata": {}, + "source": [ + "`validate_record` reports no errors. With the requirement framed, the next cells record which mechanism is chosen to satisfy it, and why. `GenerateHeat` and `HeatGenerator` commit to no energy form or mechanism (notebook 01): the selection below is what actually chooses one, not a fact already built into either of them." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "cell-18", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T09:54:22.174552Z", + "iopub.status.busy": "2026-09-28T09:54:22.174459Z", + "iopub.status.idle": "2026-09-28T09:54:22.178528Z", + "shell.execute_reply": "2026-09-28T09:54:22.178158Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ControlSystem: partDef, durationOut: portUsage\n" + ] + } + ], + "source": [ + "control_system = model.find(\"ToasterDemo::ControlSystem\")\n", + "duration_out = model.find(\"ToasterDemo::ControlSystem::durationOut\")\n", + "print(f\"ControlSystem: {control_system.kind}, durationOut: {duration_out.kind}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-19", + "metadata": {}, + "source": [ + "`ControlSystem` really does declare `durationOut`, a discrete duration signal (Chapter 5), confirmed directly rather than assumed. The selection below cites this fact alongside a domain premise about how the two mechanisms work; it does not argue from `energyIn` already being electrical, since it is not, until this record commits it." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "cell-20", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T09:54:22.180047Z", + "iopub.status.busy": "2026-09-28T09:54:22.179960Z", + "iopub.status.idle": "2026-09-28T09:54:22.181962Z", + "shell.execute_reply": "2026-09-28T09:54:22.181621Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ResistanceCoil, an electrically switched resistive element that converts energy to heat by Joule heating, is selected over a combustion-based alternative (a gas burner, the tongs-and-blowtorch alternative this tutorial already contrasts) as the mechanism HeatGenerator commits to.\n" + ] + } + ], + "source": [ + "selection_claim = (\n", + " \"ResistanceCoil, an electrically switched resistive element that converts \"\n", + " \"energy to heat by Joule heating, is selected over a combustion-based \"\n", + " \"alternative (a gas burner, the tongs-and-blowtorch alternative this \"\n", + " \"tutorial already contrasts) as the mechanism HeatGenerator commits to.\"\n", + ")\n", + "selection_model_ref = \"ToasterDemo::ResistanceCoil\"\n", + "print(selection_claim)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-21", + "metadata": {}, + "source": [ + "The standard the selection is checked against: what the model already provides that a chosen mechanism must pair with, and what it must expose to be checked." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "cell-22", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T09:54:22.183390Z", + "iopub.status.busy": "2026-09-28T09:54:22.183305Z", + "iopub.status.idle": "2026-09-28T09:54:22.185380Z", + "shell.execute_reply": "2026-09-28T09:54:22.184973Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The chosen mechanism must pair with the discrete timing control ControlSystem's durationOut already provides, and must expose a rating heatGenerationReq's power threshold can be checked against once a concrete part exists.\n" + ] + } + ], + "source": [ + "selection_scope = \"ToasterDemo::HeatGenerator and its realizations\"\n", + "selection_criteria = (\n", + " \"The chosen mechanism must pair with the discrete timing control \"\n", + " \"ControlSystem's durationOut already provides, and must expose a rating \"\n", + " \"heatGenerationReq's power threshold can be checked against once a \"\n", + " \"concrete part exists.\"\n", + ")\n", + "print(selection_criteria)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-23", + "metadata": {}, + "source": [ + "What the selection takes as given: the confirmed fact above, plus a domain premise about how the two mechanisms work, stated as two premises below." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "cell-24", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T09:54:22.186752Z", + "iopub.status.busy": "2026-09-28T09:54:22.186654Z", + "iopub.status.idle": "2026-09-28T09:54:22.189102Z", + "shell.execute_reply": "2026-09-28T09:54:22.188593Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['ControlSystem declares durationOut : DurationPort (Chapter 5), a discrete duration signal, confirmed by model.find() above.', \"Domain premise, not derived from the model: a resistive element responds to being switched on and off directly, while a combustion source needs separate ignition and fuel-metering machinery to do the same. Neither HeatGenerator nor any of its realizations is yet connected to ControlSystem's port in this model; this premise is about the physical mechanisms themselves, not about what the model currently wires together.\"]\n" + ] + } + ], + "source": [ + "selection_premises = [\n", + " \"ControlSystem declares durationOut : DurationPort (Chapter 5), a \"\n", + " \"discrete duration signal, confirmed by model.find() above.\",\n", + " \"Domain premise, not derived from the model: a resistive element \"\n", + " \"responds to being switched on and off directly, while a combustion \"\n", + " \"source needs separate ignition and fuel-metering machinery to do \"\n", + " \"the same. Neither HeatGenerator nor any of its realizations is yet \"\n", + " \"connected to ControlSystem's port in this model; this premise is \"\n", + " \"about the physical mechanisms themselves, not about what the model \"\n", + " \"currently wires together.\",\n", + "]\n", + "selection_assumption_refs = [\n", + " \"HeatGenerator::energyIn and GenerateHeat carry no energy-form commitment \"\n", + " \"(notebook 01): this record is what actually commits to an electrical \"\n", + " \"form, not a fact already built into the port or the function.\"\n", + "]\n", + "print(selection_premises)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-25", + "metadata": {}, + "source": [ + "The evidence, and the argument connecting it to the claim." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "cell-26", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T09:54:22.190254Z", + "iopub.status.busy": "2026-09-28T09:54:22.190180Z", + "iopub.status.idle": "2026-09-28T09:54:22.192231Z", + "shell.execute_reply": "2026-09-28T09:54:22.191822Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "An electrically resistive element responds to being switched on and off directly, the same shape as duration's discrete timing signal, while a combustion-based burner needs separate ignition and fuel-metering machinery to respond the same way (the domain premise above). That is a claim about how the two mechanisms work, not something this model currently shows: no realization of HeatGenerator is yet connected to ControlSystem's durationOut port, so this selection is a reasoned engineering preference argued from mechanism, not a claim that the model already connects one mechanism and not the other.\n" + ] + } + ], + "source": [ + "selection_evidence_refs = [\n", + " f\"model.find('ToasterDemo::ControlSystem::durationOut') resolves to a \"\n", + " f\"real {duration_out.kind}, confirmed above.\",\n", + "]\n", + "selection_rationale = (\n", + " \"An electrically resistive element responds to being switched on \"\n", + " \"and off directly, the same shape as duration's discrete timing \"\n", + " \"signal, while a combustion-based burner needs separate ignition \"\n", + " \"and fuel-metering machinery to respond the same way (the domain \"\n", + " \"premise above). That is a claim about how the two mechanisms \"\n", + " \"work, not something this model currently shows: no realization \"\n", + " \"of HeatGenerator is yet connected to ControlSystem's \"\n", + " \"durationOut port, so this selection is a reasoned engineering \"\n", + " \"preference argued from mechanism, not a claim that the model \"\n", + " \"already connects one mechanism and not the other.\"\n", + ")\n", + "print(selection_rationale)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-27", + "metadata": {}, + "source": [ + "The challenge: what this selection does not establish, stated plainly." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "cell-28", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T09:54:22.193449Z", + "iopub.status.busy": "2026-09-28T09:54:22.193369Z", + "iopub.status.idle": "2026-09-28T09:54:22.195525Z", + "shell.execute_reply": "2026-09-28T09:54:22.195081Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "This does not rule out a combustion design: a burner controlled by its own timed valve could equally use a duration-like signal, which is exactly why the argument above rests on a domain premise about how the two mechanisms work, not on anything the model itself already builds or connects. Joule heating's own relation (power proportional to resistance and the square of current) is still not modeled, so efficiency and response-time comparisons remain out of reach either way.\n", + "Once a supply and a control policy are modeled together, this selection could be revisited against a real trade study rather than a domain premise alone.\n" + ] + } + ], + "source": [ + "selection_counterevidence = (\n", + " \"This does not rule out a combustion design: a burner controlled by its \"\n", + " \"own timed valve could equally use a duration-like signal, which is \"\n", + " \"exactly why the argument above rests on a domain premise about how \"\n", + " \"the two mechanisms work, not on anything the model itself already \"\n", + " \"builds or connects. Joule heating's own relation (power proportional \"\n", + " \"to resistance and the square of current) is still not modeled, so \"\n", + " \"efficiency and response-time comparisons remain out of reach \"\n", + " \"either way.\"\n", + ")\n", + "selection_residual_uncertainties = (\n", + " \"Once a supply and a control policy are modeled together, this \"\n", + " \"selection could be revisited against a real trade study rather than \"\n", + " \"a domain premise alone.\"\n", + ")\n", + "print(selection_counterevidence)\n", + "print(selection_residual_uncertainties)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-29", + "metadata": {}, + "source": [ + "With every part named above, the selection record assembles from them directly." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "cell-30", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T09:54:22.197051Z", + "iopub.status.busy": "2026-09-28T09:54:22.196964Z", + "iopub.status.idle": "2026-09-28T09:54:22.199270Z", + "shell.execute_reply": "2026-09-28T09:54:22.198897Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Validation errors: []\n" + ] + } + ], + "source": [ + "selection_record = ReviewRecord(\n", + " identifier=\"AS-C06\",\n", + " kind=\"asserted_solution\",\n", + " claim=selection_claim,\n", + " model_ref=selection_model_ref,\n", + " content_hash=hash_content(source),\n", + " scope=selection_scope,\n", + " criteria=selection_criteria,\n", + " premises=selection_premises,\n", + " assumption_refs=selection_assumption_refs,\n", + " evidence_refs=selection_evidence_refs,\n", + " rationale=selection_rationale,\n", + " counterevidence=selection_counterevidence,\n", + " residual_uncertainties=selection_residual_uncertainties,\n", + " disposition=\"pending\",\n", + " dependency_freshness=\"current\",\n", + " engineering_conclusion=\"undetermined\",\n", + " record_kind=\"worked_example\",\n", + ")\n", + "\n", + "errors = validate_record(selection_record)\n", + "print(f\"Validation errors: {errors}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-31", + "metadata": {}, + "source": [ + "`validate_record` reports no errors. With the selection on record, `ResistanceCoil`'s electrically-specific name and Joule-heating doc are now admissible: the mechanism they name has a real, recorded argument behind it, not a name chosen and justified afterward." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "cell-32", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T09:54:22.200657Z", + "iopub.status.busy": "2026-09-28T09:54:22.200565Z", + "iopub.status.idle": "2026-09-28T09:54:22.202464Z", + "shell.execute_reply": "2026-09-28T09:54:22.202062Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "part def ResistanceCoil :> HeatGenerator {\n", + " attribute :>> power default = 800.0 [SI::W];\n", + " attribute resistance : ISQ::ResistanceValue default = 12.0 [SI::ohm];\n", + "}\n" + ] + } + ], + "source": [ + "RESISTANCE_COIL_DEF = \"\"\"\\\n", + "part def ResistanceCoil :> HeatGenerator {\n", + " attribute :>> power default = 800.0 [SI::W];\n", + " attribute resistance : ISQ::ResistanceValue default = 12.0 [SI::ohm];\n", + "}\"\"\"\n", + "print(RESISTANCE_COIL_DEF)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-33", + "metadata": {}, + "source": [ + "`ResistanceCoil` specializes `HeatGenerator` and binds its power slot to a default, redefined with `default =` so a candidate can still override it. `resistance` is a physical sizing value with a real unit, `ISQ::ResistanceValue` in ohms, not the bare number the mechanism-suggestive name alone would need." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "cell-34", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T09:54:22.203705Z", + "iopub.status.busy": "2026-09-28T09:54:22.203628Z", + "iopub.status.idle": "2026-09-28T09:54:22.205731Z", + "shell.execute_reply": "2026-09-28T09:54:22.205247Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "part rated : ResistanceCoil {\n", + " assert satisfy heatGenerationReq by rated;\n", + "}\n" + ] + } + ], + "source": [ + "RATED_USAGE = \"\"\"\\\n", + "part rated : ResistanceCoil {\n", + " assert satisfy heatGenerationReq by rated;\n", + "}\"\"\"\n", + "print(RATED_USAGE)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-35", + "metadata": {}, + "source": [ + "`rated` takes `ResistanceCoil`'s default power, 800 W, and asserts it satisfies `heatGenerationReq` directly: a real, evaluable claim about a real candidate." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "cell-36", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T09:54:22.206995Z", + "iopub.status.busy": "2026-09-28T09:54:22.206923Z", + "iopub.status.idle": "2026-09-28T09:54:22.208958Z", + "shell.execute_reply": "2026-09-28T09:54:22.208554Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "part weak : ResistanceCoil {\n", + " attribute :>> power = 400.0 [SI::W];\n", + " assert not satisfy heatGenerationReq by weak;\n", + "}\n" + ] + } + ], + "source": [ + "WEAK_USAGE = \"\"\"\\\n", + "part weak : ResistanceCoil {\n", + " attribute :>> power = 400.0 [SI::W];\n", + " assert not satisfy heatGenerationReq by weak;\n", + "}\"\"\"\n", + "print(WEAK_USAGE)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-37", + "metadata": {}, + "source": [ + "`weak` overrides power down to 400 W, below the threshold, and folds the claim into its own context as a negated assertion: `assert not satisfy`, not a false positive. Its failure is a design choice, a 400 W part rated below what the requirement asks for, the same class of legitimate failing branch as a chosen part's rating anywhere else in this model." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "cell-38", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T09:54:22.210341Z", + "iopub.status.busy": "2026-09-28T09:54:22.210237Z", + "iopub.status.idle": "2026-09-28T09:54:22.213394Z", + "shell.execute_reply": "2026-09-28T09:54:22.212974Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "requirement def HeatGenerationReq {\n", + " subject heatGen : HeatGenerator;\n", + " require constraint { heatGen.power >= 600.0 [SI::W] }\n", + "}\n", + "requirement heatGenerationReq : HeatGenerationReq;\n", + "part def ResistanceCoil :> HeatGenerator {\n", + " attribute :>> power default = 800.0 [SI::W];\n", + " attribute resistance : ISQ::ResistanceValue default = 12.0 [SI::ohm];\n", + "}\n", + "part rated : ResistanceCoil {\n", + " assert satisfy heatGenerationReq by rated;\n", + "}\n", + "part weak : ResistanceCoil {\n", + " attribute :>> power = 400.0 [SI::W];\n", + " assert not satisfy heatGenerationReq by weak;\n", + "}\n" + ] + } + ], + "source": [ + "TOASTER_INCREMENT = (\n", + " f\"{HEAT_GENERATION_REQ_DEF}\\n{RESISTANCE_COIL_DEF}\\n\"\n", + " f\"{RATED_USAGE}\\n{WEAK_USAGE}\"\n", + ")\n", + "print(TOASTER_INCREMENT)\n", + "\n", + "reload = conn.load_from_content(source, strict=False)\n", + "assert reload.ok, f\"Model failed: {format_diagnostics(reload.diagnostics)}\"" + ] + }, + { + "cell_type": "markdown", + "id": "cell-39", + "metadata": {}, + "source": [ + "A requirement's subject must resolve to a declared type. The negative control below types the subject by a def that was never declared." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "cell-40", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T09:54:22.214553Z", + "iopub.status.busy": "2026-09-28T09:54:22.214464Z", + "iopub.status.idle": "2026-09-28T09:54:22.239368Z", + "shell.execute_reply": "2026-09-28T09:54:22.239039Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Neg control diagnostics: 'unresolved reference: UndefinedCarrier'\n" + ] + } + ], + "source": [ + "bad_source = \"\"\"\n", + "package BadReq {\n", + " private import ScalarValues::*;\n", + " private import SI::*;\n", + " private import ISQ::*;\n", + " requirement def BadHeatReq {\n", + " subject heatGen : UndefinedCarrier;\n", + " require constraint { heatGen.power >= 600.0 [SI::W] }\n", + " }\n", + "}\n", + "\"\"\"\n", + "bad = conn.load_from_content(bad_source, strict=False)\n", + "assert not bad.ok\n", + "print(f\"Neg control diagnostics: {bad.diagnostics[0].message!r}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-41", + "metadata": {}, + "source": [ + "The diagnostic reports an unresolved reference: `UndefinedCarrier` names no declared type, so the subject cannot be typed." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "cell-42", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T09:54:22.240736Z", + "iopub.status.busy": "2026-09-28T09:54:22.240661Z", + "iopub.status.idle": "2026-09-28T09:54:22.254968Z", + "shell.execute_reply": "2026-09-28T09:54:22.254100Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "rated.power = 800 [SI::W], heatGenerationReq(rated) = True\n", + "weak.power = 400 [SI::W], heatGenerationReq(weak) = False\n" + ] + } + ], + "source": [ + "rated_power = model.eval(\"ToasterDemo::rated.power\")\n", + "weak_power = model.eval(\"ToasterDemo::weak.power\")\n", + "rated_holds = model.eval(\"ToasterDemo::heatGenerationReq(ToasterDemo::rated)\")\n", + "weak_holds = model.eval(\"ToasterDemo::heatGenerationReq(ToasterDemo::weak)\")\n", + "print(f\"rated.power = {rated_power}, heatGenerationReq(rated) = {rated_holds}\")\n", + "print(f\"weak.power = {weak_power}, heatGenerationReq(weak) = {weak_holds}\")\n", + "conn.close()" + ] + }, + { + "cell_type": "markdown", + "id": "cell-43", + "metadata": {}, + "source": [ + "`rated` evaluates True against the threshold; `weak` evaluates False, confirming the assertion folded into its own context above is the correct one to make." + ] + }, + { + "cell_type": "markdown", + "id": "cell-44", + "metadata": {}, + "source": [ + "The requirement and the candidates printed above loaded without error, the selection and framing records validated with no errors, and the evaluated results confirm what each candidate's own assertion claims." + ] + }, + { + "cell_type": "markdown", + "id": "cell-45", + "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." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/chapters/ch06-recursive-decomp/03-stopping-judgment.ipynb b/chapters/ch06-recursive-decomp/03-stopping-judgment.ipynb index b515578..b471f48 100644 --- a/chapters/ch06-recursive-decomp/03-stopping-judgment.ipynb +++ b/chapters/ch06-recursive-decomp/03-stopping-judgment.ipynb @@ -1,148 +1,678 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, - "cells": [ + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": [ + "## stopping judgment\n", + "\n", + "This notebook rebuilds `AI-C06`, an `asserted_inference` record about the level-2 heat-generation branch; after running it you can see a stopping-rule judgment checked against real analysis on the loaded model, not against the model's own declaration." + ] + }, + { + "cell_type": "markdown", + "id": "cell-01", + "metadata": {}, + "source": [ + "The previous two notebooks built one function, logical carrier, allocation and physical realization for `GenerateHeat`, then recorded the mechanism selection (`AS-C06`) and the measure framing (`AC-C06`) that decision raises. This notebook asks what the recursion's own stopping rule actually shows for this one branch, and states plainly what it does not yet show." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "cell-02", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-29T20:48:36.818145Z", + "iopub.status.busy": "2026-09-29T20:48:36.817880Z", + "iopub.status.idle": "2026-09-29T20:48:37.090476Z", + "shell.execute_reply": "2026-09-29T20:48:37.090007Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-00", - "metadata": {}, - "source": [ - "This notebook records an `asserted_inference` judgment that the second-level decomposition is sufficient to stop further refinement; after running it you can see how a chain of inference records links child and parent claims." - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "// GENERATED FIXTURE: do not edit directly.\n", + "// Run: python scripts/check_construction.py --check (to verify)\n", + "// Source: notebook cell-02 TOASTER_INCREMENT in chapter 6's construct-introducing notebooks.\n", + "\n", + "package ToasterDemo {\n", + " private import ScalarValues::*;\n", + " private import SI::*;\n", + " private import ISQ::*;\n", + "\n", + " item def Bread;\n", + " item def Toast;\n", + "\n", + " action def ApplyHeat {\n", + " in bread : Bread;\n", + " in energy : ISQ::EnergyValue[0..*];\n", + " in duration : ISQ::DurationValue[0..*] {\n", + " doc /* Signal from a control function: how long to apply heat.\n", + " * No control function is modeled in this chapter, so this input\n", + " * is declared and typed but not yet connected to a value. */\n", + " }\n", + " out toast : Toast;\n", + " out delivered : ISQ::EnergyValue;\n", + " out loss : ISQ::EnergyValue;\n", + "\n", + " assert constraint balance {\n", + " delivered >= 0.0 [SI::J] and loss >= 0.0 [SI::J] and delivered + loss <= energy\n", + " }\n", + "\n", + " first start;\n", + " then action generateHeat : GenerateHeat {\n", + " in energyIn = ApplyHeat::energy;\n", + " }\n", + " then done;\n", + " }\n", + "\n", + " action def ToastBread {\n", + " doc /* Transform bread into toast acceptable to its user. */\n", + " in bread : Bread;\n", + " out toast : Toast;\n", + " first start;\n", + " then action applyHeat : ApplyHeat {\n", + " in bread = ToastBread::bread;\n", + " }\n", + " then done;\n", + " }\n", + "\n", + " abstract part def ToastingSystem {\n", + " perform action toastBread : ToastBread;\n", + " }\n", + "\n", + " port def DurationPort {\n", + " doc /* Carries a duration signal: how long to apply heat. */\n", + " out duration : ISQ::DurationValue[0..*];\n", + " }\n", + "\n", + " abstract part def HeatingSystem {\n", + " doc /* The logical carrier of the heating mechanism: performs ApplyHeat\n", + " * and exposes a port for a duration signal from a control component. */\n", + " perform action applyHeat : ApplyHeat;\n", + " port durationIn : ~DurationPort;\n", + " }\n", + " part def ControlSystem {\n", + " port durationOut : DurationPort;\n", + " }\n", + "\n", + " part def Toaster :> ToastingSystem {\n", + " attribute cycleTime : ISQ::DurationValue;\n", + " part heating : HeatingSystem;\n", + " part control : ControlSystem;\n", + " interface durationInterface connect control.durationOut to heating.durationIn;\n", + " allocation heatAllocation allocate toastBread.applyHeat to heating;\n", + " }\n", + "\n", + " requirement def TimelyToast {\n", + " doc /*\n", + " * The toaster shall complete a toasting cycle in at most 180 seconds.\n", + " * Rationale: kitchen workflows typically span 5-15 minutes; a cycle\n", + " * exceeding 3 minutes delays meal preparation and falls outside where\n", + " * and how a user prepares a meal.\n", + " */\n", + " subject toaster : Toaster;\n", + " require constraint { toaster.cycleTime <= 180.0 [SI::s] }\n", + " }\n", + "\n", + " requirement timely : TimelyToast;\n", + "\n", + " part nominal : Toaster;\n", + " part slow : Toaster {\n", + " attribute :>> cycleTime = 200.0 [SI::s];\n", + " assert not satisfy timely by slow;\n", + " }\n", + "\n", + " verification def TimelyToastTest {\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", + " * spec: SysML v2 formal/2026-03-02 §7.24.2 (VerificationCaseDefinition).\n", + " */\n", + " subject toaster : Toaster;\n", + " objective {\n", + " verify timely;\n", + " }\n", + " }\n", + "\n", + " item def Start {\n", + " doc /* Signal marking the start of a toasting cycle, not the bread itself. */\n", + " }\n", + " item def Finish {\n", + " doc /* Signal marking the finish of a toasting cycle, not the toast itself. */\n", + " }\n", + " item def Cancel {\n", + " doc /* Signal requesting cancellation of an in-progress toasting cycle. */\n", + " }\n", + "\n", + " port def EnergyPort {\n", + " doc /* Carries an energy signal delivered to a heat generator, not\n", + " * committed to any particular energy form. */\n", + " out energy : ISQ::EnergyValue[0..*];\n", + " }\n", + "\n", + " action def GenerateHeat {\n", + " doc /* Converts a supplied energy input into a thermal energy output.\n", + " * No mechanism, and no energy form, is committed yet: a resistive\n", + " * coil and a gas flame both take some supplied energy and deliver\n", + " * heat, so any device that does this satisfies the function. */\n", + " in energyIn : ISQ::EnergyValue[0..*];\n", + " out heatOut : ISQ::EnergyValue;\n", + " }\n", + "\n", + " abstract part def HeatGenerator {\n", + " doc /* The logical carrier of heat generation, one level below\n", + " * HeatingSystem: performs GenerateHeat and exposes a port for an\n", + " * energy signal, not yet connected to a producer. Named for the\n", + " * function it carries, not for a mechanism: which mechanism\n", + " * realizes it is a selection among alternatives, recorded once a\n", + " * concrete part specializes this carrier. */\n", + " perform action generateHeat : GenerateHeat;\n", + " port energyIn : ~EnergyPort;\n", + " attribute power : ISQ::PowerValue;\n", + " }\n", + "\n", + " part def HeatingAssembly :> HeatingSystem {\n", + " part heatGen : HeatGenerator;\n", + " allocation heatGenAllocation allocate applyHeat.generateHeat to heatGen;\n", + " }\n", + "\n", + " requirement def HeatGenerationReq {\n", + " doc /*\n", + " * A heat generator shall be rated for at least 600 W.\n", + " * This is an engineering performance threshold on a component rating,\n", + " * not yet derived from a stated measure of effectiveness through the\n", + " * energy relation: no supply and no coil are modeled together yet, so\n", + " * there is nothing to derive it from. Recorded openly, not faked.\n", + " */\n", + " subject heatGen : HeatGenerator;\n", + " require constraint { heatGen.power >= 600.0 [SI::W] }\n", + " }\n", + "\n", + " requirement heatGenerationReq : HeatGenerationReq;\n", + "\n", + " part def ResistanceCoil :> HeatGenerator {\n", + " doc /* An electrically switched resistive element: converts electrical\n", + " * energy to heat by Joule heating. The mechanism selection this\n", + " * specialization commits to is recorded against the alternative\n", + " * it was chosen over, argued from a domain premise about how the\n", + " * two mechanisms work, not from anything the model connects. */\n", + " attribute :>> power default = 800.0 [SI::W];\n", + " attribute resistance : ISQ::ResistanceValue default = 12.0 [SI::ohm];\n", + " }\n", + "\n", + " part rated : ResistanceCoil {\n", + " assert satisfy heatGenerationReq by rated;\n", + " }\n", + " part weak : ResistanceCoil {\n", + " attribute :>> power = 400.0 [SI::W];\n", + " assert not satisfy heatGenerationReq by weak;\n", + " }\n", + "}\n", + "\n" + ] + } + ], + "source": [ + "from pathlib import Path\n", + "import opensysml\n", + "from toaster.evidence import ReviewRecord, hash_content, validate_record\n", + "from toaster.query import find_allocations, perform_relationships\n", + "from toaster.report import format_diagnostics\n", + "\n", + "conn = opensysml.connect(version=\"v0.9.0\")\n", + "source = Path(\"../../models/ch06-cumulative.sysml\").read_text()\n", + "print(source)\n", + "model = conn.load_from_content(source, strict=False)\n", + "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" + ] + }, + { + "cell_type": "markdown", + "id": "cell-03", + "metadata": {}, + "source": [ + "The cumulative model prints above: `GenerateHeat` nested inside `ApplyHeat`, `HeatGenerator` performing it through a declared energy port, `HeatingAssembly` composing that carrier, the usage-level allocation between them, and `ResistanceCoil`'s two candidates, `rated` and `weak`, checked against `heatGenerationReq`. The next cell checks what happens when an inference record's own required field is left empty." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "cell-04", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-29T20:48:37.092256Z", + "iopub.status.busy": "2026-09-29T20:48:37.091968Z", + "iopub.status.idle": "2026-09-29T20:48:37.095095Z", + "shell.execute_reply": "2026-09-29T20:48:37.094730Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-01", - "metadata": {}, - "source": [ - "`AI-C04` (Chapter 4) established functional completeness of the `ApplyHeat` action. This notebook adds `AI-C06`, which claims the structural decomposition of `HeatingSystem` is complete. The inference rests on two prior claims: the solution record for energy delivery (`AS-C03`) and the functional inference (`AI-C04`).\n", - "\n", - "A chain of premises connects the stopping judgment back to the measured evidence. This is the argument structure Hawkins \u00a73.1 requires: an asserted inference is only as strong as its weakest premise." - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "Validation errors for empty-premises record: ['asserted_inference requires at least one premise (Hawkins §3.1)']\n" + ] + } + ], + "source": [ + "# Negative control: an asserted_inference with empty premises fails validate_record().\n", + "# The Hawkins 3.1 schema requires at least one premise; no premises means a bare\n", + "# assertion, not an inference.\n", + "incomplete = ReviewRecord(\n", + " identifier=\"AI-BAD\",\n", + " kind=\"asserted_inference\",\n", + " claim=\"The heat-generation branch is complete.\",\n", + " model_ref=\"ToasterDemo::HeatingAssembly::heatGen\",\n", + " content_hash=hash_content(source),\n", + " scope=\"ToasterDemo\",\n", + " criteria=\"Every function allocated to HeatGenerator is realized and checked.\",\n", + " premises=[],\n", + " assumption_refs=[],\n", + " evidence_refs=[],\n", + " rationale=\"ResistanceCoil realizes GenerateHeat.\",\n", + " counterevidence=\"No producer is wired to energyIn.\",\n", + " residual_uncertainties=\"HeatingAssembly is not composed into any Toaster candidate.\",\n", + " disposition=\"pending\",\n", + " dependency_freshness=\"current\",\n", + " engineering_conclusion=\"undetermined\",\n", + " record_kind=\"worked_example\",\n", + ")\n", + "errors = validate_record(incomplete)\n", + "assert len(errors) > 0, \"Expected validation to fail on empty premises\"\n", + "print(f\"Validation errors for empty-premises record: {errors}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-05", + "metadata": {}, + "source": [ + "With the negative control confirmed, the next cell gathers the real analysis this chapter's stopping judgment is checked against, not the model's own declaration cited back at itself." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "cell-06", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-29T20:48:37.096324Z", + "iopub.status.busy": "2026-09-29T20:48:37.096226Z", + "iopub.status.idle": "2026-09-29T20:48:37.215208Z", + "shell.execute_reply": "2026-09-29T20:48:37.214773Z" + } + }, + "outputs": [ { - "cell_type": "code", - "id": "cell-02", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from pathlib import Path\n", - "import opensysml\n", - "from toaster.report import format_diagnostics\n", - "\n", - "conn = opensysml.connect(version=\"v0.9.0\")\n", - "source = Path(\"../../models/ch06-cumulative.sysml\").read_text()\n", - "print(source)\n", - "model = conn.load_from_content(source, strict=False)\n", - "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "Perform relationships: [{'performer': 'ToasterDemo::ToastingSystem', 'action': 'ToasterDemo::ToastBread'}, {'performer': 'ToasterDemo::HeatingSystem', 'action': 'ToasterDemo::ApplyHeat'}, {'performer': 'ToasterDemo::HeatGenerator', 'action': 'ToasterDemo::GenerateHeat'}]\n", + "Allocations: [{'id': 'ToasterDemo::Toaster::heatAllocation', 'type': 'AllocationUsage', 'ends': [['ToasterDemo::ToastingSystem::toastBread', 'ToasterDemo::ToastBread::applyHeat'], ['ToasterDemo::Toaster::heating']]}, {'id': 'ToasterDemo::HeatingAssembly::heatGenAllocation', 'type': 'AllocationUsage', 'ends': [['ToasterDemo::HeatingSystem::applyHeat', 'ToasterDemo::ApplyHeat::generateHeat'], ['ToasterDemo::HeatingAssembly::heatGen']]}]\n", + "heatGenerationReq(rated) = True, heatGenerationReq(weak) = False\n" + ] + } + ], + "source": [ + "performs = perform_relationships(model)\n", + "allocations = find_allocations(model)\n", + "rated_holds = model.eval(\"ToasterDemo::heatGenerationReq(ToasterDemo::rated)\")\n", + "weak_holds = model.eval(\"ToasterDemo::heatGenerationReq(ToasterDemo::weak)\")\n", + "print(f\"Perform relationships: {performs}\")\n", + "print(f\"Allocations: {allocations}\")\n", + "print(f\"heatGenerationReq(rated) = {rated_holds}, heatGenerationReq(weak) = {weak_holds}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-07", + "metadata": {}, + "source": [ + "`HeatGenerator` performs `GenerateHeat`, `heatGenAllocation` points from `applyHeat.generateHeat` to `heatGen`, declared inside `HeatingAssembly` where both resolve, and the requirement evaluates True on `rated` and False on `weak`, the deliberately failing candidate. The next cells build `AI-C06` directly from these results, following the construction zone Hawkins' taxonomy uses." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "cell-08", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-29T20:48:37.216734Z", + "iopub.status.busy": "2026-09-29T20:48:37.216595Z", + "iopub.status.idle": "2026-09-29T20:48:37.218741Z", + "shell.execute_reply": "2026-09-29T20:48:37.218420Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-03", - "metadata": {}, - "source": [ - "The `ch06-cumulative.sysml` file adds a second-level requirement: `HeatingReq` constrains `heater.power >= 600.0` W on the `Heater` sub-component. A `HeatingAssembly` decomposition adds `ResistanceCoil` and `PowerWire` sub-parts. Two candidate heaters \u2014 `efficient` (800 W) and `weak` (400 W) \u2014 exercise the new requirement using the same satisfy-assertion pattern from Chapter 3, applied one level down in the hierarchy." - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "GenerateHeat is a real, specified behavior: HeatGenerator performs it and is allocated the nested step, not merely declared syntax. energyIn is a declared, typed connection point on HeatGenerator, not yet wired to a producer. heatGenerationReq evaluates against two real candidates, rated (True) and weak (False), a genuine satisfaction check on an underived threshold, not an unevaluated assertion.\n" + ] + } + ], + "source": [ + "claim = (\n", + " \"GenerateHeat is a real, specified behavior: HeatGenerator performs it \"\n", + " \"and is allocated the nested step, not merely declared syntax. energyIn \"\n", + " \"is a declared, typed connection point on HeatGenerator, not yet wired \"\n", + " \"to a producer. heatGenerationReq evaluates against two real \"\n", + " \"candidates, rated (True) and weak (False), a genuine satisfaction \"\n", + " \"check on an underived threshold, not an unevaluated assertion.\"\n", + ")\n", + "model_ref = \"ToasterDemo::HeatingAssembly::heatGen\"\n", + "print(claim)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-09", + "metadata": {}, + "source": [ + "The standard this claim is checked against: the recursion's own stopping rule, applied one level down." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "cell-10", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-29T20:48:37.219970Z", + "iopub.status.busy": "2026-09-29T20:48:37.219881Z", + "iopub.status.idle": "2026-09-29T20:48:37.221863Z", + "shell.execute_reply": "2026-09-29T20:48:37.221560Z" + } + }, + "outputs": [ { - "cell_type": "code", - "id": "cell-04", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# Negative control: an asserted_inference with empty premises fails validate_record().\n", - "# The Hawkins \u00a73.1 schema requires at least one premise \u2014 no premises = bare assertion.\n", - "incomplete = ReviewRecord(\n", - " identifier=\"AI-BAD\",\n", - " kind=\"asserted_inference\",\n", - " claim=\"HeatingSystem decomposition is complete\",\n", - " model_ref=\"ToasterDemo::HeatingAssembly\",\n", - " content_hash=hash_content(source),\n", - " scope=\"ToasterDemo\",\n", - " criteria=\"Every function allocated to HeatingSystem is realized by a subpart\",\n", - " premises=[],\n", - " assumption_refs=[],\n", - " evidence_refs=[],\n", - " rationale=\"The coil applies heat; the wire delivers power\",\n", - " counterevidence=\"Thermal conductivity and material aging are not modeled\",\n", - " residual_uncertainties=\"Long-term coil degradation is outside this model\",\n", - " disposition=\"pending\",\n", - " dependency_freshness=\"current\",\n", - " engineering_conclusion=\"undetermined\",\n", - " record_kind=\"worked_example\",\n", - ")\n", - "errors = validate_record(incomplete)\n", - "assert len(errors) > 0, \"Expected validation to fail on empty premises\"\n", - "print(f\"Validation errors for empty-premises record: {errors}\")" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "Per the recursion's own stopping rule, a leaf performs its specified behavior, connects through its specified interfaces, and has verification evidence. At this level: GenerateHeat is a specified behavior, allocated to HeatGenerator (met). energyIn is a declared, typed connection point, not yet connected to any producer (partially met, not complete). heatGenerationReq evaluates on two real candidates against a threshold that is itself not yet derived (partial evidence, not full verification).\n" + ] + } + ], + "source": [ + "scope = \"ToasterDemo::HeatingAssembly::heatGen and its realizations\"\n", + "criteria = (\n", + " \"Per the recursion's own stopping rule, a leaf performs its specified \"\n", + " \"behavior, connects through its specified interfaces, and has \"\n", + " \"verification evidence. At this level: GenerateHeat is a specified \"\n", + " \"behavior, allocated to HeatGenerator (met). energyIn is a declared, \"\n", + " \"typed connection point, not yet connected to any producer \"\n", + " \"(partially met, not complete). heatGenerationReq evaluates on two \"\n", + " \"real candidates against a threshold that is itself not yet derived \"\n", + " \"(partial evidence, not full verification).\"\n", + ")\n", + "print(criteria)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-11", + "metadata": {}, + "source": [ + "What the claim takes as given: the two judgments this chapter already recorded, and the two from earlier chapters this branch still rests on." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "cell-12", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-29T20:48:37.222939Z", + "iopub.status.busy": "2026-09-29T20:48:37.222876Z", + "iopub.status.idle": "2026-09-29T20:48:37.225022Z", + "shell.execute_reply": "2026-09-29T20:48:37.224559Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[\"GenerateHeat's own energy input is bound to ApplyHeat::energy, printed as part of APPLY_HEAT_INCREMENT and loaded successfully in notebook 01; this does not by itself mean energyIn is wired to any producer.\"]\n" + ] + } + ], + "source": [ + "premises = [\"AC-C06\", \"AS-C06\", \"AS-C03\", \"AI-C04\"]\n", + "assumption_refs = [\n", + " \"GenerateHeat's own energy input is bound to ApplyHeat::energy, \"\n", + " \"printed as part of APPLY_HEAT_INCREMENT and loaded successfully in \"\n", + " \"notebook 01; this does not by itself mean energyIn is wired to any \"\n", + " \"producer.\"\n", + "]\n", + "print(assumption_refs)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-13", + "metadata": {}, + "source": [ + "`evidence_refs` points at the three results gathered above; `rationale` connects them to the claim, condition by condition." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "cell-14", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-29T20:48:37.226242Z", + "iopub.status.busy": "2026-09-29T20:48:37.226148Z", + "iopub.status.idle": "2026-09-29T20:48:37.228430Z", + "shell.execute_reply": "2026-09-29T20:48:37.228084Z" + } + }, + "outputs": [ { - "cell_type": "code", - "id": "cell-05", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "stopping_judgment = ReviewRecord(\n", - " identifier=\"AI-C06\",\n", - " kind=\"asserted_inference\",\n", - " claim=\"The HeatingSystem decomposition into ResistanceCoil and PowerWire is complete: \"\n", - " \"every function allocated to HeatingSystem is realized by at least one subpart.\",\n", - " model_ref=\"ToasterDemo::HeatingAssembly\",\n", - " content_hash=hash_content(source),\n", - " scope=\"ToasterDemo\",\n", - " criteria=\"allocate ApplyHeat to HeatingSystem; coil realizes heat application; \"\n", - " \"wire realizes power delivery\",\n", - " premises=[\"AS-C03\", \"AI-C04\"],\n", - " assumption_refs=[\"AC-C01\"],\n", - " evidence_refs=[\"ToasterDemo::HeatingAssembly\"],\n", - " rationale=\"ResistanceCoil applies thermal energy (the allocated function); PowerWire \"\n", - " \"delivers electrical power to the coil. Together they account for both \"\n", - " \"inputs to ApplyHeat (power and duration). No additional subparts are needed \"\n", - " \"for the functions defined at this level.\",\n", - " counterevidence=\"Thermal conductivity, mounting hardware, and material aging are not \"\n", - " \"captured. A more detailed decomposition would add thermal interface \"\n", - " \"parts and a control signal path.\",\n", - " residual_uncertainties=\"Long-term coil resistance change under repeated cycling is \"\n", - " \"outside the scope of this model.\",\n", - " disposition=\"pending\",\n", - " dependency_freshness=\"current\",\n", - " engineering_conclusion=\"undetermined\",\n", - " record_kind=\"worked_example\",\n", - ")\n", - "errors = validate_record(stopping_judgment)\n", - "print(f\"Validation errors: {errors}\")\n", - "print(f\"Premises: {stopping_judgment.premises}\")" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "Performs: real, not merely declared. HeatGenerator performing GenerateHeat and heatGenAllocation both appear directly in perform_relationships and find_allocations above, not just in the source text, the same performer-and-allocation split Chapter 5 established one level up. Connects: energyIn is declared and typed, the interface point the stopping rule names, but it is not wired to any producer, so this condition is only partially met. Verified: heatGenerationReq is genuinely evaluated, not left as an unevaluated assertion, on two real candidates, one passing and one deliberately failing for a reason about the design; but its own threshold is not yet derived from any stated measure of effectiveness (AC-C06), so this is partial verification evidence, not a completed check.\n" + ] + } + ], + "source": [ + "evidence_refs = [\n", + " f\"perform_relationships(model): {performs}\",\n", + " f\"find_allocations(model): {allocations}\",\n", + " f\"heatGenerationReq(rated) = {rated_holds}, heatGenerationReq(weak) = \"\n", + " f\"{weak_holds}, evaluated directly against the loaded model above.\",\n", + "]\n", + "rationale = (\n", + " \"Performs: real, not merely declared. HeatGenerator performing \"\n", + " \"GenerateHeat and heatGenAllocation both appear directly in \"\n", + " \"perform_relationships and find_allocations above, not just in the \"\n", + " \"source text, the same performer-and-allocation split Chapter 5 \"\n", + " \"established one level up. Connects: energyIn is declared and typed, \"\n", + " \"the interface point the stopping rule names, but it is not wired to \"\n", + " \"any producer, so this condition is only partially met. Verified: \"\n", + " \"heatGenerationReq is genuinely evaluated, not left as an unevaluated \"\n", + " \"assertion, on two real candidates, one passing and one deliberately \"\n", + " \"failing for a reason about the design; but its own threshold is not \"\n", + " \"yet derived from any stated measure of effectiveness (AC-C06), so \"\n", + " \"this is partial verification evidence, not a completed check.\"\n", + ")\n", + "print(rationale)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-15", + "metadata": {}, + "source": [ + "The challenge: what this branch does not yet establish, stated plainly rather than folded into a premature completeness claim." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "cell-16", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-29T20:48:37.229537Z", + "iopub.status.busy": "2026-09-29T20:48:37.229456Z", + "iopub.status.idle": "2026-09-29T20:48:37.231652Z", + "shell.execute_reply": "2026-09-29T20:48:37.231328Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-06", - "metadata": {}, - "source": [ - "The Hawkins \u00a73.1 schema specifies what an `asserted_inference` record must contain, including a non-empty `premises` list (A-F); filling and validating the `ReviewRecord` in Python enacts that schema (O-S); `validate_record()` returning `[]` and the printed premises confirm the chain is complete (E)." - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "energyIn has no producer wired to it: no supply or wire component exists in this model, so the interface point is declared, not yet connected end to end, the same partial state Chapter 5 left ApplyHeat::duration in before that chapter built its own port connection. HeatingAssembly is not yet composed into any Toaster candidate: Toaster::heating is still typed by the abstract HeatingSystem, so no full toaster candidate contains a resistance coil yet. The 600 W threshold is not derived from any stated measure of effectiveness (AC-C06); it is a free-standing engineering figure. This branch addresses only GenerateHeat's own energy-to-heat conversion: ApplyHeat's other flows (bread, duration, toast, delivered, loss) are not decomposed or accounted for at this level. This is a deliberate one-branch worked example of one of ApplyHeat's own flows, the same kind of honestly scoped choice Chapter 4 made for ApplyHeat itself out of Douglas's roughly fifteen functions, not a claim that Chapter 6 finishes ApplyHeat's full decomposition.\n", + "Whether GenerateHeat needs further decomposition of its own, whether HeatingAssembly is ever composed into a real Toaster candidate, and whether ApplyHeat's other flows (duration, bread, toast) get their own level-2 branches, are open for whichever chapter takes them up.\n" + ] + } + ], + "source": [ + "counterevidence = (\n", + " \"energyIn has no producer wired to it: no supply or wire component \"\n", + " \"exists in this model, so the interface point is declared, not yet \"\n", + " \"connected end to end, the same partial state Chapter 5 left \"\n", + " \"ApplyHeat::duration in before that chapter built its own port \"\n", + " \"connection. HeatingAssembly is not yet composed into any Toaster \"\n", + " \"candidate: Toaster::heating is still typed by the abstract \"\n", + " \"HeatingSystem, so no full toaster candidate contains a resistance \"\n", + " \"coil yet. The 600 W threshold is not derived from any stated \"\n", + " \"measure of effectiveness (AC-C06); it is a free-standing \"\n", + " \"engineering figure. This branch addresses only GenerateHeat's own \"\n", + " \"energy-to-heat conversion: ApplyHeat's other flows (bread, \"\n", + " \"duration, toast, delivered, loss) are not decomposed or accounted \"\n", + " \"for at this level. This is a deliberate one-branch worked example \"\n", + " \"of one of ApplyHeat's own flows, the same kind of honestly scoped \"\n", + " \"choice Chapter 4 made for ApplyHeat itself out of Douglas's roughly \"\n", + " \"fifteen functions, not a claim that Chapter 6 finishes ApplyHeat's \"\n", + " \"full decomposition.\"\n", + ")\n", + "residual_uncertainties = (\n", + " \"Whether GenerateHeat needs further decomposition of its own, \"\n", + " \"whether HeatingAssembly is ever composed into a real Toaster \"\n", + " \"candidate, and whether ApplyHeat's other flows (duration, bread, \"\n", + " \"toast) get their own level-2 branches, are open for whichever \"\n", + " \"chapter takes them up.\"\n", + ")\n", + "print(counterevidence)\n", + "print(residual_uncertainties)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-17", + "metadata": {}, + "source": [ + "With every part named above, the record assembles from them directly." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "cell-18", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-29T20:48:37.232768Z", + "iopub.status.busy": "2026-09-29T20:48:37.232689Z", + "iopub.status.idle": "2026-09-29T20:48:37.239422Z", + "shell.execute_reply": "2026-09-29T20:48:37.239082Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-07", - "metadata": {}, - "source": [ - "Try the chapter exercise in `exercises/ch06/exercise.ipynb`: write an `AI-C06-EX` inference record claiming your `BrewUnit` decomposition is complete, with `premises` referencing your Chapter 5 `allocate` exercise result, and confirm `validate_record()` returns `[]`." - ] - } - ] -} \ No newline at end of file + "name": "stdout", + "output_type": "stream", + "text": [ + "Validation errors: []\n", + "Premises: ['AC-C06', 'AS-C06', 'AS-C03', 'AI-C04']\n" + ] + } + ], + "source": [ + "stopping_judgment = ReviewRecord(\n", + " identifier=\"AI-C06\",\n", + " kind=\"asserted_inference\",\n", + " claim=claim,\n", + " model_ref=model_ref,\n", + " content_hash=hash_content(source),\n", + " scope=scope,\n", + " criteria=criteria,\n", + " premises=premises,\n", + " assumption_refs=assumption_refs,\n", + " evidence_refs=evidence_refs,\n", + " rationale=rationale,\n", + " counterevidence=counterevidence,\n", + " residual_uncertainties=residual_uncertainties,\n", + " disposition=\"pending\",\n", + " dependency_freshness=\"current\",\n", + " engineering_conclusion=\"undetermined\",\n", + " record_kind=\"worked_example\",\n", + ")\n", + "\n", + "errors = validate_record(stopping_judgment)\n", + "print(f\"Validation errors: {errors}\")\n", + "print(f\"Premises: {stopping_judgment.premises}\")\n", + "conn.close()" + ] + }, + { + "cell_type": "markdown", + "id": "cell-19", + "metadata": {}, + "source": [ + "`validate_record` returns no errors, confirming the Hawkins 3.1 schema's required fields, including a non-empty `premises` list, are present and checked, not merely printed." + ] + }, + { + "cell_type": "markdown", + "id": "cell-20", + "metadata": {}, + "source": [ + "The model built across this chapter's two prior notebooks loaded without error, and the evaluated results above, not the model's own declaration, are what this stopping judgment cites." + ] + }, + { + "cell_type": "markdown", + "id": "cell-21", + "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`." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/chapters/ch06-recursive-decomp/conclusion.md b/chapters/ch06-recursive-decomp/conclusion.md index cf638c9..bfe5326 100644 --- a/chapters/ch06-recursive-decomp/conclusion.md +++ b/chapters/ch06-recursive-decomp/conclusion.md @@ -1,15 +1,15 @@ -# Chapter 6 — Conclusion +# Chapter 6: Conclusion ## What we built -The Chapter 6 model adds three things to the cumulative model. `HeatingReq` is a requirement definition that constrains `Heater.power >= 600.0`, with an `efficient` variant and a `weak` variant that overrides power to 400.0. `HeatingElement`, `ResistanceCoil`, `PowerWire`, and `HeatingAssembly` form a second-level structural decomposition: `HeatingAssembly` specializes `HeatingSystem` and composes both subparts. `AI-C06` is an `asserted_inference` record claiming that decomposition is complete, with `premises = ["AS-C03", "AI-C04"]` connecting it to the energy delivery evidence and the functional completeness claim. +The Chapter 6 model adds one honestly scoped worked-example branch of `ApplyHeat`'s own decomposition: the energy-to-heat path, not a full accounting of every flow `ApplyHeat` declares. `GenerateHeat` is a function nested inside `ApplyHeat`, with a typed energy input and a thermal energy output, committed to no particular energy form or mechanism. `HeatGenerator` is its abstract logical carrier: it performs `GenerateHeat`, exposes `energyIn`, a port typed by the new `EnergyPort`, and declares an unbound `power` slot, also committed to no energy form. `HeatingAssembly` specializes `HeatingSystem` and composes `heatGen : HeatGenerator`; `heatGenAllocation`, nested inside `HeatingAssembly` itself, allocates the inherited `applyHeat`'s own `generateHeat` step (`applyHeat.generateHeat`) to it at the usage level, the same usage-level idiom Chapter 5 established for `heatAllocation`, and the nesting a feature chain like this one needs to resolve at all. `HeatGenerationReq`, stated on `HeatGenerator` itself rather than any one realization, states a 600 W threshold. `AS-C06` records the actual selection: a resistive, electrically switched mechanism, argued from a domain premise about how resistive and combustion mechanisms respond to a discrete timing signal like `ControlSystem`'s `durationOut`, not from the port or function already being electrical, and not from anything the model currently connects. Only after that selection does `ResistanceCoil`'s electrically-specific name and Joule-heating doc become admissible; it specializes `HeatGenerator`, with a properly unit-typed `resistance` attribute and a default `power` rating. `rated` and `weak` are two `ResistanceCoil` candidates, one satisfying the requirement and one failing it by a deliberate design choice, expressed as `assert not satisfy` folded into its own context rather than a false positive claim. `AC-C06` records that the requirement states a measure of performance, not a measure of effectiveness, and that its threshold is not yet derived. `AI-C06` records what this branch's stopping judgment honestly shows and does not yet show, checked against real analysis gathered from the loaded model. ## What this establishes -The chapter answers its engineering question: the toaster model is decomposed to a level where each allocated function maps to a structural part, and that claim is formally recorded. `ResistanceCoil` realizes heat application (the function `ApplyHeat` allocates to `HeatingSystem`); `PowerWire` delivers the electrical power input. The stopping judgment does not assert that no further decomposition is possible — it asserts that no further decomposition is *needed* for the claims at this level. The chain of premises makes the basis for that assertion auditable. +The chapter answers its engineering question for one branch: `GenerateHeat` is a real, specified behavior, allocated to a real logical carrier; `HeatGenerator` realizes no function by itself, and the model states separately that `ResistanceCoil` specializes it and that `heatGenAllocation` assigns `GenerateHeat` to `HeatGenerator`'s own usage, so allocation is never collapsed into realization. The recursion's own stopping rule is only partly met here, and `AI-C06` says so directly rather than folding the gaps into a completeness claim: `energyIn` has no producer wired to it, `HeatingAssembly` is not yet composed into any full toaster candidate, `HeatGenerationReq`'s 600 W threshold is not yet derived from a stated measure of effectiveness, and `ApplyHeat`'s other flows (`bread`, `duration`, `toast`, `delivered`, `loss`) are not decomposed or accounted for at this level at all. This chapter builds one branch honestly, the same kind of scope choice Chapter 4 made for `ApplyHeat` itself out of Douglas's roughly fifteen functions; it does not claim to finish `ApplyHeat`'s decomposition, and further branches and connections remain open work. ## What comes next -Chapter 7 asks how the model behaves at runtime. It binds `DeliveredEnergy` to sympy, evaluates it numerically, and runs `execute_state` to trace normal and cancel scenarios through the toaster's state machine. +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 decompose `BrewUnit` into an `Impeller` and a `FilterBasket`, add a `BrewReq` requirement for minimum water throughput, and write an `asserted_inference` record claiming the decomposition is complete with `premises` referencing your Chapter 5 allocation exercise result. +**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. diff --git a/chapters/ch06-recursive-decomp/index.md b/chapters/ch06-recursive-decomp/index.md index fbb2921..337eae8 100644 --- a/chapters/ch06-recursive-decomp/index.md +++ b/chapters/ch06-recursive-decomp/index.md @@ -1,18 +1,18 @@ -# Chapter 6 — Recursive Decomposition +# Chapter 6: Recursive Decomposition ## Purpose -This chapter asks: how deep should the decomposition go, and how do you know when to stop? +This chapter asks: for one branch of `ApplyHeat`'s own decomposition, what does the recursion's stopping rule actually show, and what does it not yet show, one level below where Chapter 5 stopped? -After completing this chapter, the cumulative model has a second-level structural decomposition of `HeatingSystem` into `ResistanceCoil` and `PowerWire`, a subsystem-level requirement (`HeatingReq`), and an `asserted_inference` record (`AI-C06`) that chains the stopping judgment back to the Chapter 3 and Chapter 4 evidence. +After completing this chapter, the cumulative model has a real second-level function (`GenerateHeat`, nested inside `ApplyHeat`, committed to no energy form), an abstract logical carrier for it (`HeatGenerator`, performing the function and exposing an energy port, also uncommitted), a usage-level allocation between them, a requirement stated on the carrier, a recorded mechanism selection, a concrete physical realization the selection licenses (`ResistanceCoil`), two real candidates checked against the requirement, and a stopping judgment that states plainly what this one branch does and does not establish. ## Ingredients | Notebook | Concept | |---|---| -| [01 — Subsystem Requirements](01-subsystem-requirements.ipynb) | Apply `requirement def` and `attribute :>>` override to the heating subsystem — the same two constructs from Chapter 2, one level down. | -| [02 — Second Level](02-second-level.ipynb) | Decompose `HeatingSystem` using the four structural constructs from Chapter 1: abstract def, part def, specialization, and composition. | -| [03 — Stopping Judgment](03-stopping-judgment.ipynb) | Record `AI-C06`, an `asserted_inference` that the decomposition is complete, with `premises` referencing `AS-C03` and `AI-C04`. | +| [01: Level-2 Function and Logical Carrier](01-subsystem-requirements.ipynb) | Nest `GenerateHeat` inside `ApplyHeat`, the same way `ApplyHeat` nests inside `ToastBread`, and give it a logical carrier, `HeatGenerator`, one level below `HeatingSystem`; neither commits to an energy form or mechanism. | +| [02: Level-2 Physical Realization](02-second-level.ipynb) | State the requirement `HeatGenerator`'s rating is checked against, record the measure framing and the mechanism selection that requirement raises, then build `ResistanceCoil`, the concrete realization the selection licenses. | +| [03: Stopping Judgment](03-stopping-judgment.ipynb) | Record `AI-C06`, an `asserted_inference` checked against real analysis on the loaded model, stating plainly what this one branch establishes and what it does not. | ## Equipment @@ -20,12 +20,12 @@ See [docs/setup.md](../../docs/setup.md) for environment setup. No chapter-speci ## Method -The chapter demonstrates self-similarity: the same three-notebook structure (requirement, structure, judgment) that appeared in Chapters 2–4 recurs at the second decomposition level. Notebook 01 applies the requirement and attribute override pattern to `Heater`. Notebook 02 applies the abstract-def, part-def, specialization, and composition pattern to `HeatingSystem`. Notebook 03 records the stopping judgment, which requires a non-empty `premises` list to satisfy the Hawkins §3.1 schema. +The chapter carries one branch of the recursive step through all three layers at the second level, not straight from a level-1 logical grouping to level-2 physical parts, and not by naming a mechanism-specific part before the argument for it exists. Notebook 01 builds the function and the abstract carrier that performs it, allocated at the usage level, both energy-neutral. Notebook 02 states the requirement first, records why the requirement is a measure of performance and why a resistive mechanism is chosen, then builds the concrete realization that selection licenses. Notebook 03 asks what the recursion's own stopping rule shows for this one branch, against real evidence gathered from the loaded model, and states plainly what it does not yet show. ## Expected result -After running all three notebooks, `model.query()` returns `HeatingElement`, `ResistanceCoil`, `PowerWire`, and `HeatingAssembly` as `PartDefinition` elements. `model.find("ToasterDemo::HeatingReq")` returns a symbol with `kind='requirementDef'`. `validate_record(stopping_judgment)` returns `[]`, and `stopping_judgment.premises` is `["AS-C03", "AI-C04"]`. +After running all three notebooks, `perform_relationships(model)` includes `HeatGenerator` performing `GenerateHeat`; `find_allocations(model)` includes `HeatingAssembly::heatGenAllocation`, nested in `HeatingAssembly` itself, with source end `['HeatingSystem::applyHeat', 'ApplyHeat::generateHeat']` (the inherited `applyHeat` usage, then its own nested `generateHeat` step) and target end `['HeatingAssembly::heatGen']`; `model.eval("ToasterDemo::heatGenerationReq(ToasterDemo::rated)")` is `True` and the same call on `weak` is `False`; and `validate_record()` returns `[]` for `AC-C06`, `AS-C06` and `AI-C06`. ## Experiment -Try the [Chapter 6 exercise](../../exercises/ch06/exercise.ipynb): decompose `BrewUnit` into an `Impeller` and a `FilterBasket`, add a `BrewReq` requirement, and write an `asserted_inference` record claiming the decomposition is complete. +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. diff --git a/chapters/ch07-execution/01-calc-energy.ipynb b/chapters/ch07-execution/01-calc-energy.ipynb index 9aba016..9327cb4 100644 --- a/chapters/ch07-execution/01-calc-energy.ipynb +++ b/chapters/ch07-execution/01-calc-energy.ipynb @@ -1,159 +1,447 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, - "cells": [ - { - "cell_type": "markdown", - "id": "cell-00", - "metadata": {}, - "source": [ - "## Ch7-01 \u2014 Symbolic energy binding\n", - "\n", - "This notebook introduces sympy symbolic binding for the `DeliveredEnergy` calc def; after running it you can verify the 67200 J reference value and compare the symbolic expression with the SysML formula.\n" - ] - }, - { - "cell_type": "markdown", - "id": "cell-01", - "metadata": {}, - "source": [ - "Chapter 3 introduced `DeliveredEnergy` as a `calc def` with the formula `power * duration * efficiency`. This notebook binds that same formula to a sympy expression and evaluates it with lambdify, establishing the reference value (67200 J) that the parameter sweep in notebook 03 builds on. See [Ch3-02 MoP candidate evaluation](../ch03-measures/02-mop-candidate-eval.ipynb) for the original calc def.\n" - ] - }, - { - "cell_type": "code", - "id": "cell-02", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from pathlib import Path\n", - "import opensysml\n", - "from toaster.report import format_diagnostics\n", - "\n", - "conn = opensysml.connect(version=\"v0.9.0\")\n", - "source = Path(\"../../models/ch07-cumulative.sysml\").read_text()\n", - "print(source)\n", - "model = conn.load_from_content(source, strict=False)\n", - "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" - ] - }, - { - "cell_type": "markdown", - "id": "cell-03", - "metadata": {}, - "source": [ - "The `ch07-cumulative.sysml` file adds `state Cycle` with four substates (`idle`, `heating`, `ready`, `cancelled`) and three transitions (`idle \u2192 heating` on `Start`, `heating \u2192 ready` on `Finish`, `heating \u2192 cancelled` on `Cancel`). This is construct 13 \u2014 the first executable behavior in the model. `model.execute_state()` can trace event sequences through this state machine." - ] - }, + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": [ + "## delivered energy on the heat generator\n", + "\n", + "This notebook introduces `deliveredEnergy`, a calc on `HeatGenerator` bounded by a real efficiency constraint; after running it you can query the model's own energy relation instead of recomputing it in Python." + ] + }, + { + "cell_type": "markdown", + "id": "cell-01", + "metadata": {}, + "source": [ + "The functional layer states an energy balance (`ApplyHeat`'s own `balance` constraint) but never characterizes how much energy a real conversion actually delivers: that characterization depends on a mechanism's efficiency, a logical commitment, not something every solution shares. `HeatGenerator`, the logical carrier Chapter 6 built, is where that characterization belongs. This notebook builds it there for the first time, with `efficiency` as a bounded slot instead of an unconstrained input." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "cell-02", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-30T00:47:19.902878Z", + "iopub.status.busy": "2026-09-30T00:47:19.902674Z", + "iopub.status.idle": "2026-09-30T00:47:20.146006Z", + "shell.execute_reply": "2026-09-30T00:47:20.145425Z" + } + }, + "outputs": [ { - "cell_type": "code", - "id": "cell-04", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# A calc def referencing an undefined base type fails to parse.\n", - "bad_source = \"\"\"\n", - "package P {\n", - " calc def Broken :> MissingBase {\n", - " in x : Real;\n", - " return : Real = x;\n", - " }\n", - "}\n", - "\"\"\"\n", - "bad = conn.load_from_content(bad_source, strict=False)\n", - "assert not bad.ok, \"Expected parse failure for undefined base type\"\n", - "# Expected: diagnostic pointing to 'MissingBase' as an unresolved reference\n", - "print(f\"Negative control ok: bad.ok={bad.ok}\")\n" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + " attribute efficiency : DimensionOneValue;\n", + " assert constraint efficiencyBounded {\n", + " 0.0 <= efficiency and efficiency <= 1.0\n", + " }\n" + ] + } + ], + "source": [ + "from pathlib import Path\n", + "import opensysml\n", + "from toaster.report import format_diagnostics\n", + "\n", + "conn = opensysml.connect(version=\"v0.9.0\")\n", + "\n", + "EFFICIENCY_SLOT = \"\"\"\\\n", + " attribute efficiency : DimensionOneValue;\n", + " assert constraint efficiencyBounded {\n", + " 0.0 <= efficiency and efficiency <= 1.0\n", + " }\"\"\"\n", + "print(EFFICIENCY_SLOT)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-03", + "metadata": {}, + "source": [ + "`efficiency` is a bounded slot, not a free value: a conversion cannot deliver a negative fraction of the energy it is supplied, and it cannot deliver more than all of it. The bound is a real constraint on the model, checked the same way `ApplyHeat`'s own `balance` constraint is." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "cell-04", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-30T00:47:20.147721Z", + "iopub.status.busy": "2026-09-30T00:47:20.147495Z", + "iopub.status.idle": "2026-09-30T00:47:20.149643Z", + "shell.execute_reply": "2026-09-30T00:47:20.149220Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "SysML v2's `calc def` expresses `power * duration * efficiency` as a formula in the model. Sympy lets Python work with the same formula as a symbolic expression: `sp.symbols('P t eta', positive=True)` creates three variables that know they are positive quantities, matching the `in` parameters of `DeliveredEnergy`. Jupyter renders a sympy expression as typeset math \u2014 the cell below shows what the formula looks like before any numbers go in.\n" - ], - "id": "cell-05" - }, + "name": "stdout", + "output_type": "stream", + "text": [ + " calc deliveredEnergy {\n", + " in power : ISQ::PowerValue;\n", + " in duration : ISQ::DurationValue;\n", + " return : ISQ::EnergyValue = power * duration * efficiency;\n", + " }\n" + ] + } + ], + "source": [ + "DELIVERED_ENERGY_CALC = \"\"\"\\\n", + " calc deliveredEnergy {\n", + " in power : ISQ::PowerValue;\n", + " in duration : ISQ::DurationValue;\n", + " return : ISQ::EnergyValue = power * duration * efficiency;\n", + " }\"\"\"\n", + "print(DELIVERED_ENERGY_CALC)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-05", + "metadata": {}, + "source": [ + "`deliveredEnergy` characterizes what a heat generator actually delivers: a queried power and duration, scaled by its own efficiency. `efficiency` is not one of its parameters: it is this carrier's own bound feature, resolved from whichever concrete usage the calc is queried through. `power` and `duration` stay free, queryable inputs, but there is no way to pass an arbitrary, unchecked efficiency into it: every efficiency value that flows through this relation is a real candidate's own bound value, the same value `efficiencyBounded` checks." + ] + }, + { + "cell_type": "markdown", + "id": "cell-06", + "metadata": {}, + "source": [ + "`rated`, the candidate from Chapter 6 that already satisfies `HeatGenerationReq`, also needs a concrete efficiency to query. Its declaration is likewise reprinted in full below, with the new value added." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "cell-07", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-30T00:47:20.150989Z", + "iopub.status.busy": "2026-09-30T00:47:20.150887Z", + "iopub.status.idle": "2026-09-30T00:47:20.153165Z", + "shell.execute_reply": "2026-09-30T00:47:20.152751Z" + } + }, + "outputs": [ { - "cell_type": "code", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "import sympy as sp\n", - "\n", - "P, t, eta = sp.symbols('P t eta', positive=True)\n", - "Q_sym = P * t * eta # mirrors: return : Real = power * duration * efficiency\n", - "Q_sym # Jupyter renders this as typeset math\n" - ], - "id": "cell-06" - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "abstract part def HeatGenerator {\n", + " perform action generateHeat : GenerateHeat;\n", + " port energyIn : ~EnergyPort;\n", + " attribute power : ISQ::PowerValue;\n", + " attribute efficiency : DimensionOneValue;\n", + " assert constraint efficiencyBounded {\n", + " 0.0 <= efficiency and efficiency <= 1.0\n", + " }\n", + " calc deliveredEnergy {\n", + " in power : ISQ::PowerValue;\n", + " in duration : ISQ::DurationValue;\n", + " return : ISQ::EnergyValue = power * duration * efficiency;\n", + " }\n", + "}\n", + "part rated : ResistanceCoil {\n", + " attribute :>> efficiency = 0.7 [MeasurementReferences::one];\n", + " assert satisfy heatGenerationReq by rated;\n", + "}\n" + ] + } + ], + "source": [ + "# efficiency, its bound and deliveredEnergy cannot be added to HeatGenerator in a\n", + "# separate statement, so its full declaration is reprinted here with them included.\n", + "HEAT_GENERATOR_INCREMENT = \"\"\"\\\n", + "abstract part def HeatGenerator {\n", + " perform action generateHeat : GenerateHeat;\n", + " port energyIn : ~EnergyPort;\n", + " attribute power : ISQ::PowerValue;\n", + "\"\"\" + EFFICIENCY_SLOT + \"\\n\" + DELIVERED_ENERGY_CALC + \"\\n}\"\n", + "print(HEAT_GENERATOR_INCREMENT)\n", + "\n", + "RATED_INCREMENT = \"\"\"\\\n", + "part rated : ResistanceCoil {\n", + " attribute :>> efficiency = 0.7 [MeasurementReferences::one];\n", + " assert satisfy heatGenerationReq by rated;\n", + "}\"\"\"\n", + "print(RATED_INCREMENT)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-08", + "metadata": {}, + "source": [ + "`rated`'s efficiency (0.7) is this chapter's own assumed value for the worked example: nothing in Chapters 1-6 derives it. It sits alongside `rated`'s Chapter 6 power rating (800 W), the same assumed operating point this notebook queries below." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "cell-09", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-30T00:47:20.154797Z", + "iopub.status.busy": "2026-09-30T00:47:20.154684Z", + "iopub.status.idle": "2026-09-30T00:47:20.173335Z", + "shell.execute_reply": "2026-09-30T00:47:20.172845Z" + } + }, + "outputs": [ { - "cell_type": "code", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# lambdify compiles Q_sym into a numpy-compatible function.\n", - "# [P, t, eta] fixes the argument order to match the calc def's in-parameters.\n", - "Q_fn = sp.lambdify([P, t, eta], Q_sym, 'numpy')\n", - "\n", - "# Reference value: 800 W \u00d7 120 s \u00d7 0.7 = 67200 J\n", - "ref = float(Q_fn(800.0, 120.0, 0.7))\n", - "assert abs(ref - 67200.0) < 1.0, f\"Reference mismatch: {ref}\"\n", - "print(f\"Q_fn(800, 120, 0.7) = {ref:.1f} J (expected 67200.0)\")\n" - ], - "id": "cell-07" - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "abstract part def HeatGenerator {\n", + " perform action generateHeat : GenerateHeat;\n", + " port energyIn : ~EnergyPort;\n", + " attribute power : ISQ::PowerValue;\n", + " attribute efficiency : DimensionOneValue;\n", + " assert constraint efficiencyBounded {\n", + " 0.0 <= efficiency and efficiency <= 1.0\n", + " }\n", + " calc deliveredEnergy {\n", + " in power : ISQ::PowerValue;\n", + " in duration : ISQ::DurationValue;\n", + " return : ISQ::EnergyValue = power * duration * efficiency;\n", + " }\n", + "}\n", + "part rated : ResistanceCoil {\n", + " attribute :>> efficiency = 0.7 [MeasurementReferences::one];\n", + " assert satisfy heatGenerationReq by rated;\n", + "}\n" + ] + } + ], + "source": [ + "TOASTER_INCREMENT = f\"{HEAT_GENERATOR_INCREMENT}\\n{RATED_INCREMENT}\"\n", + "print(TOASTER_INCREMENT)\n", + "\n", + "source = Path(\"../../models/ch07-cumulative.sysml\").read_text()\n", + "model = conn.load_from_content(source, strict=False)\n", + "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" + ] + }, + { + "cell_type": "markdown", + "id": "cell-10", + "metadata": {}, + "source": [ + "A constraint that references an attribute the definition never declares fails to load. The negative control below asserts a bound on a name `Widget` does not have." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "cell-11", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-30T00:47:20.174712Z", + "iopub.status.busy": "2026-09-30T00:47:20.174615Z", + "iopub.status.idle": "2026-09-30T00:47:20.190909Z", + "shell.execute_reply": "2026-09-30T00:47:20.190543Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`model.eval()` evaluates an expression through the opensysml runtime using the model's own attribute values. Passing a qualified call string invokes the calc def directly through the model, independent of the sympy binding. The two results should agree to within floating-point tolerance, confirming that the sympy expression faithfully mirrors the model formula.\n" - ], - "id": "cell-08" - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "Negative control ok: bad.ok=False\n" + ] + } + ], + "source": [ + "bad_source = \"\"\"\n", + "package P {\n", + " private import ScalarValues::*;\n", + " part def Widget {\n", + " assert constraint bad {\n", + " undeclaredAttribute >= 0.0\n", + " }\n", + " }\n", + "}\n", + "\"\"\"\n", + "bad = conn.load_from_content(bad_source, strict=False)\n", + "assert not bad.ok, \"Expected failure: undeclaredAttribute is never declared on Widget\"\n", + "print(f\"Negative control ok: bad.ok={bad.ok}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-12", + "metadata": {}, + "source": [ + "The diagnostic reports an unresolved reference: `undeclaredAttribute` was never declared, so the constraint cannot type-check." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "cell-13", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-30T00:47:20.192388Z", + "iopub.status.busy": "2026-09-30T00:47:20.192301Z", + "iopub.status.idle": "2026-09-30T00:47:20.206790Z", + "shell.execute_reply": "2026-09-30T00:47:20.206468Z" + } + }, + "outputs": [ { - "cell_type": "code", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "model_val = float(model.eval(\"ToasterDemo::DeliveredEnergy(800.0, 120.0, 0.7)\"))\n", - "assert abs(model_val - 67200.0) < 1.0, f\"Model eval mismatch: {model_val}\"\n", - "print(f\"model.eval(...) = {model_val:.1f} J\")\n", - "print(f\"Both agree: {abs(ref - model_val) < 1.0}\")\n", - "conn.close()\n" - ], - "id": "cell-09" - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "rated.deliveredEnergy(rated.power, 120 s) = 67200 [MeasurementReferences::one*SI::'kg⋅m²⋅s⁻²']\n" + ] + } + ], + "source": [ + "delivered = model.eval(\n", + " \"ToasterDemo::rated.deliveredEnergy(ToasterDemo::rated.power, 120.0 [SI::s])\"\n", + ")\n", + "print(f\"rated.deliveredEnergy(rated.power, 120 s) = {delivered}\")" + ] + }, + { + "cell_type": "markdown", + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "cell-15", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-30T00:47:20.208108Z", + "iopub.status.busy": "2026-09-30T00:47:20.208033Z", + "iopub.status.idle": "2026-09-30T00:47:20.211742Z", + "shell.execute_reply": "2026-09-30T00:47:20.211398Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-10", - "metadata": {}, - "source": [ - "The `calc def DeliveredEnergy` with formula `power * duration * efficiency` (A-F) is bound to a sympy expression and evaluated by lambdify (O-S); `Q_fn(800.0, 120.0, 0.7)` returns 67200.0, matching the `model.eval()` reference value (E).\n" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "✓ constraint ToasterDemo::HeatGenerator::efficiencyBounded holds (on ToasterDemo::rated ID: 1) - observed by run\n" + ] + } + ], + "source": [ + "holds = model.verify_constraint(\n", + " \"ToasterDemo::HeatGenerator::efficiencyBounded\",\n", + " subject=\"ToasterDemo::rated\",\n", + " engine=\"run\",\n", + ")\n", + "# opensysml's own verdict string uses an em-dash; the printed form here uses a\n", + "# plain hyphen instead (a display-only substitution; the verdict itself is untouched).\n", + "print(str(holds).replace(chr(0x2014), \"-\"))\n", + "assert holds.holds, \"Expected efficiencyBounded to hold for rated's own 0.7 value\" " + ] + }, + { + "cell_type": "markdown", + "id": "cell-16", + "metadata": {}, + "source": [ + "`engine=\"run\"` evaluates the constraint against `rated`'s own bound value and reports whether it holds; it is claim evaluation, not model checking (it observes one candidate, not every possible one). The next cell probes the same bound against a candidate the real model never commits to, appended to a scratch copy of the loaded source, and confirms the bound flags it." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "cell-17", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-30T00:47:20.213017Z", + "iopub.status.busy": "2026-09-30T00:47:20.212940Z", + "iopub.status.idle": "2026-09-30T00:47:20.237214Z", + "shell.execute_reply": "2026-09-30T00:47:20.236836Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-11", - "metadata": {}, - "source": [ - "Try the chapter exercise in `exercises/ch07/exercise.ipynb`: bind the coffee maker's brew energy formula to sympy and verify the reference value for a 1200 W heating element running for 90 seconds at 0.65 efficiency.\n" - ] - } - ] -} \ No newline at end of file + "name": "stdout", + "output_type": "stream", + "text": [ + "✗ constraint ToasterDemo::HeatGenerator::efficiencyBounded fails (on ToasterDemo::overEfficient ID: 1): condition evaluated to false: 0.0 <= efficiency and efficiency <= 1.0 - witnessed by run\n" + ] + } + ], + "source": [ + "probe_source = source.rstrip()[:-1] + \"\"\"\n", + " part overEfficient : ResistanceCoil {\n", + " attribute :>> efficiency = 1.5 [MeasurementReferences::one];\n", + " }\n", + "}\n", + "\"\"\"\n", + "probe_model = conn.load_from_content(probe_source, strict=False)\n", + "assert probe_model.ok\n", + "\n", + "violated = probe_model.verify_constraint(\n", + " \"ToasterDemo::HeatGenerator::efficiencyBounded\",\n", + " subject=\"ToasterDemo::overEfficient\",\n", + " engine=\"run\",\n", + ")\n", + "print(str(violated).replace(chr(0x2014), \"-\"))\n", + "assert not violated.holds, \"Expected efficiencyBounded to fail for overEfficient's 1.5 value\" " + ] + }, + { + "cell_type": "markdown", + "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." + ] + }, + { + "cell_type": "markdown", + "id": "cell-19", + "metadata": {}, + "source": [ + "The definitions printed above loaded without error, and the two `verify_constraint` calls show the bound doing real work: holding for `rated`'s own value and failing for one outside it, both against the constraint the model itself declares." + ] + }, + { + "cell_type": "markdown", + "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`." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/chapters/ch07-execution/02-state-traces.ipynb b/chapters/ch07-execution/02-state-traces.ipynb index a6af713..cede175 100644 --- a/chapters/ch07-execution/02-state-traces.ipynb +++ b/chapters/ch07-execution/02-state-traces.ipynb @@ -1,125 +1,579 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, - "cells": [ + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": [ + "## the toaster's own operating cycle\n", + "\n", + "This notebook introduces `Cycle`, a state def `ToastingSystem` exhibits, with a `heating` state whose `do action` invokes the heat-generation step and transitions that return `ready` and `cancelled` to `idle`; after running it you can trace the toaster's own operating modes as the model itself defines them." + ] + }, + { + "cell_type": "markdown", + "id": "cell-01", + "metadata": {}, + "source": [ + "No earlier chapter built a state machine: the toaster's modes have never been part of the model before this notebook. Building `Cycle` here means getting three things right from the start, not repairing them: `Cycle` needs a real owner, its `heating` state needs to invoke a real function rather than being an inert label, and `ready` and `cancelled` need a way back to `idle` or the chapter's own name, \"cycle,\" would not be true of the model." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "cell-02", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T11:23:55.966553Z", + "iopub.status.busy": "2026-09-28T11:23:55.966343Z", + "iopub.status.idle": "2026-09-28T11:23:56.084320Z", + "shell.execute_reply": "2026-09-28T11:23:56.083898Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "state def Cycle {\n", + " entry; then idle;\n", + " state idle;\n" + ] + } + ], + "source": [ + "from pathlib import Path\n", + "import opensysml\n", + "from toaster.report import format_diagnostics\n", + "\n", + "conn = opensysml.connect(version=\"v0.9.0\")\n", + "\n", + "STATE_CYCLE_OPEN = \"state def Cycle {\"\n", + "ENTRY = \" entry; then idle;\"\n", + "IDLE_STATE = \" state idle;\"\n", + "print(STATE_CYCLE_OPEN)\n", + "print(ENTRY)\n", + "print(IDLE_STATE)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-03", + "metadata": {}, + "source": [ + "`idle` is the machine's own entry state: waiting holds for any solution, a pop-up toaster and tongs with a blowtorch alike." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "cell-04", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T11:23:56.085837Z", + "iopub.status.busy": "2026-09-28T11:23:56.085663Z", + "iopub.status.idle": "2026-09-28T11:23:56.087969Z", + "shell.execute_reply": "2026-09-28T11:23:56.087585Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " state heating {\n", + " do action generateHeat : GenerateHeat;\n", + " }\n" + ] + } + ], + "source": [ + "HEATING_STATE = \"\"\"\\\n", + " state heating {\n", + " do action generateHeat : GenerateHeat;\n", + " }\"\"\"\n", + "print(HEATING_STATE)" + ] + }, + { + "cell_type": "markdown", + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "cell-06", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T11:23:56.089210Z", + "iopub.status.busy": "2026-09-28T11:23:56.089115Z", + "iopub.status.idle": "2026-09-28T11:23:56.091022Z", + "shell.execute_reply": "2026-09-28T11:23:56.090687Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " state ready;\n", + " state cancelled;\n" + ] + } + ], + "source": [ + "READY_CANCELLED = \"\"\"\\\n", + " state ready;\n", + " state cancelled;\"\"\"\n", + "print(READY_CANCELLED)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-07", + "metadata": {}, + "source": [ + "`ready` and `cancelled` hold for any solution too: finishing and stopping on demand are both intents, not mechanisms." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "cell-08", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T11:23:56.092326Z", + "iopub.status.busy": "2026-09-28T11:23:56.092241Z", + "iopub.status.idle": "2026-09-28T11:23:56.094092Z", + "shell.execute_reply": "2026-09-28T11:23:56.093780Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " transition first idle accept Start then heating;\n", + " transition first heating accept Finish then ready;\n", + " transition first heating accept Cancel then cancelled;\n" + ] + } + ], + "source": [ + "TRIGGERED_TRANSITIONS = \"\"\"\\\n", + " transition first idle accept Start then heating;\n", + " transition first heating accept Finish then ready;\n", + " transition first heating accept Cancel then cancelled;\"\"\"\n", + "print(TRIGGERED_TRANSITIONS)" + ] + }, + { + "cell_type": "markdown", + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "cell-10", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T11:23:56.095421Z", + "iopub.status.busy": "2026-09-28T11:23:56.095339Z", + "iopub.status.idle": "2026-09-28T11:23:56.097163Z", + "shell.execute_reply": "2026-09-28T11:23:56.096859Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-00", - "metadata": {}, - "source": [ - "## Ch7-02 \u2014 State machine and execution traces\n", - "\n", - "This notebook introduces state usage with transitions (construct 13); after running it you can simulate the toaster's operating cycle for a normal toast run and a cancelled run.\n" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + " transition first ready then idle;\n", + " transition first cancelled then idle;\n" + ] + } + ], + "source": [ + "COMPLETION_TRANSITIONS = \"\"\"\\\n", + " transition first ready then idle;\n", + " transition first cancelled then idle;\"\"\"\n", + "print(COMPLETION_TRANSITIONS)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-11", + "metadata": {}, + "source": [ + "These two transitions carry no `accept`: they fire as soon as their source state is entered, with no event required. They are what makes `Cycle` actually cycle: a finished or a cancelled run returns to `idle`, ready for another." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "cell-12", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T11:23:56.098311Z", + "iopub.status.busy": "2026-09-28T11:23:56.098250Z", + "iopub.status.idle": "2026-09-28T11:23:56.100395Z", + "shell.execute_reply": "2026-09-28T11:23:56.099983Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-01", - "metadata": {}, - "source": [ - "Chapter 4 introduced action flow for the `ApplyHeat` operation. This notebook adds a `state Cycle` that captures the toaster's discrete operating modes \u2014 idle, heating, ready, and cancelled \u2014 and uses `execute_state` to simulate how events move the system between those modes. See [Ch4-01 action def](../ch04-functional-decomp/01-action-def-ffbd.ipynb) for the action def this state machine complements.\n" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "state def Cycle {\n", + " entry; then idle;\n", + " state idle;\n", + " state heating {\n", + " do action generateHeat : GenerateHeat;\n", + " }\n", + " state ready;\n", + " state cancelled;\n", + " transition first idle accept Start then heating;\n", + " transition first heating accept Finish then ready;\n", + " transition first heating accept Cancel then cancelled;\n", + " transition first ready then idle;\n", + " transition first cancelled then idle;\n", + "}\n" + ] + } + ], + "source": [ + "CYCLE_DEF = (\n", + " f\"{STATE_CYCLE_OPEN}\\n{ENTRY}\\n{IDLE_STATE}\\n{HEATING_STATE}\\n{READY_CANCELLED}\\n\"\n", + " f\"{TRIGGERED_TRANSITIONS}\\n{COMPLETION_TRANSITIONS}\\n}}\"\n", + ")\n", + "print(CYCLE_DEF)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-13", + "metadata": {}, + "source": [ + "`Cycle` is now a complete state def. It still needs an owner: nothing exhibits it yet." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "cell-14", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T11:23:56.101574Z", + "iopub.status.busy": "2026-09-28T11:23:56.101498Z", + "iopub.status.idle": "2026-09-28T11:23:56.103661Z", + "shell.execute_reply": "2026-09-28T11:23:56.103323Z" + } + }, + "outputs": [ { - "cell_type": "code", - "id": "cell-02", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from pathlib import Path\n", - "import opensysml\n", - "from toaster.report import format_diagnostics\n", - "\n", - "conn = opensysml.connect(version=\"v0.9.0\")\n", - "source = Path(\"../../models/ch07-cumulative.sysml\").read_text()\n", - "print(source)\n", - "model = conn.load_from_content(source, strict=False)\n", - "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "abstract part def ToastingSystem {\n", + " perform action toastBread : ToastBread;\n", + " exhibit state cycle : Cycle;\n", + "}\n" + ] + } + ], + "source": [ + "# exhibit state cannot be added to ToastingSystem in a separate statement, so its\n", + "# full declaration is reprinted here with the new line included.\n", + "TOASTING_SYSTEM_INCREMENT = \"\"\"\\\n", + "abstract part def ToastingSystem {\n", + " perform action toastBread : ToastBread;\n", + " exhibit state cycle : Cycle;\n", + "}\"\"\"\n", + "print(TOASTING_SYSTEM_INCREMENT)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-15", + "metadata": {}, + "source": [ + "`ToastingSystem`, not `Toaster`, is the subject the functional, logical and physical layers all describe (Chapter 1's own ruling): `Toaster` is presently the tutorial's only concrete realization of it. A functional mode machine belongs on the subject itself, so it is `ToastingSystem` that exhibits `Cycle`. `Toaster`, and any usage of it, inherits the machine and can execute it, the same way it already inherits and performs `toastBread`." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "cell-16", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T11:23:56.104804Z", + "iopub.status.busy": "2026-09-28T11:23:56.104728Z", + "iopub.status.idle": "2026-09-28T11:23:56.122997Z", + "shell.execute_reply": "2026-09-28T11:23:56.122615Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-03", - "metadata": {}, - "source": [ - "The `ch07-cumulative.sysml` file adds `state Cycle` with four substates (`idle`, `heating`, `ready`, `cancelled`) and three transitions (`idle \u2192 heating` on `Start`, `heating \u2192 ready` on `Finish`, `heating \u2192 cancelled` on `Cancel`). This is construct 13 \u2014 the first executable behavior in the model. `model.execute_state()` can trace event sequences through this state machine." - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "state def Cycle {\n", + " entry; then idle;\n", + " state idle;\n", + " state heating {\n", + " do action generateHeat : GenerateHeat;\n", + " }\n", + " state ready;\n", + " state cancelled;\n", + " transition first idle accept Start then heating;\n", + " transition first heating accept Finish then ready;\n", + " transition first heating accept Cancel then cancelled;\n", + " transition first ready then idle;\n", + " transition first cancelled then idle;\n", + "}\n", + "abstract part def ToastingSystem {\n", + " perform action toastBread : ToastBread;\n", + " exhibit state cycle : Cycle;\n", + "}\n" + ] + } + ], + "source": [ + "TOASTER_INCREMENT = f\"{CYCLE_DEF}\\n{TOASTING_SYSTEM_INCREMENT}\"\n", + "print(TOASTER_INCREMENT)\n", + "\n", + "source = Path(\"../../models/ch07-cumulative.sysml\").read_text()\n", + "model = conn.load_from_content(source, strict=False)\n", + "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" + ] + }, + { + "cell_type": "markdown", + "id": "cell-17", + "metadata": {}, + "source": [ + "A state machine referencing an undefined transition target fails to parse. The negative control below points a transition at a state the machine never declares." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "cell-18", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T11:23:56.124160Z", + "iopub.status.busy": "2026-09-28T11:23:56.124088Z", + "iopub.status.idle": "2026-09-28T11:23:56.138068Z", + "shell.execute_reply": "2026-09-28T11:23:56.137668Z" + } + }, + "outputs": [ { - "cell_type": "code", - "id": "cell-04", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# A state machine referencing an undefined transition target fails to parse.\n", - "bad_source = \"\"\"\n", - "package P {\n", - " item def Go;\n", - " state S {\n", - " entry; then a;\n", - " state a;\n", - " transition first a accept Go then missing_state;\n", - " }\n", - "}\n", - "\"\"\"\n", - "bad = conn.load_from_content(bad_source, strict=False)\n", - "assert not bad.ok, \"Expected failure for undefined transition target\"\n", - "# Expected: diagnostic for 'missing_state' as an unresolved reference\n", - "print(f\"Negative control ok: bad.ok={bad.ok}\")\n" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "Negative control ok: bad.ok=False\n" + ] + } + ], + "source": [ + "bad_source = \"\"\"\n", + "package P {\n", + " item def Go;\n", + " state def S {\n", + " entry; then a;\n", + " state a;\n", + " transition first a accept Go then missing_state;\n", + " }\n", + "}\n", + "\"\"\"\n", + "bad = conn.load_from_content(bad_source, strict=False)\n", + "assert not bad.ok, \"Expected failure for undefined transition target\"\n", + "print(f\"Negative control ok: bad.ok={bad.ok}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-19", + "metadata": {}, + "source": [ + "The diagnostic reports an unresolved reference: `missing_state` was never declared as a state of `S`. This catches an undefined target; it says nothing about an undefined trigger, which the next cell probes directly." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "cell-20", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T11:23:56.139401Z", + "iopub.status.busy": "2026-09-28T11:23:56.139320Z", + "iopub.status.idle": "2026-09-28T11:23:56.241485Z", + "shell.execute_reply": "2026-09-28T11:23:56.241042Z" + } + }, + "outputs": [ { - "cell_type": "code", - "id": "cell-05", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# Locate the Cycle state machine by qualified name\n", - "cycle = model.find(\"ToasterDemo::Cycle\")\n", - "assert cycle is not None, \"Cycle not found\"\n", - "print(f\"Cycle: kind={cycle.kind!r}, id={cycle.id!r}\")\n", - "\n", - "# Normal run: Start \u2192 heating, Finish \u2192 ready\n", - "normal = model.execute_state(cycle.id, events=[\"Start\", \"Finish\"])\n", - "print(f\"Normal trace: {normal['states_visited']}\")\n", - "assert normal[\"states_visited\"] == [\"idle\", \"heating\", \"ready\"]\n", - "\n", - "# Cancelled run: Start \u2192 heating, Cancel \u2192 cancelled\n", - "cancelled = model.execute_state(cycle.id, events=[\"Start\", \"Cancel\"])\n", - "print(f\"Cancelled trace: {cancelled['states_visited']}\")\n", - "assert cancelled[\"states_visited\"] == [\"idle\", \"heating\", \"cancelled\"]\n", - "\n", - "conn.close()\n" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "OpenSysML itself: typo_model.ok=True\n", + "{'rule': 'unresolved-transition-trigger', 'constraint': \"SysML v2.0 formal/2026-03-02: 8.3.18.9 TransitionUsage (/triggerAction : AcceptActionUsage), 8.3.18.8 TransitionFeatureMembership (validateTransitionFeatureMembershipTriggerAction), 8.3.17.2 AcceptActionUsage (PDF p. 341-342, payloadParameter) - a trigger is a structured, resolvable element, so an `accept` trigger's payload name must resolve to a defined element in scope\", 'element': 'ToasterDemo::Cycle::@6', 'message': \"transition ToasterDemo::Cycle::@6 accepts trigger 'Strat', whose payload 'Strat' resolves to no element in scope\"}\n" + ] + } + ], + "source": [ + "typo_source = source.replace(\"accept Start then heating\", \"accept Strat then heating\")\n", + "typo_model = conn.load_from_content(typo_source, strict=False)\n", + "assert typo_model.ok, \"OpenSysML itself accepts the typo'd trigger with no diagnostic\"\n", + "print(f\"OpenSysML itself: typo_model.ok={typo_model.ok}\")\n", + "\n", + "from toaster.conformance import language_gap_findings\n", + "\n", + "findings = [\n", + " f for f in language_gap_findings(typo_model) if f[\"rule\"] == \"unresolved-transition-trigger\"\n", + "]\n", + "assert findings, \"Expected the tutorial's own guard to flag the typo'd trigger\"\n", + "# The rule's own constraint citation uses an em-dash; the printed form here uses a\n", + "# plain hyphen instead (a display-only substitution; the finding itself is untouched).\n", + "for finding in findings:\n", + " print({k: v.replace(chr(0x2014), \"-\") if isinstance(v, str) else v for k, v in finding.items()})" + ] + }, + { + "cell_type": "markdown", + "id": "cell-21", + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "cell-22", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T11:23:56.242872Z", + "iopub.status.busy": "2026-09-28T11:23:56.242788Z", + "iopub.status.idle": "2026-09-28T11:23:56.251723Z", + "shell.execute_reply": "2026-09-28T11:23:56.251307Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-06", - "metadata": {}, - "source": [ - "The `state Cycle` with four substates and three transitions (A-F) is executed by OpenSysML's `execute_state` (O-S); the states visited \u2014 `['idle', 'heating', 'ready']` for a normal run and `['idle', 'heating', 'cancelled']` for a cancel \u2014 appear in the result dict (E).\n" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "ToastingSystem::cycle: kind='stateUsage', id='ToasterDemo::ToastingSystem::cycle'\n", + "Start, Finish (run through nominal): ['idle', 'heating', 'ready', 'idle']\n" + ] + } + ], + "source": [ + "cycle = model.find(\"ToasterDemo::ToastingSystem::cycle\")\n", + "print(f\"ToastingSystem::cycle: kind={cycle.kind!r}, id={cycle.id!r}\")\n", + "\n", + "normal = model.execute_state(\n", + " \"ToasterDemo::Cycle\", events=[\"Start\", \"Finish\"], performer=\"ToasterDemo::nominal\"\n", + ")\n", + "print(f\"Start, Finish (run through nominal): {normal['states_visited']}\")\n", + "assert normal[\"states_visited\"] == [\"idle\", \"heating\", \"ready\", \"idle\"]" + ] + }, + { + "cell_type": "markdown", + "id": "cell-23", + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "cell-24", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T11:23:56.253044Z", + "iopub.status.busy": "2026-09-28T11:23:56.252960Z", + "iopub.status.idle": "2026-09-28T11:23:56.256503Z", + "shell.execute_reply": "2026-09-28T11:23:56.256073Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-07", - "metadata": {}, - "source": [ - "Try the chapter exercise in `exercises/ch07/exercise.ipynb`: add a `state BrewCycle` to the coffee maker model with an `Overheat` transition to a `fault` state, and verify the trace with `execute_state`.\n" - ] - } - ] -} \ No newline at end of file + "name": "stdout", + "output_type": "stream", + "text": [ + "Start, Cancel: ['idle', 'heating', 'cancelled', 'idle']\n", + "Start, Finish, Start, Finish: ['idle', 'heating', 'ready', 'idle', 'heating', 'ready', 'idle']\n" + ] + } + ], + "source": [ + "cancelled = model.execute_state(\"ToasterDemo::Cycle\", events=[\"Start\", \"Cancel\"])\n", + "print(f\"Start, Cancel: {cancelled['states_visited']}\")\n", + "assert cancelled[\"states_visited\"] == [\"idle\", \"heating\", \"cancelled\", \"idle\"]\n", + "\n", + "repeated = model.execute_state(\"ToasterDemo::Cycle\", events=[\"Start\", \"Finish\", \"Start\", \"Finish\"])\n", + "print(f\"Start, Finish, Start, Finish: {repeated['states_visited']}\")\n", + "assert repeated[\"states_visited\"] == [\"idle\", \"heating\", \"ready\", \"idle\", \"heating\", \"ready\", \"idle\"]" + ] + }, + { + "cell_type": "markdown", + "id": "cell-25", + "metadata": {}, + "source": [ + "Both traces are derived from `Cycle`'s own transition table, not entered as a choice: they show the machine can be run twice in a row and return to `idle` each time, catching a mistake in the table rather than establishing anything about the toaster in use. A trace like this is specification analysis, not a simulation of behavior." + ] + }, + { + "cell_type": "markdown", + "id": "cell-26", + "metadata": {}, + "source": [ + "The definitions printed above loaded without error, and `model.find` and `execute_state` both confirm `Cycle` is now part of the model, shown by the traces printed above." + ] + }, + { + "cell_type": "markdown", + "id": "cell-27", + "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`." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/chapters/ch07-execution/03-param-sweep.ipynb b/chapters/ch07-execution/03-param-sweep.ipynb index 3e0c4e4..a0745ba 100644 --- a/chapters/ch07-execution/03-param-sweep.ipynb +++ b/chapters/ch07-execution/03-param-sweep.ipynb @@ -1,142 +1,297 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": [ + "## sweeping the design space HeatGenerationReq opens\n", + "\n", + "This notebook introduces a sweep over `deliveredEnergy`'s own free `power` input, querying the relation from the model at every point and checking the sweep against `HeatGenerationReq`'s own 600 W threshold on `HeatGenerator::power`, read from the model rather than invented in Python." + ] + }, + { + "cell_type": "markdown", + "id": "cell-01", + "metadata": {}, + "source": [ + "Chapter 6 built `HeatGenerationReq`, a 600 W threshold on `HeatGenerator::power`, the attribute a real candidate's power rating binds. Notebook 01 built `deliveredEnergy`, the energy relation this notebook now sweeps; its own `power` input stays free precisely so a design space can be explored independently of any one candidate's rated value. This notebook connects the two: it sweeps `deliveredEnergy`'s `power` argument across a range, evaluates the relation at each point through the model, and marks the requirement's own threshold on the result, instead of an energy figure invented for the plot." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "cell-02", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T11:25:23.155287Z", + "iopub.status.busy": "2026-09-28T11:25:23.155168Z", + "iopub.status.idle": "2026-09-28T11:25:23.285761Z", + "shell.execute_reply": "2026-09-28T11:25:23.285270Z" } + }, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "import opensysml\n", + "from toaster.report import format_diagnostics\n", + "\n", + "conn = opensysml.connect(version=\"v0.9.0\")\n", + "source = Path(\"../../models/ch07-cumulative.sysml\").read_text()\n", + "model = conn.load_from_content(source, strict=False)\n", + "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" + ] }, - "cells": [ - { - "cell_type": "markdown", - "id": "cell-00", - "metadata": {}, - "source": [ - "## Ch7-03 \u2014 Parameter sweep\n", - "\n", - "This notebook introduces numpy parameter sweeps over the sympy-bound energy model; after running it you can show how delivered energy varies with heater power and mark the design requirement boundary on the plot.\n" - ] - }, - { - "cell_type": "markdown", - "id": "cell-01", - "metadata": {}, - "source": [ - "Notebook 01 established that `Q_fn(800.0, 120.0, 0.7)` returns 67200 J. This notebook sweeps heater power from 500 W to 1200 W at fixed duration (120 s) and efficiency (0.7) to show which power values deliver enough energy for the toaster's function. The matplotlib figure is the simulation evidence referenced by the judgment record in Chapter 8. See [Ch7-01 symbolic binding](01-calc-energy.ipynb) for the lambdify setup.\n" - ] - }, - { - "cell_type": "code", - "id": "cell-02", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from pathlib import Path\n", - "import opensysml\n", - "from toaster.report import format_diagnostics\n", - "\n", - "conn = opensysml.connect(version=\"v0.9.0\")\n", - "source = Path(\"../../models/ch07-cumulative.sysml\").read_text()\n", - "print(source)\n", - "model = conn.load_from_content(source, strict=False)\n", - "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" - ] - }, + { + "cell_type": "markdown", + "id": "cell-03", + "metadata": {}, + "source": [ + "A calc invoked without the library import its parameter types depend on fails to load. The negative control below drops the `ScalarValues` import `Real` needs." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "cell-04", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T11:25:23.287379Z", + "iopub.status.busy": "2026-09-28T11:25:23.287214Z", + "iopub.status.idle": "2026-09-28T11:25:23.301569Z", + "shell.execute_reply": "2026-09-28T11:25:23.300790Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-03", - "metadata": {}, - "source": [ - "The `ch07-cumulative.sysml` file adds `state Cycle` with four substates (`idle`, `heating`, `ready`, `cancelled`) and three transitions (`idle \u2192 heating` on `Start`, `heating \u2192 ready` on `Finish`, `heating \u2192 cancelled` on `Cancel`). This is construct 13 \u2014 the first executable behavior in the model. `model.execute_state()` can trace event sequences through this state machine." - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "Negative control ok: bad.ok=False\n" + ] + } + ], + "source": [ + "bad_source = \"\"\"\n", + "package P {\n", + " calc def Broken {\n", + " in x : Real;\n", + " return : Real = x * x;\n", + " }\n", + "}\n", + "\"\"\"\n", + "bad = conn.load_from_content(bad_source, strict=False)\n", + "assert not bad.ok, \"Expected failure: Real is undefined without ScalarValues::* import\"\n", + "print(f\"Negative control ok: bad.ok={bad.ok}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-05", + "metadata": {}, + "source": [ + "The diagnostic reports `Real` as unresolved: without the import, nothing declares it. `HeatGenerationReq`'s own 600 W threshold is real; the next cell reads it from the model instead of retyping it." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "cell-06", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T11:25:23.303397Z", + "iopub.status.busy": "2026-09-28T11:25:23.303262Z", + "iopub.status.idle": "2026-09-28T11:25:23.393554Z", + "shell.execute_reply": "2026-09-28T11:25:23.393085Z" + } + }, + "outputs": [ { - "cell_type": "code", - "id": "cell-04", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# A calc def without the ScalarValues import cannot resolve 'Real' and fails to parse.\n", - "bad_source = \"\"\"\n", - "package P {\n", - " calc def Broken {\n", - " in x : Real;\n", - " return : Real = x * x;\n", - " }\n", - "}\n", - "\"\"\"\n", - "bad = conn.load_from_content(bad_source, strict=False)\n", - "assert not bad.ok, \"Expected failure: Real is undefined without ScalarValues::* import\"\n", - "print(f\"Negative control ok: bad.ok={bad.ok}\")\n" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "HeatGenerationReq's own threshold: 600.0 W\n" + ] + } + ], + "source": [ + "import re\n", + "from toaster.query import ApiIndex\n", + "\n", + "index = ApiIndex(model)\n", + "threshold_constraint = next(\n", + " e for e in index.of_type(\"ConstraintUsage\")\n", + " if index.qn(e.get(\"owner\")) == \"ToasterDemo::HeatGenerationReq\"\n", + ")\n", + "match = re.search(r\"([\\d.]+)\\s*\\[SI::W\\]\", threshold_constraint[\"sysx:sourceText\"])\n", + "power_threshold_w = float(match.group(1))\n", + "print(f\"HeatGenerationReq's own threshold: {power_threshold_w} W\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-07", + "metadata": {}, + "source": [ + "`power_threshold_w` comes from the requirement's own source text in the loaded model, not a second, independently typed literal: whatever `HeatGenerationReq` states, this is what the sweep is checked against." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "cell-08", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T11:25:23.394833Z", + "iopub.status.busy": "2026-09-28T11:25:23.394728Z", + "iopub.status.idle": "2026-09-28T11:25:23.500300Z", + "shell.execute_reply": "2026-09-28T11:25:23.499587Z" + } + }, + "outputs": [ { - "cell_type": "code", - "id": "cell-05", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "import sympy as sp\n", - "import numpy as np\n", - "import matplotlib\n", - "matplotlib.use(\"Agg\")\n", - "import matplotlib.pyplot as plt\n", - "from toaster.simulate import sweep_1d\n", - "\n", - "# Rebuild the sympy binding (self-contained per SA-2 / fresh kernel)\n", - "P, t, eta = sp.symbols('P t eta', positive=True)\n", - "Q_fn = sp.lambdify([P, t, eta], P * t * eta, 'numpy')\n", - "\n", - "# Sweep power 500\u20131200 W; duration=120 s, efficiency=0.7\n", - "P_vals = np.linspace(500, 1200, 50)\n", - "Q_vals = sweep_1d(Q_fn, P_vals, t=120.0, eta=0.7)\n", - "\n", - "# Design threshold: 50000 J ensures toast within the cycle time at typical efficiency\n", - "threshold = 50_000.0\n", - "\n", - "fig, ax = plt.subplots(figsize=(6, 4))\n", - "ax.plot(P_vals, Q_vals / 1000, label=\"Delivered energy\")\n", - "ax.axhline(threshold / 1000, color=\"red\", linestyle=\"--\", label=f\"Threshold {threshold/1000:.0f} kJ\")\n", - "ax.set_xlabel(\"Heater power (W)\")\n", - "ax.set_ylabel(\"Delivered energy (kJ)\")\n", - "ax.set_title(\"Energy vs. heater power (t=120 s, \u03b7=0.7)\")\n", - "ax.legend()\n", - "fig.tight_layout()\n", - "\n", - "out = \"ch07_param_sweep.svg\"\n", - "fig.savefig(out)\n", - "plt.close(fig)\n", - "\n", - "crossing_idx = np.argmax(Q_vals >= threshold)\n", - "print(f\"Threshold crossed at: {P_vals[crossing_idx]:.0f} W\")\n", - "print(f\"Nominal (800 W): {Q_vals[P_vals >= 800][0] / 1000:.1f} kJ\")\n", - "print(f\"Figure saved: {out}\")\n", - "conn.close()\n" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "Assumed duration for this sweep: 120.0 s (not derived; this chapter's own worked example)\n", + "rated's own power, read from the model: 800 [SI::W]\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "\n", + "duration_s = 120.0 # this chapter's own assumed operating duration, not derived from the model\n", + "rated_power = model.eval(\"ToasterDemo::rated.power\")\n", + "print(f\"Assumed duration for this sweep: {duration_s} s (not derived; this chapter's own worked example)\")\n", + "print(f\"rated's own power, read from the model: {rated_power}\")\n", + "\n", + "power_values_w = np.linspace(500.0, 1200.0, 50)\n", + "delivered_j = np.array([\n", + " model.eval(\n", + " f\"ToasterDemo::rated.deliveredEnergy({p:.4f} [SI::W], {duration_s} [SI::s])\"\n", + " ).magnitude\n", + " for p in power_values_w\n", + "])" + ] + }, + { + "cell_type": "markdown", + "id": "cell-09", + "metadata": {}, + "source": [ + "Each point on the curve is the model's own `deliveredEnergy`, evaluated through `model.eval` at that power, not a formula rebuilt in Python: `power` and `duration` are queried, `efficiency` is `rated`'s own bound value, read automatically because the calc is invoked through `rated` itself. Duration is this chapter's own assumed operating point, stated as such; nothing in Chapters 1-6 derives it." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "cell-10", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T11:25:23.501759Z", + "iopub.status.busy": "2026-09-28T11:25:23.501614Z", + "iopub.status.idle": "2026-09-28T11:25:23.848035Z", + "shell.execute_reply": "2026-09-28T11:25:23.847385Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-06", - "metadata": {}, - "source": [ - "The requirement that delivered energy exceeds the design threshold (A-F) is evaluated across a power sweep using `sweep_1d` and lambdify (O-S); the matplotlib figure shows the energy curve and marks the threshold crossing (E).\n" + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAApEAAAGGCAYAAAAjENp1AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjExLjIsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvgI3uAAAAAAlwSFlzAAAPYQAAD2EBqD+naQAAkT5JREFUeJztnQd4FNXXxg/pjYTeOwRCgiBdkN57UUGkKuhfURHFAlioIioqKFgQEAURBETpvYOASFNC76H3EALp8z3vzTfr7maT7Cab7G7y/p5nk53ZuzN37tydeefcc87No2maJoQQQgghhNiAmy2FCSGEEEIIoYgkhBBCCCEZgpZIQgghhBBiMxSRhBBCCCHEZigiCSGEEEKIzVBEEkIIIYQQm6GIJIQQQgghNkMRSQghhBBCbIYikhBCCCGEOEZErlmzRvLkySNbtmyx+bszZ85U3z137lya61yJvn37SqlSpRxdDZKLuH37thQoUEDmz5/v6KqQHEa+fPnkpZdecnQ1XJbc3H4XL15U9/Jp06aJK+OIc3j//n3Vdh9//HGW7uf69esSEBAgf/zxR4a+T0tkNtGtWzfVIVJ7nTp1KruqQnIgY8aMkRIlSsjTTz9tWHfz5k3Vtz777DO77uvy5cvy1VdfSaNGjcTNzU0qVapksVx8fLz89ttv0rlzZylZsqQEBgbKo48+KlOmTJG4uDiL31m0aJHUrl1bfH19pUiRIvL888+r4yAkq8iq30lqJCQkqP3BWEKIo8F1dsiQIfL222+nel12ORGJGwem9C5XrpzkJNzd3dVxWXqldiMmJD1u3bol33//vbzyyitK1GU1uODgoQdPyI888kiq5ZYuXSo9e/aU4OBg2b59u7JKDB8+XD744APp2rVrivI///yzKt+7d291Y9+6davs3btXWrVqJbGxsVl8VIQQkjsZPHiwnD59Wj3E24pHltSIEJJtzJ49W5KSkkyskFkJrIs6sKikRt68eWXVqlXStm1bw7pnnnlGzpw5I++//77s2LFDWTN1q+Wbb74pHTt2VP9B1apVZdasWVK3bl1ltYFIJoQQYl/KlCkjTZo0ke+++0769Olj03dtNlvgwt+wYUM13IQdT548OdWysDwMGjRIDbN5eXlJ+fLllRUiPZOpuU8kLBpYtjRmv2nTJvWZsS+YNfvF0AW+h6G5t956S4oVKyaenp4mfp4tWrRQQ3A41scee0xWrlxpsm9YEGGNgcUUZRo0aKAsJ5kFQ37t2rVTN1vcgP39/aV48eIyatQotU9zrKmrvs1jx46pbcIH4tlnn1Wf3bhxQ/r16yf58+eXoKAgdaO/c+eOiR8ILEGFCxeWp556KsX+IQBQvy5dulg8npiYGOWv16tXrxSfJSYmqvNkbJn65ptvpEaNGqqOOC8YDs1Iuxr74yxYsEBCQkLEx8dHbdtSX4qKipJhw4ZJ2bJlVb/BECye0GDp06lVq5Z06NDB5HvNmzdX+1m4cKFhHc4d1s2YMcPkWNHvqlWrpuqB9kZ74gnQUp0XL16sLH3ol3ifGsuXL1fnF22s8/fff6vzBTBMobtNwMqfXaCfGQtIHd3qfvbsWZPrCnxzunfvblK2Tp066jpjzRNyRvsNyuvtg7bG7/mNN95Q/cHW7adXZvfu3aley/CdV1991WJfQN9C/8W1AJZZ/dqIfg2xjf5Ur149OXToUIrtWtPvAKy/AwYMSHEdyCjG9V+2bJnaP+oPd4X169dneLup7cPS7yW9c2vN7yS72i+j/Tcrr1uWziHaICwsTF13bMFe1+CM/oasqX9Gz2GMjfc4a6855thy7Lb0XQANsXPnTuVfbxOaDezdu1fz9vbWunfvrp0+fVq7du2a9sEHH2jdunWDstE2b95sKHvu3DmtaNGiWoMGDdT37t+/r23cuFErVaqU9uSTTxrKzZgxQ3337Nmzqa6Lj4/XihcvrnXq1ClFnfr06aPlz59fe/jwoU37nTRpktpH7969tdmzZ2u3b9/Wpk+fbth/njx5tOHDh2sXLlzQbt68qX3yySeam5ub9ttvvxm28c4772ienp7a119/rb5/+PBhrV27dlrz5s21kiVLmtSza9eumru7u1XtXKNGDVV/tPO+ffu0yMhIbdq0aaq+qKsx1tYV26xfv76qH9oF527BggVaTEyMVr16da1s2bLa1q1btXv37mmrV6/Wnn76aS0oKEh78cUXDdvAPjw8PLRLly6Z1AHbQd3++OOPVI/p1VdfVX3n1q1bJuuXL1+uvrt06VK1/OOPP6p2mjt3rjpulF+1apXWs2dPzVYiIiLUttH2L7zwgmof1P2VV15RbWZc37i4ONU+RYoU0VauXKn2vWXLFtUuVatW1aKiogzn3M/PT7UbiI6OVsfl6+urDRo0yLC97777zqQPJyUlqfOJvjpv3jztzp076jfUpUsXtU+9TY3rjO2hP//777/atm3bLB4j6o39Dx48OMVnN27cUNtCX7dExYoV1efpvdAuqYF+he3YQt++fdV29+zZY1g3ZcoUtW779u0pyrdp00YrUKBAmtu0V7/BeV67dq1WunRp9RuwZfvWlNm1a5c6zt9//z3Fvv39/VXf1NH7Aq6vr732mnb58mXVn2rXrq09+uijahsvv/yy6jvnz5/X6tWrp1WqVElLSEgwbMPafhcbG6vVrFlTHTeu46j/ihUrtB49eqS4DliLXv+nnnpKe/fdd9W+0Cefe+45dd00vuZntD/a8ntJ7dym9TvJivbD/Qz7w7U7s/03q69bxucQ1xj0s6tXr6plLy8v9Xl2X4Mz8huypv6Z/Q28auU9ztp+ifX43sSJEw3rbDl2a/uuDu776d3HLWGTiGzfvr0SaLpg04EwMReRzzzzjJYvXz71AzUGFURZNIa1IhKMGDFC/chwIdW5e/eu+hHg5Nm6X11EQgQbg23mzZvX5GTqoOPpN0x0RFwIjU8awIlBR7IkIlO7KJqXxY0ZYg0n3Ji6desqcWlrXfVt4kd79OhRk3IQpagDOrExP//8s1pv/MPB+YA4HTNmjEnZpk2bqn6Bi2NqHDx4UG3vyy+/NFmPG2SxYsUM33322We18uXLa/ZAv4DgAoQflDG4WFSuXNmwPGfOHFV24cKFJuXQp41vMOvXr1fLGzZsMPzw0Ca4SJcpU8bwPTyw4IaugwsIvoebhDG4UBQqVEgbOnSoSZ1RN/M6W+LixYuq/Lhx41xCRK5bt071w2bNmpmsx+8Q+4IAMAc3UnwnMTEx1e3as9+AmTNnqvrgN2bt9q0pk5EbIPqqMcuWLVPrcS0w7iO48WI9Hppt7Xc//fSTKof+bAzEjfl1wFr0+rdu3dpkPW7OuL6NHTvWbiLS2t+LpXOb1u8ku9ovo/03q69bevvid27cvleuXFG/yQkTJmT7NTgjvyFr6p/Zc3jQynuctf0ysyLS2r6bXv3Tw+rhbAjOzZs3S5s2bZRZ1Dzy2ByYips1ayaFChUyWd+yZUv1H07ztoDhaZhmf/rpJ8O6X375RR4+fKg+y+h+zYdgkaYIJuUePXqkqAOGkWAGvnTpkmzbtk0N45p/H2Zr+HDZElgDk7s5GKKqUKGCyTqYpDHcYGtddSpXrqyGE4zBOfX29pbWrVun2S4AJncMiWCoAxGG4MiRI6pNMQTg4ZG6iy2GLzCM9cMPPxjWYfgSw+7G30U5DHPC/w1DOTjnmaVTp04pfPcwtHDixAlD+2zcuFEFpZgHfKAvFSxYUH0O4MMHl4F169apZQzLYcgV/ogXLlyQ48ePK/9EuFkYtyn6Jc6/+XAthiDgfmDeLzGUlZa/oc7du3cN/oe2guCY1AK9jF8YQrEH6CsY7ilatKjJ79gYa47ZEpnpN/gNwNUD1wycI+PhTD1rgjXbz4q+C1A3Y/TfMPqicXvhmgGMrxHW9jv0b1wHzN0PzL+XEfAbMgZuN3B/MXZnyGx/TO33Ys25TYvsar+M9p2svm7p4Lpv3L4YbkebGve17LoGZwRr6m+Pc1jbinucPfqlNdh6z8Hv0vieYi1Wi8jo6Gg17o8bgDnm61AWOY7gy4iGw4HghQ6i3+yMfRysAX5UTZs2NTlBcLqHrwf8wTK6X/hcGHP16lX1Hz8ufRv4Pl66fyC2oW/HmvbICLjImoOTbHyCra1raseqfw6fIPMfONrL/GEBvPzyy+pHD/8S8O2336r/AwcOTPeY8COBz9a+ffvU8pw5c5QQN/4ufDomTJig/Dzh4wUfDvwIMuNrmtY50tPHoB3g0wI/HHNwwdHLoU1wQTa+GOOiW7NmTdWOWIafFfxojC/GOFe4McDPxvxcrVixIt1+mRrwWwX37t0TZwYXRjzY4Lg3bNig/ByNwU0CWPI/Qp/HcaYVeZ7RfgNBgvOEcwx/oAcPHiihAv8tgP5p7fYz23ct+Ttbuhbo17LU1ptfI6zpd6ldB3DNwY01M+g3J2PwO0vP/8sWLP1erD23aZFd7ZfRvpPV1y1b7kfZdQ22x2/IUv3t8Rt43op7nD36pTXHbus9R7+HoO9liYiEQzQ64rVr11J8Zr7Oz89PPfUgygcWKxwIXnjS0Z8mP/30U7EVWBxPnjyp0oX8888/6kQZWyEzsl/jYBqgWzDx9KBvA9833kb16tUNNz1r2iMjWGORsbauqR0rwHEgsMa8A+ICj4cGc/D0VLFiReUEDtGOHwkuTlWqVEm3vnBSxjnSHwQQVdy4cWNlIdVBZ3/33XeVFfX8+fMydepU9bSKyDFzy4W1pHWO9POIixccii39gFHW2LKNCwAuFOiD//77r7LO43zB2o2LNC7I+MHCcV0H30ffRJtaOlewBhhj6VylluMLv0v9gcLWB7O0cpfqLzy1ZgacRzhto23xtA+ndnP0VEGwiJiDYDDjfmyJjPYbjGbgXMG6jj6s3yzMv2PN9q0pgws6MBdQkZGR6mZiy7XA2muENf0utesAbizZlV4pM/3R0u/F2nPrDO2X0f6b1detzI4QGNfFXtdge/6GjLHHb+AZK+5xmemXthy7rfecK1euqP8IasoSEYmTALMyOpp5g8LyZ14W5muUzUx0nzmIKkIjwgKJF26eyClnz/3iBwTB/Ouvv6ZZDh0DP3zzCC+cCHtEaNuzrultA+fTfKgATyqWQBvDyolhD0S84wdmLOTTAucO5xA/Inwfw5tpfRfWKgwDfPnll+qHkNF2hcg2vzDAkooftj6zEC6k+HHpFlYduC3gCVh3h9Avxtgech5iaABR+fp6uBggrQ1cGnQroT7cBtcL8+1nFtw869evr6wI5qBvAEfmWITVGgISFzhYIFPLK4nfE6wAv//+u8l6HBcudpayAtir3+hP6To4t8hZmZntp1YGF2js6/DhwyblbY10tRZr+x3OEfqJbqnSyegsFs6CNec2rd+JI9rPlv6b1dcte2HPa3BW/YbscQ6DrLzH2XrN0bHl2G295/z111/q/q6nXbMaWxwoEU2JiCYEbSDI4vr16yrIwlJ0Nj5HRDWcvxEph6hfBKPAwfeJJ57Q9u/fb1NgjQ4irBBlhmhNRGabY+1+9cAa8wAcff9wOh42bJh28uRJFUh04sQJtR7RTjpvvfWWao9vv/1WRWeHh4drHTp0sEt0dtu2bVOshyMsgnYyUtfUtqlHZ8OpW28vBNkgQCm1iDREnyGgCe2HwB5EwFsLIsDxPUSiBQYGqihBYxApOHnyZBUAhGNBNB/qgv0h8lKnSpUqKpgnLXSnakSj4TiwjMAsRLrCqXrJkiUmkXl16tRRDtBr1qxR7YC6ol2wLz0yEMBBu3DhwmrbxhkD9P1ZCtjCd9D/0G9nzZql6oFtHjhwQHvvvfcMDt76NqZOnWp1m3722WcqyMs8KhBUqFBBa9WqlYrMywrSCqxBBgC0XcGCBZXTdnroDuw4HvSLI0eOqL5ZrVo1Q1Rpaljbb8zRAw7efvttQ/QiMjbgGmcciGfN9q2tQ//+/VXE5KZNm1Q/gwM8IpZTCwow7wsICsB67MsYSwEi1vY7tC/OJSJhERGLeunRwZauAwjmw75Ql9RIqy+jzxhny8goae3D2nOb1u8kq9rPXv03q69bmT2HWXUNzuxvyFL9M3sOrb3HWdsvLQXW2HLs1vZd4wDZRo0aabZik4gEaFxEx0HMIG0OLlh6aLixiAS4wOCgypUrp25yJUqUUJHciCzSIy1tFZFIeaN3eDSiJazZb1oiEmDbEIQ4AThWdGJ0IuPoZmwLJwLRbSiDFBvoABC3tkRn44U0ABkRkdbWNbVt6jd71Bk/FIjCXr16GYQifuyWQKdFvZG2wVYQkYfv/u9//0vxGVIwQJwjms/Hx0c9EOCH8Pfff5uUs0VE4gKCaPPg4GAl+iFKFi9enKI8okbRxvjxI3oU+0Yd8bBkDi7wliLZQkJC1HpcSMxBf0GqJlwo8SCECwzSteAigYcQ8zpbC9I6oa2++eabFJ8hUhdCDMeN7Rqn88gob775Zqr92PhCi4ertPq8pWhYpItC+hr0Y0QQop9Zav+M9htL/PDDD6o/4Xv4jwsurhXGF3Rrtm9tHXCu8RvDbw2/uYEDB6oHsawQkdb2O4B2Rvol/TqA1Ca4Dli6gXbs2FELCAhI8wHS0SLS2nOb3u8kK9rPnv03K69b9hKR9r4GZ/Y3lFr9M3MOrb3HWdsvUxOR1h67LX0XDy0Q9ThHtpIHf2yzXZLcAJxu4VPx0UcfyciRI1N8/vrrr6vhFjgJYzjVGUHUe+nSpZV/kXkS1pwGzgeGi+HvlB1TH5LcC3yrcG147bXXZOzYsRn2f0RAZFpJ9Akh2QPu8fgthoeHWwxsSgvebUiaU9shIt4c+K1gBhH4tzmrgMxtYDYjzL6kR/gRklUgoBF+Wfr0lIQQ1wXBRDC0TJo0yWYBCTh3NpGJEycqZ244LiNQY+3atTJixAiVLwtTXJpbITCFFATLF198wdZzEvTIRkKyGqSgsSbdCiHE+UFAI1IjZhRaIolKiYSINKTOQD6t9957T+W7WrJkSYooNUSVjR8/XpVBfkpCCCGE5E7oE0kIIYQQQmyGlkhCCCGEEGIzFJGEEEIIIcRmGFiTBohCRgAJ5qTN7LRPhBBCCHEdkAERUwyWKFGCqdNSgSIyDSAgkWeQEEIIIbmTiIgIw/SMxBSKyDSABVLvQIGBgWkVJRklOlqkRInk95cvYyJbtiUhhBCHc+/ePWVI0rUASQlFZBroQ9gQkBSRWYTRJPQCoU4RSQghxImgO1vqMLCGEEIIIYTYDEUkIYQQQgixGYpIQgghhBBiM/SJJI4Ffqdly/73nhBCCCEuAUUkcSx+fiLnzvEsEEIIIS4Gh7MJIYQQQojNUEQSQgghhBCboYgkjuXhQ5G6dZNfeE8IIYQQl4A+kcSxJCWJ/P33f+8JIYQQ4hLQEkkIIYQQl+VeTLyjq5BrcUoReevWLfnss8+kZcuWMmnSJItl7ty5IyNHjpR27dpJv379ZPv27RkqQwghhBDX4/ytaHn+p73Sa/puSUzSRNOSXyQXi8jDhw9L9erV5fLly3L9+nU5fvx4ijKxsbHSpEkT2bFjhzz//PNqgvQWLVrIunXrbCpDCCGEENfiYVyifLHuuLSevE02HL0uJ65FycGIO+ozznOdy30iy5cvL6dPnxYfHx9p1qyZxTJz5syRU6dOyZUrVyRfvnzy1FNPSUREhLz77rvSpk0bq8sQQgghxDWAlXFt+FUZv+KoXLqbHIjZqFIhGdMlTCoVCXB09XIlTici/f390y0DayKsjBCHOt26dZOff/5Zbt++LQUKFLCqDCGEEEKcn9M37suYZeGy/eRNtVwyn6980KmqtA0rRuujA3E6EWkN58+fl2rVqpmsK1mypOEzCERrypiDIXC8dO7du5dFR0BMKFSIDUIIISQF92MTZOqmk/LDjrMSn6iJl7ubvNi0grzcrJL4ermzxRyMS4rIuLg48fX1NVnnh+nz/v8za8uYM3HiRBk7dmwW1ZpYBJbnGzfYOIQQQkyGrpcduiwfrToq1+4lG3dahhSRDzqFSrlC6Y9YkuzBJUVk/vz5VQS3MfoyPrO2jDmI5B42bJiJJRIBOYQQQgjJHo5dvSejlobLX2dvq+UyBfxkdOdQaVm1KE+Bk+GSIrJmzZqyYsUKk3V79+6VwMBAqVChgtVlzPH29lYvQgghhGQvkQ/jZcqGEzJn13mVssfH001eaVZJXmhSQXw8OXTtjDhdih9rGDBggIrgXrhwocHCOH36dJUL0sPDw+oyxAnAVIeIwseL0x4SQkiuIylJk0V/R0jLz7fI7J3nlIBsX62YbBjWVIa0DKaAdGLyaE6WmTMxMVElGQcHDx5Ufo1VqlRRQTHz5s0zlPvuu+/U0HPFihVVoMxjjz0mS5YskYCAAJvKpAWGs4OCgiQyMlJZMEkWEB0top+P+/eTfSQJIYTkCv69GCmjlh2WAxfuquUKhf1lbJcwaRxc2NFVowZwRRGJ6mzdujXFeojJ+vXrm6yDuDt69KgULFhQgoODLW7PmjKpQRGZDVBEEkJIruNOdJx8tu64/PLXBYEK8fdyl9daBstzj5cXLw/nGCSlBnBBEelMsANlAxSRhBCSa8BQ9YK9F2TS2uNy90HynNddHy0hI9tXlWJBPuJMUAOkD50DCSGEEJLl7Dt/R0YvOyyHLyXnYA4pllcNXdevUJCt76JQRBJCCCEky7gRFSufrDkmi/ddVMt5fTxkWOvK0u+xsuLh7hxD1yRjUEQSQgghxO4kJCbJ3N3n5Yv1JyQqJkGt61G7lAxvHyKFAphOLydAEUkcz//PJEQIISRnsPvMLRm9NFyOX4tSy9VKBsq4rtWkVhnLk30Q14QikjgWpPRBcA0hhBCX52pkjJqqEFMWgnx+nvJO2xB5um5pcXfL4+jqETtDEUkIIYSQTBGXkCSzd56VrzaelOi4RMmTR6R3vTLyVpsqkt/fi62bQ6GIJIQQQkiG2X7yhoxeFi5nbiSPKtUsk0/Gd60m1UoGsVVzOBSRxLHExIg8+WTy+99+E/FxrjxhhBBCLHPxzgP5cMVRWRN+VS0XCvCSd9qFyFO1Sokbh65zBRSRxLEkJoqsWvXfe0IIIU5NTHyifL/tjHyz5ZTExCcpX8f+DcrK660qS5Cvp6OrR7IRikhCCCGEWMXGo9dk7PIjcuH2A7Vcv3wBGds1TEKKBbIFcyEUkYQQQghJk/O3opV43HTsulouGugt73UMlc7Vi0seRNGQXAlFJCGEEEIs8jAuUQ1bT996RuISk8TTPY8MalRBhrSoJP7elBC5HfYAQgghhJigaZqsOXxVPlx5VC7dfajWNQ4uJGO6hEnFwgFsLaKgiCSEEEKIgVPXo2TMsiOy49RNtVwyn6+M6hwqbUKLcuiamEARSQghhBC5H5sgUzeelFk7zkpCkiZeHm7yUtOKMrhpRfH1cmcLkRRQRBLHT3uoaTwLhBDiwKFrTFM4YeVRuR4Vq9a1qlpURnUKlTIF/XheSKpQRBJCCCG5lKNX7snopeHy17nbarlsQT8Z0zlMmocUcXTViAtAEUkIIYTkMiIfxsvk9Sdkzq5zkqSJ+Hi6yZAWwTKoUXnx8eTQNbEOikji+GkP+/VLfj93Lqc9JISQLCQpSZPF+y/KJ6uPya3oOLWu4yPF5d2OVVUADSG2QBFJHAumOly8OPn9jz/ybBBCSBbxz8W7MmppuByMuKuWKxb2l7Fdqkmj4EJsc5IhKCIJIYSQHMzt6DiZtPa4LNh7QcUx+nu5q3muBzQspyKwCckoFJGEEEJIDiQxSZNf/rogn609rnwgQfeaJWVk+xApEujj6OqRHABFJCGEEJLD2Hf+thq6Dr98Ty2HFMsr47pWk3rlCzi6aiQHQRFJCCGE5BCuR8XIJ6uPy2/7L6rlQB8PebNNFelTv4x4uHPomtgXikhCCCHExYlPTJI5u87LlPUnJCo2Qa3rWaeUvNMuRAoFeDu6eiSHQhFJCCGEuDC7Tt+S0csOy4lr99Vy9VJBMrZLmNQsk9/RVSM5HIpI4lj8/ETu3//vPSGEEKu4EvlQPlp1TJYfuqyW8/t5Ksvj03VKi5tbHrYiyXIoIoljyZMnef5sQgghVhGbkCg/7DgnUzedlAdxiQK92Kd+WXmzTWXJ5+fFViTZBkUkIYQQ4iJsPXFDxi4LlzM3o9Vy7bL51dB1tZJBjq4ayYVQRBLHEhsr8uKLye+nTxfxpgM4IYSYE3H7gYxfcUTWHbmmlhEsg3yPT9QqKXkwokOIA6CIJI4lIUHkp5+S33/9NUUkIYQYEROfKNO3npFvtpyS2IQkcXfLIwMalJPXWwdLoI8n24o4FIpIQgghxMnQNE02HL0u41aES8Tth2pdgwoFZWzXMKlcNK+jq0eIgiKSEEIIcSLO3oyWscvDZcvxG2q5WKCPvN+pqnR8pDiHrolTQRFJCCGEOAEP4hJk2qZTMnP7WYlLTBJP9zzyQuMK8krzSuLvzds1cT7YKwkhhBAHD12v+veqfLjyiFyJjFHrmlQuLGM6h0qFwgE8N8RpcVkReefOHVm4cKGcO3dOSpUqJb1795b8+U2z8yclJcmiRYvkwIEDUrhwYXnmmWekRIkSDqszIYQQYszJa1Eyelm4/Hn6llould9XPugUKm1Ci3Lomjg9Ljkb+9GjRyU4OFiWLFkiefPmlbVr10r16tXlwoULJk923bp1k+HDh4uHh4ds3rxZwsLC5PDhww6tOyGEEBIVEy8frjgi7b/crgSkt4ebvN4qWDYMayptw4pRQBKXII8GteVidO/eXW7duiXbtm0zrOvcubMEBgbKvHnz1DIEZo8ePeTEiRNSsWJFJSrbtGkjbm5uSnRaw7179yQoKEgiIyPVtkkWgO5382by+0KFkmewIYSQHAruRX8cvKSmK7wRFavWtQ4tKqM6hUrpApz61ZmgBsihw9knT55UgtCYunXryqeffqqGsCEUly5dKg0aNFACEiAZa79+/WTgwIFy//59CQign4lTANFYuLCja0EIIVlO+OVIGbMsXPaeu6OWyxfyl1GdQ6V5lSJsfeKSuKSIxLD0li1bJCEhQQ1V48lu06ZNEh0dLRcvXpQyZcooC2TlypVNvgdBmZiYKGfOnFHD3+bExsaql/FTCCGEEJIZIh/Ey+frj8vPu89Lkibi6+kur7aoJM83Li/eHu5sXOKyuKSInDhxojRv3lxq1qyprI1///23GnYGDx8mJ2V98OCB8pc0Rh+SxmepbXfs2LFZXn9iBET7sGHJ77/4gjPWEEJyDElJmizaFyGfrDkut6Pj1LqO1YvLex2qSol8vo6uHiG5U0RWqFBBjh07Jhs2bJBLly6pYerLly8r62TBggVVGQjIu3fvpojo1j+zxMiRI2WYLmj+3xJZunTpLD2WXA+mPfzmm+Rm+PRTikhCSI7gUMRdGbX0sBy6GKmWg4sEyNguYdKwUiFHV42Q3C0iga+vrwqm0XnppZdUxHYhBGeISGhoqOzfv9/kOxCeXl5eBj9Jc7y9vdWLEEIIyQiwOH665pj8+neEihsM8PZQUdcDGpYTT3eXTIhCSM4SkbA+wg8S+SEB0vbMmTNHvvzyS0OZnj17yowZM2Tv3r0q6CY+Pl5mzpwpXbp0ER8fHwfWnhBCSE4jMUmTX/acl8/WnZDIh/Fq3RO1SsqI9iFSJC/vOSRn4pIiEgKyU6dOUrVqVRV1vXz5cnnllVfkhRdeMJRp1aqVvPzyy9K2bVvp2LGjEpoY3l68eLFD604IISRn8fe52zJqabgcuZIcjBlaPFDGdQ2TOuUKOLpqhGQpLpknEiBNz+rVqyUqKkoaN26shrItsWfPHjVjDXwlO3ToIP7+/lbvgzmisoHoaBE93dL9+yI2nB9CCHEk16Ni5OPVx2TJ/ktqOdDHQ95uW0V61y8r7m7MeevqUAPkYBGZHbADZQMUkYQQFyM+MUl++vOcTNlwUu7HJqh0t0/XKa0EZMEA+tXnFKgBcuhwNiGEEOII/jx1U811ffL6fbVco3Q+GdclTP0nJLdBEUkci6+vyNmz/70nhBAn5PLdhzJh1VFZ+c8VtVzA30uGt6siPWqXFjcOXZNcCkUkcSxubiLlyvEsEEKcktiERJm5/axM23RKHsYnCvRiv8fKyrDWVSTIz9PR1SPEoVBEEkIIIRbYfPy6jF0WLuduJc9yVrdcfhnbpZqElkie/YyQ3A5FJHEscXEi772X/H7CBBEvL54RQohDuXDrgYxbcUQ2HL2mlgvn9ZZ3O4RIt0dLqrRyhJBkGJ2dBozMygYYnU0IcRJi4hPl2y2n5dutpyUuIUk83PLIsw3LydBWwZLXh0PXuQ1qgPShJZIQQkiuBpnu1h25JuNXHJGLdx6qdQ0rFlRzXQcXzevo6hGS80XkzZs3ZfPmzXL8+HGVABxzWNepU0caNmzI+agJIYQ4JWdu3Jexy4/I1hM31HLxIB95v2OodHikGIeuCclqEYnZYD766CP5448/xMvLS0qXLq1mhblz545cuHBBAgMDZdCgQTJ8+HAlLAkhhBBHEx2bINM2n5KZ289IfKImXu5u8kKT8vJK80ri58VBOkKswU0yweeff67mpi5Tpoz8+eefEhkZKceOHZN9+/bJmTNnlJCcO3euRERESGhoqOzevTszuyOEEEIyPXS9/NBlafn5VuX/CAHZrEphWftGE3m7bQgFJCE2kKnHrUcffVROnTqlrI2WyJs3r3Ts2FG9Tp48KbGxsZnZHSGEEJJhjl+NktHLDsvuM7fVcukCvjKqU5i0qlqEQ9eEZLeIbNmypdVlg4ODM7MrQgghJEPci4mXLzeclB//PCeJSZp4e7jJy80qyYtNK4iPpztblZAMkmnHD1gX4+Pj0yyDvFq+vr7ihtlJCDEGUx0ePvzfe0IIsRNJSZr8fuCSTFx9TG7eTx4JaxtWVAXOlC7gx3YmxNEiEkEz8+bNS7ecp6enhISEyIQJE6Rz586Z3S3JKeDBIizM0bUghOQwDl+KlNHLwmXf+TtquXwhfxnTJUyaVi7s6KoRkmPItIgcOnSodOvWLd1y0dHR8tdff0mvXr2UH2Xx4sUzu2tCCCHEhLsP4uSzdcfllz0XJEkT8fNylyEtgmVgo3Li7cGha0KcSkTWrVtXvRISEsTDI/XN3b17VwYMGKDySO7fv18F2xCipj386KPkhnj3XU57SAjJEPB1/HVvhExae0zuPEh2sepco4SarrB4EF1lCHHqaQ/fe+89GTx4sJQqVSrFZ2PGjJFKlSpJ3759Zf369VK2bFmpXLmyODuc8igb4LSHhJBMcuDCHTV0/c/FSLVcuWiAGrpuWJG5iUnGoQZIH7tlVEXgTJs2bWT79u1SsGBBw/rRo0fLV199JTt37lTLrVu3ttcuCSGE5GIQLPPpmmOy8O+Lajmvt4e83rqy9G9QVjzdGchJiMuISFgikWi8Q4cOsnHjRgkICJBRo0bJtGnTZMOGDSrZOCGEEJJZEhKTZN6eC/L5uuNyLyZBrXuyVikZ3r6KFMnrwwYmxNWGswH8Irt27arS/sBPcvr06Wr4unbt2uKK0JSdDXA4mxBiA3+dvS2jlh6WY1ej1HJYiUAZ1zVMapctwHYkdoUaIH3sOkEoAmsWLVqkhrUhIGGBrFWrlj13QQghJBdy/V6MfLTqqPxx8LJaDvL1lLfbVpFn6pURd7c8jq4eIbmSTInIkSNHyvLlyy2m83F3d5f+/fsb1n388cfSqVOnzOyOEEJILiM+MUlm7zyrZpyJjkuUPHlEetUtowRkAX8vR1ePkFxNpkRks2bNpGjRolaVRXQ2IYQQYi07Tt6UMcvD5dT1+2r50dL51NB19VL52IiE5DSfyJwG/SGygcREkf37k9/D9cGdyYAJye1cuvtQJqw8Iqv+vaqWC/p7yfD2IfJUrVLixqFrkk1QA2SxJXLFihVSv359KVw4/WmkDhw4INCr9JEkJkA01q3LRiGESGxCoszcflambTolD+MTBXqxf4Ny8karyhLk58kWIsTJyFQirYiICKlataq8/PLLKj9kTEyMyefXr1+XX3/9VaX9wQw1NHoSQgixxOZj16Xt5G0yae1xJSDrlSsgK19rrJKGU0ASkgMtkZihpmXLljJx4kQVkZ2YmKh8JP39/eX27dty48YNNUc2ykFM5s2b1341Jzln2sMvv0x+P3Qopz0kJJdx4dYDGbciXDYcva6Wi+T1lvc6VpUuNUpIHkTREEJyvk/k/fv31aw0mBsb7zFrDYau8UKktitCf4hsgHkiCcmVPIxLlG+3npbvtp6WuIQk8XDLIwMblZfXWgZLgLdds88RkiGoAdKHgTVpwA6UDVBEEpKrgN1ibfg1Gb/iiAqgAY0qFZIxXUKlUhGOVhHngRogffi4RwghJFs4feO+jFkWLttP3lTLJYJ85INOodKuWjEOXRPiglBEEkIIyVKiYxPkq00n5YcdZyU+URMvdzd5sWkFeblZJfH1ck13J0IIRSQhhJAsHLpe/s8VlfPx2r1Yta5FSBEZ1SlUyhXyZ7sT4uLYzRL58OFD8fX1tdfmCCGEuDDHr0bJqKWHZc/Z22q5TAE/Gd05VFpWtW6WM0JILhKRL774oly5ckUGDhwo3bt3Fx8fH3ttmhBCiIsQ+TBepmw4IXN2nZfEJE18PN3klWaV5IUmFcTHk0PXhOQkMpVs3Jhhw4ZJ2bJllZgsUaKEvPLKK7Jfn86OkNTAw8bmzckvPngQ4rIkJWmy6O8Iafn5Fpm985wSkO2rFZMNw5rKkJbBFJCE5EDsnuInOjpaFi1aJD/88IOaxaZGjRrKOtmnTx+VO9JeoNq7d++Ws2fPSlBQkDRo0EAKFCiQolx4eLiachFTMzZv3ly8vLys3gfD+wkhJH0OX4pUQ9f7L9xVyxUK+8vYLmHSODj9KXEJcVaoARycJxKWyKefflpOnTqlxBvejxo1SipVqpSp7d65c0dat24t165dkyZNmqjpF7GvuXPnqqF0neHDh8s333yjZtU5evSouLm5yaZNm9QsOtbADkQIIWlci6Pj5LN1x+WXvy4I7iR+Xu4ytGWwPPd4efHysNtAFyEOgRrAASISm9u2bZuyRC5evFhNgwhL5COPPCLTp09Xs9pA0GHIO6N89tlnMmHCBDl37pyyQgIMo2/cuFEJVoA6NG3aVFlDGzVqpOb1hrUyNDRU5s2bZ9V+2IGygfh4ke+/T37/v/+JeHpmx14JIZkAQ9UL9l5Q81zffRCv1nV9tISMbF9VigXRH57kDKgBsjGw5tKlS/Ljjz/K7NmzlWWwW7dusnTpUmUF1Oc/7dq1q7IgQkj26NEjU0LVz8/PZC7uYsWKmZSZP3++PProo0pAAgT6vPDCC/Lmm29KbGyseHt7Z3j/xM5zZ7/6avL7Z5+liCTEydl3/o6MXnZYDl+6p5ZDiuWVMV3C5LEK9nNXIoTkMhGJoWP4HiKgpn///qn6P/bu3VsF4GQGWB03b94sXbp0kXbt2snFixdlyZIlytKpc/jwYWV1NCYsLExZJE+fPp3iMwBxiZfxUwghhBCRm/dj5ZPVx2TRvouqOfL6eMgbrSpL/wZlxcOdQ9eE5EbsJiInTZpkla/hc889l+l9wb+yfPnysmbNGmWNhOUzX758JoE1EID58+c3+Z7+eWricOLEiTJ27NhM148QQnIKCYlJMnf3efli/QmJiklQ63rULiXD24dIoQCO6BCSm7GbiLQ2WMUeQOgtX75cWRsDAwPVuhEjRkinTp3kzJkzaqgaic+joqJMvqcvp5YUfeTIkSpVkQ7EZunSpbP0WAghxFnZfeaWmuv62NXka2e1koEyrms1qVXG9AGdEJI7sZuIfPXVV1UgjSXc3d1Vip1WrVrJ+++/r6yGmeHPP/9UQTO6gASdO3eWTz75RKX8CQkJURHgCLwxBp8hQrtChQoWtwvxSV9JQkhu59q9GJmw8qgsO3RZLefz85R32obI03VLi7tbso87IYTYTUQ+88wzKj9k1apVVQANhpLPnz+vgm3KlSsnHTp0kFmzZim/yQ0bNhiCbTICfCr//fdfFWCjb+fQoUNKIJYqVcogKvv27SsXLlyQMmXKqHW//PKLEp/GATmEEEKSiUtIktk7z8pXG09KdFyi4PLap34ZebN1Fcnvb32OXUJI7sBuKX6Q0mfFihUqwMWY27dvqyhpRGRDvFWuXFnWr1+vkpBnFKQIql+/vkrZA3EKn8hvv/1WDUWPHz9elUlKSpI2bdooETlo0CCVR3LlypUq9U+tWrWs2g/D+7OB6GiRgIDk9/fvi/j7Z8deCSFmbD95Q0YvC5czN6LVcq0y+dTQdbWSyWnUCMltUANkoyVy3759Kn2POQhmgYg8ePCgsg4+9thjalg5MyIS1k7kg0S+R0RaY3h89erVKvG4DqySWDdnzhxl/cQQNwJnUhvKJg4CqZZWrPjvPSEkW7l454F8uOKorAm/qpYLBXjJiPZV5YmaJcWNQ9eEkOwQkQEBAUq0vfTSSyZD1Tdu3JC//vrLELBy+fJluwi5IkWKyBtvvJFmGU9PT2WFJE6Mh4dIx46OrgUhuY6Y+ESZse2MfL3llMTEJylfxwENysnrrYMl0IdJ/wkh2SgikbuxTp060rBhQxOfSFgCK1asKI0bN1ZTDsIyWb16dXvtlhBCiI1sPHpNxi4/IhduP1DL9csXkLFdwySk2H/BioQQkq3THkI0YsgY/o83b95UAS1PPPGEDBkyRM0w42rQHyKbpj3Up6Hs04cz1hCShZy/Fa3E46Zj19Vy0UBvea9jqHSuXjxTwY6E5ESoAbJRRMLnEWl8SpYsKTkFdqBsgIE1hGQ5D+MS5Zstp2T61jMSl5gknu55ZGCj8vJai2Dx97bbgBQhOQpqgPSx29VjypQpap7sfv362WuThBBCMgFsBGsOX5UPVx6VS3cfqnWNgwvJ6M5hUqnI/2dFIIQQR4tI+D0eOXLEXpsjhBCSCU5dj5Ixy47IjlM31XLJfL7yQadQaRtWlEPXhBDnEpEDBgxQKXaQCBypfoKCTHOLIUckZ4MhhJCs5X5sgkzdeFJm7TgrCUmaeHm4yUtNKsjgZpXE18udzU8IcT4R+e6776rAmsGDB1v8fO7cuWoGGUIIIVkzdI1pCjFd4fWoWLWuVdUiyvpYtiCT+BNCnDiwBjPDYHaa1ICFEml/XAk61WYDDKwhJNMcvXJPzTbz19nka3DZgn4yunOotAgpytYlJINQA2SjJRLpfPQ5qgkhhGQ9kQ/jZfL6EzJn1zlJ0kR8PN3k1eaV5PnGFcTHk0PXhJCsxe65HTDF4KFDh1TSccyTfevWLeULiRltCEkBpjpcuPC/94SQdElK0mTx/ovyyepjcis6Tq3r8EgxebdDVSmV3/Vy8hJCXBO7ikgE18yfP1+8vLzku+++UyJy165dMnPmTPnjjz/suSuSk6Y97NHD0bUgxGX45+JdGbU0XA5G3FXLFQv7y9gu1aRRcCFHV40Qkstws9eGFi5cKHv37lW+kd27dzes79Spk0r9c+LECXvtihBCch23o+Nk5JJ/pevXO5WA9Pdyl/c6VJXVQ5tQQBJCXNsSuWPHDnnppZekWLFiKT4LDQ1VQ9ywTBJiQkKCyO+/J7/Hwwcsk4QQA4lJmsz/64J8tu643H0Qn/xTqVlSRrQPkaKBPmwpQojDsNsdOyEhQZKSktR78zlYIyIiVJ5IQlIQGyvSs2fy+/v3KSIJMWLf+dtq6Dr88j21HFIsr4zrWk3qlS/AdiKE5BwR2aJFCxk/frwMHDjQREROmzZNDWUj0IYQQkj63IiKlY9XH5Pf9l9Uy3l9POStNlWkT/0y4uFuNy8kQghxDhH5xBNPyKJFiyQ4OFg8PDyUH+S4cePk9OnTMmvWLAkMDLTXrgghJEcSn5gkc3adlynrT0hUbIJa93Sd0vJ2uypSKIDZCwghOVREurm5yYIFC2Tp0qWyevVqlXi8dOnSapaaWrVq2Ws3hBCSI9l1+paMWRYux69FqeXqpYJkbJcwqVnGtSZpIITkHuw2Y01OhNnqswHOWENyOVciH8pHq47J8kOX1XJ+P095p12IskC6uZn6lxNCsg9qgPSxeygsUvxcunRJEhMTTdZXqVJFChcubO/dEUKISxKXkCSzdpyVqZtOyoO4RIFe7FO/rLzZprLk8/NydPUIIST7ROT9+/dVTsitW7da/Hzu3LlqaJsQQnI7207cUEPXZ25Gq+XaZfOroetqJYMcXTVCCMl+Efn111/L3bt35eDBgyq4Bj6SxmAWG0JSgH4xe/Z/7wnJwUTcfiAfrjwia8OvqWUEy4xsHyJP1CqZIjUaIYTkGhF55swZlWy8Ro0a9tokyQ14eoo8+6yja0FIlhITnyjTt56Rb7acktiEJHF3yyPPNiwnQ1sFS6CPJ1ufEJK7RSRmo7l4MTmnGSGEEBHELW44el3GrQiXiNsPVZM0qFBQxnYNk8pFOQEDIcS1sZuIfPrpp6V58+YSEhIizZo1Ex8f0+m4MGONtzfznBEL0x6uXZv8vm1bzlhDcgxnb0bLuOXhsvn4DbVcLNBH3utYVTpVL86ha0JIjsBuInLEiBFy6tQp6devn8XPGVhDUp32sFOn5Pec9pDkAB7EJcjXm0/JjG1nJS4xSTzd88jzjSvIq80rib8354YnhOQc7JYnEql9kGA8NcqWLSv587tW0lzmiMoGmCeS5BBwKV19+Kp8uOKIXI6MUeuaVC4sYzqHSoXCAY6uHiHERqgB0sduj8VlypRRL0IIyW2cvBYlY5aHy85Tt9Ryqfy+MqpTqLQOLcqha0JIjsXuYysHDhyQQ4cOScOGDVWwza1bt5QvZEAAn8QJITmLqJh4+WrjSZm985wkJGni5eEmg5tWlMHNKoqPp7ujq0cIIa4jIgcMGCDz589XOSG/++47JSJ37dolM2fOlD/++MOeuyKEEIcOXS89eFk+WnVUrkfFqnWwOn7QMVTKFPTjmSGE5ApMM4JngoULF8revXuVb2T37t0N6zGLzZEjR+TEiRP22hUhhDiMI5fvSc/pu+T1Xw8qAVm+kL/Mfq6uzOhfhwKSEJKrsJslcseOHSrZeLFixVJ8Fhoaqoa4YZkkhBBXJPJBvHyx/rjM3X1ekjQRX093ebVFJXm+cXnx9uDQNSEk92E3EZmQkCBJSUnqvfn0XRERESpPJCEpwFSH06b9954QJyMpSZNF+yLkkzXH5XZ0nFrXsXpxea9DVSmRz9fR1SOEENcXkS1atJDx48fLwIEDTUTktGnT1FA2Am0IsTjt4SuvsGGIU3Io4q6MWhau/oPgIgEytkuYNKxUyNFVI4SQnCMin3jiCVm0aJEEBweLh4eH8oMcN26cnD59WmbNmiWBgYH22hUhhGQpsDhOWntMFuyNEGTSDfD2kNdbBcuAhuXE091uruSEEOLS2E1Eurm5yYIFC2Tp0qWyevVqlXi8dOnS0rdvX6lVq5a9dkNyGomJItu3J79v3FjEnb5lxIHdMUmTX/acl8/WnZDIh/Fq3RM1S8qI9iFSJNB0KldCCMnt2G3GmuwEEeDXr1+3KGTNBWtMTIyajrFQoUIWg37SgtnqswHOWEOchH3nb8sHf4TLkSv31HLV4oEyrmuY1C1XwNFVI4Q4AGqA9HHJiVznzZsnv/32m8k6+F1iyPzixYuGdT///LO8/PLLUqBAAbl69ap06NBBfdfXl87whJBkrkfFyMerj8mS/ZfUcqCPh7zdtor0rl9W3N1MgwQJIYS4uCXSnIcPH0rx4sXllVdekQkTJqh18MmsXr26SnT+7LPPypUrV6R+/fry1FNPyRdffGHVdvkUkg3QEkkcRHxikvz05zmZsuGk3I9NEMQDPl2ntBKQBQO8eV4IyeVQA6RPjvAQX7x4sTrZgwYNMqybPXu2mssbAhJAZA4ePFitT4QfHiEk1/Ln6ZvS8avt8uHKo0pA1igVJL+//Lh8/GR1CkhCCMnJw9nmIPq7ZcuWUqFCBcO6/fv3S926dU3KwRJ59+5dOXv2rFSqVMkBNSWEOJIrkQ9lwsqjsuKfK2q5gL+XDG9XRXrULi1uHLomhJDsE5EYIo6MjLSqbIkSJbIkzQ+CZrZt26Yiw425deuWVKlSxWQdgmv0zyyJyNjYWPXSgXWTEOL6xCYkyqwdZ2XqxlPyMD5RoBf7PVZWhrWuIkF+no6uHiGE5D4R+fbbb6tAFWuYO3euSvdjb3744QcpWLCgdOvWzWS9p6eniSDUfSf1zywxceJEGTt2rN3rSAhxHFuOX5exy4/I2ZvRarlO2fwytmuYhJUI4mkhhBBHicivv/5aPvvsM/X+zp07ataa5557TgWvICL63LlzKogFYg7r7A18G3/66ScZMGCAeJlNmQd/yMuXL5us05fxmSVGjhwpw4YNM7FEItclyUIg6D/99L/3hNiJiNsPZNyKI7L+yDW1XDivt4xsHyLda5ZMMTUrIYSQbBaRQUFB6qVbGnv16iUfffSR4fNy5cpJ48aNVZQ0hp2rVasm9gRJzSEMn3/++RSfwUcSltKoqCjDvN3Lly9XddGHtc3x9vZWL5KNQPy//TabnNiNmPhE+W7rafl2y2mJTUhSaXqea1hOhrYKlrw+fFAhhBCnC6w5efKkPPLIIynWu7u7S9myZVUeR3uLSKTvgUgNCQlJ8RmisqdMmaKmY3zrrbdUoM2cOXPk999/t2sdCCHOAbKVweoI6+PFO8muKw0rFpQxXcKkctHkB0lCCCFOmOIHgSrTp0+XGzdumKzfuXOnbNmyRc2pbU+io6Pl2rVr8vrrr1v83M/PT7Zv364E5vjx42X37t2yYsUK6dy5s13rQTIJ0i3t3Zv8YuolkkHO3Lgvz87eK/+bu08JyOJBPvJ171oy7/n6FJCEEOLsycbv378vrVu3ln/++UdZB/Pnzy/nz59X4g3Dyp988om4Gkw0mg0w2TjJBA/iEmTqplMyc/sZiU/UxMvdTV5oUl5eaV5J/LxyRAYzQoiDoAZIH7tdZQMCApTVEYm/YQFEGp0mTZrIV199JXXq1LHXbgghRA1dr/z3isr5eCUyRrVIsyqFZXTnMClfyJ8tRAgh2UCOmPYwq+BTSDZASySxkRPXomT00nDZdeaWWi5dwFc+6BgqrUOLMuqaEGI3qAHSx+7jPQcOHJBDhw5Jw4YNpXLlysoiiYhnWCoJISSj3IuJly83nJQf/zwniUmaeHu4yeBmFeWlphXFx9OdDUsIIa4sIpGvcf78+Spn43fffadE5K5du1QU9R9//GHPXRFCcgkYLFmy/5JMXH1Mbt5PnkCgTWhR+aBTqJQu4Ofo6hFCSK7FbtHZCxculL1798qFCxeke/fuhvWdOnWSI0eOqBQ/hBBiC+GXI6XHd7vkzUWHlICsUMhffhpYT77vX4cCkhBCcoolcseOHfLSSy9JsWLFUnwWGhqqhrhhmSSEkPS4+yBOPl93QubtOS9Jmoifl7sMaREsgxqVFy8Puz37EkIIcQYRmZCQIElJSeq9+ZRiERERhlljCDEBUx2OHv3fe5Krga/jwr8j5NM1x+TOg3i1rnONEvJuhxApHuTr6OoRQgjJChGJebOR1HvgwIEmInLatGlqKBuBNoRYnPZwzBg2DJGDEXdl1NLD8s/FSNUalYsGyNgu1aRBxYJsHUIIyckiEtMLLlq0SM1M4+Hhofwgx40bJ6dPn5ZZs2ZJYGCgvXZFCMlB3LofK5+uOS6//h2hlvN6e8jrrStL/wZlxdOdQ9eEEJLjRaSbm5ssWLBAli5dKqtXr5bbt29L6dKlpW/fvlKrVi177YbkNOACcfRo8vuqVdGRHF0jkk0kJCbJvD0X5PN1x+VeTIJa92StUjK8fRUpkteH54EQQnJLsnFYG8PCwuSxxx6TnAITjWYDTDaeK/nr7G01dH3sapRaDisRKOO6hkntsgUcXTVCCFFQA2SjJXLPnj0qn1tOEpGEEPty/V6Myvf4+4FLajnI11PebltFnqlXRtzdTAPyCCGE5BIR+fjjj8uKFSvk+eeft9cmCSE5hPjEJPlx5zmZsuGERMclCmLvetUtowRkAX8vR1ePEEKII0VkmTJlZNu2bdKuXTtp3bq1BAUFmXzevHlzqVixor12RwhxEXaeuimjl4XLqev31fKjpfOpoevqpfI5umqEEEKcQUQuW7ZMfH195dixY+plTqFChSgiCclFXLr7UD5aeVRW/ntFLRf095Lh7UPkqVqlxI1D14QQ4vLYLbAmJ0Kn2myAgTU5jtiERJm5/axM23RKHsYnCvRi/wbl5I3WlZUPJCGEuALUANloiSSEkM3HrsvY5eFy7tYD1Rj1yhWQsV3DpGpx5oklhJCchl1F5J07d2TKlClqnuwhQ4ZIy5Yt5c8//xRPT0+pW7euPXdFcgqY6vCtt/57T1ySC7ceyLgV4bLh6HW1XCSvt7zXsap0qVEixTSohBBCcgYe9jT71qxZUwXYXL9+Xa5cSfaDKly4sDz55JNy4MABcXd3t9fuSE6a9nDSJEfXgmSQh3GJ8u3W0/Ld1tMSl5AkHm55ZGCj8vJay2AJ8OZAByGE5GTsdpWfOXOm1K5dW3777Tfp16+fYT2mQfTx8ZFdu3ZJo0aN7LU7QogDgSv12vBrMn7FERVAAxpVKiRjuoRKpSJ5eW4IISQXYDcRiYjstm3bqvfmw1clSpSQy5cv22tXJKdNe3jhQvL7MmU47aELcPrGfRmzLFy2n7yplksE+cgHnUKlXbViHLomhJBchN1EZP78+eXC/4sBYxEZExMj+/btk+HDh9trVyQn8fChSPnyye/v3xfx93d0jUgqRMcmyNRNp2TWjjMSn6iJl7ubvNi0grzcrJL4etFVhRBCcht2E5G9evVSScYxZJ2YmKiGu44cOaLEo7+/v9SrV89euyKEZCP4LS//54rK+Xj1Xoxa1yKkiIzqFCrlClH0E0JIbsVuIhJBNZMnT5aePXtKVFSUzJ8/X5KSkqRSpUqydOlSBtUQ4oIcvxolo5Yelj1nb6vlMgX8ZHTnUGlZtaijq0YIISSnJRuHgNyxY4fcvn1bSpcuLQ0bNhQPD9eM0mSi0WyAycadksiH8Wqe6zm7zktikiY+nm5q2Pp/TSqIjyeHrgkhOR9qgPSxm7qbMWOGFChQQDp16iTt27e312YJIdlIUpImv+2/KJ+sOSY378epde2rFVM5H0vl9+O5IIQQYn8RiUTjQ4cOFW9vb3nqqaekb9++0qRJE0ZrEuIiHL4UqYau91+4q5YrFPaXsV3CpHFwYUdXjRBCSE4fzsZQ9pIlS2TevHmyadMmKVmypPTu3VvljQwNDRVXg6bsbIDD2Q7nTnScfLbuuPzy1wXB1cDPy12GtgyW5x4vL14ebo6uHiGEOARqAAf4ROpcvXpVFixYoATl33//LYsWLVIWSleCHSgbiI0VGTYs+f0XX4h4e2fHXomI8nVcsPeCTFp7XO4+iFdtgmkK3+1QVYoF+bCNCCG5GmqA9MnSiBfki+S8uSRNIBq//pqNlM3sO39HRi87LIcv3VPLVYrmlbFdw+SxCgV5LgghhGS/iMRw9u+//66sjxs3bpRSpUqp4ewff/zRJYezCclp3LwfK5+sPiaL9l1Uy3m9PeSN1pWlf4Oy4uHOoWtCCCEOEJGffvqpjBkzRnx9fdWw9ebNm1XicVoiSZrAm+Jm8vR5UqgQzNdssCwgITFJ5u4+L1+sPyFRMQlq3VO1S8nwdiFSOC9dCAghhDhQRBYsWFB++eUX6dChg3h5edlrsySn8+CBSJEiye857WGWsOfMLRm9LFyOXY1Sy9VKBsrYLtWkdtn8WbNDQgghuQK7ichBgwbZa1OEEDtw7V6MfLTqqCw9eFkt5/PzlLfbVpFedcuIuxstvoQQQhwoIteuXStHjx6Vdu3ayfnz59X71ECZkJCQzOyOEGIFcQlJMnvnWflq40mJjktUHgLP1Csjb7epIvn9OUpACCHECUTkli1bZPny5Wp+7J07d6r3qYEyFJGEZC3bT95QQ9dnbkSr5Zpl8sm4LtXkkVJBbHpCCCGukScyq4mJiVGBPD///LPcunVLWrRoIVOnTpUKFSoYyuzevVuGDBkiBw8elEKFCsngwYPlgw8+sDrYhzmisgEmG7cLF+88kAkrj8rqw1fVcqEALxU082StUuLGoWtCCLEZagAH54nMShABfubMGVm2bJnUqFFDtm7dKgsXLpQRI0aoz69cuSJt27aV559/XtavXy/79++Xbt26Sd68eeWNN95wdPUJsQsx8YkyY9sZ+XrLKYmJT1K+jkjX83qryhLk68lWJoQQ4pyWSN0n0hrs6RO5YsUK6dy5s7IwQkBaYvz48coyCTHp7u6u1g0fPlxFkEdERFi1Hz6FZAO0RGaYjUevydjlR+TC7QdquV75Amqu66rFA+13fgghJJdCDZBNPpHWYE+fyKVLl6rk5akJSPDnn3+qPJW6gAQY8kY+ywsXLkiZMmXsUheSSTw8RAYM+O89SZfzt6KVeNx07LpaLhroraYqxJSFzMtKCCEku3BJn8hWrVqJv7+/mhHnp59+Ek9PT2nSpIl88cUXUrFiRVWmVq1aUrduXZk+fbrhewcOHFDr//rrL/WZObGxsepl/BRSunRpiYyMlMBAWneIY3kYlyhfbz4l3287I3GJSeLhlkcGNS4vQ1oES4A3BTghhNgTWiLTxyXnOYPuhQU0KChIrl69KocPH1bir2PHjhIfH5/q95KSktT/1Kw1EydOVNvUXxCQhDhDf1/97xVp9cVWmbb5lBKQjSoVkjWvN5GR7atSQBJCCHF9EXnnzh0ZPXq0CmDB3Nn6sPLevXvtuRspXry4EnkTJkyQgIAAKVmypBKAx48fV4JSL3P9evJwn86NGzfU/2LFilnc7siRI5XVUX9Z6ztJMgEM4fCLxMv1jOJZzqnrUdJv1l8yeN5+uXT3oZTM5yvf9a0tcwfVk0pFAhxdPUIIIbkYD3uafWvWrKl8DSHeENACChcuLE8++aQaSjb2T8wMjRs3VsE1sNDoVsXExET1X9/H448/roa3sV5fB2GL+mEY3BLe3t7qRbJ52sOA/xdDnPbQwP3YBJm68aTM2nFWEpI08fJwk5eaVJDBzSqJr5d9fkeEEEKIU1giZ86cKbVr15Zt27aZ+BsGBweLj4+P7Nq1y167kj59+igfxbfffltu3rypUv0g8hoiNiwsTJVBah8MXw8dOlSuXbsmq1evlm+//VZ9hxBnBQ9GSw9ekhafbZHp284oAdmqahFZ/0YTGdamCgUkIYSQnGeJPHbsmMrLaMnnsESJEnL5cvL8vfYAQ9iwKkIgIrk4cj+2bNlS5s6da7A6FilSROWHfP3111WwTcGCBWXUqFHy6quv2q0ehNiTo1fuqdlm/jp7Wy2XLegnozuHSouQomxoQgghOVdE5s+fX6XOMReRmFlm3759ylJoT2DhXLVqVZplYBndvn27XfdLiL2JfBgvk9efkLm7z0tikiY+nm4q4npQo/Li48mha0IIITlcRPbq1Utat26tcjPCDxHDckeOHFHiEel46tWrZ69dEZIjSErSZPH+i/LJ6mNyKzpOrevwSDF5r2OoCqAhhBBCcoWIhD/i5MmTpWfPnhIVFSXz589XPolIMo7k4PYKqiEkJ/DPxbsyamm4HIy4q5YrFvaXsV2qSaPgQo6uGiGEEOKYZOMQkDt27JDbt2+rPIsNGzYUDxediYSJRrOBXDbt4e3oOJm09rgs2HtBZTTy93JX81wPaFhORWATQghxDqgB0sfu6g5BLu3bt7f3ZklOBRbqp576730OBb6O8/+6IJ+tOy53HyQnxO/2aAkZ2aGqFA30cXT1CCGEEMeJyKNHj8qMGTNUPsjo6GiVH7Jp06byv//9T/Lly2ev3ZCcho+PyKJFkpPZd/6OjFp6WMIv31PLIcXyytguYVK/QkFHV40QQghx7HD24sWLpXfv3ipCu06dOsoaiekI9+zZo1LtbN26VcqVKyeuBk3ZJDPciIqVj1cfk9/2X1TLeX085M3WlaXvY2XFw51D14QQ4sxQA2SDiEQjYxaY1157TT744APx9PQ0mWYQUdt+fn5qrmtXgx2IZIT4xCSZs+u8TFl/QqJiE9S6nnVKyTvtQqRQAGdEIoQQV4AaIBuGsyEOq1WrJuPGjUvxGYa0EaVdvnx5uXXrlkr4TUhODqzZdfqWjFkWLsevRanlR0oGybiuYVKzTH5HV40QQghxLhF58OBB6dixY6qfYzgbSb8PHTokLVq0yOzuCHFKrkQ+lI9WHZPlh5JnZsrv56ksjz3rlBZ3N9MZnAghhJCcQKZFJCyMsESmBaY9RDlCchpxCUkya8dZmbrppDyISxRM1tSnfhl5q00Vyefn5ejqEUIIIc4rIuPi4tJNJI48kbGxsZndFSFOxbYTN9TQ9Zmb0Wq5Vpl8Mq5rNalWMsjRVSOEEEJcI8XPrFmzZMuWLal+vnv3bmnXrp09dkWIw4m4/UA+XHlE1oZfU8sIlhnRPkSeqFlS3Dh0TQghJJeQaRFZpUoVOXfunBw7dizVMsWKFVO+kYS4MjHxiTJ96xn5ZsspiU1IUr6OAxqUk9dbB0ugz39ZCQghhJDcgN2nPcxJMLw/G3CB6Gz8RDYevS7jVhyRC7cfqHWPVSig5rquUiyvo6tHCCEkC6AGSB/XnNSa5BzgT9uhw3/vnYxzN6Nl7PJw2Xz8hlouFugj73WsKp2qF5c8iKIhhBBCcikUkcTx0x6uXOl0Z+FBXIJ8s/m0fL/tjMQlJomnex55vnEFebV5JfH35s+GEEII4d2QELOh69WHr8qHK47I5cgYta5xcCEZ0yVMKhb+/2F3QgghhFBEEqJz8lqUjFkeLjtPJec0LZnPVz7oFCptw4py6JoQQggxg5ZI4vjAGj1y//p1hwTWRMXEy1cbT8rsneckIUkTLw83Gdy0ogxuVlF8PJ3PT5MQQghxBigiieN5kBzx7Iih66UHL8tHq47K9ajkZPitqhaVUZ1CpUxBP4fUiRBCCHEVKCJJruTI5Xsyetlh2XvujlouV9BPRncOk+YhzGdKCCGEWANFJMlVRD6Ily/WH5e5u89Lkibi6+kur7aoJM83Li/eHhy6JoQQQqyFIpLkCpKSNFm0L0I+WXNcbkfHqXUdHymucj6WyOfr6OoRQgghLgdFJMnxHIq4K6OWhav/oFKRABnbJUwer1TI0VUjhBBCXBaKSJJjgcVx0tpjsmBvhGByzwBvD3m9VbAMaFhOPN3dHF09QgghxKWhiCSOxc1NpGnT/97bgcQkTX7Zc14+W3dCIh/Gq3VP1CwpI9qHSJFAH7vsgxBCCMntUEQSx+LrK7Jli9029/e52zJqabgcuXJPLYcUyyvju1WTuuUK2G0fhBBCCKGIJDmE61Ex8vHqY7Jk/yW1HOjjIW+1rSK965URDw5dE0IIIXaHlkji0sQnJslPf56TKRtOyv3YBMmTR6Rn7dLyTrsqUjDA29HVI4QQQnIsFJHE8dMeliuX/P7cOZumPfzz9E0ZvTRcTl6/r5arlwqScV2ryaOl82VVbQkhhBDy/1BEEsdz86ZNxS/ffSgTVh2Vlf9cUcv5/TxleLsQ6VmntLi55cmiShJCCCHEGIpI4jLEJiTKzO1nZdqmU/IwPlGgF/s+VlaGta4s+fy8HF09QgghJFdBEUlcgi3Hr8vY5Ufk7M1otVynbH4Z2zVMwkoEObpqhBBCSK6EIpI4NRG3H8i4FUdk/ZFrarlwXm95t0OIdHu0pORBFA0hhBBCHAJFJHFKYuIT5dstp+W7raclNiFJPNzyyLMNy8nQVsGS18fT0dUjhBBCcj0UkcSp0DRNWR1hfbx456Fa17BiQTXXdXDRvI6uHiGEEEJcWUTu379fevfunWL9H3/8ISEhIYbliIgIGTVqlBw4cEAKFy4sgwcPlieeeCKba0vSBFMd1qmj3p699UDG/BouW0/cUMvFg3zk/Y6h0uGRYhy6JoQQQpwMlxSRDx48kOPHj8uhQ4fEy+u/qNzy5csb3t+/f1+aNGkijzzyiEybNk0Jz6efflrmz58vTz31lINqTlLg6ysPdu6SqZtOyczpeyU+URNP9zzyQuMK8mqLSuLn5ZJdlBBCCMnxuPQdunLlyuLj42Pxsx9++EFu3rwpCxYsED8/P2nUqJGEh4fL6NGjKSKdaOh65b9XZMLKo3IlMkata1q5sIzuHCoVCgc4unq5jsTERImPj3d0NQghJNuAIcoNI2Ik94nIli1bSlxcnISGhsrbb78t1apVM3y2efNmZYmEgNTp2LGjfP/993L9+nUpUqSIg2pNwIlrUWq2mV1nbqnlUvl9ZXTnMGlVtQiHrh0g5q9evSp3795l5ySE5CogIDGKaTyqSXK4iERql379+kn//v3F29tbfvzxR6lVq5bs2LFD6tWrZ/CHfPTRR02+V6xYMfX/4sWLFkVkbGyseuncu3cvy48lt3EvJl6+3HBSfvzznCQmaRKkxcn2n4ZIXh8PyTPkCE6uo6uY69AFJH4TeOhi6iRCSG4gKSlJLl++LFeuXJEyZcrw2pdbRORjjz0mjz/+uGG5cePGcu7cOfnggw9k7dq1hqE58ycLCE6QkJBgcbsTJ06UsWPHZmndc7O1a8n+SzJx9TG5eT9ZqLcJLSqjmpeVwE8v6YUcW8lcCH4nuoAsWLCgo6tDCCHZCoJuISShCzw9mT7OVlzSEcDd3T3FOgxdw+dRBzdE+EQaoy+ndrMcOXKkREZGGl6wZpLME345Unp8t0veXHRICcjyhfzlx+fqyvf960ipAv+5G5DsR/eBNHb7IISQ3IJubMIDNckllkhLXLhwQYKC/psCr27duvLLL7+YlPnzzz+VgDSO4ja3VOrWSpJ57j6Ik8/XnZB5e85Lkibi5+UuQ1oEy8BG5cTbI+WDAHEcHMImhORGeO3LhZbIr776Sv755x/D8vLly2Xu3LkyYMAAw7rnnntO+Tl8/fXXahnD3d9++6288MILjMTKYpKSNJn/1wVp/tkWmbs7WUB2ql5cNr7ZVAY3q0gBSbIMjCYgqM4Whg0bpvypU1t2NnBde/bZZ9VoSW5gypQpNp9Te4H9ok/Zwpo1a1R+Yp0VK1bIjBkzsqB2hDgelxSRderUkRdffFFZFQsVKqQuqB9++KGK0DZO/4OckGPGjFE+D1hu1aoVfR6zmIMRd6X7Nztl5JJ/5c6DeKlcNEB+eaG+TOtdS4oH+Wb17kku5/fff1c5ZG1h4cKFcurUqVSXnQ2Ix59++kkePkye0QnZJnANtPRCflxX5uDBg8pXvXbt2g7ZP/oS+pQtHD58WJYtW2Zyvxo+fLicPn06C2pIiGNxyeHshg0byq5du1RAAKKpixYtarHck08+KV27dlXR2Pnz5zcZ7ib25db9WPl0zXH59e9kP9K83h7yeuvK0r9BWfF0d8lnFZJLmTx5stSsWVNcBWSRgKh89913JTg42OQzPGS7MhMmTFBiODAwUFwVZAXBfWjSpEny3XffObo6hNgVlxSROvny5Uu3jIeHh5QrVy5b6pMbSUhMknl7Lsjn647LvZjkqPcna5WS4e2rSJG8lhPBm4CUPqGh/70nxAZgNcSQY8mSJaVHjx4Wy8Aat2jRIomKipKwsDDl6pLaJAUAD6jFixeXSpUqKXcY5JHDlKnGjB8/Xn3+zDPPWLWPl19+WYkhBP/t2bNHmjdvrmbQQoqRJUuWyNatW1X5Zs2aqXy2xmCbcMU5f/68mtYVD9GWaN++vZpUwRLHjh2Tjz/+WAkZuP6cOXNGqlSpokZ0jLNYpFcffTuffPKJyrmLOsHKBvF64sQJNckDHuyRQQMR/+vWrVOWRFjhMFoEgW583d69e7f6Do7PPGDyxo0bairbv//+27AOy0eOHJE2bdrIypUrlRW2devW0q1bN9m7d6/qD4iyhQHBvC3gBoB9oc6lS5eWgQMHqn5jDOo5c+ZMiYmJUcdgCcyGhrRysDjCgIGpdGvUqCFp0atXL1UnDM2n1fcIcTVoIiIZZu+529J52k4ZvSxcCcjQ4oGy+KUG8nnPGtYJSICoYETV48UIYWID77//vvJxhuADcFe5dOn/00X9P/BFa9u2rRKCVatWVSIEw4v6ULAljIezEbWO1GHGM/lAuIwbN065yVi7DwT5wRoFfznktK1YsaISbBAgEFd40IXoeuWVV+TNN980fA/7ReYJuOZAtGKq106dOmUoFyislUiHdvv2bSX64Fveu3dvQxlr6qNvBynWMP0s/mOEB8O+OGb8R+AihBi2rQ8FY3vr169PEez4+eefy61btyxm3NiyZYv4+vqqqWuNh7c//fRTg3UyICBAzUAGMf/8889LiRIlVKBEixYt5K+//jJ8Dz7x1atXl507d6pJKfAZtmvstoD3GDaHUMYxQCSbp3y7du2ayj+8YcMG9X1MdtG0aVMlvNMCwh/9AfsnJEehkVSJjIxE4kL1n/zHtciH2usLDmhlh69Qr+pj1mpzdp3TEhKT2EwuxsOHD7UjR46o/zpJSUladGy8Q17YtzVcunRJ8/b21pYvX25Yt3r1avV7/fbbb9Xy9evXNR8fH23Hjh2GMomJidojjzyiTZ482bCuZMmS2uzZsy0u47ePbSxbtszw+ZdffqmVKFFCbcvafQQFBWlPPfWUyTHMmTNHK1u2rBYdHW1Yd+LECc3NzU07c+aMWp45c6aWP39+k2vQiy++qI7zypUravnkyZNquX379tqAAQNMXnfv3lVlNm/erMr88ccfhu1s2rTJ5PpmTX307fz8888mx/LMM89oLVu2NCwnJCRo1atX16pUqWJY9/7772u1a9c2LN+8eVPz8vLSVqxYoVli3LhxWrVq1UzWjR49WvP19dWuXr1qWPfEE09oefPmVdvTadOmjTZkyBDDcr9+/bTHH3/c0L/wv2nTplrPnj0NZfr27ZviGMLCwkyOYeDAgSnO4/fff6/aTWfSpElajRo1UhxPkSJFtKlTp1o8VuJc10AdaoD0cenhbJK9xCcmyY87z8mUDSckOi5RjT73qltG3m5bRQr4c8qonMLD+EQJHZWctD+7OTKurfh5pX9ZwjAoLE7GQ63t2rWTvHnzGpY3btyoLEWzZ89WLyS8xwsWNFi0rAHWrs6dO8u8efPUf4D3sHzB8mjLPmCtNAbDsdjGa6+9ZvgeXrDKweIIaxiGlTFca+wT2LNnT5k+fXqKusKP09wn0nzCBUwVq4PhbADrLbZvTX1SO5bt27crn0wdfAdDzL/++qth3aBBg5SP47///quseGhH+GzivFkCw/j+/v4p1mOaW2M/eFh1YSE0zv+LdcZWabQjLKp6Ohf8xxAzAi+NyxhHYuMYYJmFZVoHbYRtw+qptw8sqRgih49+Wi5WsJrimAjJSVBEEqvYeeqmGrY+df2+Wq5ROp+M6xKm/meKBw+Q1DP5/d69HNImVgF/OdywzXO8FShQwPAeN3f4n5n7xmF42BY/6T59+ijRCAEAsYKhUF3E2bIPc4GB78Inz/y7GB7F0Kt+nObbMT5Ga30idYz98SAYjZMsW1Of1I4FEzmY18t8GccBlwP4JcI3EqIbadksDWUDiMI7d+6keQz6cVhaZ5w8Gu1oPskEBCzqDSGIfoQy6R0D2ggPE8YzpoHu3bunO/cyRCZnhSI5DYpIkiaX7j6Uj1YelZX/XlHLsDiOaBciT9UuJW5udgiEwVSHR4789544HF9Pd2URdNS+rQHz3OKmDz8z+M0BBHTAZ0+nVKlSyiIIq5nuN5kROnTooPYB/76zZ8+qwBn4xWV2H/gugkTg35fWcWIiBWNg9coKrKlPaiBQxbye5ssAFjz4WcIKCEutsZXPHLQx2tv4HGcUtCP8Io3BtlFv/UHEmrZGG0FY2tpG6JfwRXVUqiJCsgoG1hCLxCYkytebT0mrz7cqAQm9+GzDcrL5zWbSs25p+whI4pTgpoohZUe8rJ09AgEiuJlPnTrVsA6R1BCSOhgGRnoV5I9FxK4OAidsyZ+I+XQR+Y3hVwSG9O3b1y776Nevn7JqLliwwGQ9AnP0oBwMpyIgRZ/SFftAQExWYE19UgOWOFgYEbkMIPDNg2gAhrhB//791Tk0H343Bp9DPNoj8TvOH+qnWzaRaxMBUQjK0UFbz5o1S6VMArA645wbg3rDCm2cixR97rfffktz/8gggKAfV0odRYg10BJJUrD52HUZuzxczt16oJbrlSsgY7qESWgJ183VRnIW8C+bNm2auqkj4hnDl4iaRkSxDiKrly5dqoQCUuPgBh4REaEE0c8//2zT/iAcIWogco0jmjOzD0QQY1gX6YAghmHJhL8gLJ2wfgJYOLE/RPciLRDS6KRm8fzoo49Mjh/gu0iHYw3W1Cc1kOZn1apVKvIZ1jak5YHvoPlwNIZ8cc6++OKLdGeCQduiLogGh1jPDKgfor1RP6TugVjG0D0i/I3LrF69Wvlr4hiQMgh+oxDEOiiP9EiIsEd/QAo5tNH//ve/NPePtEpIp6S7EBCSU8iD6BpHV8JZwRMp0lfgqdWVk91ay4VbD2TciiOy4eg1tVwkr7e817GqdKmRnDYjS4iOhiJIfg8rhgVHepJ1IB8ehvUQNOGK+esw/Ii8jrDy1K1bV4kABF7oQSMAgS9IrQLLEoQNBICxHx7yO0L8IYWOpWWAyyREIQI9YLEyJ719wCoHP7qyZcum+C788vBd+PAh3yC+bymQCMeKFEIYdsXQOvJMwlIHX83ULGH16tVT7YHhVIht+CDqv2UIXQS+wDpo7OOYVn0sbce4DfRAIxw/LHYIuEHAijFz5syRV199VeVttBQ4YwzaE8IPuTUrVKighsDxPfh/6uzbt0/5KhqLZZSPjo5Wwtj4HP7555+GPJE4H+aiDimVcAywLuIY4KqAIX5YWo1BOiBYmhHIhdRGeronAKsx8k126dJFLSMoCfWFdTo33Edy0jUwt2mAjEARmQa5pQM9jEuUb7eelu+2npa4hCTxcMsjAxuVlyEtKkleH8+s3TlFpENxdRFJnAMIUognCCoAUQchjojs0aNHm5SFBQ/iFJZka8BwNq7DxvkiXQlYZZGDE4KeOB8UkZmDw9m5GDyZrw2/JuNXHFEBNODxSgVlbJcwqVTkv1QphBCSFrC6Dh06VA3vwlcVFkgMCRsnKsesNIsXL1aWPSRPt5b0Is6dHV1YE5IToYjMpZy+cV/GLAuX7SdvquUSQT7yQadQaVetWNYNXVsC+9KH+DjtISEuCXwdIRzha3jx4kUZNWpUiiASDKtjhiGkDMpMtDwhxHmgiMxlRMcmyNRNp2TWjjMSn6iJl7ubvNi0ggxuVtGqJM92B1MdmqXeIIS4HvAvTG2+aQDxSAjJWVBE5qKh6+X/XFE5H6/ei1HrmlcpLKM7h0m5QgxmIYQQQohtUETmAo5fjZJRSw/LnrO31XKZAn4yunOotKz639RhhBBCCCG2QBGZg7kXEy+T15+QObvOS2KSJj6ebvJys0ryvyYVxMfKmUGyHCQxbtIk+f22bSKZnJmCEEIIIdkDRWQOJClJkyUHLsnHq4/Kzftxal27sGLyfqeqUiq/nzgVSUnIgfHfe0IIIYS4BBSROYzDlyLV0PX+C3fVcoXC/jKmc5g0qfxfMlxCCCGEkMzCOZhyCHei4+S93/+VztN2KAHp5+UuI9qHyJqhTSggCSGZZtmyZSoxfU7nwIEDKl2RI1i+fLmaVjGzYFYhTJGZFhs2bJCjR4+mG5CJGZzSmzvdmcHsTpgwhGQNFJEuDnwdf9lzQZp/vkXm7bkgmMQS0xRuerOZvNS0onh58BSTnAlulJgGzxzcPFObBjCzYJ5sTJuX2swXmFYPNy1MG2g857KrYek4X3755RRTGNrrPC5YsEC9sF9ME5hd/PPPP2pObWMwV/cnn3wijmDIkCGyadOmTG/nrbfeUnOZpwXmAUd7p8UPP/wgX3/9tZpe05jbt2+rKUYxm1BCQkKK72HKTPwW/vjjj1RFsTVljKexxHSU5t9Hn8Ec58ZgvnasRx0B2vODDz5Ic/sk43A424XZf+GOjF4aLv9eSn7KqlI0r4ztGiaPVSjo6KoRkuXgRtmuXTt59NFHTdbj5okb5JNPPmn3fb744ovy2WefpZgD+4svvpDx48dLyZIlJTg4WN3AML9yw4YN1fR+RYu6ViYES8fZtWtXNT1mVpxHCBGcR1i8IMAxX/fatWtN5vTOCjCnOaYlbNasWZbuxxXBPOIQXxDV5n0dyeQxEw+mSr17964So3ofx5SXbdu2VfOeh4SEKKGIczx27FjDNqwpY8y2bduUsMeDmT4ZBs7bM888Iw0aNFDfN7aYP//880pMgnfeeUcqVaqkto9554l9oYh0QW7ej5VPVh+TRfsuquW83h7yRuvK0q9BWfF0p+WREEvExcXJrl27JCoqSs2eUqFCBZPP161bp8QfblIlSpRQoiZv3rwmFjNYG3fv3q2m9/P09FRC9cMPP5SPPvpIWSBxY9TBfMmwsmB/xiIyvXosWbJEzbOMfcDSGhgYqJJ4I5m3Lcejbwf12L9/v5QrV04dU0aPE8dmfhO+d++euoGjLvXr108hlq09lk6dOinRCq5fvy5Vq1aViRMnprAIYvj1+PHjqt6YEQd1Mxc+GIrGMeO4IDouXbokrVq1MusNoraD7UHIwHJlPsUi2gB1hkiqW7euFCxY0GTI+/79+2pqRxw/3nfr1k19BkGMtkMbQyBVrlw5xb5hbYWlF2I5LCwsxeeoN/YBCyDay/w4o6OjldiG6Ebd0B7pAeGG7+AcmT94WQLWfExn2bJlS8O6hQsXKlGGPtSiRQuDNdd4uHv48OGq7dC2AQEBati8devW6qW3rzVljGnevLnaL/aFedfB5s2bVR1gDUV7+Pv7G9ajTbBdULp0abXN77//Xv1WiX2hiHQhEhKTZO7u8/LF+hMSFZM8hPBU7VIyvF2IFM7rLS5LoUKOrgHJ4UBE4SZfuHBhdcOF+Hr66afVUJ0OhmpPnz6t/MBgRYT4gAiCNRFgOC02Nlb27dsnN2/eVFYY3NwgIIcNG2YiIAGE0hNPPGFzPQYOHKisKydPnlRiChYXiBHcaHFTt2U7EHYQS7Vq1VJCEOIhI8eJ72I4Gzdh3RoJIdGzZ09lecUNHFMefvnll2pqQ1uOxZwiRYoocWY8rA3B0bdvXyWCYAGDbyZEMHwIIY51kQRRAfFWrVo1+ffff5VIw7FYEpGoE14oD7EP9GPD9rEftC0sWmiv9evXq3oBWOdw/GjDUqVKqe/hfEAUdenSRYk/1AvDsOgXP/74o+oPEJiw6EKcQuhASGIf2L8uFH/99VcloPFggHLFihVTQsnbO/kajzbAvmD1xjzlEKw4L+iDqYEhXXynSpUq6lzhvEJ4pcWKFSuUhdZY8KOvo5/pAhJUr17d8B7HB0GOcrqIQ9ujzLx585SYs6aMOei/sErD9UAXkXj/1FNPqYcOtI/++8P6Pn36mHwf9YVvJ0VkFqCRVImMjNTQRPjvaHafvqm1nbxVKzt8hXp1/Gqb9ve5246uFnFxHj58qB05ckT9T8H9+6m/zMunVfbBA+vK2khYWJjWqVMnbf78+Sav/v37a/7+/oZyMTExWunSpbWpU6ca1l29elUrWrSotmjRolS3P3bsWO3RRx81WYfvzJ0717D8xx9/qGvE33//nW59ra1HUFCQ1qBBA+3B/7fblStXNF9fX23ZsmU2b6dGjRpaVFRUmvWy5jhByZIltdmzZ6v30dHRannEiBGGz2fOnKn5+Pho586ds/pY9PP45ptvGpYTExO1SpUqaf369TOse+edd7RGjRoZtpOUlKQ999xzWseOHQ1lhgwZYnK8p06dUv2gfv36qR778OHDtZYtW5qsGzp0qObh4aHt3bvXsK579+7aE088YVImT5482rZt20zqXbVqVW38+PGGdXfu3NHKlSunzZgxQy1v2bJFHf/du3cNZdasWaPaE5QtW1Z75JFHDMeA7+fPn1+bM2eOWo6Li9OCg4O1l156yfD9xYsXa+7u7lp4eLhJm06ePFm9j4+P1ypWrKgNGzbM8Pm3336r+u3EiRNTbRscy0cffWRYRp3xnR9//FG1Lfo+2gjHrXPixAlVZuPGjSbb6tOnj/b4449bXcYSXbp00bp27WpoB5xbXLteeeUVdR7BmTNn1LY3bNhg8t2lS5eq8xUbG2vTNdCZNICzQkukk3PtXox8tOqoLD14WS3n8/OUt9tWkV51y4i7W7JvCCFZwv9bCSzSoYPIypX/LRcpIvLggeWymDPZOHgBlqObN1OWQ1SYjcCSpFuRdMyjUmFZg7WtUKFCsnjxYmU9wgvDvxj6gjVDB5ahY8eOqSFMWH5gDYMlS7cCmXP16lX133iYFxYeWMh0YBXDy5Z6PPvss4ZgBliiYEGCRbFz5842bQeWQN3aY4ytx2kOhowvX74sI0aMMNkXfOXgH/faa69ZdSw6WIZ1CsOi8GmDVfHtt982fD579mx1XCtXrjQcL7aF4VW8h1US7+FTpx8vrJCwoGLbtgIrISyRxvN+z5gxw6QMrLqNGzc2LMMiCEskLJPG5wX+eDgv8NNDO8ASd+TIEWWhBeYWbFhc9WOA9Q2WN/0YYIFGnzcOMsExwhqMfaL9zYH1F5ZUDAfrwFr87rvvptkGsFbmz5/fsKwHisFCiXZGn0Z9YDnGeSlevLghChoWUmPgCqAHwVlTxhKw+mO/cFVAMA3aCNZtnBvdFQLtjD6sW9V1cBw4F+hXqCexHxSRTkpcQpLM3nlWvtp4UqLjEgW+xM/UKyNvt6ki+f29HF09QpwCY186nSlTpqjAGp1z586Jl5dXCrEJ4Yebrw6Ga+fMmaN8+HBDe/Dggbrx4GaKoUNL6Dd73JwwtAzwPX1fEJMQQ7jhWlsPSzdY3BgxpGvL8QBLN8yMHKclEYrvBgUFGdZByGFY1zyqO61jMX8YgH8nhMAbb7whjzzyiPoM9YOAgfDSI26Nzz/8MQF8G/WhbR3UJyMi0po6m7ctzguGfuFTagzaCUPTAG2OvtmxY0e1DwyzQtBBtFqzb7Qt/Evh52cMBHNqWQMuXLigXBKM/VXhSpBekAn6tvGQN7YBIEhxLrCMc4NjgkCdO3eu4SEEPqLGYFn/vjVlUhOR6BcQmugjEI8A/3v37q38c7EePqTm0eT6cRj7/hL7QBHphGw/eUNGLwuXMzeSO37NMvlkXJdq8kip/y7YOQY4ZLdvn/x+9WpOe+hMmF3kTTD3Z7t+PfWyZkEUcu6cZCcI5oDQQLoSPz/LMzbBnw+O9/AR1IUI/KwQ6Q2BlRq4gQL4vsHXD0BM6oEaxqLGmnrY63h09EjWzB6nObCCwqIEq5CxzxxEHj7LzMMArFuw0qE9+/Xrp4QFRDOEwv/+979UtwGBoEfk6pgv2xPztsV5QXvALzStaHxYC2EFROAMrKc4Vvi0GgvJ1EDbwpIJsW0siNDulgJ0dBELEQorr7G4Sq9tEBBknBcUohnfhwDWxR76X/v27ZUlUhftaBcIV2MgcPXAL2vKWAI+kzgWCEW89OwLsITC2gvrOPwhjX1ydXAc8D21ZJUnmYOhvE7ExTsPZPDP+6TfrL+UgCwU4CWTnqouv73UMGcKSH2qQ+Sew4vTHjoXiHZM7WVuMUirrPl86KmVyyJg7YHlZebMmSbrcTOG9UoflsYN0djCg+FBc3ATMrZIwfIHAYRhNjj4Z7Ye9jqe1MjocVoSz3pgi87hw4fVELmlwAhbQBAFgkRgwYV1CiIVUbsYTkZuQGMwrK/z+OOPm1hnEaltXD9LpHectoDjRtDK9OnTTdZDWOpuDzg/OE+wJkI0Tpo0SVl/zXMdpgaG0FFnBEIZiy98P7V2RxQ76mWcExJBU+YizhxEZSOIRwd9rkOHDuocG4MhfL0/oW5NmjRRQSw6V65cUSl6ID6tLWMJ9DdYHZH6CRHxxmmZsB4PRxcvXlQWS3NwHJaCq0jmoSXSCYiJT5QZ287I11tOSUx8kvJ17N+grLzeqrIE+ZqmdiCE2AYsEJMnT5bXX39dDcPh5h0REaFSmOAmjlyTECAYZkPkKW6UsAxZSlgOPzmIN5SFGIM1BJG3iLiFf9Zzzz2nLGgQDrhRY5hbt/hZUw97HU9qZOY4jYHfH9K09O/fX/1Hmc8//1x69Ohh4ieYUeBrCVGAbY4ePVq5KEAoQCjBIon2RZQ5BAmG5gHydOJznANY92ANhsUOlqrUwHEiSvirr75S5TIjgOG/+O2338qgQYOU5QttDXGE1E84HkSyw/qIfIVoJ/QLiCH4pbZp08aqfWCoGw8scEnA8DmWUXc8WBj7mJp/B/uHhQ5D0RCUaE9YTtNiwIABKk8kRKNuZUdboW2RRxSR/xBn8M80TpD+6aefqnMFH1lEs0NU48HAOGLamjKWwHG++uqrytKL35uOPqSNfoh6GQMLLB4m4MtJ7A8tkQ5m07Fr0nbKNvl8/QklIOuVLyArhjSS0Z3DKCAJSQMMo8HKYg4CN4yDS8Arr7yihpzhw4dhL1hVYM3RBReGyRAYgeE0iBMMuWFoDGLLeMgYKXRgLUG6F93Khe9imz///LNahkUFN1749CHIB4El1tYDQLCZ+7zBEmc8XJnR7WTmOM2TjY8bN04JOFjCkL8PIg4pWoyx5lgsnUcc19SpU5VlCdZHDFfC0gkhhuFuCCi0qy4gdUGIY4NIQqAQRNObb74paYH2glgODw9XVkxYNiFmYCkzBvuHxVnHUhmA4XfUD0O/OC+wOsJXEPXW9wdRCdcBtDvKoa7YPoAQhH+jMbC4GbcPrLQ417BqYl+w2JpbXNGm+B3owA8Tlly4McAqigcHBD/pvpqWgKiG+wCG542HuOGTiM/Qz/EwYRwkpFupUS+IV7hPQNRj+BnWV1vKWALth75qns4I1kesh0CH64MxSMeE9rPHww1JSR6EaFtYT/4/kS4uZvD9Se+pzVbO34qWccuPyMZjyUNgRQO95d0OVdWUhea+NjkaODzrfirwwcvCYU2SEgzlwWoCcZCWUzshrsjHH3+sxCHEJbEd+E0imAUWVvNgFVcBDxJ44NADtWy5BmalBsgpcDjbQUnDn/l+t1yOjBEPtzwyqFF5GdIyWAK8eToIIYQ4B0iNA3cNVwYuESTroGpxAB7ubvJ668qy7OBlGdMlTCoVYcQYIYTYGwzXpjczCyEk41BEOogetUupV64auk6NTKQ7IYSQ1MAUhHgRQrIGikgHQfH4/8AHkpYCQgghxOVgdDYhhBBCCMl9lkhETSFVAfKZIa+YedQV8k8hNxdmkUAagbRSGhCSW2GSBkJIboTXvlxuiUQeK+TbMs7gD5BbDBnqkQMM+cMwLRSSmjLVg5OB2SIwSwFedpo5gliPp2dyMnvMgUsIIbkNfe515FolucwSieSpSA6L7P2YqcGY+fPnqySmSISLhK4AQhKJWZEEljgJmMZs1ar/3pNsBRdOzLShT9mHhNP01yWE5AYw89GNGzfUdS+9ROfEMi7basiSj4nsMW2UpWm7Vq1apaaw0gUkQEZ7TI2EIXAkECWEiBQrVkw1Q3pzPxNCSE4Dc7OXKVOGD8+5SUTC1xGCEPNvGk/DZQzmCDWeWxOULVtW+T8gOz0msjcnNjZWvYyz1ROS04HlEQ9bmMosPj7e0dUhhJBsA9MkQkiSXCQiX3/9dTX3KuYpTUtoBujT6f0/+jI+s8TEiRPV5PaE5NahbfoFEUIIsRaXk9+wDiLi+u7du9KrVy/1mjdvnkRFRan3W7duVeUwXA0fSGNu3bql/sMHzBIjR45UQ936KyIiIhuOiBBCCCHE9XA5SyQmgUfQjDErVqxQw9fdunVTvg2gevXqsm3bNpNy//77r3KgrVChgsVtI00QXoQQQgghJIeJSKQkgcXRmIsXL8qaNWtM1vfp00e+/vpr2bBhg0r1g/lTYcHs2bOn8oGwJX8UfSOzEOPZauCDyghtQgghToB+72cuyRwkIq2lQYMGyr+xa9eu0rBhQzlx4oQUKFAgRSqgtMAQOShdunQW1pQYKFGCjUEIIcSpgBZgRhfL5NFygMQ+fvy4SvnTvXv3FJ8hEvvQoUNSsGBBJSZtCRxADqnLly9L3rx57R7+jycciFP4XQYGBkpuhm3BtmCf4O+D1wpeM53t/gF5BAFZokQJRnDnZEtklSpV1MsSSAGUWhqg9EDYf6lSpSQrQafP7SJSh23BtmCf4O+D1wpeM53p/kELZA6LziaEEEIIIY6HIpIQQgghhNgMRaSDQCqh0aNHM6UQ24L9gr8PXit43eT9g/dSlyRHBNYQQgghhJDshZZIQgghhBBiMxSRhBBCCCHEZigiCSGEEEJI7swT6SwcO3ZMbt68mSLH1COPPGIxCfrt27clJCRE/P39LW7PmjLODuqP46hcubJK2m5OQkKChIeHi4eHh4SGhlpM6m5NGWfl6NGjcuvWrRTrfXx8pE6dOibr7ty5o+aAL168uJQsWdLi9qwp48wkJiaq/oAEwWXLllWTAFji3Llz6reEvh8QEJDhMs7MgwcP5OTJk+Ln5yfBwcGpttfhw4fVJAno+8hdm5EyzgaO+9q1a2oCiNTqi0kkMF1ttWrVUp2q1l5lHAXO3b59+9T1PSwsLMNlYmJi1IQbuMam1pesKeNI7t69q/ox6la0aNEUnyN848yZMxIbGysVK1ZMNSj1+vXrcv78eSlXrpwULlw4w2WIlSCwhtiHJ598UitWrJj2+OOPG16vvvqqSZnIyEitVatWWkBAgFa5cmX1f86cOTaXcXYePnyoDRw4UPP19dVq166tlS5dWps8ebJJmT///FMrWbKk+qxIkSJaSEiIdvz4cZvLODMjR4406Q94oU3q1q1rUu7jjz/WfHx8tKpVq6r/vXv31uLi4mwu48zs2rVLq1ixolaiRAmtZs2aqh369etncgxRUVFa27ZtNX9/f61KlSrq/+zZs022Y00ZZ+ejjz5S9X7kkUe04sWLq99IRESESZm9e/dqZcqU0UqVKqUVLVpUq1SpkhYeHm5zGWdi6dKlWuPGjbX8+fMjoFOdS3MuXryo+keBAgW08uXLa4UKFdLWrl2bJWUcRUxMjPbhhx9q5cqV0wIDA1V/zkgZvU3Rnjj3+fLl0x577DHt2rVrNpdxFCdPnlT3CvwO8uTJo82YMSNFmZkzZ6p2qFChgroH4Bi+//57kzJJSUnakCFDNG9vby00NFT9f/PNN20uQ2yDItLOIvLFF19Ms8zzzz+vbny3b99Wy/jBeHh4mAgja8o4O3379lWC4fz582oZQsH4R//gwQMlJl5++WW1nJCQoHXo0EGrVauWTWVcjcuXL6tz+fXXXxvWbdiwQXNzc1P/wdmzZ9UNb8KECTaVcXZw0e7Ro4eWmJiolo8dO6Yu4sY3jZdeekkLDg7Wbt26pZYhDt3d3bUjR47YVMaZWblypTqXGzduVMvx8fFanz59tKZNmxrKxMbGamXLltUGDRqkltFm3bt318LCwtSN0Noyziiet2zZov3++++pikg8QENoQkSB9957TwsKCjKcb3uWcRQ3btzQ3n33Xe3cuXPq3FsSiNaUwfXEz89P+/TTT9VydHS0uj6iH9hSxpGsWLFCiUTUy/x6oAMxrd9LwNy5c5XgxEOUDrYBg8s///yjlvEZtjdv3jybyhDboIi0s4iEZQUdEz988ws5Lmb4MU+bNs2wDmUglHCBs7aMswOxixvEkiVLUi2Dz3ARwAVOZ8eOHep7Bw4csLqMqzFx4kRlgbt7965hHSyKjRo1Min3+uuvKxFuSxlnp3Dhwtpnn31msg5WZggL/UEDF/gpU6aYlIGlbfjw4VaXcXaGDRumBLUx27ZtU/1af1BctWqVWsbDgs7ff/+t1sGia20ZZyU1EXnhwgW1HsLCeGTGWFzYq4yzkJpAtKbMF198oayUeKDQ+fnnn9VD1c2bN60u4yzYcn5wHfjyyy8Nyw0bNlTGC2O6deumtWzZ0qYyxDac33nGxfj1119l0KBBUqtWLeUHuG3bNhPfHPhB1a5d27AO/n3wjTtw4IDVZZydjRs3Kv/Fdu3aKf+3f/75Rx2TMTgWTGoP3z6devXqGT6ztoyr8cMPP0jPnj1N5mPFsRifb/044fsYFRVldRlnZ8KECTJ16lSZM2eOrF+/Xl555RU11+3AgQMNfnL3799PcZzGfd+aMs5OgQIF5MaNGxIXF2dYd+nSJfUfvm8AxwJ/Ufhs6eCaAr9H499HemVcDb3exucXfaRKlSomx22PMjkBHAt87o19PXFdgB8lrrvWlnE14GuO60ClSpUM61K7Rhqfb2vKENugiLQjEAdwFj906JBcuXJFmjVrJt27d1fr9CATYB5MgGX9M2vKODuXL19W9X3hhRekefPm0rt3bylSpIhMmTLFUAbHYn6Mnp6eyunbuC3SK+NK4IECIgjtYoyl49SX02oL8zLODh4qqlatKsOHD5d33nlH5s+fL4MHDzY40eeW38eAAQMkPj5enn76aVm9erX8+OOPMmbMGCX+0jrfeJiEALWljKthrz6QE/qJNeSWa4d5gNBzzz2nxF/btm0N6x4+fGjxOBGMiFFXa8oQ26GItLOIzJcvn3qPpz6IpsjISFm3bp1BAAF0ZmPQsfWnRGvKODs4BgjnYsWKKUskIu5mz54tw4YNk927dxvKmB8jwDrjtkivjCsxa9YsJaIef/xxk/WWjhPnG6TVFuZlnBlE2Lds2VIJxoiICPXkv2fPHhk1apRMmzYtV/0+SpUqpY6/TJky6hqB68O8efMkKSlJfH190+z75m2RXhlXw159ICf0E2vIDdcOY/DwhfssLPlLlixRD17pnW+MiuHhypoyxHYoIrMQpGRA6hF9qAopTYC+rINl3FCsLePs6MNrL774ouGH2aNHD8mfP7/s2LHDcJxXr15VN07jtAu4SBi3RXplXAWktFm8eHEKK6R+nJbON1JYwIJrbRlnBm4asMKiT+CCDZDKo02bNrJs2bJc9fvQfyNffvmlrF27Vn755ReVtgSWEKSh0Y9T7+vGfQhDeMZtkV4ZV8NefSCn9JP0SO26AIzbIr0yroBuvYdRYvPmzSYpziAmsWzpOPW+YE0ZYjsUkXbs4MY+TgCWFlgi9RsDLBDIaaffNAFuArt27ZLWrVtbXcbZgcUJP1jjHytubvDd03Ny4ViwbsuWLYYyS5cuVU/GTZo0sbqMqwChAB+k/v37p/gMxwkxYdx/cJwtWrQwPGlbU8aZ0c/7xYsXTdbDKql/Bss1fivGfR85Nnfu3Gno+9aUcQV0S5DOt99+q6zUdevWVcutWrVSwhK+o8bnGwIcLiLWlnE1cPzwFzY+v7Daop/o59deZXICOBbk0IVvtHEfwO9Ez09sTRlXGMno1auXHDx4UN0PLIlfHOfy5csNw9IwPmDZ+HxbU4bYiI2BOCQVkHMLOd+++uorlYsM0dXIGYmoLz2liR6ViKi48ePHq/cNGjTQatSoYZIrz5oyzs7bb7+tcpL98ssv2vLly7UWLVqo5Xv37hnKIJIdUbVIr4CIPKTfGDVqlMl2rCnjCtSpU0fr1auXxc+QcgR5/jp37qwtW7ZMGzp0qIpS/Ouvv2wq4+w8/fTTKssA0mysWbNGGzx4sOrn27dvN5RBX8G6sWPHqr6PvJr4XRlHllpTxtlBlKjeDkjRkzdv3hTn8oUXXlDthXQmP/zwg8p3+M4779hcxpk4ffq0Ot+IyMftZ926dWpZT2cGEHGLDAZTp07VFi5cqNKdIbWXMfYq40j27Nmjjr1NmzZa/fr11fudO3faVAaZO5AaCveHxYsXa59//rnm6empzZo1y6YyjgRR8zguvLy8vFSWBbw3Tmn3zDPPqHOJ+4BeFi9kQdE5ceKEikJ/9tln1TUSGS2QG9PWMsQ28uCPrcKTSKozaMC/C+b2QoUKKUsBLE/mMzLAcjBjxgzl1IyoUgQaYKjX1jLODLoV/CB///139bSHiLg33njD5BhgvUV7wcIG68mTTz4pzz77rIlvijVlXCHQCMMwEydOlEaNGlksAwvdxx9/rKIOEZH+2muvGaxStpRxZnAu4Re6adMmNTtFhQoV5KWXXpJHH300RXT/9OnTlYUR/WbEiBEqWMTWMs4MZsvAuTx16pSahWTo0KFSvnz5FNYXWChXrVqlriFdu3aV559/3uR6Yk0ZZ2Ly5Mny22+/pVj/6aefqtlrjLNcIPAKWR2aNm2q/Kl1f1F7l3EUOFfms1nBPQV925YymI1n0qRJyhqPoEPcc7p162byHWvKOIp///1XBdiZg6CZDz74QL1v3769xSwUffv2VdcQHczI8/nnnytffMxq8/bbb6ssKcZYU4ZYD0UkIYQQQgixGed8XCWEEEIIIU4NRSQhhBBCCLEZikhCCCGEEGIzFJGEEEIIIcRmKCIJIYQQQojNUEQSQgghhBCboYgkhBBCCCE2QxFJiBVJshcsWKASZNsyrR2+oyfINV92RpB8F1OAEZJTwLSQCxcuVBMeZBR8F9vAtgghpjDZOCHpAPGImXYw96757CqpgdllSpcurWaXwVzo5svOyI8//ihjxoxRMy/ps6pgznZLYLYLHx+fbK4hIcksWbJE6tWrJ6VKlUqzSSZMmKBmRMED3NatW8XT09NkZpz79+/LihUr1KxBxvNIX7lyRZXHjDGY4aZHjx5Ss2ZNeffdd3kKCDGClkhCsgE/Pz819WFgYKDLtPf27dvlmWeeUVNX/vHHHyYvWmWIIxk4cKDs3r073Yc/TC353nvvqeVly5apKSGNwfSB6OOjR482WT9nzhx5+eWX1RSDYOTIkWra0nv37tn9WAhxZTwcXQFCnJHjx4+rV6VKldQ81ZaIjIxUljrMUwwLZZEiRVLdHqwZsN5h3lrdkoLvYP5oYzCvMCwe+vq09oGh8ZUrVyprycmTJ9Wrfv36BuvMmTNnlBWmcOHCUqtWrRSWQwzTYS5dzKubloV13rx5at5yS8CyGhERIc2bN5dDhw7JtWvX1L6KFy+eomxa9dG306xZM3W82A6sP5gjHdaibdu2SUBAgGobzH0LcKzh4eFy4cIFNbeuMdjPpUuXpF27dinqAQtTUFCQlCtXTlmX4a7QpEmTFO2D9X/++aeav75atWoSHBxs+Az7xHfR9iAxMVEWLVokoaGhUr16dbUO82LjnOh1w3zy+/fvV8eJebJr1KhhsV5ly5ZVAsnLy0tatmyZov7YxokTJ9R7zBeO7RQtWtTqbaFd/v77b9VP8N3Vq1er40OdMBf34sWLpXXr1lKwYEHD9iDAYKnT5/fGsT7++OPqmP755x+1rwYNGqjzdfr0aTl8+LBqX/NjBHgAwTnGecV+Uc4YfdvYFvpUvnz5lNVRnw8cfR7nBn0X9YXQ6969u0XLOuZE1i2M6KNffPGF6lt6e23evFm1C9oLx4J96uvRF/V9or+WKVNGictXX301xb4IybVohBATxo4dq/n4+GitWrXSqlatqrVv317DT+XAgQOGMr/88ouWL18+rVmzZlrbtm21oKAg7fvvvzd8HhERob5z9OhRi8udOnXSBgwYYLLf/fv3qzLHjx+3ah/YFspjW6hnjx49tN27d2uJiYnaCy+8oBUqVEjr2LGjVqtWLa18+fLaoUOHDN+Njo7WGjdurBUpUkQdX4kSJbSWLVtqZcuWNZSZO3eu2n58fHyqPWTixIlamTJltNq1a2stWrTQGjVqpPn5+Wnr1683lLGmPvp2Hn30Ua1p06ba008/rb7377//akWLFtVCQkK01q1ba+XKldNq1qxpaLvt27dr7u7u2uXLl03q9dhjj2mvvfaaxTqjLfF56dKl1XvUpVKlSuoc6Zw6dUqtq1ixoirj7++vDR482PD5wYMHtTx58mg3b95Uy2h3tFXnzp0NZV566SWtZ8+e6v21a9e0Bg0aaBUqVNC6dOmijqN58+bavXv3TOqFMqhPhw4dtNGjR1us/w8//KDaBy9sA3WbOXNmimO0tK158+Zp3t7e6jzh83r16mkFCxbUZs+erT6PiopSx7Fr1y6T7ZUsWdJQBmAb6D9oH/S/wMBAdVzvv/++FhwcrM5zQECAWjYG7YS+VqdOHVWmQIEC2htvvGFSBttGn9S3jT6K40lKSlKfDx06VPP09NQef/xx1QboW5ZAf37rrbcMy5GRkaqvLFiwwLCuRo0a2pIlS7TChQurcwrQ31H3r776ymR7r776qqoHIeQ/KCIJMeLw4cOam5ubtm7dOrUMIfPEE0+YiMhjx46pG/eff/5p+N7OnTuV8Dx9+rRVInL+/Pla3rx5tQcPHhi28eabb2p169a1eh+6iOzXr5/hBgu++OILLSwsTLt7965h3YgRI5TQ05kwYYISjDdu3FDLV65c0YoVK2ZRREJ4oL76a9myZSbiD2VWr15tIp4aNmxoU3307SxatMikP0KYQpwkJCSo5W3btqlyxgIcAhrf19HbRRcF5kAIQIToIjYmJkYJkt69exvKtGvXTmvTpo0WFxenlnHu8Z2lS5eqZbQ3xNdvv/2mlj/++GMljCD60WcAhO8333yj3uMY+vfvbziO2NhYrUmTJuqcG9cL4uXMmTOaLaxdu1b1lVu3bqW5LYio/Pnza5MmTTKsGz9+vGqrjIhICFgcB9i8ebP6HtpNf+hYvny5ajP9vOPBBX1sxowZhu1cvHhRtSPKGm8bDxI4L+DChQual5eX4TcJ8EBl3lfMwbFCcBsD0fziiy+q93gAgKjE/yeffFKbPHmyWo/fHI4F1wJjpk+frkQvIeQ/6BNJiNlQGoYjMZwHMJz11ltvpRjeLVasmBoWRHlEbiJwBkPVGGKzBn0YVI+GxtAynP/79u1r8z4wvKYPw4HZs2erY1i/fr3huxgS3Ldvn8GnC+uee+45KVSokFrGvvr372+xrkuXLjXxh1y3bp3J5xjiNB42xjAgXAFsqQ/AEPhTTz1lWL5z545s2rRJhg0bJu7u7mpd48aN5bHHHjPZ/6BBg9Q+dGbNmiW1a9e2OJSq07ZtW8OwM4ZDhw4dqoZxcR5QpzVr1qjzjkAMgOH+jh07qnME0N6oC4Y9wZYtW+TFF19UbgsY5saQ6bFjx1Rb3Lp1S51nDK3CvxRtgHbEMK7+fZ3OnTsbhozTAtvEd9GWGG6Pi4tTQ/tpbQv+fw8ePJAhQ4YY1hm3ra2g/2CYHGAoW/dV1F0fsA7Dzoj6B2hT1Bt+wWhrtAP6Mupo3g7Yju6PiIA0uHcY96n0gHsB+g8C4ozBkLa+LwxhI6AGw/ZNmzY1rMd/uI3gM2OwLWwzM5HehOQ06BNJiBHwdTP30TK/qSN6OSYmRt0IjWnRokWKm1ZqQGzAjwtisWfPnkqEQHj06tXL5n2Y+x/iuwjkMf8uAnuQagg3cWuO0xqfSN0vzxjc/FF3W+pj6TjgOwjM62m+PGDAABU1u2PHDiUw586dK6NGjUq1vpa2gWOHELt69arcvHlTrTP3V61YsaJJtDoE4owZM5RggRiaOnWqQYxA+MDvrmrVqsqHEaM+e/bsUb6axpgLYku+pOZ8//338uabbyqRA39dXchdv349zW2hPbFOF2cA5wU+qhnBuB/q27S0Tu8L6AeoK/yBzdvVvK3T61PpAWGMY4O/r7mI/OSTT+Ty5csGv0eA84Y+A4GI9ShnDraFbep+koQQikhCTIBVAgEgxsD6YAxEDywVulUqo8DqCOsWLEkQarB+6oEztuzD2Aqpf7dDhw5pCikcp/lxmS/bC2vqY+k4dCGBKFsEiBjXE5ZTHVhTYdn94YcflKULwUi9e/dOc1+Wjh37NxYvOC8QOMbLuuUWQIC88cYbKjAFgSUIwsI6WB0hIiFM9OMHsGwigMeWNjAHlr3XXntNCWUEHgGIX1j1IFTT2haOzVKuU+N1ukAyt7bZIuBSA+2A+qPuuoU3K0EglG4F1WnUqJHaN4QiXuPHj1frEXyDY4fQRzDV5MmTU2wP26pSpUqW15sQV4KPVISY3WQQzQqLlI655QRDtwcPHkyRYgTixdzykRawKkKU4KaKqGx9KDuz+8B3f/rpJ2XlMwZD48bHiSFVHQgQDLVmBdbUxxIlS5ZU4hHD6cZCDhZHc5C6BUIK1sAnnnhCDZenBYbkMbRrfI4xBI4IbVj3YBkzPu8ou2rVKtVuOhgOhzAbO3aswaKF/0iNhGF4fR3EJV7fffddinqk1wbmQOwiutlYzKCe1gyxwuqJoXpEuuusXbvWRCDC0oa2Q2S5DqynEOeZpVWrVspqa+x6ACAsb9y4YdO2EKmfnrBF1LW564e/v7/UrVtXuQEgI4Au6nX3BKQEwrm2ZInEtnAMhJD/4HA2IUZ06dJF3WRwA0KeOAwBQgAZ06lTJ+U/CL86+JdhKBQ3JIgdDEvjRmXtkBuGr99//321jBRA9tgHEizjhoi0KPAtwxAihmExTAu/NIB9Im0JfBDbtGmjEi7D0mJpu7jhmg/hmaeASQtr6mMJ3NiRmw/D1RDOGILGUC6EnrmVDfWBIIff34YNG9KtE0QXzjG2DZE0c+ZMZVHUmTJlijz55JNK+CI5PNLFYKjWOL2LLjwgxgcPHqzWQdyhDZGCRxeRup8mrM7wU8S5xcMARClEiX7+rQHWafgawg8UPpgYIkabWGPZg5B94YUXlAXznXfeUW0wbdo05Vph3J7od8iLCMEJwYq62yOxPM7fp59+qtoQqXvQ/+BWATcHiH88VFlLnTp1ZPr06erhB4LSUoofPFjAlxUC1XjIHvv58MMP1UOAcR+G5Rg+okh9ZJzOCcDVBOL7m2++yfDxE5IToYgkxAjcTCFscHNFLj3ceCF4RowYYeLvBVGBmzHKYvgL+e4wFKYPh5onF08t2TjEAPyzcFNEGWPS2we2hW2afw++eAjuQE47HANEDYQLfC91MEy7d+9eZR2Dzx6GgzE0a2x9w00f20eOQHNQX9yAkRcRQtcY3ISNA2SsqY+l7QAkgsZ+YGXEsC382SA49HybxucNDwCoqzVi5Nlnn1UCEKITYgoCQQ8OARB7aHO4GeD8Q1D+73//U4LLfDvw19MDsQCECPIkwh9SBxYv5LfEAwm2C99EDKWiDjoQnRgGTw+IXfRPWDyxHfyHODP+bmrb+vrrr5XFFVY1nCf0LeTbNG5P5FJE3XGuYA3GA8bnn39u4jOLc4fPjEFfMXYzgLDFOmMXALQNrLkQjqg3fl8Y/jf2ibS0bVizjWd6gnCGoEOwFgSuJRGJY0C/RjnjZOIoi/yd5v0E+Tzx+0J7mPPtt9+q7yE4ihDyH5z2kBDitMBfD/6GuqUMFjwIYIgaWBF1YJGCaIBLQHqWPQgSCPLPPvtMchtwBzD2+4RrAIQsrNDmwUY5AVg6YQmHCMxoQAweMl566SX54IMPrBL5hOQmaIkkhDgtSOuCoVdYAiEUMewMC5qxFROWLFjnMGwJFwSSOvBHhFUYQ7fw+/3yyy/VEHdOFJAAs8xg2DszQHzC8kkISQktkYQQpwbDyRhmxzSPyP1onEMQwGcUvnvwEcRQbXogeAIWpT59+khuA0IcPq7IkYg2xDC7paFgQgixBopIQgghhBBiM0zxQwghhBBCbIYikhBCCCGE2AxFJCGEEEIIsRmKSEIIIYQQYjMUkYQQQgghxGYoIgkhhBBCiM1QRBJCCCGEEJuhiCSEEEIIITZDEUkIIYQQQsRW/g+BFZmbcswchQAAAABJRU5ErkJggg==", + "text/plain": [ + "
" ] + }, + "metadata": {}, + "output_type": "display_data" }, { - "cell_type": "markdown", - "id": "cell-07", - "metadata": {}, - "source": [ - "Try the chapter exercise in `exercises/ch07/exercise.ipynb`: sweep brew duration (60\u2013180 s) at fixed power and efficiency for the coffee maker model and find the minimum duration that meets a 40 kJ energy threshold.\n" - ] + "name": "stdout", + "output_type": "stream", + "text": [ + "At the 600 W threshold: 50.4 kJ\n", + "At rated's own 800 W: 67.2 kJ\n" + ] } - ] -} \ No newline at end of file + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "threshold_energy_j = model.eval(\n", + " f\"ToasterDemo::rated.deliveredEnergy({power_threshold_w} [SI::W], {duration_s} [SI::s])\"\n", + ").magnitude\n", + "\n", + "power_unit = rated_power.unit.text.split(\"::\")[-1]\n", + "energy_unit = model.eval(\n", + " f\"ToasterDemo::rated.deliveredEnergy({power_threshold_w} [SI::W], {duration_s} [SI::s])\"\n", + ").unit.text.split(\"::\")[-1]\n", + "\n", + "fig, ax = plt.subplots(figsize=(6, 4))\n", + "ax.plot(power_values_w, delivered_j / 1000, label=\"deliveredEnergy (model)\")\n", + "ax.axvline(\n", + " power_threshold_w, color=\"red\", linestyle=\"--\",\n", + " label=f\"HeatGenerationReq threshold ({power_threshold_w:.0f} {power_unit})\",\n", + ")\n", + "ax.set_xlabel(f\"deliveredEnergy power argument ({power_unit})\")\n", + "ax.set_ylabel(f\"Delivered energy (k{energy_unit})\")\n", + "ax.set_title(\n", + " f\"deliveredEnergy vs. power (t={duration_s:.0f} s assumed, \"\n", + " f\"\\u03b7=rated's own bound value)\"\n", + ")\n", + "ax.legend()\n", + "fig.tight_layout()\n", + "plt.show()\n", + "\n", + "print(f\"At the {power_threshold_w:.0f} {power_unit} threshold: {threshold_energy_j / 1000:.1f} k{energy_unit}\")\n", + "print(f\"At rated's own {float(rated_power.magnitude):.0f} {power_unit}: \"\n", + " f\"{delivered_j[np.argmin(np.abs(power_values_w - float(rated_power.magnitude)))] / 1000:.1f} k{energy_unit}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-11", + "metadata": {}, + "source": [ + "Delivered energy from `HeatGenerator::deliveredEnergy`, evaluated by the model across a sweep of the calc's own free `power` input, at this chapter's own assumed duration and `rated`'s own bound efficiency; the vertical line marks `HeatGenerationReq`'s own 600 W threshold on `HeatGenerator::power`, read from the model. It does not derive cycle time or efficiency, and it says nothing about toast quality or user acceptance." + ] + }, + { + "cell_type": "markdown", + "id": "cell-12", + "metadata": {}, + "source": [ + "The threshold read from the model, the sweep computed through `model.eval`, and the plot rendered above are the same model, queried three different ways: nothing here is written to a file." + ] + }, + { + "cell_type": "markdown", + "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." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/chapters/ch07-execution/conclusion.md b/chapters/ch07-execution/conclusion.md index 735b77b..e902b65 100644 --- a/chapters/ch07-execution/conclusion.md +++ b/chapters/ch07-execution/conclusion.md @@ -2,16 +2,16 @@ ## What we built -The cumulative model now includes a `state Cycle` with four substates and three transitions. The `DeliveredEnergy` calc def is bound to a sympy expression and evaluated by lambdify, confirming the 67200 J reference value. A parameter sweep over heater power (500–1200 W) produces a matplotlib figure with the design threshold marked. +The cumulative model now has `deliveredEnergy`, a calc on `HeatGenerator` with a real, bounded `efficiency` slot (`0 <= efficiency <= 1`), and `rated`'s own concrete efficiency value. `efficiency` is not a free parameter of the calc: it is always the invoking carrier's own bound value, so the relation can never be evaluated against an efficiency `efficiencyBounded` does not also cover; `power` and `duration` stay free, queryable inputs, since exploring the power design space against `HeatGenerationReq` is this chapter's own point. `Cycle` is a real `state def`, exhibited by `ToastingSystem`, the abstract subject, as `cycle`, inherited and executable through `Toaster` and any usage of it; its `heating` state has a `do action` that invokes `GenerateHeat`, the level-2 function Chapter 6 built (`GenerateHeat` itself has no body yet, so nothing is computed; what changes is which mode the machine is in); and `ready` and `cancelled` both transition back to `idle`, so a run completes and the machine is ready for another. A parameter sweep evaluates `deliveredEnergy` across a range of its own `power` input, at every point through the model rather than a formula rebuilt in Python, and marks `HeatGenerationReq`'s own 600 W threshold on `HeatGenerator::power`, read from the model. ## What this establishes -The execution and simulation results show that the toaster model is behaviourally consistent: normal and cancel scenarios follow distinct state paths, and the nominal heater (800 W) delivers well above the 50 kJ threshold. The sympy binding proves that the calc def formula is correctly expressed — lambdify and `model.eval()` agree to within 1 J. +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). ## What comes next -Chapter 8 turns the simulation results into formal engineering verdicts by calling `verify_satisfaction()` on the model's `assert satisfy` declarations and recording violation witnesses as ReviewRecords. +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. ## Exercise -See `exercises/ch07/exercise.ipynb`: bind the coffee maker's brew energy formula to sympy, sweep duration, and find the minimum duration that meets a 40 kJ threshold. +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. diff --git a/chapters/ch07-execution/index.md b/chapters/ch07-execution/index.md index 0afd4df..ba29e8f 100644 --- a/chapters/ch07-execution/index.md +++ b/chapters/ch07-execution/index.md @@ -1,31 +1,31 @@ -# Chapter 7 — Execution and Experiments +# Chapter 7: Execution and Experiments ## Purpose -This chapter asks: does the toaster model behave correctly when we execute it, and does the HeatingSystem deliver enough energy across the operating range? +This chapter asks: what does the model actually do when it is executed, and what does the design space `HeatGenerationReq` opens actually deliver? -After completing this chapter, the cumulative model has a `state Cycle` that captures the toaster's discrete operating modes, a sympy-bound energy expression checked against the 67200 J reference value, and a matplotlib figure showing energy vs. heater power with the design threshold marked. +After completing this chapter, the cumulative model has `deliveredEnergy`, a calc on `HeatGenerator` bounded by a real efficiency constraint, and `Cycle`, a state def `ToastingSystem` exhibits, inherited and executable through `Toaster`, with a `heating` state whose `do action` invokes the heat-generation step and transitions that return `ready` and `cancelled` to `idle`. ## Ingredients | Notebook | Concept | |---|---| -| [01 — Symbolic energy binding](01-calc-energy.ipynb) | Bind `DeliveredEnergy` to a sympy expression; verify the 67200 J reference value with lambdify and `model.eval()`. | -| [02 — State machine traces](02-state-traces.ipynb) | Introduce `state Cycle` with transitions (construct 13); simulate normal and cancel scenarios with `execute_state`. | -| [03 — Parameter sweep](03-param-sweep.ipynb) | Sweep heater power with `sweep_1d`; plot energy vs. power and mark the design threshold. | +| [01: Delivered energy on the heat generator](01-calc-energy.ipynb) | Add a bounded `efficiency` and `calc deliveredEnergy` to `HeatGenerator`; query the relation and the bound through `model.eval` and `verify_constraint`. | +| [02: The toaster's own operating cycle](02-state-traces.ipynb) | Build `Cycle` as a real `state def`; have `ToastingSystem`, the abstract subject, exhibit it; give `heating` a `do action`; add transitions that return `ready` and `cancelled` to `idle`; trace the result with `execute_state`. | +| [03: Sweeping the design space HeatGenerationReq opens](03-param-sweep.ipynb) | Sweep `deliveredEnergy`'s own free `power` input, and mark `HeatGenerationReq`'s own 600 W threshold on `HeatGenerator::power`, read from the model. | ## Equipment -See [docs/setup.md](../../docs/setup.md) for environment setup. Chapter 7 requires `sympy`, `numpy`, and `matplotlib` (all in `pyproject.toml`). +See [docs/setup.md](../../docs/setup.md) for environment setup. Chapter 7 requires `numpy` and `matplotlib` (both in `pyproject.toml`). ## Method -Notebook 01 establishes the sympy binding and reference value — the anchor for all downstream numerical claims. Notebook 02 introduces the state machine and shows that `execute_state` correctly routes two distinct event sequences. Notebook 03 uses the established binding with `sweep_1d` to produce simulation evidence for the energy requirement. +Notebook 01 builds `HeatGenerator` a bounded `efficiency` slot and a `calc deliveredEnergy` that characterizes what the carrier actually delivers, with `efficiency` resolved from the carrier's own bound value rather than passed as a free argument. Notebook 02 gives `Cycle` an owner on the abstract subject, a `heating` state whose `do action` invokes a real function, and transitions that complete what the chapter's own name promises. Notebook 03 connects the two: it sweeps the design space `HeatGenerationReq` opens and checks it against the relation notebook 01 built. ## Expected result -After running all three notebooks, `model.find("ToasterDemo::Cycle")` returns a symbol with `kind='stateDef'` or equivalent. `execute_state(cycle.id, events=['Start', 'Finish'])` returns `states_visited=['idle', 'heating', 'ready']`. `Q_fn(800.0, 120.0, 0.7)` returns 67200.0. The parameter sweep figure shows the energy curve crossing the threshold between 590 W and 600 W. +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. ## Experiment -Try the [Chapter 7 exercise](../../exercises/ch07/exercise.ipynb): adapt the symbolic binding and sweep for the coffee maker's brew energy formula. +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. diff --git a/chapters/ch08-checking/01-invariant-def.ipynb b/chapters/ch08-checking/01-invariant-def.ipynb index ed9c30a..ea631a8 100644 --- a/chapters/ch08-checking/01-invariant-def.ipynb +++ b/chapters/ch08-checking/01-invariant-def.ipynb @@ -1,129 +1,370 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - } + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": [ + "## Ch8-01 -- A hand-restated lemma of the same shape\n", + "\n", + "This notebook states one new SysML construct, `deliveredEnergyBoundedBySupply`, a real-arithmetic lemma of the same shape as `HeatGenerator`'s own conservation entailment; after running it you can confirm the construct is really in the loaded model." + ] }, - "cells": [ - { - "cell_type": "markdown", - "id": "cell-00", - "metadata": {}, - "source": [ - "## Ch8-01 \u2014 Satisfaction evaluation\n", - "\n", - "This notebook introduces `verify_satisfaction()` to evaluate `assert satisfy` declarations; after running it you can confirm which design variants satisfy the TimelyToast requirement and which do not.\n" - ] - }, - { - "cell_type": "markdown", - "id": "cell-01", - "metadata": {}, - "source": [ - "Chapter 3 declared `assert satisfy timely by nominal` and `assert satisfy timely by slow`. This notebook calls `verify_satisfaction()` to evaluate both declarations computationally, producing `Verdict` objects that report whether each candidate holds. See [Ch3-01 MoE definition](../ch03-measures/01-moe-definition.ipynb) for the requirement and satisfy declarations.\n" - ] - }, + { + "cell_type": "markdown", + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "cell-02", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T12:47:55.823657Z", + "iopub.status.busy": "2026-09-28T12:47:55.823511Z", + "iopub.status.idle": "2026-09-28T12:47:55.949472Z", + "shell.execute_reply": "2026-09-28T12:47:55.948972Z" + } + }, + "outputs": [ { - "cell_type": "code", - "id": "cell-02", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from pathlib import Path\n", - "import opensysml\n", - "from toaster.report import format_diagnostics\n", - "\n", - "conn = opensysml.connect(version=\"v0.9.0\")\n", - "source = Path(\"../../models/ch08-cumulative.sysml\").read_text()\n", - "print(source)\n", - "model = conn.load_from_content(source, strict=False)\n", - "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + " part heatGenCheck : HeatGenerator;\n" + ] + } + ], + "source": [ + "from pathlib import Path\n", + "import opensysml\n", + "from toaster.report import format_diagnostics\n", + "\n", + "conn = opensysml.connect(version=\"v0.9.0\")\n", + "\n", + "# A fresh, unbound usage of HeatGenerator: no efficiency, no power. Its own two\n", + "# features stay free so the lemma below is about every value they could take,\n", + "# not one candidate's fixed choice.\n", + "HEAT_GEN_CHECK_USAGE = \" part heatGenCheck : HeatGenerator;\"\n", + "print(HEAT_GEN_CHECK_USAGE)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-03", + "metadata": {}, + "source": [ + "`heatGenCheck` adds nothing to the model but an unbound usage of `HeatGenerator`. It is not a design candidate the way `rated` or `weak` are; it exists only so the lemma below has a subject whose `efficiency` and `power` are still free." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "cell-04", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T12:47:55.951070Z", + "iopub.status.busy": "2026-09-28T12:47:55.950843Z", + "iopub.status.idle": "2026-09-28T12:47:55.953634Z", + "shell.execute_reply": "2026-09-28T12:47:55.953187Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-03", - "metadata": {}, - "source": [ - "The `ch08-cumulative.sysml` file is identical to the Chapter 7 model. Chapter 8 introduces analysis operations \u2014 `verify_satisfaction()`, violation witnesses, and stale record detection \u2014 not new SysML constructs. The `assert satisfy` declarations for `TimelyToast` and `HeatingReq` are the targets of Chapter 8's bounded checks." - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + " attribute heatGenCheckDuration : ISQ::DurationValue;\n" + ] + } + ], + "source": [ + "# duration is deliveredEnergy's other free parameter; a fresh unbound attribute\n", + "# stands in for it the same way heatGenCheck stands in for efficiency and power.\n", + "CHECK_DURATION_ATTR = \" attribute heatGenCheckDuration : ISQ::DurationValue;\"\n", + "print(CHECK_DURATION_ATTR)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-05", + "metadata": {}, + "source": [ + "`heatGenCheckDuration` is not owned by `HeatGenerator`: `deliveredEnergy`'s `duration` is only ever a calc parameter, not a feature of the carrier, so a separate top-level attribute stands in for some duration the same way `heatGenCheck` stands in for some heat generator." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "cell-06", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T12:47:55.955079Z", + "iopub.status.busy": "2026-09-28T12:47:55.954960Z", + "iopub.status.idle": "2026-09-28T12:47:55.957719Z", + "shell.execute_reply": "2026-09-28T12:47:55.957173Z" + } + }, + "outputs": [ { - "cell_type": "code", - "id": "cell-04", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# A require constraint referencing an undefined attribute fails.\n", - "bad_source = \"\"\"\n", - "package P {\n", - " private import ScalarValues::*;\n", - " part def Thing { attribute x : Real default = 5.0; }\n", - " requirement def Check {\n", - " subject t : Thing;\n", - " require constraint { t.undeclared <= 10.0 }\n", - " }\n", - "}\n", - "\"\"\"\n", - "bad = conn.load_from_content(bad_source, strict=False)\n", - "assert not bad.ok, \"Expected failure for undeclared attribute in constraint\"\n", - "# Expected: diagnostic for 'undeclared' as an unresolved attribute reference\n", - "print(f\"Negative control ok: bad.ok={bad.ok}\")\n" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + " assert constraint deliveredEnergyBoundedBySupply {\n", + " doc /* A real-arithmetic lemma of the same shape as the relation\n", + " * efficiencyBounded (0 <= efficiency <= 1) and deliveredEnergy's own\n", + " * definition (power * duration * efficiency) together would imply:\n", + " * given efficiency in [0,1] and non-negative power and duration,\n", + " * power * duration * efficiency never exceeds power * duration.\n", + " * Restated by hand on a fresh, unbound usage (heatGenCheck) rather\n", + " * than a solver-checked reference to HeatGenerator's own\n", + " * efficiencyBounded and deliveredEnergy: this toolchain's Z3 backend\n", + " * does not compose two separately declared assert constraints,\n", + " * whether sibling or inherited (D-030), and cannot reason through a\n", + " * chained calc invocation such as heatGenCheck.deliveredEnergy(...)\n", + " * (D-031). Proved by Z3 over the unbound heatGenCheck.efficiency,\n", + " * heatGenCheck.power and heatGenCheckDuration features\n", + " * (verify --solve): this restated lemma holds for all such values,\n", + " * but the proof does not track HeatGenerator's own efficiencyBounded\n", + " * or deliveredEnergy if either changes; a content-hash-based record\n", + " * against this file does go stale when either changes (any edit to\n", + " * the file changes the hash), which is a partial safeguard, not a\n", + " * check that the restated copy stays in sync. */\n", + " (heatGenCheck.efficiency >= 0.0 and heatGenCheck.efficiency <= 1.0\n", + " and heatGenCheck.power >= 0.0 [SI::W] and heatGenCheckDuration >= 0.0 [SI::s])\n", + " implies (heatGenCheck.power * heatGenCheckDuration * heatGenCheck.efficiency)\n", + " <= heatGenCheck.power * heatGenCheckDuration\n", + " }\n" + ] + } + ], + "source": [ + "DELIVERED_ENERGY_BOUND = \"\"\"\\\n", + " assert constraint deliveredEnergyBoundedBySupply {\n", + " doc /* A real-arithmetic lemma of the same shape as the relation\n", + " * efficiencyBounded (0 <= efficiency <= 1) and deliveredEnergy's own\n", + " * definition (power * duration * efficiency) together would imply:\n", + " * given efficiency in [0,1] and non-negative power and duration,\n", + " * power * duration * efficiency never exceeds power * duration.\n", + " * Restated by hand on a fresh, unbound usage (heatGenCheck) rather\n", + " * than a solver-checked reference to HeatGenerator's own\n", + " * efficiencyBounded and deliveredEnergy: this toolchain's Z3 backend\n", + " * does not compose two separately declared assert constraints,\n", + " * whether sibling or inherited (D-030), and cannot reason through a\n", + " * chained calc invocation such as heatGenCheck.deliveredEnergy(...)\n", + " * (D-031). Proved by Z3 over the unbound heatGenCheck.efficiency,\n", + " * heatGenCheck.power and heatGenCheckDuration features\n", + " * (verify --solve): this restated lemma holds for all such values,\n", + " * but the proof does not track HeatGenerator's own efficiencyBounded\n", + " * or deliveredEnergy if either changes; a content-hash-based record\n", + " * against this file does go stale when either changes (any edit to\n", + " * the file changes the hash), which is a partial safeguard, not a\n", + " * check that the restated copy stays in sync. */\n", + " (heatGenCheck.efficiency >= 0.0 and heatGenCheck.efficiency <= 1.0\n", + " and heatGenCheck.power >= 0.0 [SI::W] and heatGenCheckDuration >= 0.0 [SI::s])\n", + " implies (heatGenCheck.power * heatGenCheckDuration * heatGenCheck.efficiency)\n", + " <= heatGenCheck.power * heatGenCheckDuration\n", + " }\"\"\"\n", + "print(DELIVERED_ENERGY_BOUND)" + ] + }, + { + "cell_type": "markdown", + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "cell-08", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T12:47:55.959062Z", + "iopub.status.busy": "2026-09-28T12:47:55.958956Z", + "iopub.status.idle": "2026-09-28T12:47:55.978662Z", + "shell.execute_reply": "2026-09-28T12:47:55.978167Z" + } + }, + "outputs": [ { - "cell_type": "code", - "id": "cell-05", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# Evaluate all assert-satisfy declarations in the model\n", - "verdicts = model.verify_satisfaction()\n", - "print(f\"Verdicts returned: {len(verdicts)}\")\n", - "for v in verdicts:\n", - " status = \"PASS\" if v.holds else \"FAIL\"\n", - " print(f\" [{status}] {v.element}\")\n", - "\n", - "# Check the TimelyToast verdicts specifically\n", - "timely_verdicts = [v for v in verdicts if \"timely\" in (v.element or \"\").lower()]\n", - "nominal_verdict = next((v for v in timely_verdicts if \"nominal\" in (v.element or \"\")), None)\n", - "slow_verdict = next((v for v in timely_verdicts if \"slow\" in (v.element or \"\")), None)\n", - "\n", - "assert nominal_verdict is not None, \"Nominal verdict not found\"\n", - "assert slow_verdict is not None, \"Slow verdict not found\"\n", - "assert nominal_verdict.holds is True, f\"Expected nominal to hold: {nominal_verdict}\"\n", - "assert slow_verdict.holds is False, f\"Expected slow to fail: {slow_verdict}\"\n", - "\n", - "print(f\"\\nnominal holds={nominal_verdict.holds} (cycleTime=120 \u2264 180)\")\n", - "print(f\"slow holds={slow_verdict.holds} (cycleTime=200 > 180)\")\n", - "conn.close()\n" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + " part heatGenCheck : HeatGenerator;\n", + " attribute heatGenCheckDuration : ISQ::DurationValue;\n", + "\n", + " assert constraint deliveredEnergyBoundedBySupply {\n", + " doc /* A real-arithmetic lemma of the same shape as the relation\n", + " * efficiencyBounded (0 <= efficiency <= 1) and deliveredEnergy's own\n", + " * definition (power * duration * efficiency) together would imply:\n", + " * given efficiency in [0,1] and non-negative power and duration,\n", + " * power * duration * efficiency never exceeds power * duration.\n", + " * Restated by hand on a fresh, unbound usage (heatGenCheck) rather\n", + " * than a solver-checked reference to HeatGenerator's own\n", + " * efficiencyBounded and deliveredEnergy: this toolchain's Z3 backend\n", + " * does not compose two separately declared assert constraints,\n", + " * whether sibling or inherited (D-030), and cannot reason through a\n", + " * chained calc invocation such as heatGenCheck.deliveredEnergy(...)\n", + " * (D-031). Proved by Z3 over the unbound heatGenCheck.efficiency,\n", + " * heatGenCheck.power and heatGenCheckDuration features\n", + " * (verify --solve): this restated lemma holds for all such values,\n", + " * but the proof does not track HeatGenerator's own efficiencyBounded\n", + " * or deliveredEnergy if either changes; a content-hash-based record\n", + " * against this file does go stale when either changes (any edit to\n", + " * the file changes the hash), which is a partial safeguard, not a\n", + " * check that the restated copy stays in sync. */\n", + " (heatGenCheck.efficiency >= 0.0 and heatGenCheck.efficiency <= 1.0\n", + " and heatGenCheck.power >= 0.0 [SI::W] and heatGenCheckDuration >= 0.0 [SI::s])\n", + " implies (heatGenCheck.power * heatGenCheckDuration * heatGenCheck.efficiency)\n", + " <= heatGenCheck.power * heatGenCheckDuration\n", + " }\n", + "\n" + ] + } + ], + "source": [ + "TOASTER_INCREMENT = f\"{HEAT_GEN_CHECK_USAGE}\\n{CHECK_DURATION_ATTR}\\n\\n{DELIVERED_ENERGY_BOUND}\\n\"\n", + "print(TOASTER_INCREMENT)\n", + "\n", + "source = Path(\"../../models/ch08-cumulative.sysml\").read_text()\n", + "model = conn.load_from_content(source, strict=False)\n", + "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" + ] + }, + { + "cell_type": "markdown", + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "cell-10", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T12:47:55.980269Z", + "iopub.status.busy": "2026-09-28T12:47:55.980142Z", + "iopub.status.idle": "2026-09-28T12:47:55.987519Z", + "shell.execute_reply": "2026-09-28T12:47:55.987116Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-06", - "metadata": {}, - "source": [ - "The `assert satisfy timely by nominal` and `assert satisfy timely by slow` declarations (A-F) are evaluated by `verify_satisfaction()` in OpenSysML (O-S); the Verdict objects show `nominal` holds=True and `slow` holds=False (E).\n" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "model.find(): deliveredEnergyBoundedBySupply (constraintUsage)\n", + "model.query() sees it too, among 3 named ConstraintUsage elements.\n" + ] + } + ], + "source": [ + "sym = model.find(\"ToasterDemo::deliveredEnergyBoundedBySupply\")\n", + "assert sym is not None, \"deliveredEnergyBoundedBySupply not found in the loaded model\"\n", + "print(f\"model.find(): {sym}\")\n", + "\n", + "constraint_names = []\n", + "for e in model.query():\n", + " d = e.as_dict()\n", + " if d.get(\"@type\") == \"ConstraintUsage\":\n", + " constraint_names.append(d[\"qualifiedName\"])\n", + "assert \"ToasterDemo::deliveredEnergyBoundedBySupply\" in constraint_names\n", + "print(f\"model.query() sees it too, among {len(constraint_names)} named ConstraintUsage elements.\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-11", + "metadata": {}, + "source": [ + "A require constraint referencing an undefined attribute still fails to load, exactly as it always has: this checks the language tier before the chapter's own genuinely new negative controls appear in [Ch8-02](02-violation-witness.ipynb), which shows the model-checking loop itself catching both a fully broken entailment and a merely weakened one." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "cell-12", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T12:47:55.988801Z", + "iopub.status.busy": "2026-09-28T12:47:55.988720Z", + "iopub.status.idle": "2026-09-28T12:47:55.996768Z", + "shell.execute_reply": "2026-09-28T12:47:55.996245Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-07", - "metadata": {}, - "source": [ - "Try the chapter exercise in `exercises/ch08/exercise.ipynb`: add a `fast : Toaster` variant with `cycleTime = 90.0` and confirm via `verify_satisfaction()` that it also satisfies the TimelyToast requirement.\n" - ] + "name": "stdout", + "output_type": "stream", + "text": [ + "Negative control ok: bad.ok=False\n" + ] } - ] -} \ No newline at end of file + ], + "source": [ + "# A require constraint referencing an undefined attribute fails.\n", + "bad_source = \"\"\"\n", + "package P {\n", + " private import ScalarValues::*;\n", + " part def Thing { attribute x : Real default = 5.0; }\n", + " requirement def Check {\n", + " subject t : Thing;\n", + " require constraint { t.undeclared <= 10.0 }\n", + " }\n", + "}\n", + "\"\"\"\n", + "bad = conn.load_from_content(bad_source, strict=False)\n", + "assert not bad.ok, \"Expected failure for undeclared attribute in constraint\"\n", + "print(f\"Negative control ok: bad.ok={bad.ok}\")\n", + "conn.close()" + ] + }, + { + "cell_type": "markdown", + "id": "cell-13", + "metadata": {}, + "source": [ + "The definitions printed above loaded without error, and both `model.find()` and `model.query()` confirmed `deliveredEnergyBoundedBySupply` is now part of the model, shown by the symbol and the qualified name printed earlier in this notebook." + ] + }, + { + "cell_type": "markdown", + "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." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/chapters/ch08-checking/02-violation-witness.ipynb b/chapters/ch08-checking/02-violation-witness.ipynb index ef0f4cb..644581c 100644 --- a/chapters/ch08-checking/02-violation-witness.ipynb +++ b/chapters/ch08-checking/02-violation-witness.ipynb @@ -1,160 +1,757 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, - "cells": [ + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": [ + "## Ch8-02 -- Proof, point evaluation, a genuine violation, and a genuine \"not sure\"\n", + "\n", + "This notebook proves `deliveredEnergyBoundedBySupply` for every value of its unbound features with `verify_holds()`, contrasts that with `verify_satisfaction()`'s point evaluation of the model's existing `assert satisfy` claims, shows the checking loop catching a fully broken variant of the lemma as `violated` and a merely weakened variant as `undecided` with a real witness, and records the proof as engineering evidence with its own real limits stated plainly." + ] + }, + { + "cell_type": "markdown", + "id": "cell-01", + "metadata": {}, + "source": [ + "Notebook 01 stated `deliveredEnergyBoundedBySupply` and confirmed it is really part of `ch08-cumulative.sysml`. This notebook does not add anything new to the model: it loads the same cumulative fixture and runs analysis against it. See [Ch8-01](01-invariant-def.ipynb) for the construct itself." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "cell-02", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T13:09:56.452555Z", + "iopub.status.busy": "2026-09-28T13:09:56.452345Z", + "iopub.status.idle": "2026-09-28T13:09:56.738609Z", + "shell.execute_reply": "2026-09-28T13:09:56.736928Z" + } + }, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "import opensysml\n", + "from toaster.report import format_diagnostics\n", + "\n", + "conn = opensysml.connect(version=\"v0.9.0\")\n", + "source = Path(\"../../models/ch08-cumulative.sysml\").read_text()\n", + "model = conn.load_from_content(source, strict=False)\n", + "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" + ] + }, + { + "cell_type": "markdown", + "id": "cell-03", + "metadata": {}, + "source": [ + "The model is unchanged from notebook 01: `deliveredEnergyBoundedBySupply` is already in `ch08-cumulative.sysml`, so this notebook only needs to load it, not build anything." + ] + }, + { + "cell_type": "markdown", + "id": "cell-04", + "metadata": {}, + "source": [ + "A require constraint referencing an undefined attribute still fails to load, the same language-tier control every chapter carries; this notebook's own new negative controls, further down, check the model-checking loop itself rather than the language tier." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "cell-05", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T13:09:56.742829Z", + "iopub.status.busy": "2026-09-28T13:09:56.742270Z", + "iopub.status.idle": "2026-09-28T13:09:56.749169Z", + "shell.execute_reply": "2026-09-28T13:09:56.748507Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-00", - "metadata": {}, - "source": [ - "## Ch8-02 \u2014 Violation witness\n", - "\n", - "This notebook introduces the violation witness pattern using `verify_satisfaction()`; after running it you can show that the slow variant's 200-second cycle time violates the TimelyToast requirement and record the failing verdict as a ReviewRecord.\n" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "Negative control ok: bad.ok=False\n" + ] + } + ], + "source": [ + "# A require constraint referencing an undefined attribute fails.\n", + "bad_source = \"\"\"\n", + "package P {\n", + " private import ScalarValues::*;\n", + " part def T { attribute x : Real default = 5.0; }\n", + " requirement def R {\n", + " subject t : T;\n", + " require constraint { t.missingAttr <= 10.0 }\n", + " }\n", + "}\n", + "\"\"\"\n", + "bad = conn.load_from_content(bad_source, strict=False)\n", + "assert not bad.ok, \"Expected failure for undeclared attribute\"\n", + "print(f\"Negative control ok: bad.ok={bad.ok}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-06", + "metadata": {}, + "source": [ + "`verify_satisfaction()` evaluates every `assert satisfy` / `assert not satisfy` declaration the model carries, each at the one set of values its subject happens to have. These are the same three real claims Chapter 3 and Chapter 6 already declared: nothing about them changes in Chapter 8." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "cell-07", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T13:09:56.753182Z", + "iopub.status.busy": "2026-09-28T13:09:56.753008Z", + "iopub.status.idle": "2026-09-28T13:09:56.775644Z", + "shell.execute_reply": "2026-09-28T13:09:56.774688Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-01", - "metadata": {}, - "source": [ - "Notebook 01 showed that `slow` holds=False for the TimelyToast requirement. This notebook uses the failing Verdict as an explicit violation witness: it extracts the failing verdict, creates a ReviewRecord that references it, and validates the record. A violation witness is engineering evidence \u2014 it establishes that the requirement boundary is real and that the slow design falls outside it. See [Ch8-01 satisfaction evaluation](01-invariant-def.ipynb) for the `verify_satisfaction()` call.\n" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + " [holds] not satisfy timely by slow\n", + " [FAILS] satisfy timely (satisfaction satisfy timely: require condition evaluation failed: no value for feature toaster)\n", + " [holds] satisfy heatGenerationReq by rated\n", + " [holds] not satisfy heatGenerationReq by weak\n", + "\n", + "Each of the three claims above is observed at one fixed set of attribute values.\n" + ] + } + ], + "source": [ + "verdicts = model.verify_satisfaction()\n", + "for v in verdicts:\n", + " status = \"holds\" if v.holds else \"FAILS\"\n", + " extra = f\" ({v.error})\" if v.error else \"\"\n", + " print(f\" [{status}] {v.element}{extra}\")\n", + "\n", + "by_element = {v.element: v for v in verdicts}\n", + "assert by_element[\"not satisfy timely by slow\"].holds is True\n", + "assert by_element[\"satisfy heatGenerationReq by rated\"].holds is True\n", + "assert by_element[\"not satisfy heatGenerationReq by weak\"].holds is True\n", + "print(\"\\nEach of the three claims above is observed at one fixed set of attribute values.\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-08", + "metadata": {}, + "source": [ + "These three claims are not all the same kind of check. `timely` compares `Toaster::cycleTime`, still a settable attribute with no relation deriving it from anything: `not satisfy timely by slow` holding shows the evaluation mechanics working correctly, not a finding about the toaster's actual timing. `heatGenerationReq` compares `HeatGenerator::power`, a chosen physical rating: comparing a rated value against a threshold is a legitimate feasibility check of a design choice, which is why `satisfy heatGenerationReq by rated` and `not satisfy heatGenerationReq by weak` are real findings about those two candidates. The fourth verdict above is `TimelyToastTest`'s own `verify timely;` objective, not a `satisfy` claim about a specific subject: it has no subject to evaluate against, which is why it fails here with an evaluation error. `conformance.satisfaction_claims_evaluated()`, run through `conformance.report()` below, correctly skips this exact case entirely rather than reporting it as a finding: a `verify` relationship is not itself a claim about one subject, and the check's own docstring says so." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "cell-09", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T13:09:56.778275Z", + "iopub.status.busy": "2026-09-28T13:09:56.778025Z", + "iopub.status.idle": "2026-09-28T13:09:57.147489Z", + "shell.execute_reply": "2026-09-28T13:09:57.146574Z" + } + }, + "outputs": [ { - "cell_type": "code", - "id": "cell-02", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from pathlib import Path\n", - "import opensysml\n", - "from toaster.report import format_diagnostics\n", - "\n", - "conn = opensysml.connect(version=\"v0.9.0\")\n", - "source = Path(\"../../models/ch08-cumulative.sysml\").read_text()\n", - "print(source)\n", - "model = conn.load_from_content(source, strict=False)\n", - "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "Result(check_id='port-type', status='passed', findings=[], applies_from=(5, 3), reason=None, unblock_when=None)\n", + "Result(check_id='satisfaction-claims-evaluated', status='passed', findings=[], applies_from=(3, 1), reason=None, unblock_when=None)\n", + "\n", + "satisfaction-claims-evaluated now reports passed on a real, non-vacuous check: every claim this check can evaluate evaluates to what it says it evaluates to.\n" + ] + } + ], + "source": [ + "from toaster import conformance\n", + "\n", + "report = conformance.report(model, stage=(8, 1))\n", + "for r in report[\"project\"]:\n", + " print(r)\n", + "\n", + "result = next(r for r in report[\"project\"] if r.check_id == \"satisfaction-claims-evaluated\")\n", + "assert result.status == \"passed\"\n", + "assert result.findings == []\n", + "print(\"\\nsatisfaction-claims-evaluated now reports passed on a real, non-vacuous check: \"\n", + " \"every claim this check can evaluate evaluates to what it says it evaluates to.\")" + ] + }, + { + "cell_type": "markdown", + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "cell-11", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T13:09:57.150324Z", + "iopub.status.busy": "2026-09-28T13:09:57.150201Z", + "iopub.status.idle": "2026-09-28T13:09:57.570836Z", + "shell.execute_reply": "2026-09-28T13:09:57.570341Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-03", - "metadata": {}, - "source": [ - "The `ch08-cumulative.sysml` file is identical to the Chapter 7 model. Chapter 8 introduces analysis operations \u2014 `verify_satisfaction()`, violation witnesses, and stale record detection \u2014 not new SysML constructs. The `assert satisfy` declarations for `TimelyToast` and `HeatingReq` are the targets of Chapter 8's bounded checks." - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "[satisfied] deliveredEnergyBoundedBySupply (z3: holds for all values of unbound features)\n", + "\n", + "Proved for every value of heatGenCheck.efficiency, heatGenCheck.power and heatGenCheckDuration the antecedent admits, not evaluated at one.\n" + ] + } + ], + "source": [ + "from pathlib import Path\n", + "from toaster import modelcheck as mc\n", + "\n", + "BINARY = Path.home() / \"Documents/GitHub/sysml-toolkit/target/release/sysmlv2\"\n", + "LIB = Path.home() / \"Documents/GitHub/sysml-toolkit/spec-refs/SysML-v2-Release/sysml.library\"\n", + "assert BINARY.exists(), f\"sysmlv2 binary not found at {BINARY} (see work contract PASS4-008)\"\n", + "\n", + "# A fixed scratch directory and fixed filenames (not tempfile.NamedTemporaryFile's own\n", + "# randomized name) keep this notebook's own printed output, including any error message\n", + "# that embeds a companion file's path, byte-for-byte reproducible run to run.\n", + "SCRATCH_DIR = Path(\"companion-check-scratch\") # repo-relative, portable across machines\n", + "SCRATCH_DIR.mkdir(exist_ok=True)\n", + "\n", + "def _write_companion(name: str, content: str) -> Path:\n", + " p = SCRATCH_DIR / name\n", + " p.write_text(content)\n", + " return p\n", + "\n", + "# Restates deliveredEnergyBoundedBySupply exactly as committed in ch08-cumulative.sysml,\n", + "# with a minimal HeatGenerator stub, and no assert satisfy declaration (D-029).\n", + "COMPANION_POSITIVE = \"\"\"\\\n", + "package ConservationCheck {\n", + " private import ScalarValues::*;\n", + " private import SI::*;\n", + " private import ISQ::*;\n", + " private import MeasurementReferences::*;\n", + "\n", + " abstract part def HeatGenerator {\n", + " attribute power : ISQ::PowerValue;\n", + " attribute efficiency : DimensionOneValue;\n", + " }\n", + " part heatGenCheck : HeatGenerator;\n", + " attribute heatGenCheckDuration : ISQ::DurationValue;\n", + "\n", + " assert constraint deliveredEnergyBoundedBySupply {\n", + " (heatGenCheck.efficiency >= 0.0 and heatGenCheck.efficiency <= 1.0\n", + " and heatGenCheck.power >= 0.0 [SI::W] and heatGenCheckDuration >= 0.0 [SI::s])\n", + " implies (heatGenCheck.power * heatGenCheckDuration * heatGenCheck.efficiency)\n", + " <= heatGenCheck.power * heatGenCheckDuration\n", + " }\n", + "}\n", + "\"\"\"\n", + "\n", + "def _ascii(reason: str) -> str:\n", + " \"\"\"The CLI's own reason text may carry an em dash; this display copy swaps it for a\n", + " plain double hyphen so what this notebook prints stays plain ASCII. Only the printed\n", + " copy changes; the verdict objects themselves keep the CLI's own text.\"\"\"\n", + " return reason.replace(\"\\u2014\", \"--\")\n", + "\n", + "companion_path = _write_companion(\"conservation_check.sysml\", COMPANION_POSITIVE)\n", + "\n", + "pos_verdicts = mc.verify_holds(str(companion_path), lib=str(LIB), binary=str(BINARY), solve=True)\n", + "pos_verdict = pos_verdicts[0]\n", + "print(f\"[{pos_verdict.status}] {pos_verdict.element} ({_ascii(pos_verdict.reason)})\")\n", + "assert pos_verdict.status == \"satisfied\"\n", + "# \"satisfied\" can come from interval propagation alone, not necessarily Z3 (DEFERRED.md,\n", + "# the note beside D-029): this checks the reason text actually names z3, not just the\n", + "# status, confirming the solver itself resolved this one.\n", + "assert \"z3\" in pos_verdict.reason\n", + "assert mc.holds(str(companion_path), lib=str(LIB), binary=str(BINARY), solve=True) is True\n", + "print(\"\\nProved for every value of heatGenCheck.efficiency, heatGenCheck.power and \"\n", + " \"heatGenCheckDuration the antecedent admits, not evaluated at one.\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-12", + "metadata": {}, + "source": [ + "A proof is only worth trusting if the same machinery can also report a real violation, not just agree with whatever is asked of it. The cell below restates the **full negation** of the lemma: efficiency in range and power, duration non-negative, **and** delivered energy strictly *exceeds* supplied energy (`A and not B`, not `A implies not B`, which is a different, weaker statement built from the same pieces and genuinely `undecided` here, since it is vacuously true wherever the antecedent itself fails, e.g. `efficiency = 2`). `A and not B` is the one Z3 must actually resolve as unsatisfiable to report `violated`: given the same bounded hypothesis, delivered energy can never strictly exceed supplied energy, so no assignment can make this conjunction true, and Z3 must reason about a product of three bounded unbound features to see that, not fold a literal constant the way `1 == 2` would." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "cell-13", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T13:09:57.572641Z", + "iopub.status.busy": "2026-09-28T13:09:57.572505Z", + "iopub.status.idle": "2026-09-28T13:09:57.894357Z", + "shell.execute_reply": "2026-09-28T13:09:57.893875Z" + } + }, + "outputs": [ { - "cell_type": "code", - "id": "cell-04", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# A bad constraint expression produces no failure verdicts (empty list or error).\n", - "bad_source = \"\"\"\n", - "package P {\n", - " private import ScalarValues::*;\n", - " part def T { attribute x : Real default = 5.0; }\n", - " requirement def R {\n", - " subject t : T;\n", - " require constraint { t.missingAttr <= 10.0 }\n", - " }\n", - "}\n", - "\"\"\"\n", - "bad = conn.load_from_content(bad_source, strict=False)\n", - "assert not bad.ok, \"Expected failure for undeclared attribute\"\n", - "print(f\"Negative control ok: bad.ok={bad.ok}\")\n" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "[violated] deliveredEnergyExceedsSupply (z3: unsatisfiable -- no assignment can make this hold)\n", + "\n", + "The loop catches a fully broken lemma: violated, not undecided, not silently accepted.\n" + ] + } + ], + "source": [ + "# Negative control: the FULL negation of the lemma (A and not B), which Z3 must resolve\n", + "# as unsatisfiable to report violated -- not \"A implies not B\" (a different, weaker\n", + "# statement, confirmed undecided in this toolchain: see the markdown above).\n", + "COMPANION_NEGATIVE = \"\"\"\\\n", + "package ConservationCheckBroken {\n", + " private import ScalarValues::*;\n", + " private import SI::*;\n", + " private import ISQ::*;\n", + " private import MeasurementReferences::*;\n", + "\n", + " abstract part def HeatGenerator {\n", + " attribute power : ISQ::PowerValue;\n", + " attribute efficiency : DimensionOneValue;\n", + " }\n", + " part heatGenCheck : HeatGenerator;\n", + " attribute heatGenCheckDuration : ISQ::DurationValue;\n", + "\n", + " assert constraint deliveredEnergyExceedsSupply {\n", + " (heatGenCheck.efficiency >= 0.0 and heatGenCheck.efficiency <= 1.0\n", + " and heatGenCheck.power >= 0.0 [SI::W] and heatGenCheckDuration >= 0.0 [SI::s])\n", + " and (heatGenCheck.power * heatGenCheckDuration * heatGenCheck.efficiency)\n", + " > heatGenCheck.power * heatGenCheckDuration\n", + " }\n", + "}\n", + "\"\"\"\n", + "\n", + "negative_path = _write_companion(\"conservation_check_broken.sysml\", COMPANION_NEGATIVE)\n", + "\n", + "neg_verdicts = mc.verify_holds(str(negative_path), lib=str(LIB), binary=str(BINARY), solve=True)\n", + "neg_verdict = neg_verdicts[0]\n", + "print(f\"[{neg_verdict.status}] {neg_verdict.element} ({_ascii(neg_verdict.reason)})\")\n", + "assert neg_verdict.status == \"violated\"\n", + "assert mc.holds(str(negative_path), lib=str(LIB), binary=str(BINARY), solve=True) is False\n", + "print(\"\\nThe loop catches a fully broken lemma: violated, not undecided, not silently accepted.\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-14", + "metadata": {}, + "source": [ + "A fully broken lemma is not the only interesting failure mode. A **merely weakened** variant, loosening the antecedent's own bound from `<= 1.0` to `<= 1.2` (the same change [Ch8-03](03-revision-flow.ipynb) uses to demonstrate staleness), no longer holds for every value **or** fails for every value: it holds when `efficiency <= 1.0` and fails when `efficiency` is between `1.0` and `1.2`. `verify_holds()` correctly reports this as `undecided`, with a genuine Z3-found witness satisfying it, not `satisfied` and not `violated`; `holds()` correctly refuses to collapse that into a clean `True` or `False`, raising `ModelCheckInconclusiveError` instead. This is the real, three-way distinction this toolchain draws that a learner should see directly: a property can definitely hold everywhere, definitely fail everywhere, or genuinely not be settled either way by what's stated, and only the first of those is something to build on without further work." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "cell-15", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T13:09:57.896014Z", + "iopub.status.busy": "2026-09-28T13:09:57.895884Z", + "iopub.status.idle": "2026-09-28T13:09:58.272744Z", + "shell.execute_reply": "2026-09-28T13:09:58.271856Z" + } + }, + "outputs": [ { - "cell_type": "code", - "id": "cell-05", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from toaster.evidence import ReviewRecord, hash_content, validate_record\n", - "\n", - "# Locate the slow variant's failing verdict\n", - "verdicts = model.verify_satisfaction()\n", - "slow_verdict = next(\n", - " (v for v in verdicts if \"slow\" in (v.element or \"\") and not v.holds),\n", - " None,\n", - ")\n", - "assert slow_verdict is not None, f\"Expected a failing slow verdict; got: {verdicts}\"\n", - "print(f\"Violation witness: {slow_verdict.element!r} holds={slow_verdict.holds}\")\n", - "\n", - "# Record the violation as a worked-example ReviewRecord (AS-C08)\n", - "violation = ReviewRecord(\n", - " identifier=\"AS-C08\",\n", - " kind=\"asserted_solution\",\n", - " claim=(\n", - " \"The slow variant (cycleTime=200) violates TimelyToast (cycleTime \u2264 180): \"\n", - " \"verify_satisfaction() returns holds=False for the slow candidate.\"\n", - " ),\n", - " model_ref=\"ToasterDemo::slow\",\n", - " content_hash=hash_content(source),\n", - " scope=\"ToasterDemo\",\n", - " criteria=\"verify_satisfaction() returns holds=False for the slow candidate\",\n", - " premises=[],\n", - " assumption_refs=[\"AS-C03\"],\n", - " evidence_refs=[slow_verdict.element or \"satisfy timely by slow\"],\n", - " rationale=(\n", - " \"The Verdict from verify_satisfaction() is direct computational evidence. \"\n", - " \"The model evaluates toaster.cycleTime <= 180.0 against slow.cycleTime=200.0, \"\n", - " \"which is False. The violation is bounded to the defined cycleTime attribute; \"\n", - " \"real toasters have variable cycle times depending on load and ambient temperature.\"\n", - " ),\n", - " counterevidence=(\n", - " \"The slow variant is a synthetic stress case, not a production design. \"\n", - " \"A real toaster with cycleTime=200 might still satisfy a user if the toast \"\n", - " \"is acceptable quality \u2014 the requirement captures one dimension of acceptability.\"\n", - " ),\n", - " residual_uncertainties=(\n", - " \"cycleTime is a fixed attribute; the model does not capture variation within \"\n", - " \"a single toast cycle. Thermal modelling would be needed to assess that.\"\n", - " ),\n", - " disposition=\"pending\",\n", - " dependency_freshness=\"current\",\n", - " engineering_conclusion=\"refuted\",\n", - " record_kind=\"worked_example\",\n", - ")\n", - "\n", - "errors = validate_record(violation)\n", - "assert errors == [], f\"Validation errors: {errors}\"\n", - "print(f\"Violation record valid: identifier={violation.identifier!r}\")\n", - "print(f\"engineering_conclusion={violation.engineering_conclusion!r}\")\n", - "conn.close()\n" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "[undecided] deliveredEnergyBoundedBySupply (result is indeterminate over unbound features -- z3: satisfiable, e.g. heatGenCheck.efficiency = 0, heatGenCheck.power = 0 [W], heatGenCheckDuration = 1 [s])\n", + "holds() correctly refuses to answer: undecided, not proven either way: deliveredEnergyBoundedBySupply (companion-check-scratch/conservation_check_weakened.sysml:15:9)\n" + ] + } + ], + "source": [ + "# A realistically weakened variant: the same lemma, its own bound loosened from 1.0 to\n", + "# 1.2. Neither a tautology nor a contradiction any more: undecided, with a witness.\n", + "COMPANION_WEAKENED = \"\"\"\\\n", + "package ConservationCheckWeakened {\n", + " private import ScalarValues::*;\n", + " private import SI::*;\n", + " private import ISQ::*;\n", + " private import MeasurementReferences::*;\n", + "\n", + " abstract part def HeatGenerator {\n", + " attribute power : ISQ::PowerValue;\n", + " attribute efficiency : DimensionOneValue;\n", + " }\n", + " part heatGenCheck : HeatGenerator;\n", + " attribute heatGenCheckDuration : ISQ::DurationValue;\n", + "\n", + " assert constraint deliveredEnergyBoundedBySupply {\n", + " (heatGenCheck.efficiency >= 0.0 and heatGenCheck.efficiency <= 1.2\n", + " and heatGenCheck.power >= 0.0 [SI::W] and heatGenCheckDuration >= 0.0 [SI::s])\n", + " implies (heatGenCheck.power * heatGenCheckDuration * heatGenCheck.efficiency)\n", + " <= heatGenCheck.power * heatGenCheckDuration\n", + " }\n", + "}\n", + "\"\"\"\n", + "\n", + "weakened_path = _write_companion(\"conservation_check_weakened.sysml\", COMPANION_WEAKENED)\n", + "\n", + "weak_verdicts = mc.verify_holds(str(weakened_path), lib=str(LIB), binary=str(BINARY), solve=True)\n", + "weak_verdict = weak_verdicts[0]\n", + "print(f\"[{weak_verdict.status}] {weak_verdict.element} ({_ascii(weak_verdict.reason)})\")\n", + "assert weak_verdict.status == \"undecided\"\n", + "assert \"z3\" in weak_verdict.reason\n", + "\n", + "try:\n", + " mc.holds(str(weakened_path), lib=str(LIB), binary=str(BINARY), solve=True)\n", + " raise AssertionError(\"expected holds() to raise ModelCheckInconclusiveError\")\n", + "except mc.ModelCheckInconclusiveError as exc:\n", + " print(f\"holds() correctly refuses to answer: {exc}\")\n", + "\n", + "# Clean up the fixed scratch files now that every demo in this notebook has used them.\n", + "for p in (companion_path, negative_path, weakened_path):\n", + " p.unlink(missing_ok=True)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-16", + "metadata": {}, + "source": [ + "The proof above is engineering evidence, not a passing test result: it is what would have to be re-checked if `HeatGenerator`'s bound or `deliveredEnergy`'s definition ever changed, by hand, since nothing in this toolchain checks that the restated copy stays in sync with either. Recording it as a `ReviewRecord` states, in a reader's terms, what makes this evidence appropriate, sufficient and trustworthy (Hawkins et al. 2011, SS3.1-3.4), the same way earlier chapters recorded their own judgment sites." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "cell-17", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T13:09:58.275340Z", + "iopub.status.busy": "2026-09-28T13:09:58.275148Z", + "iopub.status.idle": "2026-09-28T13:09:58.278209Z", + "shell.execute_reply": "2026-09-28T13:09:58.277639Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "deliveredEnergyBoundedBySupply, a hand-restated real-arithmetic lemma of the same shape as HeatGenerator's efficiencyBounded constraint and deliveredEnergy's own definition, holds for every value of efficiency in [0,1] and every non-negative power and duration a companion restatement admits.\n" + ] + } + ], + "source": [ + "from toaster.evidence import ReviewRecord, hash_content, validate_record\n", + "\n", + "claim = (\n", + " \"deliveredEnergyBoundedBySupply, a hand-restated real-arithmetic lemma of the same \"\n", + " \"shape as HeatGenerator's efficiencyBounded constraint and deliveredEnergy's own \"\n", + " \"definition, holds for every value of efficiency in [0,1] and every non-negative \"\n", + " \"power and duration a companion restatement admits.\"\n", + ")\n", + "model_ref = \"ToasterDemo::deliveredEnergyBoundedBySupply\"\n", + "print(claim)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-18", + "metadata": {}, + "source": [ + "What standard is this claim checked against? `verify_holds()` reports a single verdict for `deliveredEnergyBoundedBySupply`, proved by Z3 over the unbound features the companion restatement carries, not merely evaluated at one point." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "cell-19", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T13:09:58.280598Z", + "iopub.status.busy": "2026-09-28T13:09:58.280495Z", + "iopub.status.idle": "2026-09-28T13:09:58.283305Z", + "shell.execute_reply": "2026-09-28T13:09:58.282783Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-06", - "metadata": {}, - "source": [ - "The slow variant's 200-second cycle time failing the TimelyToast constraint (A-F) is confirmed by the holds=False Verdict from `verify_satisfaction()` (O-S); the ReviewRecord captures this as a simulation-backed engineering judgment with a non-empty `counterevidence` field (E).\n" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "The lemma governs the companion restatement's own heatGenCheck.efficiency, heatGenCheck.power and heatGenCheckDuration features; it is not a solver-checked reference to HeatGenerator's own efficiencyBounded or deliveredEnergy (DEFERRED.md D-030, D-031), only a hand-restated copy of the same shape.\n", + "verify_holds() reports a single satisfied verdict for deliveredEnergyBoundedBySupply, with the reason text naming z3 (not propagation alone), proved for all values of the unbound heatGenCheck.efficiency, heatGenCheck.power and heatGenCheckDuration features.\n" + ] + } + ], + "source": [ + "scope = (\n", + " \"The lemma governs the companion restatement's own heatGenCheck.efficiency, \"\n", + " \"heatGenCheck.power and heatGenCheckDuration features; it is not a solver-checked \"\n", + " \"reference to HeatGenerator's own efficiencyBounded or deliveredEnergy (DEFERRED.md \"\n", + " \"D-030, D-031), only a hand-restated copy of the same shape.\"\n", + ")\n", + "criteria = (\n", + " \"verify_holds() reports a single satisfied verdict for deliveredEnergyBoundedBySupply, \"\n", + " \"with the reason text naming z3 (not propagation alone), proved for all values of the \"\n", + " \"unbound heatGenCheck.efficiency, heatGenCheck.power and heatGenCheckDuration features.\"\n", + ")\n", + "print(scope)\n", + "print(criteria)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-20", + "metadata": {}, + "source": [ + "What is this claim taking as given? The proof rests on the companion file restating `deliveredEnergyBoundedBySupply` correctly, and on the lemma's own hypothesis (non-negative power and duration), which the model states here but does not enforce as a standing constraint on `HeatGenerator` or `ApplyHeat` elsewhere." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "cell-21", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T13:09:58.285816Z", + "iopub.status.busy": "2026-09-28T13:09:58.285555Z", + "iopub.status.idle": "2026-09-28T13:09:58.288010Z", + "shell.execute_reply": "2026-09-28T13:09:58.287367Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "No prior ReviewRecord is assumed; this claim rests on the proof itself.\n" + ] + } + ], + "source": [ + "premises = []\n", + "assumption_refs = []\n", + "print(\"No prior ReviewRecord is assumed; this claim rests on the proof itself.\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-22", + "metadata": {}, + "source": [ + "What supports the claim, and how? The Z3-derived verdict above, cited directly, is the evidence; the rationale states what kind of check produced it and why that is a materially different kind of evidence from a point evaluation, while being explicit about what it does not establish." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "cell-23", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T13:09:58.289972Z", + "iopub.status.busy": "2026-09-28T13:09:58.289812Z", + "iopub.status.idle": "2026-09-28T13:09:58.292497Z", + "shell.execute_reply": "2026-09-28T13:09:58.291992Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-07", - "metadata": {}, - "source": [ - "Try the chapter exercise in `exercises/ch08/exercise.ipynb`: produce a violation witness for the weak Heater variant against the HeatingReq requirement using `verify_satisfaction()`.\n" - ] - } - ] -} \ No newline at end of file + "name": "stdout", + "output_type": "stream", + "text": [ + "['verify_holds: deliveredEnergyBoundedBySupply satisfied (z3: holds for all values of unbound features)']\n", + "verify_holds() calls sysml-toolkit's real verify --solve command, which runs Z3 over the unbound features of a companion restatement of this lemma (see the narration above for why a companion file is used) and reports deliveredEnergyBoundedBySupply as satisfied: proved for all values the restatement admits, not read back from one entered value. This is a materially different kind of evidence from an evaluate-only verdict: verify_satisfaction() could only ever check a relation at whichever single power, duration and efficiency a candidate happens to carry. It is also, deliberately, a narrower claim than 'this proves HeatGenerator's own conservation property': see counterevidence.\n" + ] + } + ], + "source": [ + "evidence_refs = [f\"verify_holds: {pos_verdict.element} {pos_verdict.status} ({_ascii(pos_verdict.reason)})\"]\n", + "rationale = (\n", + " \"verify_holds() calls sysml-toolkit's real verify --solve command, which runs Z3 \"\n", + " \"over the unbound features of a companion restatement of this lemma (see the \"\n", + " \"narration above for why a companion file is used) and reports \"\n", + " \"deliveredEnergyBoundedBySupply as satisfied: proved for all values the restatement \"\n", + " \"admits, not read back from one entered value. This is a materially different kind of \"\n", + " \"evidence from an evaluate-only verdict: verify_satisfaction() could only ever check \"\n", + " \"a relation at whichever single power, duration and efficiency a candidate happens to \"\n", + " \"carry. It is also, deliberately, a narrower claim than 'this proves HeatGenerator's \"\n", + " \"own conservation property': see counterevidence.\"\n", + ")\n", + "print(evidence_refs)\n", + "print(rationale)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-24", + "metadata": {}, + "source": [ + "What could be wrong, and what is still open? A record that hides its own weak points is not more trustworthy, it is less checkable." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "cell-25", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T13:09:58.294039Z", + "iopub.status.busy": "2026-09-28T13:09:58.293884Z", + "iopub.status.idle": "2026-09-28T13:09:58.296541Z", + "shell.execute_reply": "2026-09-28T13:09:58.296113Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "This proof is NOT a solver-checked reference to HeatGenerator's own efficiencyBounded constraint or deliveredEnergy calc: this toolchain's Z3 backend does not compose two separately declared assert constraints, whether sibling or inherited (DEFERRED.md D-030), and cannot reason through a chained calc invocation like heatGenCheck.deliveredEnergy(...) (D-031). Confirmed directly: loosening efficiencyBounded's own literal bound to <= 1.5, or doubling deliveredEnergy's own definition by a factor of 2.0, in the real committed model changes neither the original elements' own verdicts nor this lemma's verdict at all, because the lemma restates its own copy of both rather than referencing either. The companion file used by verify_holds also restates the construct rather than checking the committed model directly, because toaster.modelcheck's own text parser does not yet handle the extra annotation the CLI prints for assert satisfy declarations (DEFERRED.md D-029).\n", + "Whether efficiency, power and duration ever take values outside the bound in a real candidate is not addressed by this proof; it establishes only that the restated lemma respects conservation wherever its own bound is honored. If HeatGenerator's own efficiencyBounded or deliveredEnergy is ever edited, this record's content_hash (computed from the whole model file) does go stale, which forces a re-review, but nothing automatically re-checks that the restated copy still matches the edited original; that check would be manual. No physical heat generator has been checked against this property; HeatGenerator remains an abstract carrier with no concrete realization of its own.\n" + ] + } + ], + "source": [ + "counterevidence = (\n", + " \"This proof is NOT a solver-checked reference to HeatGenerator's own \"\n", + " \"efficiencyBounded constraint or deliveredEnergy calc: this toolchain's Z3 backend \"\n", + " \"does not compose two separately declared assert constraints, whether sibling or \"\n", + " \"inherited (DEFERRED.md D-030), and cannot reason through a chained calc invocation \"\n", + " \"like heatGenCheck.deliveredEnergy(...) (D-031). Confirmed directly: loosening \"\n", + " \"efficiencyBounded's own literal bound to <= 1.5, or doubling deliveredEnergy's own \"\n", + " \"definition by a factor of 2.0, in the real committed model changes neither the \"\n", + " \"original elements' own verdicts nor this lemma's verdict at all, because the lemma \"\n", + " \"restates its own copy of both rather than referencing either. The companion file \"\n", + " \"used by verify_holds also restates the construct rather than checking the \"\n", + " \"committed model directly, because toaster.modelcheck's own text parser does not \"\n", + " \"yet handle the extra annotation the CLI prints for assert satisfy declarations \"\n", + " \"(DEFERRED.md D-029).\"\n", + ")\n", + "residual_uncertainties = (\n", + " \"Whether efficiency, power and duration ever take values outside the bound in a \"\n", + " \"real candidate is not addressed by this proof; it establishes only that the \"\n", + " \"restated lemma respects conservation wherever its own bound is honored. If \"\n", + " \"HeatGenerator's own efficiencyBounded or deliveredEnergy is ever edited, this \"\n", + " \"record's content_hash (computed from the whole model file) does go stale, which \"\n", + " \"forces a re-review, but nothing automatically re-checks that the restated copy \"\n", + " \"still matches the edited original; that check would be manual. No physical heat \"\n", + " \"generator has been checked against this property; HeatGenerator remains an \"\n", + " \"abstract carrier with no concrete realization of its own.\"\n", + ")\n", + "print(counterevidence)\n", + "print(residual_uncertainties)" + ] + }, + { + "cell_type": "markdown", + "id": "cell-26", + "metadata": {}, + "source": [ + "Assembling the record from the parts above, the same way a construction-zone cell assembles a model fragment from its own named pieces." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "cell-27", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T13:09:58.298420Z", + "iopub.status.busy": "2026-09-28T13:09:58.298296Z", + "iopub.status.idle": "2026-09-28T13:09:58.310567Z", + "shell.execute_reply": "2026-09-28T13:09:58.309784Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Record valid: identifier='AS-C08' engineering_conclusion='supported'\n" + ] + } + ], + "source": [ + "record = ReviewRecord(\n", + " identifier=\"AS-C08\",\n", + " kind=\"asserted_solution\",\n", + " claim=claim,\n", + " model_ref=model_ref,\n", + " content_hash=hash_content(source),\n", + " scope=scope,\n", + " criteria=criteria,\n", + " premises=premises,\n", + " assumption_refs=assumption_refs,\n", + " evidence_refs=evidence_refs,\n", + " rationale=rationale,\n", + " counterevidence=counterevidence,\n", + " residual_uncertainties=residual_uncertainties,\n", + " disposition=\"pending\",\n", + " dependency_freshness=\"current\",\n", + " engineering_conclusion=\"supported\",\n", + " record_kind=\"worked_example\",\n", + ")\n", + "\n", + "errors = validate_record(record)\n", + "assert errors == [], f\"Validation errors: {errors}\"\n", + "print(f\"Record valid: identifier={record.identifier!r} engineering_conclusion={record.engineering_conclusion!r}\")\n", + "conn.close()" + ] + }, + { + "cell_type": "markdown", + "id": "cell-28", + "metadata": {}, + "source": [ + "The claim printed above, the proof it points to, and the record's own counterevidence stating plainly what that proof does and does not establish are three distinct things this notebook watched connect: a written lemma, a real solver's verdict on it, and a record that never overstates what the verdict actually covers." + ] + }, + { + "cell_type": "markdown", + "id": "cell-29", + "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." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/chapters/ch08-checking/03-revision-flow.ipynb b/chapters/ch08-checking/03-revision-flow.ipynb index 58ac5f6..59e6efa 100644 --- a/chapters/ch08-checking/03-revision-flow.ipynb +++ b/chapters/ch08-checking/03-revision-flow.ipynb @@ -1,156 +1,251 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" + "cells": [ + { + "cell_type": "markdown", + "id": "cell-00", + "metadata": {}, + "source": [ + "## Ch8-03 -- Stale record detection\n", + "\n", + "This notebook demonstrates `check_stale()` against the record notebook 02 built; after running it you can show that loosening the lemma's own bound makes that record stale." + ] + }, + { + "cell_type": "markdown", + "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 rebuilds the `AS-C08` record from [Ch8-02](02-violation-witness.ipynb), confirms it is current against `ch08-cumulative.sysml`, then loosens the lemma's own bound and shows the record go stale." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "cell-02", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T12:48:01.075443Z", + "iopub.status.busy": "2026-09-28T12:48:01.075347Z", + "iopub.status.idle": "2026-09-28T12:48:01.207114Z", + "shell.execute_reply": "2026-09-28T12:48:01.206608Z" } + }, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "import opensysml\n", + "from toaster.report import format_diagnostics\n", + "\n", + "conn = opensysml.connect(version=\"v0.9.0\")\n", + "source = Path(\"../../models/ch08-cumulative.sysml\").read_text()\n", + "model = conn.load_from_content(source, strict=False)\n", + "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" + ] }, - "cells": [ - { - "cell_type": "markdown", - "id": "cell-00", - "metadata": {}, - "source": [ - "## Ch8-03 \u2014 Stale record detection\n", - "\n", - "This notebook introduces stale record detection with `check_stale()`; after running it you can show how a stored content hash identifies records that need re-review when the model changes.\n" - ] - }, - { - "cell_type": "markdown", - "id": "cell-01", - "metadata": {}, - "source": [ - "Evidence records are only as good as the model they reference. When a model changes, records hashed against the old source become stale \u2014 they cannot be trusted until re-reviewed. `check_stale()` in `evidence.py` detects this by comparing a ReviewRecord's `content_hash` against the current model source string. This notebook shows the full pattern: create a record, confirm it is current, change the model, confirm it becomes stale. See [Ch8-02 violation witness](02-violation-witness.ipynb) for the record structure.\n" - ] - }, - { - "cell_type": "code", - "id": "cell-02", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from pathlib import Path\n", - "import opensysml\n", - "from toaster.report import format_diagnostics\n", - "\n", - "conn = opensysml.connect(version=\"v0.9.0\")\n", - "source = Path(\"../../models/ch08-cumulative.sysml\").read_text()\n", - "print(source)\n", - "model = conn.load_from_content(source, strict=False)\n", - "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"" - ] - }, - { - "cell_type": "markdown", - "id": "cell-03", - "metadata": {}, - "source": [ - "The `ch08-cumulative.sysml` file is identical to the Chapter 7 model. Chapter 8 introduces analysis operations \u2014 `verify_satisfaction()`, violation witnesses, and stale record detection \u2014 not new SysML constructs. The `assert satisfy` declarations for `TimelyToast` and `HeatingReq` are the targets of Chapter 8's bounded checks." - ] - }, - { - "cell_type": "code", - "id": "cell-04", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "# A record with an empty identifier fails validation (not stale \u2014 invalid from the start).\n", - "from toaster.evidence import ReviewRecord, hash_content, validate_record\n", - "\n", - "broken = ReviewRecord(\n", - " identifier=\"\", # intentionally empty\n", - " kind=\"asserted_solution\",\n", - " claim=\"Some claim\",\n", - " model_ref=\"ToasterDemo\",\n", - " content_hash=hash_content(source),\n", - " scope=\"ToasterDemo\",\n", - " criteria=\"Some criteria\",\n", - " rationale=\"Some rationale\",\n", - " counterevidence=\"Some counterevidence\",\n", - " record_kind=\"worked_example\",\n", - ")\n", - "errors = validate_record(broken)\n", - "assert len(errors) > 0, \"Expected validation errors for empty identifier\"\n", - "print(f\"Negative control ok: errors={errors}\")\n" - ] - }, + { + "cell_type": "markdown", + "id": "cell-03", + "metadata": {}, + "source": [ + "A record with an empty identifier fails validation outright, distinct from staleness: it was never a valid record to begin with, regardless of which model it references." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "cell-04", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T12:48:01.209090Z", + "iopub.status.busy": "2026-09-28T12:48:01.208850Z", + "iopub.status.idle": "2026-09-28T12:48:01.212290Z", + "shell.execute_reply": "2026-09-28T12:48:01.211835Z" + } + }, + "outputs": [ { - "cell_type": "code", - "id": "cell-05", - "metadata": {}, - "outputs": [], - "execution_count": null, - "source": [ - "from toaster.evidence import ReviewRecord, hash_content, check_stale\n", - "\n", - "# Create a valid record hashed against the current model source\n", - "record = ReviewRecord(\n", - " identifier=\"AS-C08-REV\",\n", - " kind=\"asserted_solution\",\n", - " claim=\"The nominal variant satisfies TimelyToast (cycleTime=120 \u2264 180).\",\n", - " model_ref=\"ToasterDemo::nominal\",\n", - " content_hash=hash_content(source), # hash of current source\n", - " scope=\"ToasterDemo\",\n", - " criteria=\"verify_satisfaction() returns holds=True for nominal\",\n", - " rationale=(\n", - " \"nominal.cycleTime=120 satisfies the constraint cycleTime \u2264 180. \"\n", - " \"The attribute is set at the part definition level with no override.\"\n", - " ),\n", - " counterevidence=(\n", - " \"This uses a fixed cycleTime attribute. Real toasters vary with load. \"\n", - " \"The claim is bounded to the model's defined operating conditions.\"\n", - " ),\n", - " residual_uncertainties=\"Thermal variability within a single cycle is not modelled.\",\n", - " disposition=\"pending\",\n", - " dependency_freshness=\"current\",\n", - " engineering_conclusion=\"supported\",\n", - " record_kind=\"worked_example\",\n", - ")\n", - "\n", - "# Confirm the record is current against the current source\n", - "assert not check_stale(record, source), \"Record should be current\"\n", - "print(f\"Record is current: check_stale={check_stale(record, source)}\")\n", - "\n", - "# Simulate a model change: lower the requirement threshold\n", - "revised_source = source.replace(\n", - " \"toaster.cycleTime <= 180.0\",\n", - " \"toaster.cycleTime <= 150.0\",\n", - ")\n", - "revised_model = conn.load_from_content(revised_source, strict=False)\n", - "assert revised_model.ok, \"Revised model should parse\"\n", - "\n", - "# Now check staleness against the revised source\n", - "stale = check_stale(record, revised_source)\n", - "assert stale, \"Record should be stale after model change\"\n", - "print(f\"After constraint change: check_stale={stale}\")\n", - "print(\"Record requires re-review: the stored hash no longer matches the current model.\")\n", - "conn.close()\n" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "Negative control ok: errors=['identifier is empty']\n" + ] + } + ], + "source": [ + "from toaster.evidence import ReviewRecord, hash_content, validate_record\n", + "\n", + "broken = ReviewRecord(\n", + " identifier=\"\", # intentionally empty\n", + " kind=\"asserted_solution\",\n", + " claim=\"Some claim\",\n", + " model_ref=\"ToasterDemo\",\n", + " content_hash=hash_content(source),\n", + " scope=\"Some scope\",\n", + " criteria=\"Some criteria\",\n", + " rationale=\"Some rationale\",\n", + " counterevidence=\"Some counterevidence\",\n", + " record_kind=\"worked_example\",\n", + ")\n", + "errors = validate_record(broken)\n", + "assert len(errors) > 0, \"Expected validation errors for empty identifier\"\n", + "print(f\"Negative control ok: errors={errors}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-05", + "metadata": {}, + "source": [ + "`AS-C08` is rebuilt here exactly as notebook 02 built it, hashed against the current `ch08-cumulative.sysml` source, so `check_stale()` has something to compare against." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "cell-06", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T12:48:01.214282Z", + "iopub.status.busy": "2026-09-28T12:48:01.214176Z", + "iopub.status.idle": "2026-09-28T12:48:01.217235Z", + "shell.execute_reply": "2026-09-28T12:48:01.216718Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-06", - "metadata": {}, - "source": [ - "A ReviewRecord's `content_hash` bound to the original model source (A-F) is checked by `check_stale()` against the current model (O-S); changing the requirement threshold in the model source makes `check_stale()` return True, marking the record as stale (E).\n" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "Record is current: check_stale=False\n" + ] + } + ], + "source": [ + "from toaster.evidence import check_stale\n", + "\n", + "record = ReviewRecord(\n", + " identifier=\"AS-C08\",\n", + " kind=\"asserted_solution\",\n", + " claim=(\n", + " \"deliveredEnergyBoundedBySupply, a hand-restated real-arithmetic lemma of the \"\n", + " \"same shape as HeatGenerator's efficiencyBounded constraint and deliveredEnergy's \"\n", + " \"own definition, holds for every value of efficiency in [0,1] and every \"\n", + " \"non-negative power and duration a companion restatement admits.\"\n", + " ),\n", + " model_ref=\"ToasterDemo::deliveredEnergyBoundedBySupply\",\n", + " content_hash=hash_content(source),\n", + " scope=(\n", + " \"The lemma governs the companion restatement's own features; it is not a \"\n", + " \"solver-checked reference to HeatGenerator's own efficiencyBounded or \"\n", + " \"deliveredEnergy (DEFERRED.md D-030, D-031).\"\n", + " ),\n", + " criteria=\"verify_holds() reports deliveredEnergyBoundedBySupply satisfied, proved for all values.\",\n", + " evidence_refs=[\"verify_holds: deliveredEnergyBoundedBySupply satisfied (Ch8-02)\"],\n", + " rationale=(\n", + " \"verify_holds() proves the lemma for every value of the unbound features a \"\n", + " \"companion restatement carries, a materially different kind of evidence from a \"\n", + " \"point evaluation.\"\n", + " ),\n", + " counterevidence=(\n", + " \"The proof runs against a companion restatement, not the committed file directly \"\n", + " \"(DEFERRED.md D-029), and is not solver-linked to HeatGenerator's own \"\n", + " \"efficiencyBounded or deliveredEnergy (D-030, D-031): editing either does not \"\n", + " \"change this lemma's own verdict.\"\n", + " ),\n", + " residual_uncertainties=\"No physical heat generator has been checked against this property.\",\n", + " disposition=\"pending\",\n", + " dependency_freshness=\"current\",\n", + " engineering_conclusion=\"supported\",\n", + " record_kind=\"worked_example\",\n", + ")\n", + "\n", + "assert not check_stale(record, source), \"Record should be current\"\n", + "print(f\"Record is current: check_stale={check_stale(record, source)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "cell-07", + "metadata": {}, + "source": [ + "Loosening `deliveredEnergyBoundedBySupply`'s own bound from `<= 1.0` to `<= 1.2` is exactly the kind of change that should invalidate a record built against the tighter bound: the model still loads, but it no longer says what the record claims it says. This is the same partial safeguard the lemma's own doc comment names: the record's `content_hash` is computed from the whole model file, so any edit anywhere in it, including to `HeatGenerator`'s own `efficiencyBounded` or `deliveredEnergy`, would also stale this record, even though nothing automatically checks that the restated lemma still matches whatever changed." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "cell-08", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T12:48:01.219093Z", + "iopub.status.busy": "2026-09-28T12:48:01.218926Z", + "iopub.status.idle": "2026-09-28T12:48:01.241698Z", + "shell.execute_reply": "2026-09-28T12:48:01.241131Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "cell-07", - "metadata": {}, - "source": [ - "Try the chapter exercise in `exercises/ch08/exercise.ipynb`: create a record for the HeatingReq satisfaction, change the minimum power threshold, and confirm that `check_stale()` fires.\n" - ] + "name": "stdout", + "output_type": "stream", + "text": [ + "After loosening the bound: check_stale=True\n", + "Record requires re-review: the stored hash no longer matches the current model.\n" + ] } - ] -} \ No newline at end of file + ], + "source": [ + "# Loosen the bound the proof protects; the model still parses, the claim no longer matches.\n", + "revised_source = source.replace(\n", + " \"heatGenCheck.efficiency <= 1.0\",\n", + " \"heatGenCheck.efficiency <= 1.2\",\n", + ")\n", + "assert revised_source != source, \"Expected the replacement to change the source\"\n", + "revised_model = conn.load_from_content(revised_source, strict=False)\n", + "assert revised_model.ok, \"Revised model should still parse\"\n", + "\n", + "stale = check_stale(record, revised_source)\n", + "assert stale, \"Record should be stale after the bound changes\"\n", + "print(f\"After loosening the bound: check_stale={stale}\")\n", + "print(\"Record requires re-review: the stored hash no longer matches the current model.\")\n", + "conn.close()" + ] + }, + { + "cell_type": "markdown", + "id": "cell-09", + "metadata": {}, + "source": [ + "The record printed above, current against the model it was built from, went stale the moment the bound it cites changed underneath it: exactly the mismatch the loop is built to catch, whether the change is to the model or to the property a record depends on. [Ch8-02](02-violation-witness.ipynb) shows the same loosened bound reported `undecided` by `verify_holds()` itself, a second, independent way this exact change is caught." + ] + }, + { + "cell_type": "markdown", + "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." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/chapters/ch08-checking/conclusion.md b/chapters/ch08-checking/conclusion.md index 4196925..be7e4da 100644 --- a/chapters/ch08-checking/conclusion.md +++ b/chapters/ch08-checking/conclusion.md @@ -2,16 +2,16 @@ ## What we built -`verify_satisfaction()` evaluates the model's `assert satisfy timely by nominal` and `assert satisfy timely by slow` declarations, returning Verdict objects with `holds` fields. A violation witness ReviewRecord (`AS-C08`) captures the slow variant's failure with a non-empty `counterevidence` field. The stale detection pattern (`check_stale()`) is exercised by changing the requirement threshold and confirming that the stored hash no longer matches. +`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 -The checking results show that the requirement boundary is real and correctly encoded: the nominal design (cycleTime=120) satisfies TimelyToast; the slow design (cycleTime=200) does not. The violation witness is formal engineering evidence, not just a test result — it is attached to the model by `model_ref`, scoped by `scope`, and bounded by `counterevidence` and `residual_uncertainties`. +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. ## What comes next -Chapter 9 broadens the analysis: instead of checking two specific candidates, it queries all requirement declarations and all satisfy relationships to produce a coverage table that shows which requirements have been addressed and which have not. +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. ## Exercise -See `exercises/ch08/exercise.ipynb`: produce a violation witness for the `weak` Heater variant and demonstrate stale detection after changing the HeatingReq threshold. +See `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 88569dc..16ca211 100644 --- a/chapters/ch08-checking/index.md +++ b/chapters/ch08-checking/index.md @@ -1,31 +1,33 @@ -# Chapter 8 — Constraint Checking +# Chapter 8 - Constraint Checking ## Purpose -This chapter asks: do the design candidates formally satisfy the stated requirements, and how do we record what happens when they do not? +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 been evaluated with `verify_satisfaction()`, a violation witness ReviewRecord has been created for the slow variant, and a stale record detection pattern has been demonstrated for the case where the model changes after the record was written. +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). ## Ingredients | Notebook | Concept | |---|---| -| [01 — Satisfaction evaluation](01-invariant-def.ipynb) | Call `verify_satisfaction()` to evaluate `assert satisfy` declarations; confirm `nominal` holds and `slow` fails. | -| [02 — Violation witness](02-violation-witness.ipynb) | Extract the failing Verdict for `slow`; record it as a ReviewRecord with `engineering_conclusion='refuted'`. | -| [03 — Stale record detection](03-revision-flow.ipynb) | Change the requirement threshold; show that `check_stale()` fires, marking the existing record for re-review. | +| [01 - A hand-restated lemma of the same shape](01-invariant-def.ipynb) | State `deliveredEnergyBoundedBySupply` as a real SysML constraint; confirm it is really in the loaded model. | +| [02 - Proof, point evaluation, a genuine violation, and a genuine "not sure"](02-violation-witness.ipynb) | Contrast `verify_holds()`'s universal proof with `verify_satisfaction()`'s point evaluation; show the loop catching a fully broken variant as `violated` and a merely weakened variant as `undecided`; record the proof as engineering evidence with its own real limits stated. | +| [03 - Stale record detection](03-revision-flow.ipynb) | Loosen the lemma's own bound; show `check_stale()` marking the existing record for re-review. | ## Equipment -See [docs/setup.md](../../docs/setup.md) for environment setup. No chapter-specific tools are required beyond the base environment. +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. ## Method -Notebook 01 uses `verify_satisfaction()` — the computational evaluation of the model's `assert satisfy` declarations — to determine which candidates pass and which fail. Notebook 02 treats the failing Verdict as engineering evidence and encodes it in a ReviewRecord following the Hawkins §3.3 `asserted_solution` pattern. Notebook 03 shows that records are not static: when the model changes, `check_stale()` detects the mismatch between the stored hash and the current source. +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. + +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. ## Expected result -After running all three notebooks, `verify_satisfaction()` returns two Verdict objects: `nominal` holds=True, `slow` holds=False. `validate_record(violation)` returns `[]`. `check_stale(record, revised_source)` returns True after the requirement threshold changes. +After running all three notebooks: `deliveredEnergyBoundedBySupply` is confirmed present in the loaded model by `model.find()` and `model.query()`; `verify_holds()` reports it `satisfied`, with the reason text naming `z3`, proved for all values a companion restatement admits, not evaluated at one; a fully broken variant of the same shape is reported `violated`; a merely weakened variant is reported `undecided`, with `holds()` raising an inconclusive error rather than answering `True` or `False`; `verify_satisfaction()` still reports the model's three existing claims exactly as it always has; `conformance.report()` shows `satisfaction-claims-evaluated` reporting `passed`, not `blocked`, on ch08's own fixture for the first time (it was already passing on ch03 through ch07); `check_stale()` returns `True` once the lemma's bound is loosened. ## Experiment -Try the [Chapter 8 exercise](../../exercises/ch08/exercise.ipynb): produce a violation witness for the `weak` Heater variant and demonstrate stale detection after changing the HeatingReq power threshold. +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. diff --git a/chapters/ch09-coverage-sufficiency/01-requirement-coverage.ipynb b/chapters/ch09-coverage-sufficiency/01-requirement-coverage.ipynb index e1ad481..6ee1189 100644 --- a/chapters/ch09-coverage-sufficiency/01-requirement-coverage.ipynb +++ b/chapters/ch09-coverage-sufficiency/01-requirement-coverage.ipynb @@ -1,78 +1,372 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, "cells": [ { "cell_type": "markdown", - "id": "cell-0", + "id": "ce13ae13", "metadata": {}, "source": [ - "## model.query(RequirementUsage) + to_api_json(SatisfyRequirementUsage)\n\n**Concept statement (stub):** This notebook introduces model.query(RequirementUsage) + to_api_json(SatisfyRequirementUsage); after running it you can [TODO]." + "## Ch9-01 -- Querying the model for what has, and has not, been claimed\n", + "\n", + "This notebook introduces a real requirement-coverage report, built by joining every named `RequirementUsage` against every `SatisfyRequirementUsage` the model actually carries; after running it you can see which requirements have a real claim of satisfaction against them, which candidates were checked and with what polarity, and which have none at all." ] }, { "cell_type": "markdown", - "id": "cell-1", + "id": "bc30d0cd", "metadata": {}, "source": [ - "**Context (stub):** [TODO \u2014 one paragraph locating this notebook in the chapter arc.]" + "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))." ] }, { "cell_type": "code", - "id": "cell-2", + "execution_count": 1, + "id": "5de63c72", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T01:57:38.465943Z", + "iopub.status.busy": "2026-10-01T01:57:38.465818Z", + "iopub.status.idle": "2026-10-01T01:57:38.695697Z", + "shell.execute_reply": "2026-10-01T01:57:38.695010Z" + } + }, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "import opensysml\n", + "from toaster.report import format_diagnostics\n", + "\n", + "conn = opensysml.connect(version=\"v0.9.0\")\n", + "source = Path(\"../../models/ch08-cumulative.sysml\").read_text()\n", + "model = conn.load_from_content(source, strict=False)\n", + "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"\n" + ] + }, + { + "cell_type": "markdown", + "id": "0fbdccbb", "metadata": {}, "source": [ - "import opensysml\n\n# TODO: full cumulative SysML source (SA-2)\nsource = \"\"\"\n# stub \u2014 replace with full model\n\"\"\"\n\nconn = opensysml.connect(version=\"v0.9.0\")\nmodel = conn.load_from_content(source, strict=False)\nassert model.ok, f\"Model failed: {model.diagnostics}\"" + "The model loads cleanly. Before querying it, the same language-tier control every chapter carries: a `satisfy` naming a requirement that was never declared still fails to load, which matters directly for a chapter about what has and has not been claimed -- a broken reference can never silently count as a claim." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "9bae63f2", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T01:57:38.697078Z", + "iopub.status.busy": "2026-10-01T01:57:38.696902Z", + "iopub.status.idle": "2026-10-01T01:57:38.711599Z", + "shell.execute_reply": "2026-10-01T01:57:38.710883Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Negative control ok: bad.ok=False\n" + ] + } ], - "outputs": [], - "execution_count": null + "source": [ + "# A satisfy claim naming a requirement that doesn't exist fails to load; it can\n", + "# never silently show up as a \"claim\" this chapter's coverage query would count.\n", + "bad_source = \"\"\"\n", + "package Bad {\n", + " private import ScalarValues::*;\n", + " part def Widget { attribute x : Real default = 1.0; }\n", + " part w : Widget;\n", + " assert satisfy missingReq by w;\n", + "}\n", + "\"\"\"\n", + "bad = conn.load_from_content(bad_source, strict=False)\n", + "assert not bad.ok, \"Expected failure for an undeclared requirement\"\n", + "print(f\"Negative control ok: bad.ok={bad.ok}\")\n" + ] + }, + { + "cell_type": "markdown", + "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)." + ] }, { "cell_type": "code", - "id": "cell-3", + "execution_count": 3, + "id": "56669dba", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T01:57:38.712940Z", + "iopub.status.busy": "2026-10-01T01:57:38.712826Z", + "iopub.status.idle": "2026-10-01T01:57:38.882345Z", + "shell.execute_reply": "2026-10-01T01:57:38.881822Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "requirements: ['ToasterDemo::heatGenerationReq', 'ToasterDemo::timely']\n", + "{'id': 'ToasterDemo::slow::@1', 'requirement': 'ToasterDemo::timely', 'subject': 'ToasterDemo::slow', 'is_verify': False, 'is_negated': True}\n", + "{'id': 'ToasterDemo::TimelyToastTest::@2::@0', 'requirement': 'ToasterDemo::timely', 'subject': None, 'is_verify': True, 'is_negated': False}\n", + "{'id': 'ToasterDemo::rated::@1', 'requirement': 'ToasterDemo::heatGenerationReq', 'subject': 'ToasterDemo::rated', 'is_verify': False, 'is_negated': False}\n", + "{'id': 'ToasterDemo::weak::@1', 'requirement': 'ToasterDemo::heatGenerationReq', 'subject': 'ToasterDemo::weak', 'is_verify': False, 'is_negated': True}\n" + ] + } + ], + "source": [ + "from toaster.query import find_requirements, get_satisfy_relationships, ApiIndex\n", + "\n", + "idx = ApiIndex(model)\n", + "requirements = sorted(r.id for r in find_requirements(model))\n", + "raw_satisfies = get_satisfy_relationships(model)\n", + "\n", + "claims = []\n", + "for s in raw_satisfies:\n", + " claims.append({\n", + " \"id\": s.get(\"qualifiedName\"),\n", + " \"requirement\": idx.qn(s[\"subsets\"]) if \"subsets\" in s else None,\n", + " \"subject\": idx.qn(s[\"subject\"]) if \"subject\" in s else None,\n", + " \"is_verify\": s.get(\"sysx:declaredKeyword\") == \"verify\",\n", + " \"is_negated\": bool(s.get(\"isNegated\", False)),\n", + " })\n", + "\n", + "print(f\"requirements: {requirements}\")\n", + "for c in claims:\n", + " print(c)\n" + ] + }, + { + "cell_type": "markdown", + "id": "ff6d8b94", "metadata": {}, "source": [ - "# Negative control \u2014 TODO: intentional error matching chapter construct\nbad_source = \"part def Missing { part x : NonExistent; }\"\nbad = conn.load_from_content(bad_source, strict=False)\nassert not bad.ok" + "Four `SatisfyRequirementUsage` relationships in total, over two requirements -- but not four claims in the sense this report cares about. Three are real claims about a specific candidate, positive or negative: `slow` fails `timely`, `rated` satisfies `heatGenerationReq`, `weak` fails it. The fourth, `TimelyToastTest`'s own `verify timely;` objective, is a verification-case objective, not a claim about any subject: `TimelyToastTest` (the verification case) does declare its own `subject toaster : Toaster`, but that is a type-level placeholder, not a binding to a specific candidate like `nominal`, and the objective relationship itself, the one this query actually sees, carries no `subject` at all in the exported model. Joining the three real claims by requirement, split by polarity, is the actual coverage report; the fourth is reported separately, since it names a requirement without claiming anything about a subject." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "e39d4e38", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T01:57:38.883447Z", + "iopub.status.busy": "2026-10-01T01:57:38.883363Z", + "iopub.status.idle": "2026-10-01T01:57:38.887694Z", + "shell.execute_reply": "2026-10-01T01:57:38.887219Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "claims dropped (requirement not in the named list above): 0\n", + "ToasterDemo::heatGenerationReq:\n", + " positive satisfy claims: ['ToasterDemo::rated']\n", + " negative satisfy claims: ['ToasterDemo::weak']\n", + " verify objectives (no subject): []\n", + " covered (has >=1 positive claim): True\n", + "ToasterDemo::timely:\n", + " positive satisfy claims: []\n", + " negative satisfy claims: ['ToasterDemo::slow']\n", + " verify objectives (no subject): ['ToasterDemo::TimelyToastTest::@2::@0']\n", + " covered (has >=1 positive claim): False\n" + ] + } ], - "outputs": [], - "execution_count": null + "source": [ + "from collections import defaultdict\n", + "\n", + "coverage = {r: {\"positive\": [], \"negative\": [], \"verify_objectives\": []} for r in requirements}\n", + "dropped = []\n", + "for c in claims:\n", + " if c[\"requirement\"] not in coverage:\n", + " dropped.append(c)\n", + " continue\n", + " if c[\"is_verify\"]:\n", + " coverage[c[\"requirement\"]][\"verify_objectives\"].append(c[\"id\"])\n", + " elif c[\"is_negated\"]:\n", + " coverage[c[\"requirement\"]][\"negative\"].append(c[\"subject\"])\n", + " else:\n", + " coverage[c[\"requirement\"]][\"positive\"].append(c[\"subject\"])\n", + "\n", + "print(f\"claims dropped (requirement not in the named list above): {len(dropped)}\")\n", + "assert dropped == [], f\"Unexpected dropped claims: {dropped}\"\n", + "\n", + "for r in requirements:\n", + " cov = coverage[r]\n", + " positive, negative, verify_objectives = cov[\"positive\"], cov[\"negative\"], cov[\"verify_objectives\"]\n", + " covered = bool(positive)\n", + " print(f\"{r}:\")\n", + " print(f\" positive satisfy claims: {positive}\")\n", + " print(f\" negative satisfy claims: {negative}\")\n", + " print(f\" verify objectives (no subject): {verify_objectives}\")\n", + " print(f\" covered (has >=1 positive claim): {covered}\")\n", + "\n", + "assert coverage[\"ToasterDemo::heatGenerationReq\"][\"positive\"] == [\"ToasterDemo::rated\"]\n", + "assert coverage[\"ToasterDemo::heatGenerationReq\"][\"negative\"] == [\"ToasterDemo::weak\"]\n", + "assert coverage[\"ToasterDemo::timely\"][\"positive\"] == []\n" + ] + }, + { + "cell_type": "markdown", + "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." + ] + }, + { + "cell_type": "markdown", + "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." + ] }, { "cell_type": "code", - "id": "cell-4", + "execution_count": 5, + "id": "db0d48df", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T01:57:38.888867Z", + "iopub.status.busy": "2026-10-01T01:57:38.888779Z", + "iopub.status.idle": "2026-10-01T01:57:38.891497Z", + "shell.execute_reply": "2026-10-01T01:57:38.891089Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "A polarity-blind join would report:\n", + " ToasterDemo::heatGenerationReq: covered=True claimed by=['ToasterDemo::rated', 'ToasterDemo::weak']\n", + " ToasterDemo::timely: covered=True claimed by=['ToasterDemo::slow']\n" + ] + } + ], + "source": [ + "# A polarity-blind join: exactly the mistake of counting a FAILING claim as coverage.\n", + "naive_covered = defaultdict(list)\n", + "for c in claims:\n", + " if c[\"requirement\"] and c[\"subject\"]:\n", + " naive_covered[c[\"requirement\"]].append(c[\"subject\"])\n", + "\n", + "print(\"A polarity-blind join would report:\")\n", + "for r in requirements:\n", + " claimants = naive_covered.get(r, [])\n", + " print(f\" {r}: covered={bool(claimants)} claimed by={claimants}\")\n", + "\n", + "assert naive_covered[\"ToasterDemo::timely\"] == [\"ToasterDemo::slow\"]\n" + ] + }, + { + "cell_type": "markdown", + "id": "762d34e7", "metadata": {}, "source": [ - "# Demonstration \u2014 TODO: one key operation\npass" + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "a870c4f2", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T01:57:38.892682Z", + "iopub.status.busy": "2026-10-01T01:57:38.892589Z", + "iopub.status.idle": "2026-10-01T01:57:38.963115Z", + "shell.execute_reply": "2026-10-01T01:57:38.962669Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'requirement': 'ToasterDemo::heatGenerationReq', 'satisfied_by': ['ToasterDemo::rated'], 'failed_by': ['ToasterDemo::weak'], 'covered': True}\n", + "{'requirement': 'ToasterDemo::timely', 'satisfied_by': [], 'failed_by': ['ToasterDemo::slow'], 'covered': False}\n" + ] + } ], - "outputs": [], - "execution_count": null + "source": [ + "from toaster.query import requirement_coverage\n", + "\n", + "repo_coverage = {c[\"requirement\"]: c for c in requirement_coverage(model)}\n", + "for r in requirements:\n", + " print(repo_coverage[r])\n", + "\n", + "assert repo_coverage[\"ToasterDemo::timely\"][\"covered\"] is False\n", + "assert repo_coverage[\"ToasterDemo::timely\"][\"satisfied_by\"] == []\n", + "assert repo_coverage[\"ToasterDemo::timely\"][\"failed_by\"] == [\"ToasterDemo::slow\"]\n", + "assert repo_coverage[\"ToasterDemo::heatGenerationReq\"][\"covered\"] is True\n", + "assert repo_coverage[\"ToasterDemo::heatGenerationReq\"][\"satisfied_by\"] == [\"ToasterDemo::rated\"]\n", + "assert repo_coverage[\"ToasterDemo::heatGenerationReq\"][\"failed_by\"] == [\"ToasterDemo::weak\"]\n", + "conn.close()\n" + ] }, { "cell_type": "markdown", - "id": "cell-5", + "id": "d99e797b", "metadata": {}, "source": [ - "**Tall seam (stub):** [TODO \u2014 one sentence: the SysML construct (A-F) is executed by OpenSysML (O-S); the result is (E).]" + "Two independently-written joins over the same raw data, one built cell by cell in this notebook and one already living in the repository's own query module, now agree exactly: `timely` is not covered, `heatGenerationReq` is. They agree only because both actually track polarity; a join that does not, as shown above, reaches the opposite, wrong answer for `timely`. That is what \"coverage\" has to mean for this check to be worth anything at all: a positive claim someone actually made, not merely a claim of any kind." ] }, { "cell_type": "markdown", - "id": "cell-6", + "id": "2438803d", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch09/exercise.ipynb`: [TODO \u2014 one-line description]." + "One honest caveat about what `covered=False` means, since this tutorial keeps building on this same model after this chapter: Chapter 10 later adds a third named requirement, `energyConservationReq`, tied to Chapter 8's own Z3-proved conservation lemma (`deliveredEnergyBoundedBySupply`) by subsetting from within the requirement's own body, not by any instance-level `assert satisfy` (see that chapter's own `AC-C10` judgment record for the full account). `requirement_coverage()` -- this same helper, confirmed against the same two independently-written joins above -- reports `covered=False` for it too, with `satisfied_by=[]`, exactly as empty as `timely`'s own. But the two `False`s mean structurally different things: `timely`'s is a real, present gap, because `nominal` has never been positively checked against it at all, and could be; `energyConservationReq`'s is `False` by design, because this helper only counts a positive `assert satisfy` declaration, and that construct was deliberately not used for it (a direct test in Chapter 10 shows it would not do real evaluative work here). A reader who reaches Chapter 10 should not read `energyConservationReq`'s own `covered=False` as a second, undiscovered version of this chapter's `timely` finding." + ] + }, + { + "cell_type": "markdown", + "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." + ] + }, + { + "cell_type": "markdown", + "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." ] } - ] -} \ No newline at end of file + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/chapters/ch09-coverage-sufficiency/02-evidence-completeness.ipynb b/chapters/ch09-coverage-sufficiency/02-evidence-completeness.ipynb index 557752b..c018e6d 100644 --- a/chapters/ch09-coverage-sufficiency/02-evidence-completeness.ipynb +++ b/chapters/ch09-coverage-sufficiency/02-evidence-completeness.ipynb @@ -1,78 +1,521 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, "cells": [ { "cell_type": "markdown", - "id": "cell-0", + "id": "d2c44710", "metadata": {}, "source": [ - "## ReviewRecord completeness check \u2192 gap report\n\n**Concept statement (stub):** This notebook introduces ReviewRecord completeness check \u2192 gap report; after running it you can [TODO]." + "## Ch9-02 -- Evidence sufficiency, applied to two real records\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." ] }, { "cell_type": "markdown", - "id": "cell-1", + "id": "9b78b2f5", "metadata": {}, "source": [ - "**Context (stub):** [TODO \u2014 one paragraph locating this notebook in the chapter arc.]" + "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." ] }, { "cell_type": "code", - "id": "cell-2", + "execution_count": 1, + "id": "419ecbee", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:23:22.899928Z", + "iopub.status.busy": "2026-09-28T14:23:22.899767Z", + "iopub.status.idle": "2026-09-28T14:23:23.072242Z", + "shell.execute_reply": "2026-09-28T14:23:23.071741Z" + } + }, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "import opensysml\n", + "from toaster.report import format_diagnostics\n", + "\n", + "conn = opensysml.connect(version=\"v0.9.0\")\n", + "source = Path(\"../../models/ch08-cumulative.sysml\").read_text()\n", + "model = conn.load_from_content(source, strict=False)\n", + "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"\n" + ] + }, + { + "cell_type": "markdown", + "id": "37afe6e8", "metadata": {}, "source": [ - "import opensysml\n\n# TODO: full cumulative SysML source (SA-2)\nsource = \"\"\"\n# stub \u2014 replace with full model\n\"\"\"\n\nconn = opensysml.connect(version=\"v0.9.0\")\nmodel = conn.load_from_content(source, strict=False)\nassert model.ok, f\"Model failed: {model.diagnostics}\"" + "Before reconstructing two real records, the mechanized half of a sufficiency check: `validate_record()` already refuses a record whose `counterevidence` field is empty outright, distinct from asking whether a populated field is substantive, which this notebook goes on to ask." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "a94c115a", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:23:23.074091Z", + "iopub.status.busy": "2026-09-28T14:23:23.073906Z", + "iopub.status.idle": "2026-09-28T14:23:23.076713Z", + "shell.execute_reply": "2026-09-28T14:23:23.076154Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Negative control ok: errors=['counterevidence is empty']\n" + ] + } ], - "outputs": [], - "execution_count": null + "source": [ + "from toaster.evidence import ReviewRecord, hash_content, validate_record\n", + "\n", + "blank_counterevidence = ReviewRecord(\n", + " identifier=\"AS-BAD\",\n", + " kind=\"asserted_solution\",\n", + " claim=\"Some claim\",\n", + " model_ref=\"ToasterDemo\",\n", + " content_hash=hash_content(source),\n", + " scope=\"Some scope\",\n", + " criteria=\"Some criteria\",\n", + " rationale=\"Some rationale\",\n", + " counterevidence=\"\", # intentionally empty\n", + " record_kind=\"worked_example\",\n", + ")\n", + "errors = validate_record(blank_counterevidence)\n", + "assert len(errors) > 0, \"Expected validation to fail on empty counterevidence\"\n", + "print(f\"Negative control ok: errors={errors}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "655df44e", + "metadata": {}, + "source": [ + "`AS-C06`, rebuilt below **verbatim** from Chapter 6 notebook 02's own field strings, not paraphrased: 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." + ] }, { "cell_type": "code", - "id": "cell-3", + "execution_count": 3, + "id": "80e1a2ae", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:23:23.078385Z", + "iopub.status.busy": "2026-09-28T14:23:23.078280Z", + "iopub.status.idle": "2026-09-28T14:23:23.081881Z", + "shell.execute_reply": "2026-09-28T14:23:23.081469Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "AS-C06 validation errors: []\n" + ] + } + ], + "source": [ + "ch06_source = Path(\"../../models/ch06-cumulative.sysml\").read_text()\n", + "\n", + "as_c06 = ReviewRecord(\n", + " identifier=\"AS-C06\",\n", + " kind=\"asserted_solution\",\n", + " claim=(\n", + " \"ResistanceCoil, an electrically switched resistive element that converts \"\n", + " \"energy to heat by Joule heating, is selected over a combustion-based \"\n", + " \"alternative (a gas burner, the tongs-and-blowtorch alternative this \"\n", + " \"tutorial already contrasts) as the mechanism HeatGenerator commits to.\"\n", + " ),\n", + " model_ref=\"ToasterDemo::ResistanceCoil\",\n", + " content_hash=hash_content(ch06_source),\n", + " scope=\"ToasterDemo::HeatGenerator and its realizations\",\n", + " criteria=(\n", + " \"The chosen mechanism must pair with the discrete timing control \"\n", + " \"ControlSystem's durationOut already provides, and must expose a rating \"\n", + " \"heatGenerationReq's power threshold can be checked against once a \"\n", + " \"concrete part exists.\"\n", + " ),\n", + " premises=[\n", + " \"ControlSystem declares durationOut : DurationPort (Chapter 5), a \"\n", + " \"discrete duration signal, confirmed by model.find() above.\",\n", + " \"Domain premise, not derived from the model: a resistive element \"\n", + " \"responds to being switched on and off directly, while a combustion \"\n", + " \"source needs separate ignition and fuel-metering machinery to do \"\n", + " \"the same. Neither HeatGenerator nor any of its realizations is yet \"\n", + " \"connected to ControlSystem's port in this model; this premise is \"\n", + " \"about the physical mechanisms themselves, not about what the model \"\n", + " \"currently wires together.\",\n", + " ],\n", + " assumption_refs=[\n", + " \"HeatGenerator::energyIn and GenerateHeat carry no energy-form commitment \"\n", + " \"(notebook 01): this record is what actually commits to an electrical \"\n", + " \"form, not a fact already built into the port or the function.\"\n", + " ],\n", + " evidence_refs=[\n", + " \"model.find('ToasterDemo::ControlSystem::durationOut') resolves to a \"\n", + " \"real portUsage, confirmed above.\"\n", + " ],\n", + " rationale=(\n", + " \"An electrically resistive element responds to being switched on \"\n", + " \"and off directly, the same shape as duration's discrete timing \"\n", + " \"signal, while a combustion-based burner needs separate ignition \"\n", + " \"and fuel-metering machinery to respond the same way (the domain \"\n", + " \"premise above). That is a claim about how the two mechanisms \"\n", + " \"work, not something this model currently shows: no realization \"\n", + " \"of HeatGenerator is yet connected to ControlSystem's \"\n", + " \"durationOut port, so this selection is a reasoned engineering \"\n", + " \"preference argued from mechanism, not a claim that the model \"\n", + " \"already connects one mechanism and not the other.\"\n", + " ),\n", + " counterevidence=(\n", + " \"This does not rule out a combustion design: a burner controlled by its \"\n", + " \"own timed valve could equally use a duration-like signal, which is \"\n", + " \"exactly why the argument above rests on a domain premise about how \"\n", + " \"the two mechanisms work, not on anything the model itself already \"\n", + " \"builds or connects. Joule heating's own relation (power proportional \"\n", + " \"to resistance and the square of current) is still not modeled, so \"\n", + " \"efficiency and response-time comparisons remain out of reach \"\n", + " \"either way.\"\n", + " ),\n", + " residual_uncertainties=(\n", + " \"Once a supply and a control policy are modeled together, this \"\n", + " \"selection could be revisited against a real trade study rather than \"\n", + " \"a domain premise alone.\"\n", + " ),\n", + " disposition=\"pending\",\n", + " dependency_freshness=\"current\",\n", + " engineering_conclusion=\"undetermined\",\n", + " record_kind=\"worked_example\",\n", + ")\n", + "\n", + "errors = validate_record(as_c06)\n", + "print(f\"AS-C06 validation errors: {errors}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "3e8a56b3", "metadata": {}, "source": [ - "# Negative control \u2014 TODO: intentional error matching chapter construct\nbad_source = \"part def Missing { part x : NonExistent; }\"\nbad = conn.load_from_content(bad_source, strict=False)\nassert not bad.ok" + "`AS-C08`, rebuilt the same way, verbatim, from Chapter 8 notebook 02: 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." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "6c7c7a99", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:23:23.083004Z", + "iopub.status.busy": "2026-09-28T14:23:23.082926Z", + "iopub.status.idle": "2026-09-28T14:23:23.086419Z", + "shell.execute_reply": "2026-09-28T14:23:23.086032Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "AS-C08 validation errors: []\n" + ] + } ], - "outputs": [], - "execution_count": null + "source": [ + "as_c08 = ReviewRecord(\n", + " identifier=\"AS-C08\",\n", + " kind=\"asserted_solution\",\n", + " claim=(\n", + " \"deliveredEnergyBoundedBySupply, a hand-restated real-arithmetic lemma of the same \"\n", + " \"shape as HeatGenerator's efficiencyBounded constraint and deliveredEnergy's own \"\n", + " \"definition, holds for every value of efficiency in [0,1] and every non-negative \"\n", + " \"power and duration a companion restatement admits.\"\n", + " ),\n", + " model_ref=\"ToasterDemo::deliveredEnergyBoundedBySupply\",\n", + " content_hash=hash_content(source),\n", + " scope=(\n", + " \"The lemma governs the companion restatement's own heatGenCheck.efficiency, \"\n", + " \"heatGenCheck.power and heatGenCheckDuration features; it is not a solver-checked \"\n", + " \"reference to HeatGenerator's own efficiencyBounded or deliveredEnergy (DEFERRED.md \"\n", + " \"D-030, D-031), only a hand-restated copy of the same shape.\"\n", + " ),\n", + " criteria=(\n", + " \"verify_holds() reports a single satisfied verdict for deliveredEnergyBoundedBySupply, \"\n", + " \"with the reason text naming z3 (not propagation alone), proved for all values of the \"\n", + " \"unbound heatGenCheck.efficiency, heatGenCheck.power and heatGenCheckDuration features.\"\n", + " ),\n", + " premises=[],\n", + " assumption_refs=[],\n", + " evidence_refs=[\n", + " \"verify_holds: deliveredEnergyBoundedBySupply satisfied \"\n", + " \"(z3: holds for all values of unbound features)\"\n", + " ],\n", + " rationale=(\n", + " \"verify_holds() calls sysml-toolkit's real verify --solve command, which runs Z3 \"\n", + " \"over the unbound features of a companion restatement of this lemma (see the \"\n", + " \"narration above for why a companion file is used) and reports \"\n", + " \"deliveredEnergyBoundedBySupply as satisfied: proved for all values the restatement \"\n", + " \"admits, not read back from one entered value. This is a materially different kind of \"\n", + " \"evidence from an evaluate-only verdict: verify_satisfaction() could only ever check \"\n", + " \"a relation at whichever single power, duration and efficiency a candidate happens to \"\n", + " \"carry. It is also, deliberately, a narrower claim than 'this proves HeatGenerator's \"\n", + " \"own conservation property': see counterevidence.\"\n", + " ),\n", + " counterevidence=(\n", + " \"This proof is NOT a solver-checked reference to HeatGenerator's own \"\n", + " \"efficiencyBounded constraint or deliveredEnergy calc: this toolchain's Z3 backend \"\n", + " \"does not compose two separately declared assert constraints, whether sibling or \"\n", + " \"inherited (DEFERRED.md D-030), and cannot reason through a chained calc invocation \"\n", + " \"like heatGenCheck.deliveredEnergy(...) (D-031). Confirmed directly: loosening \"\n", + " \"efficiencyBounded's own literal bound to <= 1.5, or doubling deliveredEnergy's own \"\n", + " \"definition by a factor of 2.0, in the real committed model changes neither the \"\n", + " \"original elements' own verdicts nor this lemma's verdict at all, because the lemma \"\n", + " \"restates its own copy of both rather than referencing either. The companion file \"\n", + " \"used by verify_holds also restates the construct rather than checking the \"\n", + " \"committed model directly, because toaster.modelcheck's own text parser does not \"\n", + " \"yet handle the extra annotation the CLI prints for assert satisfy declarations \"\n", + " \"(DEFERRED.md D-029).\"\n", + " ),\n", + " residual_uncertainties=(\n", + " \"Whether efficiency, power and duration ever take values outside the bound in a \"\n", + " \"real candidate is not addressed by this proof; it establishes only that the \"\n", + " \"restated lemma respects conservation wherever its own bound is honored. If \"\n", + " \"HeatGenerator's own efficiencyBounded or deliveredEnergy is ever edited, this \"\n", + " \"record's content_hash (computed from the whole model file) does go stale, which \"\n", + " \"forces a re-review, but nothing automatically re-checks that the restated copy \"\n", + " \"still matches the edited original; that check would be manual. No physical heat \"\n", + " \"generator has been checked against this property; HeatGenerator remains an \"\n", + " \"abstract carrier with no concrete realization of its own.\"\n", + " ),\n", + " disposition=\"pending\",\n", + " dependency_freshness=\"current\",\n", + " engineering_conclusion=\"supported\",\n", + " record_kind=\"worked_example\",\n", + ")\n", + "\n", + "errors = validate_record(as_c08)\n", + "print(f\"AS-C08 validation errors: {errors}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "36a3665e", + "metadata": {}, + "source": [ + "Both records validate cleanly. Hawkins' sufficiency idea is about whether stated PREMISES make a conclusion's probable truth follow, so the premises themselves belong in this check, not only `counterevidence` and `residual_uncertainties`. `AS-C06`'s two `premises` entries are exactly what its own selection argument rests on (a confirmed model fact, plus a domain premise about how the two mechanisms work); `AS-C08`'s `premises` and `assumption_refs` are both genuinely empty." + ] }, { "cell_type": "code", - "id": "cell-4", + "execution_count": 5, + "id": "589670af", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:23:23.087658Z", + "iopub.status.busy": "2026-09-28T14:23:23.087574Z", + "iopub.status.idle": "2026-09-28T14:23:23.089657Z", + "shell.execute_reply": "2026-09-28T14:23:23.089343Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "AS-C06 premises: ['ControlSystem declares durationOut : DurationPort (Chapter 5), a discrete duration signal, confirmed by model.find() above.', \"Domain premise, not derived from the model: a resistive element responds to being switched on and off directly, while a combustion source needs separate ignition and fuel-metering machinery to do the same. Neither HeatGenerator nor any of its realizations is yet connected to ControlSystem's port in this model; this premise is about the physical mechanisms themselves, not about what the model currently wires together.\"]\n", + "AS-C06 assumption_refs: ['HeatGenerator::energyIn and GenerateHeat carry no energy-form commitment (notebook 01): this record is what actually commits to an electrical form, not a fact already built into the port or the function.']\n", + "\n", + "AS-C08 premises: []\n", + "AS-C08 assumption_refs: []\n" + ] + } + ], + "source": [ + "print(f\"AS-C06 premises: {as_c06.premises}\")\n", + "print(f\"AS-C06 assumption_refs: {as_c06.assumption_refs}\")\n", + "print()\n", + "print(f\"AS-C08 premises: {as_c08.premises}\")\n", + "print(f\"AS-C08 assumption_refs: {as_c08.assumption_refs}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "cbe866f5", "metadata": {}, "source": [ - "# Demonstration \u2014 TODO: one key operation\npass" + "Is `AS-C08`'s empty `premises` field itself a sufficiency concern? Read against what the record actually claims, no: `AS-C06`'s claim is an inductive engineering judgment (a mechanism selected FOR stated reasons, reasons that could be wrong), exactly what Hawkins' idea is about, so it needs premises to carry the inductive weight. `AS-C08`'s claim, narrowly read (see its own `claim` and `scope` above), is that a stated lemma was PROVED by Z3 -- a deductive result, not an inductive one, so there is no separate premise beyond the proof itself for the record to name; `evidence_refs` carries the proof, and that is the right place for it to live. The genuine risk is not the empty `premises` field but a claim that quietly outgrows its own narrow scope -- which is exactly what `AS-C08`'s own `counterevidence` exists to prevent, by refusing to let the proved lemma stand in for a claim about `HeatGenerator`'s real `efficiencyBounded` and `deliveredEnergy`." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "0fc8e98d", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:23:23.090750Z", + "iopub.status.busy": "2026-09-28T14:23:23.090673Z", + "iopub.status.idle": "2026-09-28T14:23:23.093387Z", + "shell.execute_reply": "2026-09-28T14:23:23.092778Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "AS-C06:\n", + " counterevidence (78 words): This does not rule out a combustion design: a burner controlled by its own timed...\n", + " residual_uncertainties (26 words): Once a supply and a control policy are modeled together, this selection could be...\n", + " disposition: pending\n", + " engineering_conclusion: undetermined\n", + "AS-C08:\n", + " counterevidence (133 words): This proof is NOT a solver-checked reference to HeatGenerator's own efficiencyBo...\n", + " residual_uncertainties (101 words): Whether efficiency, power and duration ever take values outside the bound in a r...\n", + " disposition: pending\n", + " engineering_conclusion: supported\n" + ] + } ], - "outputs": [], - "execution_count": null + "source": [ + "def word_count(text: str) -> int:\n", + " return len(text.split())\n", + "\n", + "\n", + "def word_label(text: str) -> str:\n", + " n = word_count(text)\n", + " return f\"{n} word\" if n == 1 else f\"{n} words\"\n", + "\n", + "for record in (as_c06, as_c08):\n", + " print(f\"{record.identifier}:\")\n", + " print(f\" counterevidence ({word_label(record.counterevidence)}): {record.counterevidence[:80]}...\")\n", + " print(f\" residual_uncertainties ({word_label(record.residual_uncertainties)}): {record.residual_uncertainties[:80]}...\")\n", + " print(f\" disposition: {record.disposition}\")\n", + " print(f\" engineering_conclusion: {record.engineering_conclusion}\")\n" + ] + }, + { + "cell_type": "markdown", + "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." + ] + }, + { + "cell_type": "markdown", + "id": "457835e4", + "metadata": {}, + "source": [ + "A structural check alone cannot tell substantive text from a placeholder that happens to be non-empty. The record below has every field `validate_record()` requires, populated with text that would pass it outright." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "d3bcae55", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:23:23.094894Z", + "iopub.status.busy": "2026-09-28T14:23:23.094801Z", + "iopub.status.idle": "2026-09-28T14:23:23.101394Z", + "shell.execute_reply": "2026-09-28T14:23:23.101002Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "validate_record: errors=[]\n", + "counterevidence: 'None known.' (2 words)\n", + "residual_uncertainties: 'None.' (1 word)\n" + ] + } + ], + "source": [ + "placeholder_record = ReviewRecord(\n", + " identifier=\"AS-PLACEHOLDER\",\n", + " kind=\"asserted_solution\",\n", + " claim=\"The chosen design meets its requirement.\",\n", + " model_ref=\"ToasterDemo\",\n", + " content_hash=hash_content(source),\n", + " scope=\"ToasterDemo\",\n", + " criteria=\"The design meets its requirement.\",\n", + " rationale=\"Analysis shows it does.\",\n", + " counterevidence=\"None known.\",\n", + " residual_uncertainties=\"None.\",\n", + " disposition=\"pending\",\n", + " dependency_freshness=\"current\",\n", + " engineering_conclusion=\"supported\",\n", + " record_kind=\"worked_example\",\n", + ")\n", + "\n", + "errors = validate_record(placeholder_record)\n", + "print(f\"validate_record: errors={errors}\")\n", + "print(f\"counterevidence: {placeholder_record.counterevidence!r} ({word_label(placeholder_record.counterevidence)})\")\n", + "print(f\"residual_uncertainties: {placeholder_record.residual_uncertainties!r} ({word_label(placeholder_record.residual_uncertainties)})\")\n", + "assert errors == [], \"validate_record accepts this outright: every required field is non-empty\"\n", + "conn.close()\n" + ] }, { "cell_type": "markdown", - "id": "cell-5", + "id": "b316be7b", "metadata": {}, "source": [ - "**Tall seam (stub):** [TODO \u2014 one sentence: the SysML construct (A-F) is executed by OpenSysML (O-S); the result is (E).]" + "`validate_record()` accepts this record outright, the same as `AS-C06` and `AS-C08` above: the mechanized floor only checks non-emptiness, and \"None known.\" and \"None.\" both satisfy it. A sufficiency reading must reject it anyway: neither field names a specific design alternative, a specific unmodeled relation, or a specific toolchain limit the way `AS-C06`'s and `AS-C08`'s own fields do; there is nothing here a reader could go check, disagree with, or find wrong. That is the actual difference sufficiency asks for, and it is a judgment a person makes by reading the field's content, not something `validate_record()` -- or any fixed rule -- can certify." ] }, { "cell_type": "markdown", - "id": "cell-6", + "id": "65441e63", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch09/exercise.ipynb`: [TODO \u2014 one-line description]." + "Sufficiency and staleness are different, related questions. A record can be well-argued and honest -- sufficient, by the reading above -- and still go stale later as the model it cites keeps growing around it; being sufficient when written is not a guarantee of staying so. [Notebook 03](03-stale-detection.ipynb) checks exactly that, for both records above." ] + }, + { + "cell_type": "markdown", + "id": "19b0cc33", + "metadata": {}, + "source": [ + "The two records rebuilt above, verbatim from their own chapters, both validate cleanly, and reading their actual counterevidence, residual_uncertainties and premises (not just checking they are non-empty) shows two different, both honest, levels of confidence -- exactly what a reader would need to decide how much to lean on each one." + ] + }, + { + "cell_type": "markdown", + "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." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.2" } - ] -} \ No newline at end of file + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/chapters/ch09-coverage-sufficiency/03-stale-detection.ipynb b/chapters/ch09-coverage-sufficiency/03-stale-detection.ipynb index a0fabdc..c2c3934 100644 --- a/chapters/ch09-coverage-sufficiency/03-stale-detection.ipynb +++ b/chapters/ch09-coverage-sufficiency/03-stale-detection.ipynb @@ -1,78 +1,500 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, "cells": [ { "cell_type": "markdown", - "id": "cell-0", + "id": "ff4016c0", "metadata": {}, "source": [ - "## change assumption \u2192 stale-marker \u2192 re-review\n\n**Concept statement (stub):** This notebook introduces change assumption \u2192 stale-marker \u2192 re-review; after running it you can [TODO]." + "## Ch9-03 -- Stale detection, at scale\n", + "\n", + "This notebook introduces `check_stale()` applied across a small set of tracked records at once, not just one; after running it you can tell, for each of two real records, whether it is still current against the real, committed model, and watch what a real model edit actually does to each one." ] }, { "cell_type": "markdown", - "id": "cell-1", + "id": "38d9fc5f", "metadata": {}, "source": [ - "**Context (stub):** [TODO \u2014 one paragraph locating this notebook in the chapter arc.]" + "Chapter 8 notebook 03 already showed `check_stale()` on one record against one lemma. This notebook keeps the chapter's original filename (`03-stale-detection.ipynb`): the content is genuinely about scale -- checking several tracked records against the real, current model in one pass -- but \"at scale\" over two records is still a small, honestly-scoped demonstration, not a claim that every record this tutorial has ever produced is being tracked (notebook 02's own scope note applies here too). It reuses notebook 02's own two records, `AS-C06` and `AS-C08`, rebuilt verbatim below (the exact same field strings notebook 02 uses) since each notebook in this tutorial loads and runs independently." ] }, { "cell_type": "code", - "id": "cell-2", + "execution_count": 1, + "id": "dc06bb34", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:23:24.838041Z", + "iopub.status.busy": "2026-09-28T14:23:24.837831Z", + "iopub.status.idle": "2026-09-28T14:23:24.968994Z", + "shell.execute_reply": "2026-09-28T14:23:24.968378Z" + } + }, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "import opensysml\n", + "from toaster.report import format_diagnostics\n", + "\n", + "conn = opensysml.connect(version=\"v0.9.0\")\n", + "source = Path(\"../../models/ch08-cumulative.sysml\").read_text()\n", + "model = conn.load_from_content(source, strict=False)\n", + "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"\n" + ] + }, + { + "cell_type": "markdown", + "id": "d898f0f1", "metadata": {}, "source": [ - "import opensysml\n\n# TODO: full cumulative SysML source (SA-2)\nsource = \"\"\"\n# stub \u2014 replace with full model\n\"\"\"\n\nconn = opensysml.connect(version=\"v0.9.0\")\nmodel = conn.load_from_content(source, strict=False)\nassert model.ok, f\"Model failed: {model.diagnostics}\"" + "A record with an empty identifier fails validation outright, distinct from staleness: it was never a valid record to begin with, regardless of which model it references (the same control Chapter 8 notebook 03 used)." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "ec9a3aef", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:23:24.970621Z", + "iopub.status.busy": "2026-09-28T14:23:24.970433Z", + "iopub.status.idle": "2026-09-28T14:23:24.973252Z", + "shell.execute_reply": "2026-09-28T14:23:24.972776Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Negative control ok: errors=['identifier is empty']\n" + ] + } ], - "outputs": [], - "execution_count": null + "source": [ + "from toaster.evidence import ReviewRecord, hash_content, validate_record\n", + "\n", + "broken = ReviewRecord(\n", + " identifier=\"\", # intentionally empty\n", + " kind=\"asserted_solution\",\n", + " claim=\"Some claim\",\n", + " model_ref=\"ToasterDemo\",\n", + " content_hash=hash_content(source),\n", + " scope=\"Some scope\",\n", + " criteria=\"Some criteria\",\n", + " rationale=\"Some rationale\",\n", + " counterevidence=\"Some counterevidence\",\n", + " record_kind=\"worked_example\",\n", + ")\n", + "errors = validate_record(broken)\n", + "assert len(errors) > 0, \"Expected validation errors for empty identifier\"\n", + "print(f\"Negative control ok: errors={errors}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "e833756d", + "metadata": {}, + "source": [ + "`AS-C06` and `AS-C08`, rebuilt below **verbatim**, the exact same field strings notebook 02 uses (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." + ] }, { "cell_type": "code", - "id": "cell-3", + "execution_count": 3, + "id": "c46a2e31", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:23:24.974660Z", + "iopub.status.busy": "2026-09-28T14:23:24.974546Z", + "iopub.status.idle": "2026-09-28T14:23:24.978368Z", + "shell.execute_reply": "2026-09-28T14:23:24.977873Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "AS-C06 validation errors: []\n" + ] + } + ], + "source": [ + "ch06_source = Path(\"../../models/ch06-cumulative.sysml\").read_text()\n", + "\n", + "as_c06 = ReviewRecord(\n", + " identifier=\"AS-C06\",\n", + " kind=\"asserted_solution\",\n", + " claim=(\n", + " \"ResistanceCoil, an electrically switched resistive element that converts \"\n", + " \"energy to heat by Joule heating, is selected over a combustion-based \"\n", + " \"alternative (a gas burner, the tongs-and-blowtorch alternative this \"\n", + " \"tutorial already contrasts) as the mechanism HeatGenerator commits to.\"\n", + " ),\n", + " model_ref=\"ToasterDemo::ResistanceCoil\",\n", + " content_hash=hash_content(ch06_source),\n", + " scope=\"ToasterDemo::HeatGenerator and its realizations\",\n", + " criteria=(\n", + " \"The chosen mechanism must pair with the discrete timing control \"\n", + " \"ControlSystem's durationOut already provides, and must expose a rating \"\n", + " \"heatGenerationReq's power threshold can be checked against once a \"\n", + " \"concrete part exists.\"\n", + " ),\n", + " premises=[\n", + " \"ControlSystem declares durationOut : DurationPort (Chapter 5), a \"\n", + " \"discrete duration signal, confirmed by model.find() above.\",\n", + " \"Domain premise, not derived from the model: a resistive element \"\n", + " \"responds to being switched on and off directly, while a combustion \"\n", + " \"source needs separate ignition and fuel-metering machinery to do \"\n", + " \"the same. Neither HeatGenerator nor any of its realizations is yet \"\n", + " \"connected to ControlSystem's port in this model; this premise is \"\n", + " \"about the physical mechanisms themselves, not about what the model \"\n", + " \"currently wires together.\",\n", + " ],\n", + " assumption_refs=[\n", + " \"HeatGenerator::energyIn and GenerateHeat carry no energy-form commitment \"\n", + " \"(notebook 01): this record is what actually commits to an electrical \"\n", + " \"form, not a fact already built into the port or the function.\"\n", + " ],\n", + " evidence_refs=[\n", + " \"model.find('ToasterDemo::ControlSystem::durationOut') resolves to a \"\n", + " \"real portUsage, confirmed above.\"\n", + " ],\n", + " rationale=(\n", + " \"An electrically resistive element responds to being switched on \"\n", + " \"and off directly, the same shape as duration's discrete timing \"\n", + " \"signal, while a combustion-based burner needs separate ignition \"\n", + " \"and fuel-metering machinery to respond the same way (the domain \"\n", + " \"premise above). That is a claim about how the two mechanisms \"\n", + " \"work, not something this model currently shows: no realization \"\n", + " \"of HeatGenerator is yet connected to ControlSystem's \"\n", + " \"durationOut port, so this selection is a reasoned engineering \"\n", + " \"preference argued from mechanism, not a claim that the model \"\n", + " \"already connects one mechanism and not the other.\"\n", + " ),\n", + " counterevidence=(\n", + " \"This does not rule out a combustion design: a burner controlled by its \"\n", + " \"own timed valve could equally use a duration-like signal, which is \"\n", + " \"exactly why the argument above rests on a domain premise about how \"\n", + " \"the two mechanisms work, not on anything the model itself already \"\n", + " \"builds or connects. Joule heating's own relation (power proportional \"\n", + " \"to resistance and the square of current) is still not modeled, so \"\n", + " \"efficiency and response-time comparisons remain out of reach \"\n", + " \"either way.\"\n", + " ),\n", + " residual_uncertainties=(\n", + " \"Once a supply and a control policy are modeled together, this \"\n", + " \"selection could be revisited against a real trade study rather than \"\n", + " \"a domain premise alone.\"\n", + " ),\n", + " disposition=\"pending\",\n", + " dependency_freshness=\"current\",\n", + " engineering_conclusion=\"undetermined\",\n", + " record_kind=\"worked_example\",\n", + ")\n", + "\n", + "errors = validate_record(as_c06)\n", + "print(f\"AS-C06 validation errors: {errors}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "32a7cde1", "metadata": {}, "source": [ - "# Negative control \u2014 TODO: intentional error matching chapter construct\nbad_source = \"part def Missing { part x : NonExistent; }\"\nbad = conn.load_from_content(bad_source, strict=False)\nassert not bad.ok" + "`AS-C08`, the record Chapter 8's own Z3 proof grounds, rebuilt the same verbatim way." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "5d39d8e4", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:23:24.979774Z", + "iopub.status.busy": "2026-09-28T14:23:24.979691Z", + "iopub.status.idle": "2026-09-28T14:23:24.982983Z", + "shell.execute_reply": "2026-09-28T14:23:24.982664Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "AS-C08 validation errors: []\n" + ] + } ], - "outputs": [], - "execution_count": null + "source": [ + "as_c08 = ReviewRecord(\n", + " identifier=\"AS-C08\",\n", + " kind=\"asserted_solution\",\n", + " claim=(\n", + " \"deliveredEnergyBoundedBySupply, a hand-restated real-arithmetic lemma of the same \"\n", + " \"shape as HeatGenerator's efficiencyBounded constraint and deliveredEnergy's own \"\n", + " \"definition, holds for every value of efficiency in [0,1] and every non-negative \"\n", + " \"power and duration a companion restatement admits.\"\n", + " ),\n", + " model_ref=\"ToasterDemo::deliveredEnergyBoundedBySupply\",\n", + " content_hash=hash_content(source),\n", + " scope=(\n", + " \"The lemma governs the companion restatement's own heatGenCheck.efficiency, \"\n", + " \"heatGenCheck.power and heatGenCheckDuration features; it is not a solver-checked \"\n", + " \"reference to HeatGenerator's own efficiencyBounded or deliveredEnergy (DEFERRED.md \"\n", + " \"D-030, D-031), only a hand-restated copy of the same shape.\"\n", + " ),\n", + " criteria=(\n", + " \"verify_holds() reports a single satisfied verdict for deliveredEnergyBoundedBySupply, \"\n", + " \"with the reason text naming z3 (not propagation alone), proved for all values of the \"\n", + " \"unbound heatGenCheck.efficiency, heatGenCheck.power and heatGenCheckDuration features.\"\n", + " ),\n", + " premises=[],\n", + " assumption_refs=[],\n", + " evidence_refs=[\n", + " \"verify_holds: deliveredEnergyBoundedBySupply satisfied \"\n", + " \"(z3: holds for all values of unbound features)\"\n", + " ],\n", + " rationale=(\n", + " \"verify_holds() calls sysml-toolkit's real verify --solve command, which runs Z3 \"\n", + " \"over the unbound features of a companion restatement of this lemma (see the \"\n", + " \"narration above for why a companion file is used) and reports \"\n", + " \"deliveredEnergyBoundedBySupply as satisfied: proved for all values the restatement \"\n", + " \"admits, not read back from one entered value. This is a materially different kind of \"\n", + " \"evidence from an evaluate-only verdict: verify_satisfaction() could only ever check \"\n", + " \"a relation at whichever single power, duration and efficiency a candidate happens to \"\n", + " \"carry. It is also, deliberately, a narrower claim than 'this proves HeatGenerator's \"\n", + " \"own conservation property': see counterevidence.\"\n", + " ),\n", + " counterevidence=(\n", + " \"This proof is NOT a solver-checked reference to HeatGenerator's own \"\n", + " \"efficiencyBounded constraint or deliveredEnergy calc: this toolchain's Z3 backend \"\n", + " \"does not compose two separately declared assert constraints, whether sibling or \"\n", + " \"inherited (DEFERRED.md D-030), and cannot reason through a chained calc invocation \"\n", + " \"like heatGenCheck.deliveredEnergy(...) (D-031). Confirmed directly: loosening \"\n", + " \"efficiencyBounded's own literal bound to <= 1.5, or doubling deliveredEnergy's own \"\n", + " \"definition by a factor of 2.0, in the real committed model changes neither the \"\n", + " \"original elements' own verdicts nor this lemma's verdict at all, because the lemma \"\n", + " \"restates its own copy of both rather than referencing either. The companion file \"\n", + " \"used by verify_holds also restates the construct rather than checking the \"\n", + " \"committed model directly, because toaster.modelcheck's own text parser does not \"\n", + " \"yet handle the extra annotation the CLI prints for assert satisfy declarations \"\n", + " \"(DEFERRED.md D-029).\"\n", + " ),\n", + " residual_uncertainties=(\n", + " \"Whether efficiency, power and duration ever take values outside the bound in a \"\n", + " \"real candidate is not addressed by this proof; it establishes only that the \"\n", + " \"restated lemma respects conservation wherever its own bound is honored. If \"\n", + " \"HeatGenerator's own efficiencyBounded or deliveredEnergy is ever edited, this \"\n", + " \"record's content_hash (computed from the whole model file) does go stale, which \"\n", + " \"forces a re-review, but nothing automatically re-checks that the restated copy \"\n", + " \"still matches the edited original; that check would be manual. No physical heat \"\n", + " \"generator has been checked against this property; HeatGenerator remains an \"\n", + " \"abstract carrier with no concrete realization of its own.\"\n", + " ),\n", + " disposition=\"pending\",\n", + " dependency_freshness=\"current\",\n", + " engineering_conclusion=\"supported\",\n", + " record_kind=\"worked_example\",\n", + ")\n", + "\n", + "errors = validate_record(as_c08)\n", + "print(f\"AS-C08 validation errors: {errors}\")\n" + ] + }, + { + "cell_type": "markdown", + "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." + ] }, { "cell_type": "code", - "id": "cell-4", + "execution_count": 5, + "id": "cd1f0798", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:23:24.984138Z", + "iopub.status.busy": "2026-09-28T14:23:24.984060Z", + "iopub.status.idle": "2026-09-28T14:23:24.986753Z", + "shell.execute_reply": "2026-09-28T14:23:24.986358Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Before any edit:\n", + " AS-C06: stale=True\n", + " AS-C08: stale=False\n" + ] + } + ], + "source": [ + "from toaster.evidence import check_stale\n", + "\n", + "tracked = {\"AS-C06\": as_c06, \"AS-C08\": as_c08}\n", + "\n", + "print(\"Before any edit:\")\n", + "for identifier, record in tracked.items():\n", + " print(f\" {identifier}: stale={check_stale(record, source)}\")\n", + "\n", + "assert check_stale(as_c06, source) is True\n", + "assert check_stale(as_c08, source) is False\n" + ] + }, + { + "cell_type": "markdown", + "id": "283a8248", "metadata": {}, "source": [ - "# Demonstration \u2014 TODO: one key operation\npass" + "`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." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "0319ca53", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:23:24.988401Z", + "iopub.status.busy": "2026-09-28T14:23:24.988274Z", + "iopub.status.idle": "2026-09-28T14:23:24.991315Z", + "shell.execute_reply": "2026-09-28T14:23:24.990902Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "18 added lines mention efficiency, including:\n", + " + attribute efficiency : DimensionOneValue;\n", + " + assert constraint efficiencyBounded {\n", + " + doc /* Efficiency is the fraction of supplied energy delivered as\n" + ] + } ], - "outputs": [], - "execution_count": null + "source": [ + "import difflib\n", + "\n", + "ch06_lines = ch06_source.splitlines()\n", + "ch08_lines = source.splitlines()\n", + "added = [\n", + " line for line in difflib.unified_diff(ch06_lines, ch08_lines, lineterm=\"\")\n", + " if line.startswith(\"+\") and not line.startswith(\"+++\")\n", + "]\n", + "efficiency_lines = [line for line in added if \"efficiency\" in line.lower()]\n", + "print(f\"{len(efficiency_lines)} added lines mention efficiency, including:\")\n", + "for line in efficiency_lines[:3]:\n", + " print(f\" {line.strip()}\")\n", + "\n", + "assert efficiency_lines, \"Expected efficiency-related growth between ch06 and the current model\"\n" + ] + }, + { + "cell_type": "markdown", + "id": "c1e557c7", + "metadata": {}, + "source": [ + "`AS-C08`'s own `residual_uncertainties` names a different, specific risk: editing `HeatGenerator`'s own `efficiencyBounded` or `deliveredEnergy` would stale this record. The edit applied next is NOT that: it loosens `deliveredEnergyBoundedBySupply`'s own bound (the companion lemma copy the record is actually about), a different part of the same file. `check_stale()` still reports the record stale, for a reason its own text is explicit about: `content_hash` is computed from the WHOLE model file, so any edit anywhere in it, not only to the specific elements a record's own residual names, invalidates the hash -- a real, partial safeguard, honestly not a check that the specifically-named risk is what actually happened." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "a550031d", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:23:24.992526Z", + "iopub.status.busy": "2026-09-28T14:23:24.992452Z", + "iopub.status.idle": "2026-09-28T14:23:25.013485Z", + "shell.execute_reply": "2026-09-28T14:23:25.012810Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "After loosening deliveredEnergyBoundedBySupply's own bound (1.0 -> 1.2):\n", + " AS-C06: stale=True\n", + " AS-C08: stale=True\n" + ] + } + ], + "source": [ + "# Loosen the lemma's own bound (the companion copy AS-C08 is actually about), not\n", + "# HeatGenerator's efficiencyBounded/deliveredEnergy (the elements its own residual\n", + "# names as a risk) -- a different part of the same file.\n", + "revised_source = source.replace(\n", + " \"heatGenCheck.efficiency <= 1.0\",\n", + " \"heatGenCheck.efficiency <= 1.2\",\n", + ")\n", + "assert revised_source != source, \"Expected the replacement to change the source\"\n", + "assert \"0.0 <= efficiency and efficiency <= 1.0\" in revised_source, (\n", + " \"efficiencyBounded's own bare-`efficiency` bound must be untouched by this edit\"\n", + ")\n", + "revised_model = conn.load_from_content(revised_source, strict=False)\n", + "assert revised_model.ok, \"Revised model should still parse\"\n", + "\n", + "print(\"After loosening deliveredEnergyBoundedBySupply's own bound (1.0 -> 1.2):\")\n", + "for identifier, record in tracked.items():\n", + " print(f\" {identifier}: stale={check_stale(record, revised_source)}\")\n", + "\n", + "assert check_stale(as_c06, revised_source) is True\n", + "assert check_stale(as_c08, revised_source) is True\n", + "conn.close()\n" + ] + }, + { + "cell_type": "markdown", + "id": "ac912478", + "metadata": {}, + "source": [ + "Both records now report stale, but the two histories are genuinely different. `AS-C06` was stale before this edit too, and for a reason this specific edit has nothing to do with: real growth (Chapter 7) partly overtook the gap its own counterevidence named, without closing it (the Joule relation and response-time comparisons are still unmodeled). `AS-C08` newly went stale from an edit to a different construct than the one its own residual specifically warned about, caught only because the hash covers the whole file. Reading each record's own fields against the real model diff, not just its stale/current status, is what surfaces why each one changed: checking either record alone, at either point, would only have reported \"stale\" or \"current,\" not the reason." + ] }, { "cell_type": "markdown", - "id": "cell-5", + "id": "c1ffa13d", "metadata": {}, "source": [ - "**Tall seam (stub):** [TODO \u2014 one sentence: the SysML construct (A-F) is executed by OpenSysML (O-S); the result is (E).]" + "Two records, tracked and checked together against the same file before and after one real edit, reported two different histories: one substantively overtaken by real growth, the other caught by a coarse, whole-file safeguard rather than a targeted one." ] }, { "cell_type": "markdown", - "id": "cell-6", + "id": "509ec634", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch09/exercise.ipynb`: [TODO \u2014 one-line description]." + "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." ] } - ] -} \ No newline at end of file + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/chapters/ch09-coverage-sufficiency/conclusion.md b/chapters/ch09-coverage-sufficiency/conclusion.md index d3a8ff0..133ed89 100644 --- a/chapters/ch09-coverage-sufficiency/conclusion.md +++ b/chapters/ch09-coverage-sufficiency/conclusion.md @@ -1,9 +1,19 @@ # Chapter 9 Conclusion -**What we built (stub):** [TODO — model state after this chapter.] +## What we built -**What this establishes (stub):** [TODO — engineering conclusion.] +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. -**What comes next (stub):** [TODO — one sentence bridging to Chapter 10.]. +## What this establishes -**Exercise:** See `exercises/ch09/exercise.ipynb`: [TODO — one-line description]. +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. + +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. + +## 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. + +## 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. diff --git a/chapters/ch09-coverage-sufficiency/index.md b/chapters/ch09-coverage-sufficiency/index.md index 6bb64a9..a838e6f 100644 --- a/chapters/ch09-coverage-sufficiency/index.md +++ b/chapters/ch09-coverage-sufficiency/index.md @@ -1,13 +1,31 @@ -# Chapter 9: Coverage and Sufficiency +# Chapter 9 - Coverage and Sufficiency -**Purpose (stub):** [TODO — engineering question and model state after completing this chapter.] +## Purpose -**Ingredients:** [TODO — links to sub-notebooks with one-sentence concept statements.] +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. -**Equipment:** See [setup](../../docs/setup.md). +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. -**Method (stub):** [TODO — one-paragraph narrative.] +## Ingredients -**Expected result (stub):** [TODO — cumulative model state.] +| Notebook | Concept | +|---|---| +| [01 - Querying the model for what has, and has not, been claimed](01-requirement-coverage.ipynb) | Build a real coverage report by joining every requirement usage against every satisfy relationship, contrast it against a polarity-blind join that gets it wrong, and confirm it against the repository's own (now-fixed) `requirement_coverage()` helper. | +| [02 - Evidence sufficiency, applied to two real records](02-evidence-completeness.ipynb) | Apply Hawkins' sufficiency idea to two real ReviewRecords, reconstructed verbatim from Chapter 6 and Chapter 8. | +| [03 - Stale detection, at scale](03-stale-detection.ipynb) | Check several tracked records against the real, current model in one pass, before and after a real edit. | -**Experiment:** See `exercises/ch09/exercise.ipynb`. +## Equipment + +See [docs/setup.md](../../docs/setup.md) for environment setup. No additional tooling beyond earlier chapters. + +## Method + +Notebook 01 queries `model.query()` for every named `RequirementUsage` and `get_satisfy_relationships()` for every `SatisfyRequirementUsage`, joins them by requirement, and surfaces which requirements have a real positive claim of satisfaction, which have only a negative one, and which have none. It finds that `heatGenerationReq` is checked on both sides (`rated` passes, `weak` fails) while `timely` has never had a positive claim: `nominal` has no `assert satisfy timely by nominal` anywhere in the model, an absence, not a finding that `nominal` fails `timely` (`cycleTime` is still not derived from anything). (Chapter 10 later adds a third named requirement, `energyConservationReq`, which this same `requirement_coverage()` helper also reports as `covered=False` -- but for a structurally different reason: by design, tied to an already-proved lemma by subsetting rather than by any `assert satisfy`, not by omission like `timely`'s; see that chapter's own `AC-C10` judgment record.) The notebook also shows what a polarity-blind join would have wrongly concluded (`timely` "covered" by `slow`'s own failing claim) -- the exact bug the repository's own `requirement_coverage()` helper carried until this chapter's work found and fixed it -- and confirms its own join against that now-corrected helper. Notebook 02 reconstructs `AS-C06` and `AS-C08` verbatim, field for field, and asks whether each one's own counterevidence, residual_uncertainties and premises are substantive and whether its engineering_conclusion matches what its own evidence supports, honestly scoped to these two records, not a claim about every judgment record this tutorial has ever produced. Notebook 03 checks both records' staleness together against the real model, before and after loosening `deliveredEnergyBoundedBySupply`'s own bound: `AS-C06` is already stale, and substantively so (Chapter 7 added `HeatGenerator`'s `efficiency`/`efficiencyBounded`/`deliveredEnergy`, so an efficiency comparison its own counterevidence called out of reach can now at least be started, though the Joule-heating relation it also names, and any response-time comparison, are still not modeled); `AS-C08` goes stale from an edit to a different part of the file than the one its own residual specifically names as a risk, caught only because its `content_hash` covers the whole file. + +## Expected result + +After running all three notebooks: notebook 01's coverage report shows `heatGenerationReq` covered (a positive claim by `rated`, a negative one by `weak`) and `timely` not covered (only a negative claim, by `slow`), with a polarity-blind join and the now-fixed `requirement_coverage()` helper both checked directly against that result; notebook 02's two reconstructed records both validate cleanly, both carry non-empty, substantive counterevidence, residual_uncertainties and (where present) premises, and both keep `disposition = "pending"`, never the forbidden alternative; notebook 03 shows `AS-C06` already stale before any edit, for a reason substantively tied to real model growth, and `AS-C08` current until the shared edit is applied, after which both report stale, for two genuinely different reasons. + +## Experiment + +See `exercises/ch09/exercise.ipynb`. diff --git a/chapters/ch10-traceability-signoff/01-traceability-graph.ipynb b/chapters/ch10-traceability-signoff/01-traceability-graph.ipynb index 0d86b78..a08f16c 100644 --- a/chapters/ch10-traceability-signoff/01-traceability-graph.ipynb +++ b/chapters/ch10-traceability-signoff/01-traceability-graph.ipynb @@ -1,78 +1,1338 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, "cells": [ { "cell_type": "markdown", - "id": "cell-0", + "id": "0b8d588f", "metadata": {}, "source": [ - "## model.query + to_api_json \u2192 DOT \u2192 full argument graph\n\n**Concept statement (stub):** This notebook introduces model.query + to_api_json \u2192 DOT \u2192 full argument graph; after running it you can [TODO]." + "## Ch10-01 -- A real traceability graph, and one real, unjustified widget\n", + "\n", + "This notebook introduces a real traceability graph over two of the model's three named requirement usages (the third, added later in this notebook, closes a different gap this same graph finds -- see below); after running it you can tell, for each of the two, what functional intent it expresses, what allocation and realization carry it forward, and what verification evidence, if any, actually exists, and you will have found one of this tutorial's own strongest pieces of formal evidence tied to no requirement at all, then closed that gap directly." ] }, { "cell_type": "markdown", - "id": "cell-1", + "id": "f1090e58", "metadata": {}, "source": [ - "**Context (stub):** [TODO \u2014 one paragraph locating this notebook in the chapter arc.]" + "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." ] }, { "cell_type": "code", - "id": "cell-2", + "execution_count": 1, + "id": "b8bb18ce", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T02:17:53.377323Z", + "iopub.status.busy": "2026-10-01T02:17:53.377186Z", + "iopub.status.idle": "2026-10-01T02:17:53.655747Z", + "shell.execute_reply": "2026-10-01T02:17:53.655257Z" + } + }, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "import opensysml\n", + "from toaster.report import format_diagnostics\n", + "\n", + "conn = opensysml.connect(version=\"v0.9.0\")\n", + "source = Path(\"../../models/ch10-cumulative.sysml\").read_text()\n", + "model = conn.load_from_content(source, strict=False)\n", + "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"\n" + ] + }, + { + "cell_type": "markdown", + "id": "39d4dcae", "metadata": {}, "source": [ - "import opensysml\n\n# TODO: full cumulative SysML source (SA-2)\nsource = \"\"\"\n# stub \u2014 replace with full model\n\"\"\"\n\nconn = opensysml.connect(version=\"v0.9.0\")\nmodel = conn.load_from_content(source, strict=False)\nassert model.ok, f\"Model failed: {model.diagnostics}\"" + "The model loads cleanly. Before tracing anything through it, the same language-tier control every chapter carries, chosen here to match what this chapter actually traces: an `allocate` naming a feature that was never declared still fails to load, which matters directly for a chapter about following allocation and realization chains -- an allocation naming a feature that does not exist can never silently stand in for a real one. This control does not test a different, real gap: whether an allocation naming a feature that DOES exist, but is the wrong one (a genuine mismatch, not an undeclared reference), would be caught the same way. That case is untested here." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "a45bdbbc", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T02:17:53.657609Z", + "iopub.status.busy": "2026-10-01T02:17:53.657407Z", + "iopub.status.idle": "2026-10-01T02:17:53.671302Z", + "shell.execute_reply": "2026-10-01T02:17:53.670840Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Negative control ok: bad.ok=False\n" + ] + } ], - "outputs": [], - "execution_count": null + "source": [ + "# An allocate naming an undeclared feature fails to load; it can never silently\n", + "# stand in for a real allocation link this chapter's own traceability graph follows.\n", + "bad_source = \"\"\"\n", + "package Bad {\n", + " action def A;\n", + " part def W { perform action a : A; }\n", + " part w : W;\n", + " allocation badAlloc allocate A::missing to w;\n", + "}\n", + "\"\"\"\n", + "bad = conn.load_from_content(bad_source, strict=False)\n", + "assert not bad.ok, \"Expected failure for an allocation naming an undeclared feature\"\n", + "print(f\"Negative control ok: bad.ok={bad.ok}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "ed18ec1d", + "metadata": {}, + "source": [ + "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)." + ] }, { "cell_type": "code", - "id": "cell-3", + "execution_count": 3, + "id": "802a9057", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T02:17:53.672585Z", + "iopub.status.busy": "2026-10-01T02:17:53.672492Z", + "iopub.status.idle": "2026-10-01T02:17:53.775756Z", + "shell.execute_reply": "2026-10-01T02:17:53.775165Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ToasterDemo::energyConservationReq: def=ToasterDemo::EnergyConservationReq subject=None : None\n", + "ToasterDemo::heatGenerationReq: def=ToasterDemo::HeatGenerationReq subject=heatGen : ToasterDemo::HeatGenerator\n", + "ToasterDemo::timely: def=ToasterDemo::TimelyToast subject=toaster : ToasterDemo::Toaster\n" + ] + } + ], + "source": [ + "from toaster.query import ApiIndex, find_requirements\n", + "\n", + "idx = ApiIndex(model)\n", + "\n", + "\n", + "def requirement_subject(idx, req_def_qn):\n", + " \"\"\"The (name, type) of req_def_qn's own declared `subject` feature, found by\n", + " reading each of its owned ReferenceUsage features for the literal `subject`\n", + " keyword in its own sysx:sourceText, not assumed from the requirement's name.\"\"\"\n", + " for e in idx.of_type(\"ReferenceUsage\"):\n", + " qn = e.get(\"qualifiedName\", \"\")\n", + " if qn.startswith(req_def_qn + \"::\") and e.get(\"sysx:sourceText\", \"\").strip().startswith(\"subject \"):\n", + " return e[\"declaredName\"], idx.type_names(qn)[0]\n", + " return None, None\n", + "\n", + "\n", + "requirements = sorted(r.id for r in find_requirements(model))\n", + "for r in requirements:\n", + " req_def = idx.type_names(r)[0]\n", + " subject_name, subject_type = requirement_subject(idx, req_def)\n", + " print(f\"{r}: def={req_def} subject={subject_name} : {subject_type}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "642b074e", "metadata": {}, "source": [ - "# Negative control \u2014 TODO: intentional error matching chapter construct\nbad_source = \"part def Missing { part x : NonExistent; }\"\nbad = conn.load_from_content(bad_source, strict=False)\nassert not bad.ok" + "`heatGenerationReq`'s own subject is `heatGen : HeatGenerator`, an engineering rating on the logical carrier one level below the heating system (Chapter 6). `timely`'s own subject is `toaster : Toaster`, the toaster as a whole. Both are real functional intents the requirement definitions themselves state; the next cells trace what carries each one forward. A third requirement usage, `energyConservationReq`, also printed above, prints `subject=None : None`: it declares no explicit `subject` line at all (see the remediation cells below for why -- a real spec question, not an oversight), and it is not a functional-decomposition requirement like the other two. This notebook's own traceability graph below (functional intent, allocation, realization) is not built for it -- it exists to close a different gap this same notebook finds later, addressed directly where that gap is found." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "042cfd1c", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T02:17:53.777484Z", + "iopub.status.busy": "2026-10-01T02:17:53.777368Z", + "iopub.status.idle": "2026-10-01T02:17:53.824210Z", + "shell.execute_reply": "2026-10-01T02:17:53.823793Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "heatGenerationReq's subject is allocated from:\n", + " {'id': 'ToasterDemo::HeatingAssembly::heatGenAllocation', 'type': 'AllocationUsage', 'ends': [['ToasterDemo::HeatingSystem::applyHeat', 'ToasterDemo::ApplyHeat::generateHeat'], ['ToasterDemo::HeatingAssembly::heatGen']]}\n", + "ResistanceCoil (the realizer chosen for HeatGenerator) itself specializes: {'ToasterDemo::HeatGenerator'}\n" + ] + } ], - "outputs": [], - "execution_count": null + "source": [ + "from toaster.query import allocations_for, supertypes_transitively\n", + "\n", + "heatgen_allocations = allocations_for(model, \"ToasterDemo::HeatingAssembly::heatGen\", inherit=True, index=idx)\n", + "heatgen_realizers = supertypes_transitively(model, \"ToasterDemo::ResistanceCoil\")\n", + "print(\"heatGenerationReq's subject is allocated from:\")\n", + "for a in heatgen_allocations:\n", + " print(f\" {a}\")\n", + "print(f\"ResistanceCoil (the realizer chosen for HeatGenerator) itself specializes: {heatgen_realizers}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "d52622fa", + "metadata": {}, + "source": [ + "`heatGenAllocation` allocates `applyHeat.generateHeat` to `heatGen`, declared inside `HeatingAssembly` where both resolve, the functional action Chapter 4 defined reaching the logical carrier; `ResistanceCoil` specializes `HeatGenerator` (Chapter 6's own mechanism selection, `AS-C06`), and `rated`/`weak` are its two physical candidates. The same pattern, traced for `timely`'s own subject next." + ] + }, + { + "cell_type": "markdown", + "id": "06715ea1", + "metadata": {}, + "source": [ + "`Toaster` itself, unlike `HeatGenerator`, has no further physical subtype: its own candidates are usages typed directly by it. `specializes_transitively()` reports every usage that specializes or is typed by `Toaster`, which also catches the two requirement definitions' own internal `subject` placeholders (`TimelyToast::toaster`, `TimelyToastTest::toaster`, nested inside the requirement definitions themselves, not real candidates); filtering to elements owned directly by the top-level package leaves the two real ones." + ] }, { "cell_type": "code", - "id": "cell-4", + "execution_count": 5, + "id": "c91bbd5d", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T02:17:53.825278Z", + "iopub.status.busy": "2026-10-01T02:17:53.825184Z", + "iopub.status.idle": "2026-10-01T02:17:53.862596Z", + "shell.execute_reply": "2026-10-01T02:17:53.862192Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "timely's subject is allocated from:\n", + " {'id': 'ToasterDemo::Toaster::heatAllocation', 'type': 'AllocationUsage', 'ends': [['ToasterDemo::ToastingSystem::toastBread', 'ToasterDemo::ToastBread::applyHeat'], ['ToasterDemo::Toaster::heating']]}\n", + "specializes_transitively(Toaster), raw: ['ToasterDemo::TimelyToast::toaster', 'ToasterDemo::TimelyToastTest::toaster', 'ToasterDemo::nominal', 'ToasterDemo::slow']\n", + "filtered to real, top-level candidates: ['ToasterDemo::nominal', 'ToasterDemo::slow']\n" + ] + } + ], + "source": [ + "from toaster.query import specializes_transitively\n", + "\n", + "toaster_allocations = allocations_for(model, \"ToasterDemo::Toaster::heating\", inherit=True, index=idx)\n", + "toaster_candidates_raw = specializes_transitively(model, \"ToasterDemo::Toaster\")\n", + "toaster_candidates = sorted(\n", + " qn for qn in toaster_candidates_raw if idx.by_qn[qn].get(\"owner\", {}).get(\"@id\") == \"ToasterDemo\"\n", + ")\n", + "print(\"timely's subject is allocated from:\")\n", + "for a in toaster_allocations:\n", + " print(f\" {a}\")\n", + "print(f\"specializes_transitively(Toaster), raw: {sorted(toaster_candidates_raw)}\")\n", + "print(f\"filtered to real, top-level candidates: {toaster_candidates}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "bc03f838", "metadata": {}, "source": [ - "# Demonstration \u2014 TODO: one key operation\npass" + "`heatAllocation` allocates `toastBread.applyHeat` to `heating`, declared inside `Toaster` where both resolve; `Toaster`'s own real candidates are `nominal` and `slow`. Both chains reach real physical candidates. What verification evidence actually exists for each is the last, and most consequential, link." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "8b69fdfa", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T02:17:53.863707Z", + "iopub.status.busy": "2026-10-01T02:17:53.863618Z", + "iopub.status.idle": "2026-10-01T02:17:53.865964Z", + "shell.execute_reply": "2026-10-01T02:17:53.865483Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'requirement': 'ToasterDemo::energyConservationReq', 'satisfied_by': [], 'failed_by': [], 'covered': False}\n", + "{'requirement': 'ToasterDemo::heatGenerationReq', 'satisfied_by': ['ToasterDemo::rated'], 'failed_by': ['ToasterDemo::weak'], 'covered': True}\n", + "{'requirement': 'ToasterDemo::timely', 'satisfied_by': [], 'failed_by': ['ToasterDemo::slow'], 'covered': False}\n" + ] + } ], - "outputs": [], - "execution_count": null + "source": [ + "from toaster.query import requirement_coverage\n", + "\n", + "coverage = {c[\"requirement\"]: c for c in requirement_coverage(model, idx)}\n", + "for r in requirements:\n", + " print(coverage[r])\n" + ] + }, + { + "cell_type": "markdown", + "id": "953b7d40", + "metadata": {}, + "source": [ + "`heatGenerationReq` is covered on both sides: `rated` really satisfies it, `weak` really fails it, and both claims are genuine positive/negative evidence about a real candidate. `timely` is not covered: the only claim against it is negative (`slow` fails it), and `TimelyToastTest`'s own `verify timely;` objective names the requirement without binding any subject (Chapter 9's own finding, reproduced here directly against the same real model). This is an absence of a claim, not evidence that `nominal` fails `timely`: `Toaster::cycleTime` is still a settable attribute, not derived from anything, so no one has ever actually checked whether `nominal` satisfies `timely` in the first place. `energyConservationReq`'s own coverage entry, also printed above, reports `covered=False` too (`satisfied_by=[]`, `failed_by=[]`) -- but for a structurally different reason than `timely`'s: not an omission anyone could still close with more querying, but the deliberate result of a design choice explained in the remediation cells below (and in `AC-C10`), where this requirement is tied to its own evidence by subsetting an already-proved lemma, not by any instance-level `assert satisfy`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "a4778ed8", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T02:17:53.867079Z", + "iopub.status.busy": "2026-10-01T02:17:53.866989Z", + "iopub.status.idle": "2026-10-01T02:17:53.869976Z", + "shell.execute_reply": "2026-10-01T02:17:53.869554Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ToasterDemo::heatGenerationReq:\n", + " functional_intent: heatGen : HeatGenerator (a heat generator rated for at least 600 W)\n", + " allocation: heatGenAllocation: applyHeat.generateHeat -> heatGen (nested in HeatingAssembly)\n", + " realization: ResistanceCoil (:> HeatGenerator); candidates rated, weak\n", + " verification_evidence: satisfied_by=['ToasterDemo::rated'], failed_by=['ToasterDemo::weak'] (bidirectional: a real positive claim AND a real negative claim)\n", + "ToasterDemo::timely:\n", + " functional_intent: toaster : Toaster (a toasting cycle completing in at most 180 s)\n", + " allocation: heatAllocation: toastBread.applyHeat -> heating (nested in Toaster)\n", + " realization: Toaster itself; candidates nominal, slow\n", + " verification_evidence: satisfied_by=[] (none), failed_by=['ToasterDemo::slow'] (one-sided: only a negative claim, plus an unbound verify objective)\n" + ] + } + ], + "source": [ + "traceability_graph = [\n", + " {\n", + " \"requirement\": \"ToasterDemo::heatGenerationReq\",\n", + " \"functional_intent\": \"heatGen : HeatGenerator (a heat generator rated for at least 600 W)\",\n", + " \"allocation\": \"heatGenAllocation: applyHeat.generateHeat -> heatGen (nested in HeatingAssembly)\",\n", + " \"realization\": \"ResistanceCoil (:> HeatGenerator); candidates rated, weak\",\n", + " \"verification_evidence\": (\n", + " f\"satisfied_by={coverage['ToasterDemo::heatGenerationReq']['satisfied_by']}, \"\n", + " f\"failed_by={coverage['ToasterDemo::heatGenerationReq']['failed_by']} \"\n", + " \"(bidirectional: a real positive claim AND a real negative claim)\"\n", + " ),\n", + " },\n", + " {\n", + " \"requirement\": \"ToasterDemo::timely\",\n", + " \"functional_intent\": \"toaster : Toaster (a toasting cycle completing in at most 180 s)\",\n", + " \"allocation\": \"heatAllocation: toastBread.applyHeat -> heating (nested in Toaster)\",\n", + " \"realization\": \"Toaster itself; candidates nominal, slow\",\n", + " \"verification_evidence\": (\n", + " f\"satisfied_by={coverage['ToasterDemo::timely']['satisfied_by']} (none), \"\n", + " f\"failed_by={coverage['ToasterDemo::timely']['failed_by']} \"\n", + " \"(one-sided: only a negative claim, plus an unbound verify objective)\"\n", + " ),\n", + " },\n", + "]\n", + "for row in traceability_graph:\n", + " print(row[\"requirement\"] + \":\")\n", + " for key, value in row.items():\n", + " if key != \"requirement\":\n", + " print(f\" {key}: {value}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "6eff9749", + "metadata": {}, + "source": [ + "`heatGenerationReq` traces all the way from a stated functional intent through a real allocation and a real mechanism selection to genuine, opposite-polarity verification evidence. `timely` traces just as far through intent, allocation and realization, but stops one link short: no candidate has ever been positively checked against it. Both are real findings about the model as it actually stands, not a contrast built to make one requirement look better than the other." + ] + }, + { + "cell_type": "markdown", + "id": "d4f77f1e", + "metadata": {}, + "source": [ + "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", + "\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", + "\n", + "This is a deliberate, narrow pair of checks, not an attempt at completeness, and it says so plainly: by design, it does **not** detect a connection-end (`end e1 ::> lemma;`, port/connector typing); a `dependency`/`allocate`/`metadata about`/`connection`/`#derivation`-style relationship naming the lemma and a requirement as two sibling elements where neither owns the other; `Concern`/`Viewpoint` ownership; a transitive chain through an intermediate element not itself owned by a requirement; or any other relationship shape not named above." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "a7413515", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T02:17:53.870975Z", + "iopub.status.busy": "2026-10-01T02:17:53.870893Z", + "iopub.status.idle": "2026-10-01T02:17:53.952334Z", + "shell.execute_reply": "2026-10-01T02:17:53.951919Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "deliveredEnergyBoundedBySupply @type: AssertConstraintUsage\n", + "Satisfy-by-subject or direct-reference-from-a-requirement-body hits, before remediation: []\n", + "deliveredEnergyBoundedBySupply tied to any requirement, before remediation (by these two narrow checks): False\n" + ] + } + ], + "source": [ + "from toaster.query import requirement_ties, tied_to_any_requirement\n", + "\n", + "delivered_energy_bounded = idx.by_qn[\"ToasterDemo::deliveredEnergyBoundedBySupply\"]\n", + "\n", + "# 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", + "# itself, the same way Ch8-03's own `source.replace(...)` reconstructs a different\n", + "# state for its own demonstration, rather than checking a second committed file.\n", + "before_marker = \"\\n requirement def EnergyConservationReq {\"\n", + "source_before_remediation = source[:source.index(before_marker)] + \"\\n}\\n\"\n", + "model_before = conn.load_from_content(source_before_remediation, strict=False)\n", + "assert model_before.ok, f\"Model failed: {format_diagnostics(model_before.diagnostics)}\"\n", + "idx_before = ApiIndex(model_before)\n", + "\n", + "ties = requirement_ties(model_before, \"ToasterDemo::deliveredEnergyBoundedBySupply\", idx_before)\n", + "tied_to_a_requirement = tied_to_any_requirement(model_before, \"ToasterDemo::deliveredEnergyBoundedBySupply\", idx_before)\n", + "\n", + "print(f\"deliveredEnergyBoundedBySupply @type: {delivered_energy_bounded['@type']}\")\n", + "print(f\"Satisfy-by-subject or direct-reference-from-a-requirement-body hits, before remediation: {ties}\")\n", + "print(f\"deliveredEnergyBoundedBySupply tied to any requirement, before remediation (by these two narrow checks): {tied_to_a_requirement}\")\n", + "assert not tied_to_a_requirement" + ] + }, + { + "cell_type": "markdown", + "id": "9c8a21b8", + "metadata": {}, + "source": [ + "It was not, under these two narrow checks' own stated scope, before this chapter's own remediation (the reconstructed state checked above). Neither check found an element referencing `deliveredEnergyBoundedBySupply`'s own id: no `assert satisfy` named it as a `by`-subject, and no element owned by a requirement's own body subset, redefined, referenced, or bare-named it directly. This was not a claim that no tie could exist by any conceivable mechanism -- only that these two specific, narrow, well-understood checks found none. Douglas's own traceability concern names the inverse of this: a design element (\"widget\") with no requirement to justify it (an unjustified widget). Here the shape is reversed but the concern is the same: a genuine piece of verification evidence with no requirement to justify *it*, the strongest formal proof this tutorial has built connecting to no stated need at all, at least not through either of the two ways this check knows to look. `Toaster::cycleTime` is not derived from anything and `deliveredEnergyBoundedBySupply` was not tied to any requirement (by this narrow check) are two different gaps in the same graph. The `cycleTime` gap stays open (a non-goal of this chapter, and not something a traceability graph alone can close); the `deliveredEnergyBoundedBySupply` gap is closed directly below, precisely because finding it is what this chapter's own analysis did -- not papered over by forcing a connection the model does not actually make, but remediated the same way any other real gap this tutorial has found gets remediated: construct, then re-analyze." + ] }, { "cell_type": "markdown", - "id": "cell-5", + "id": "4a3a8808", "metadata": {}, "source": [ - "**Tall seam (stub):** [TODO \u2014 one sentence: the SysML construct (A-F) is executed by OpenSysML (O-S); the result is (E).]" + "The graph above, and the search that follows it, say the same thing two different ways: a traceability graph is not just a map of what connects, it is just as much a map of what does not, and both kinds of gap are real findings, not defects in the query." ] }, { "cell_type": "markdown", - "id": "cell-6", + "id": "9d2e5fb8", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch10/exercise.ipynb`: [TODO \u2014 one-line description]." + "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." ] + }, + { + "cell_type": "markdown", + "id": "9d918eda", + "metadata": {}, + "source": [ + "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?" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "4d859570", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T02:17:53.953443Z", + "iopub.status.busy": "2026-10-01T02:17:53.953357Z", + "iopub.status.idle": "2026-10-01T02:17:53.957283Z", + "shell.execute_reply": "2026-10-01T02:17:53.956964Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Confirmed: the base `requirement def RequirementCheck` (Systems Library/Requirements.sysml) declares `subject subj : Anything[1];` as its own default subject.\n" + ] + } + ], + "source": [ + "requirements_lib_path = (\n", + " Path.home() / \"Documents/GitHub/sysml-toolkit/spec-refs/SysML-v2-Release/sysml.library\"\n", + " / \"Systems Library\" / \"Requirements.sysml\"\n", + ")\n", + "requirements_lib_text = requirements_lib_path.read_text()\n", + "assert \"subject subj : Anything[1]\" in requirements_lib_text, (\n", + " \"expected the base requirement def RequirementCheck to still declare \"\n", + " \"subject subj : Anything[1] as its own default -- confirm directly, not assumed\"\n", + ")\n", + "print(\"Confirmed: the base `requirement def RequirementCheck` (Systems Library/Requirements.sysml) \"\n", + " \"declares `subject subj : Anything[1];` as its own default subject.\")\n" + ] + }, + { + "cell_type": "markdown", + "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)." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "25c99d26", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T02:17:53.958445Z", + "iopub.status.busy": "2026-10-01T02:17:53.958372Z", + "iopub.status.idle": "2026-10-01T02:17:53.960674Z", + "shell.execute_reply": "2026-10-01T02:17:53.960326Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "requirement def EnergyConservationReq {\n", + " doc /* A heat generator shall never deliver more energy than it is supplied:\n", + " * energy conservation, a physical law any real heat generator design\n", + " * must obey, independent of any one proof of it. No subject type is\n", + " * declared here: this requirement's own required constraint never\n", + " * references any subject at all -- it is a closed, already-proved\n", + " * proposition over its own free-standing elements -- so declaring\n", + " * one would commit to an arbitrary, unused type, not state anything\n", + " * real; the subject is left to inherit RequirementCheck's own\n", + " * default, `subject subj : Anything[1]` (Systems\n", + " * Library/Requirements.sysml). Deliberately no `assert satisfy`\n", + " * line either. Confirmed directly, not merely argued: attempting\n", + " * `assert satisfy energyConservationReq by\n", + " * deliveredEnergyBoundedBySupply;` makes model.verify_satisfaction()\n", + " * error identically no matter what is bound as the satisfying\n", + " * feature (\"require condition evaluation failed: no value for\n", + " * feature heatGenCheck.efficiency\"), not pass -- demonstrated\n", + " * directly as a negative control in notebook 01, and recorded in\n", + " * full in\n", + " * docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md.\n", + " * See AC-C10 (this chapter's own judgment record, notebook 01) for\n", + " * how this requirement's own need was actually identified, and for\n", + " * the honest distinction between a tie and a solver-level proof. */\n", + " require constraint c :> deliveredEnergyBoundedBySupply;\n", + "}\n", + "requirement energyConservationReq : EnergyConservationReq;\n" + ] + } + ], + "source": [ + "ENERGY_CONSERVATION_REQ_DEF = \"\"\"\\\n", + "requirement def EnergyConservationReq {\n", + " doc /* A heat generator shall never deliver more energy than it is supplied:\n", + " * energy conservation, a physical law any real heat generator design\n", + " * must obey, independent of any one proof of it. No subject type is\n", + " * declared here: this requirement's own required constraint never\n", + " * references any subject at all -- it is a closed, already-proved\n", + " * proposition over its own free-standing elements -- so declaring\n", + " * one would commit to an arbitrary, unused type, not state anything\n", + " * real; the subject is left to inherit RequirementCheck's own\n", + " * default, `subject subj : Anything[1]` (Systems\n", + " * Library/Requirements.sysml). Deliberately no `assert satisfy`\n", + " * line either. Confirmed directly, not merely argued: attempting\n", + " * `assert satisfy energyConservationReq by\n", + " * deliveredEnergyBoundedBySupply;` makes model.verify_satisfaction()\n", + " * error identically no matter what is bound as the satisfying\n", + " * feature (\"require condition evaluation failed: no value for\n", + " * feature heatGenCheck.efficiency\"), not pass -- demonstrated\n", + " * directly as a negative control in notebook 01, and recorded in\n", + " * full in\n", + " * docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md.\n", + " * See AC-C10 (this chapter's own judgment record, notebook 01) for\n", + " * how this requirement's own need was actually identified, and for\n", + " * the honest distinction between a tie and a solver-level proof. */\n", + " require constraint c :> deliveredEnergyBoundedBySupply;\n", + "}\n", + "requirement energyConservationReq : EnergyConservationReq;\"\"\"\n", + "print(ENERGY_CONSERVATION_REQ_DEF)\n" + ] + }, + { + "cell_type": "markdown", + "id": "25424edb", + "metadata": {}, + "source": [ + "`EnergyConservationReq` restates exactly the property `deliveredEnergyBoundedBySupply` proves, as a stakeholder-facing requirement rather than the lemma's own real-arithmetic form, with no explicit `subject` line (the fix confirmed above); its own `require constraint c :> deliveredEnergyBoundedBySupply;` is itself a direct reference from within a requirement's own body -- Check B's own idiom, made real rather than hypothetical. Deliberately absent: an `assert satisfy energyConservationReq by deliveredEnergyBoundedBySupply;` line, Check A's own idiom, the same pattern already used above for `rated`/`weak` against `heatGenerationReq`. The next cells show directly why it is absent, rather than only asserting it in the doc comment above." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "c177450c", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T02:17:53.961609Z", + "iopub.status.busy": "2026-10-01T02:17:53.961539Z", + "iopub.status.idle": "2026-10-01T02:17:54.001218Z", + "shell.execute_reply": "2026-10-01T02:17:54.000835Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "the lemma itself (deliveredEnergyBoundedBySupply): assert satisfy energyConservationReq by deliveredEnergyBoundedBySupply;\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "the lemma's own free-standing usage (heatGenCheck): assert satisfy energyConservationReq by heatGenCheck;\n", + "an unrelated real candidate (rated): assert satisfy energyConservationReq by rated;\n" + ] + } + ], + "source": [ + "ENERGY_CONSERVATION_SATISFY_ATTEMPTS = {\n", + " \"the lemma itself\": \"deliveredEnergyBoundedBySupply\",\n", + " \"the lemma's own free-standing usage\": \"heatGenCheck\",\n", + " \"an unrelated real candidate\": \"rated\",\n", + "}\n", + "\n", + "# 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", + "attempt_models = {}\n", + "for label, binding in ENERGY_CONSERVATION_SATISFY_ATTEMPTS.items():\n", + " attempt_line = f\"assert satisfy energyConservationReq by {binding};\"\n", + " attempt_source = source.rstrip()[:-1] + f\"\\n {attempt_line}\\n}}\\n\"\n", + " attempt_model = conn.load_from_content(attempt_source, strict=False)\n", + " assert attempt_model.ok, f\"Model failed ({label}): {format_diagnostics(attempt_model.diagnostics)}\"\n", + " attempt_models[label] = (binding, attempt_model)\n", + " print(f\"{label} ({binding}): {attempt_line}\")\n" + ] + }, + { + "cell_type": "markdown", + "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`." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "55d20aaa", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T02:17:54.002379Z", + "iopub.status.busy": "2026-10-01T02:17:54.002299Z", + "iopub.status.idle": "2026-10-01T02:17:54.037374Z", + "shell.execute_reply": "2026-10-01T02:17:54.037033Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " [FAILS] the lemma itself (deliveredEnergyBoundedBySupply): satisfy energyConservationReq by deliveredEnergyBoundedBySupply (satisfaction satisfy energyConservationReq by deliveredEnergyBoundedBySupply: require condition evaluation failed: no value for feature heatGenCheck.efficiency)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " [FAILS] the lemma's own free-standing usage (heatGenCheck): satisfy energyConservationReq by heatGenCheck (satisfaction satisfy energyConservationReq by heatGenCheck: require condition evaluation failed: no value for feature heatGenCheck.efficiency)\n", + " [FAILS] an unrelated real candidate (rated): satisfy energyConservationReq by rated (satisfaction satisfy energyConservationReq by rated: require condition evaluation failed: no value for feature heatGenCheck.efficiency)\n" + ] + } + ], + "source": [ + "for label, (binding, attempt_model) in attempt_models.items():\n", + " attempt_verdicts = attempt_model.verify_satisfaction()\n", + " attempt_verdict = next(\n", + " v for v in attempt_verdicts\n", + " if v.element == f\"satisfy energyConservationReq by {binding}\"\n", + " )\n", + " status = \"holds\" if attempt_verdict.holds else \"FAILS\"\n", + " print(f\" [{status}] {label} ({binding}): {attempt_verdict.element} ({attempt_verdict.error})\")\n", + " assert attempt_verdict.holds is False\n", + " assert \"require condition evaluation failed: no value for feature heatGenCheck.efficiency\" in attempt_verdict.error\n" + ] + }, + { + "cell_type": "markdown", + "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 below is how that is stated." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "41047070", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T02:17:54.038615Z", + "iopub.status.busy": "2026-10-01T02:17:54.038532Z", + "iopub.status.idle": "2026-10-01T02:17:54.040423Z", + "shell.execute_reply": "2026-10-01T02:17:54.040163Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "requirement def EnergyConservationReq {\n", + " doc /* A heat generator shall never deliver more energy than it is supplied:\n", + " * energy conservation, a physical law any real heat generator design\n", + " * must obey, independent of any one proof of it. No subject type is\n", + " * declared here: this requirement's own required constraint never\n", + " * references any subject at all -- it is a closed, already-proved\n", + " * proposition over its own free-standing elements -- so declaring\n", + " * one would commit to an arbitrary, unused type, not state anything\n", + " * real; the subject is left to inherit RequirementCheck's own\n", + " * default, `subject subj : Anything[1]` (Systems\n", + " * Library/Requirements.sysml). Deliberately no `assert satisfy`\n", + " * line either. Confirmed directly, not merely argued: attempting\n", + " * `assert satisfy energyConservationReq by\n", + " * deliveredEnergyBoundedBySupply;` makes model.verify_satisfaction()\n", + " * error identically no matter what is bound as the satisfying\n", + " * feature (\"require condition evaluation failed: no value for\n", + " * feature heatGenCheck.efficiency\"), not pass -- demonstrated\n", + " * directly as a negative control in notebook 01, and recorded in\n", + " * full in\n", + " * docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md.\n", + " * See AC-C10 (this chapter's own judgment record, notebook 01) for\n", + " * how this requirement's own need was actually identified, and for\n", + " * the honest distinction between a tie and a solver-level proof. */\n", + " require constraint c :> deliveredEnergyBoundedBySupply;\n", + "}\n", + "requirement energyConservationReq : EnergyConservationReq;\n" + ] + } + ], + "source": [ + "TOASTER_INCREMENT = ENERGY_CONSERVATION_REQ_DEF\n", + "print(TOASTER_INCREMENT)\n" + ] + }, + { + "cell_type": "markdown", + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "7bab30ed", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T02:17:54.041554Z", + "iopub.status.busy": "2026-10-01T02:17:54.041485Z", + "iopub.status.idle": "2026-10-01T02:17:54.044216Z", + "shell.execute_reply": "2026-10-01T02:17:54.043882Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Satisfy-by-subject or direct-reference-from-a-requirement-body hits, after remediation: [{'tying_element': 'ToasterDemo::EnergyConservationReq::c', 'field': 'subsets', 'requirement': 'ToasterDemo::EnergyConservationReq'}]\n", + "deliveredEnergyBoundedBySupply tied to any requirement, after remediation: True\n" + ] + } + ], + "source": [ + "ties_after = requirement_ties(model, \"ToasterDemo::deliveredEnergyBoundedBySupply\", idx)\n", + "tied_after_remediation = tied_to_any_requirement(model, \"ToasterDemo::deliveredEnergyBoundedBySupply\", idx)\n", + "\n", + "print(f\"Satisfy-by-subject or direct-reference-from-a-requirement-body hits, after remediation: {ties_after}\")\n", + "print(f\"deliveredEnergyBoundedBySupply tied to any requirement, after remediation: {tied_after_remediation}\")\n", + "assert tied_after_remediation\n", + "assert len(ties_after) == 1\n", + "assert ties_after[0][\"requirement\"] == \"ToasterDemo::EnergyConservationReq\"\n", + "assert ties_after[0][\"field\"] == \"subsets\"\n" + ] + }, + { + "cell_type": "markdown", + "id": "5f7e70e0", + "metadata": {}, + "source": [ + "It is `True` now, genuinely, and precisely so: `ties_after` carries exactly one entry, not the three a design that also kept an `assert satisfy` line would show. It is Check B's own hit -- `EnergyConservationReq::c`'s own `require constraint c :> deliveredEnergyBoundedBySupply;` subsets the lemma directly from within the requirement's own body -- and it is attributed to the DEFINITION, `ToasterDemo::EnergyConservationReq`, not the usage `energyConservationReq`: `_nearest_requirement_owner`'s owner-chain walk finds `c`'s nearest exact-type `RequirementDefinition`/`RequirementUsage` ancestor, and `c` is owned by the definition, not the usage. Check A finds nothing here, by design -- there is no `assert satisfy` for it to find, for exactly the reason the negative control just above demonstrated. This is the construct-and-analyze loop this whole tutorial teaches, run once more, start to finish: build, query, find a gap, remediate, re-query, confirm. The gap this chapter's own traceability graph found is closed by this same chapter, because finding it is what this chapter's own analysis is for." + ] + }, + { + "cell_type": "markdown", + "id": "9b06c2cf", + "metadata": {}, + "source": [ + "The tie is real, and the subject-type fix keeps it spec-legitimate. One honest question is still open: is this tie -- a requirement whose own formal condition is defined by subsetting the very lemma it restates -- actually a legitimate way to close this chapter's own \"unjustified widget\" finding, or is it circular, a requirement manufactured FROM the evidence rather than one that independently motivates it? A doc comment cannot carry that answer honestly; a judgment record can. Build one the same way `AS-C06` (Chapter 6) is built: name each group of fields, narrate what it's for, print it, then assemble." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "dcb8884d", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T02:17:54.045229Z", + "iopub.status.busy": "2026-10-01T02:17:54.045167Z", + "iopub.status.idle": "2026-10-01T02:17:54.047379Z", + "shell.execute_reply": "2026-10-01T02:17:54.047051Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "EnergyConservationReq/energyConservationReq's own tie to deliveredEnergyBoundedBySupply is a spec-legitimate use of SysML v2's own require-constraint-subsetting idiom (SS7.21.2), and its own general motivation -- energy conservation, a physical law any real heat generator design must obey -- is genuine and independent of any one proof of it. But this requirement's own NEED did not predate Chapter 8's proof: it was identified retroactively, specifically to close the gap this chapter's own traceability analysis found, and the tie itself (requirement_ties()'s own Check-B hit on the DEFINITION) is a structural fact about the model, not a solver-level proof that this requirement's own formal condition holds for every value -- that proof is AS-C08's own, cited and not repeated here. Deliberately absent is any assert-satisfy claim: this requirement's own required constraint never references its subject, confirmed directly by the negative control above, so requirement_coverage() correctly reports covered=False for it -- by design, not as a residual gap of the same kind as timely's.\n" + ] + } + ], + "source": [ + "from toaster.evidence import ReviewRecord, validate_record, hash_content\n", + "\n", + "ac_c10_claim = (\n", + " \"EnergyConservationReq/energyConservationReq's own tie to \"\n", + " \"deliveredEnergyBoundedBySupply is a spec-legitimate use of SysML v2's own \"\n", + " \"require-constraint-subsetting idiom (SS7.21.2), and its own general \"\n", + " \"motivation -- energy conservation, a physical law any real heat generator \"\n", + " \"design must obey -- is genuine and independent of any one proof of it. But \"\n", + " \"this requirement's own NEED did not predate Chapter 8's proof: it was \"\n", + " \"identified retroactively, specifically to close the gap this chapter's own \"\n", + " \"traceability analysis found, and the tie itself (requirement_ties()'s own \"\n", + " \"Check-B hit on the DEFINITION) is a structural fact about the model, not a \"\n", + " \"solver-level proof that this requirement's own formal condition holds for \"\n", + " \"every value -- that proof is AS-C08's own, cited and not repeated here. \"\n", + " \"Deliberately absent is any assert-satisfy claim: this requirement's own \"\n", + " \"required constraint never references its subject, confirmed directly by \"\n", + " \"the negative control above, so requirement_coverage() correctly reports \"\n", + " \"covered=False for it -- by design, not as a residual gap of the same kind \"\n", + " \"as timely's.\"\n", + ")\n", + "ac_c10_model_ref = \"ToasterDemo::EnergyConservationReq\"\n", + "print(ac_c10_claim)\n" + ] + }, + { + "cell_type": "markdown", + "id": "5c2df7cc", + "metadata": {}, + "source": [ + "What standard this claim is checked against: SysML v2's own account of what a requirement is, and what conformance its own satisfy relationship requires." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "0c05ac8a", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T02:17:54.048448Z", + "iopub.status.busy": "2026-10-01T02:17:54.048381Z", + "iopub.status.idle": "2026-10-01T02:17:54.050566Z", + "shell.execute_reply": "2026-10-01T02:17:54.050236Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "EnergyConservationReq/energyConservationReq and its own require-constraint subsetting tie to deliveredEnergyBoundedBySupply, as added by this notebook. Not a claim about deliveredEnergyBoundedBySupply's own proof (AS-C08 already states that proof's real limits) or about any other requirement in this model.\n", + "Per SysML v2 formal/2026-03-02 SS7.21.1/SS7.21.2: (a) does the requirement's own choice to leave its subject undeclared honestly reflect that its required constraint never references a subject at all, rather than hiding an arbitrary, unjustified type choice? (b) is the requirement's own need independently motivated -- a real, antecedent stakeholder concern (Douglas's own need/rationale/verification anatomy) -- or does its content trace back only to the evidence it was built to accommodate? (c) does requirement_coverage()'s own covered=False result for this requirement honestly reflect a deliberate design choice -- tied by subsetting an already-proved universal lemma, not by a point check -- rather than reading as an undiscovered gap of the same kind as timely's?\n" + ] + } + ], + "source": [ + "ac_c10_scope = (\n", + " \"EnergyConservationReq/energyConservationReq and its own require-constraint \"\n", + " \"subsetting tie to deliveredEnergyBoundedBySupply, as added by this notebook. \"\n", + " \"Not a claim about deliveredEnergyBoundedBySupply's own proof (AS-C08 already \"\n", + " \"states that proof's real limits) or about any other requirement in this \"\n", + " \"model.\"\n", + ")\n", + "ac_c10_criteria = (\n", + " \"Per SysML v2 formal/2026-03-02 SS7.21.1/SS7.21.2: (a) does the requirement's \"\n", + " \"own choice to leave its subject undeclared honestly reflect that its \"\n", + " \"required constraint never references a subject at all, rather than hiding \"\n", + " \"an arbitrary, unjustified type choice? (b) is the requirement's own need \"\n", + " \"independently motivated -- a real, antecedent stakeholder concern \"\n", + " \"(Douglas's own need/rationale/verification anatomy) -- or does its content \"\n", + " \"trace back only to the evidence it was built to accommodate? (c) does \"\n", + " \"requirement_coverage()'s own covered=False result for this requirement \"\n", + " \"honestly reflect a deliberate design choice -- tied by subsetting an \"\n", + " \"already-proved universal lemma, not by a point check -- rather than \"\n", + " \"reading as an undiscovered gap of the same kind as timely's?\"\n", + ")\n", + "print(ac_c10_scope)\n", + "print(ac_c10_criteria)\n" + ] + }, + { + "cell_type": "markdown", + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "2422340c", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T02:17:54.051632Z", + "iopub.status.busy": "2026-10-01T02:17:54.051566Z", + "iopub.status.idle": "2026-10-01T02:17:54.054344Z", + "shell.execute_reply": "2026-10-01T02:17:54.053981Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[\"SysML v2.0 formal/2026-03-02 SS7.21.2 explicitly supports reference-subsetting an existing constraint as a requirement's own required constraint (`require ;` / `require constraint c :> ;`) -- the spec's own intended idiom for reusing an already-declared constraint, confirmed by reading the spec directly.\", \"SS7.21.1: 'A requirement usage can only be satisfied by an entity that conforms to the definition of its subject.' The base `requirement def RequirementCheck` (Systems Library/Requirements.sysml) declares `subject subj : Anything[1];`, confirmed directly above; EnergyConservationReq leaves its own subject undeclared, inheriting that default, since its own required constraint never references a subject at all. Two different earlier drafts hit two different problems: the reverted Approach A typed the subject `heatGen : HeatGenerator` but never added an assert-satisfy line at all, so its own defect (a declared, unused subject with nothing live bound to it) was found by direct spec reading, not by any tool diagnostic. A real pilot warning ('Bound features should have conforming types') did fire, but against a different, separately-built draft's own first commit: Approach B's own earliest version paired a typed subject with a real assert-satisfy line binding the lemma against it; that draft's own author fixed it by dropping the subject two commits later, before this reconciliation began. This design has no assert-satisfy at all, so that specific mechanical trigger does not even apply here either way, but the deeper reason for leaving the subject undeclared stands regardless -- recorded in full in docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md.\", \"Direct test, not argument alone (the negative control above): `assert satisfy energyConservationReq by deliveredEnergyBoundedBySupply;` makes model.verify_satisfaction() error identically regardless of its own binding, because this requirement's own required constraint never references its subject at all -- the register `satisfy` is built for (a candidate's own values substituted into a formula that depends on them) is not what this construct offers, so no `assert satisfy` is given for it.\", \"Douglas's own requirement anatomy (AGENTS.md SS1, Part 4): a need, a rationale, and a means of verification. This requirement supplies a real, general rationale (energy conservation) and a real means (the subsetted, Z3-proved lemma itself), but its own need was identified only after Chapter 8's proof already existed, in response to this chapter's own traceability search -- not before it.\"]\n", + "[\"AS-C08's own residual_uncertainties (Chapter 8): deliveredEnergyBoundedBySupply is a hand-restated companion lemma, not a solver-checked reference to HeatGenerator's own efficiencyBounded/deliveredEnergy (D-030, D-031); this record does not strengthen that proof, only frames how its tie to a requirement should be read.\"]\n" + ] + } + ], + "source": [ + "ac_c10_premises = [\n", + " \"SysML v2.0 formal/2026-03-02 SS7.21.2 explicitly supports \"\n", + " \"reference-subsetting an existing constraint as a requirement's own \"\n", + " \"required constraint (`require ;` / `require constraint c :> \"\n", + " \";`) -- the spec's own intended idiom for reusing an \"\n", + " \"already-declared constraint, confirmed by reading the spec directly.\",\n", + " \"SS7.21.1: 'A requirement usage can only be satisfied by an entity that \"\n", + " \"conforms to the definition of its subject.' The base `requirement def \"\n", + " \"RequirementCheck` (Systems Library/Requirements.sysml) declares \"\n", + " \"`subject subj : Anything[1];`, confirmed directly above; \"\n", + " \"EnergyConservationReq leaves its own subject undeclared, inheriting that \"\n", + " \"default, since its own required constraint never references a subject \"\n", + " \"at all. Two different earlier drafts hit two different problems: the \"\n", + " \"reverted Approach A typed the subject `heatGen : HeatGenerator` but \"\n", + " \"never added an assert-satisfy line at all, so its own defect (a \"\n", + " \"declared, unused subject with nothing live bound to it) was found by \"\n", + " \"direct spec reading, not by any tool diagnostic. A real pilot warning \"\n", + " \"('Bound features should have conforming types') did fire, but \"\n", + " \"against a different, separately-built draft's own first commit: \"\n", + " \"Approach B's own earliest version paired a typed subject with a real \"\n", + " \"assert-satisfy line binding the lemma against it; that draft's own \"\n", + " \"author fixed it by dropping the subject two commits later, before \"\n", + " \"this reconciliation began. This design has no assert-satisfy at all, \"\n", + " \"so that specific mechanical trigger does not even apply here either \"\n", + " \"way, but the deeper reason for leaving the subject undeclared stands \"\n", + " \"regardless -- recorded in full in \"\n", + " \"docs/case-studies/2026-09-30-energy-conservation-requirement-tie.md.\",\n", + " \"Direct test, not argument alone (the negative control above): \"\n", + " \"`assert satisfy energyConservationReq by \"\n", + " \"deliveredEnergyBoundedBySupply;` makes model.verify_satisfaction() error \"\n", + " \"identically regardless of its own binding, because this requirement's \"\n", + " \"own required constraint never references its subject at all -- the \"\n", + " \"register `satisfy` is built for (a candidate's own values substituted \"\n", + " \"into a formula that depends on them) is not what this construct offers, \"\n", + " \"so no `assert satisfy` is given for it.\",\n", + " \"Douglas's own requirement anatomy (AGENTS.md SS1, Part 4): a need, a \"\n", + " \"rationale, and a means of verification. This requirement supplies a \"\n", + " \"real, general rationale (energy conservation) and a real means (the \"\n", + " \"subsetted, Z3-proved lemma itself), but its own need was identified \"\n", + " \"only after Chapter 8's proof already existed, in response to this \"\n", + " \"chapter's own traceability search -- not before it.\",\n", + "]\n", + "ac_c10_assumption_refs = [\n", + " \"AS-C08's own residual_uncertainties (Chapter 8): deliveredEnergyBoundedBySupply \"\n", + " \"is a hand-restated companion lemma, not a solver-checked reference to \"\n", + " \"HeatGenerator's own efficiencyBounded/deliveredEnergy (D-030, D-031); this \"\n", + " \"record does not strengthen that proof, only frames how its tie to a \"\n", + " \"requirement should be read.\",\n", + "]\n", + "print(ac_c10_premises)\n", + "print(ac_c10_assumption_refs)\n" + ] + }, + { + "cell_type": "markdown", + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "3850dee3", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T02:17:54.055551Z", + "iopub.status.busy": "2026-10-01T02:17:54.055467Z", + "iopub.status.idle": "2026-10-01T02:17:54.342100Z", + "shell.execute_reply": "2026-10-01T02:17:54.341568Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Lines mentioning EnergyConservationReq/energyConservationReq: []\n", + "../../models/ch10-cumulative.sysml:291:9 deliveredEnergyBoundedBySupply (AssertConstraintUsage): satisfied (z3: holds for all values of unbound features)\n" + ] + } + ], + "source": [ + "import subprocess\n", + "\n", + "# Shelling out directly rather than toaster.modelcheck's own wrapper: that\n", + "# wrapper's own line parser cannot read a verdict line for a constraint that is\n", + "# also the subject of an assert satisfy/assert not satisfy declaration at all\n", + "# (DEFERRED.md D-029) -- this model already has two such declarations\n", + "# (timely, heatGenerationReq), so the wrapper would fail before ever reaching\n", + "# this chapter's own construct.\n", + "SYSMLV2_BINARY = Path.home() / \"Documents/GitHub/sysml-toolkit/target/release/sysmlv2\"\n", + "SYSMLV2_LIB = Path.home() / \"Documents/GitHub/sysml-toolkit/spec-refs/SysML-v2-Release/sysml.library\"\n", + "assert SYSMLV2_BINARY.exists(), f\"sysmlv2 binary not found at {SYSMLV2_BINARY}\"\n", + "\n", + "solve_result = subprocess.run(\n", + " [str(SYSMLV2_BINARY), \"verify\", \"../../models/ch10-cumulative.sysml\",\n", + " \"--lib\", str(SYSMLV2_LIB), \"--solve\"],\n", + " capture_output=True, text=True, timeout=30,\n", + ")\n", + "solve_lines = solve_result.stdout.splitlines()\n", + "\n", + "energy_conservation_lines = [\n", + " line for line in solve_lines\n", + " if \"EnergyConservationReq\" in line or \"energyConservationReq\" in line\n", + "]\n", + "lemma_line = next(line for line in solve_lines if \"deliveredEnergyBoundedBySupply (AssertConstraintUsage)\" in line)\n", + "\n", + "print(f\"Lines mentioning EnergyConservationReq/energyConservationReq: {energy_conservation_lines}\")\n", + "print(lemma_line)\n", + "assert energy_conservation_lines == []\n", + "assert \"satisfied\" in lemma_line\n" + ] + }, + { + "cell_type": "markdown", + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "9cc5f8de", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T02:17:54.343618Z", + "iopub.status.busy": "2026-10-01T02:17:54.343490Z", + "iopub.status.idle": "2026-10-01T02:17:54.572752Z", + "shell.execute_reply": "2026-10-01T02:17:54.572220Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "companion-check-scratch/ch10_with_satisfy.sysml:291:9 c (ConstraintUsage, satisfies ToasterDemo::energyConservationReq): undecided (result is indeterminate over unbound features)\n" + ] + } + ], + "source": [ + "SCRATCH_DIR = Path(\"companion-check-scratch\")\n", + "SCRATCH_DIR.mkdir(exist_ok=True)\n", + "with_satisfy_source = source.rstrip()[:-1] + (\n", + " \"\\n assert satisfy energyConservationReq by deliveredEnergyBoundedBySupply;\\n}\\n\"\n", + ")\n", + "with_satisfy_path = SCRATCH_DIR / \"ch10_with_satisfy.sysml\"\n", + "with_satisfy_path.write_text(with_satisfy_source)\n", + "\n", + "with_satisfy_result = subprocess.run(\n", + " [str(SYSMLV2_BINARY), \"verify\", str(with_satisfy_path),\n", + " \"--lib\", str(SYSMLV2_LIB), \"--solve\"],\n", + " capture_output=True, text=True, timeout=30,\n", + ")\n", + "with_satisfy_lines = with_satisfy_result.stdout.splitlines()\n", + "with_satisfy_energy_line = next(\n", + " line for line in with_satisfy_lines\n", + " if \"EnergyConservationReq\" in line or \"energyConservationReq\" in line\n", + ")\n", + "print(with_satisfy_energy_line)\n", + "assert \"undecided\" in with_satisfy_energy_line\n", + "\n", + "with_satisfy_path.unlink(missing_ok=True)\n" + ] + }, + { + "cell_type": "markdown", + "id": "cc0e3b72", + "metadata": {}, + "source": [ + "The solver reports nothing at all for `EnergyConservationReq::c` in the current, satisfy-less model -- not `undecided`, not anything. A bare `:>` subsetting reference to an already-checked element is not itself a new thing for the solver to check, so it emits no separate verdict for it; the only solver-level fact in scope here is `deliveredEnergyBoundedBySupply`'s own already-cited result (`AS-C08`, confirmed again live above): `satisfied`. Confirmed by contrast above, run for real, not merely described: adding `assert satisfy energyConservationReq by deliveredEnergyBoundedBySupply;` back DOES make the solver emit a line -- `c (ConstraintUsage, satisfies ToasterDemo::energyConservationReq): undecided (result is indeterminate over unbound features)` -- a real verdict, not nothing, but not `satisfied` either: the same indeterminate result every other requirement's own unbound required constraint already gets in this model. So dropping `assert satisfy` does not leave an unresolved verdict hanging; it removes the only thing that would have generated one here in the first place, and either way the solver never reports this requirement's own required constraint as independently satisfied on its own terms. This requirement's own tie to the lemma's real `satisfied` result is a structural fact about the model -- the subsetting relationship itself, confirmed by `requirement_ties()`'s own Check-B hit above -- not a second, independently re-verified claim layered on top of it, under either register. That line only exists at all because of the very `assert satisfy` idiom this chapter deliberately does not use, and the negative control above already showed that idiom fails outright under `model.verify_satisfaction()`, across three different bindings." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "cc09043c", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T02:17:54.574260Z", + "iopub.status.busy": "2026-10-01T02:17:54.574010Z", + "iopub.status.idle": "2026-10-01T02:17:54.577424Z", + "shell.execute_reply": "2026-10-01T02:17:54.577088Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[\"Systems Library/Requirements.sysml, confirmed directly above: `subject subj : Anything[1];` is RequirementCheck's own default subject.\", \"model.verify_satisfaction() against assert satisfy energyConservationReq by (the negative control above, three real bindings tested -- the lemma itself, its own free-standing usage, and an unrelated real candidate): holds=False, error='require condition evaluation failed: no value for feature heatGenCheck.efficiency' in every case.\", \"sysmlv2 verify --solve, real output captured live above: no line at all for EnergyConservationReq/energyConservationReq in the current, satisfy-less model ([]), only the lemma's own already-cited result (../../models/ch10-cumulative.sysml:291:9 deliveredEnergyBoundedBySupply (AssertConstraintUsage): satisfied (z3: holds for all values of unbound features)); confirmed by contrast, also run live above, that adding assert satisfy back in makes the solver emit a real verdict after all (companion-check-scratch/ch10_with_satisfy.sysml:291:9 c (ConstraintUsage, satisfies ToasterDemo::energyConservationReq): undecided (result is indeterminate over unbound features)) -- just an undecided one, not a satisfied one.\", \"requirement_ties(model, LEMMA, idx)'s own Check-B hit, reproduced above: exactly one entry, attributed to the DEFINITION (EnergyConservationReq).\"]\n", + "Three things are true at once here, and none should be softened into the others. First, energy conservation really is a general physical constraint on any heat generator, independent of whether Chapter 8 happened to prove one instance of it, so once stated this way EnergyConservationReq is a legitimate requirement, and SS7.21.2's own subsetting idiom is a spec-sanctioned way to state it against an existing constraint. Second, that legitimacy does not by itself make this requirement's own NEED independently motivated: it is honest to say the need was not antecedent -- it exists because this chapter's own traceability analysis found deliveredEnergyBoundedBySupply tied to nothing, and EnergyConservationReq was built specifically to close that gap. Third, the tie this requirement actually has is purely structural -- the subsetting relationship itself, detected by Check B -- and deliberately NOT reinforced by an assert-satisfy claim: two separate, direct tests confirm that register would not do real evaluative work here, run above for real rather than merely argued -- model.verify_satisfaction() errors outright on all three bindings tested, and sysmlv2 verify --solve, while it DOES emit a real verdict once assert satisfy is added back (undecided, not nothing), never resolves it to satisfied either. requirement_coverage()'s own covered=False for energyConservationReq is therefore correct and expected, not a residual gap: there is no assert-satisfy declaration for it to find, by design.\n" + ] + } + ], + "source": [ + "ac_c10_evidence_refs = [\n", + " \"Systems Library/Requirements.sysml, confirmed directly above: \"\n", + " \"`subject subj : Anything[1];` is RequirementCheck's own default subject.\",\n", + " \"model.verify_satisfaction() against assert satisfy energyConservationReq by \"\n", + " \" (the negative control above, three real bindings tested -- the \"\n", + " \"lemma itself, its own free-standing usage, and an unrelated real \"\n", + " \"candidate): holds=False, error='require condition evaluation failed: no \"\n", + " \"value for feature heatGenCheck.efficiency' in every case.\",\n", + " f\"sysmlv2 verify --solve, real output captured live above: no line at all \"\n", + " f\"for EnergyConservationReq/energyConservationReq in the current, \"\n", + " f\"satisfy-less model ({energy_conservation_lines}), only the lemma's own \"\n", + " f\"already-cited result ({lemma_line.strip()}); confirmed by contrast, also \"\n", + " f\"run live above, that adding assert satisfy back in makes the solver emit \"\n", + " f\"a real verdict after all ({with_satisfy_energy_line.strip()}) -- just an \"\n", + " f\"undecided one, not a satisfied one.\",\n", + " \"requirement_ties(model, LEMMA, idx)'s own Check-B hit, reproduced above: \"\n", + " \"exactly one entry, attributed to the DEFINITION (EnergyConservationReq).\",\n", + "]\n", + "ac_c10_rationale = (\n", + " \"Three things are true at once here, and none should be softened into the \"\n", + " \"others. First, energy conservation really is a general physical constraint \"\n", + " \"on any heat generator, independent of whether Chapter 8 happened to prove \"\n", + " \"one instance of it, so once stated this way EnergyConservationReq is a \"\n", + " \"legitimate requirement, and SS7.21.2's own subsetting idiom is a \"\n", + " \"spec-sanctioned way to state it against an existing constraint. Second, \"\n", + " \"that legitimacy does not by itself make this requirement's own NEED \"\n", + " \"independently motivated: it is honest to say the need was not antecedent \"\n", + " \"-- it exists because this chapter's own traceability analysis found \"\n", + " \"deliveredEnergyBoundedBySupply tied to nothing, and EnergyConservationReq \"\n", + " \"was built specifically to close that gap. Third, the tie this requirement \"\n", + " \"actually has is purely structural -- the subsetting relationship itself, \"\n", + " \"detected by Check B -- and deliberately NOT reinforced by an assert-satisfy \"\n", + " \"claim: two separate, direct tests confirm that register would not do real \"\n", + " \"evaluative work here, run above for real rather than merely argued -- \"\n", + " \"model.verify_satisfaction() errors outright on all three bindings tested, \"\n", + " \"and sysmlv2 verify --solve, while it DOES emit a real verdict once \"\n", + " \"assert satisfy is added back (undecided, not nothing), never resolves it \"\n", + " \"to satisfied either. requirement_coverage()'s own covered=False for \"\n", + " \"energyConservationReq is therefore correct and expected, not a residual \"\n", + " \"gap: there is no assert-satisfy declaration for it to find, by design.\"\n", + ")\n", + "print(ac_c10_evidence_refs)\n", + "print(ac_c10_rationale)" + ] + }, + { + "cell_type": "markdown", + "id": "85f666a3", + "metadata": {}, + "source": [ + "What could be wrong, and what is still open (trustworthiness): named plainly, not folded into a tidier-sounding conclusion." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "0cce3210", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T02:17:54.578501Z", + "iopub.status.busy": "2026-10-01T02:17:54.578417Z", + "iopub.status.idle": "2026-10-01T02:17:54.581209Z", + "shell.execute_reply": "2026-10-01T02:17:54.580776Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "This record's own framing could be read as too generous: a requirement invented after the fact to absorb existing evidence is exactly the shape Douglas's own traceability concern warns against in reverse -- a requirement manufactured FROM the evidence rather than one that independently motivates it. That reading is not wrong; it is why this record exists rather than a cleaner doc comment alone. What keeps this from being circular in the way Douglas warns against is narrow: the underlying physical law (conservation of energy) would be a real constraint on any heat generator design whether or not Chapter 8 had ever proved deliveredEnergyBoundedBySupply -- the requirement's own general content is not invented, only the TIMING of its discovery is retroactive. A reader who judges retroactive discovery disqualifying regardless of the underlying law's own independence would reasonably disagree with this record's own engineering_conclusion. Separately: this tie rests entirely on a structural check (Check B's subsetting hit), with no solver-level re-verification of the requirement's own required constraint at all -- the solver simply does not generate one for a bare subsetting reference (confirmed above). A reader could reasonably want more than structural detection before treating a requirement as genuinely tied; this record does not manufacture an assert-satisfy claim to supply that, because the one tested above does not hold up (it errors, not passes).\n", + "Whether a future chapter would accept a requirement whose own need was identified after its own evidence, or whether this tutorial should adopt a stricter rule requiring every requirement's need to predate its own evidence, is not settled here -- an open question this record surfaces rather than resolves. Whether this tie should ever be strengthened beyond structural subsetting -- a genuinely parametrized, per-candidate check, rather than a closed, already-proved universal lemma -- is also open, and would require re-authoring deliveredEnergyBoundedBySupply as an open template (SS7.21.1's own massLimit idiom) rather than the closed proposition Chapter 8 deliberately built.\n" + ] + } + ], + "source": [ + "ac_c10_counterevidence = (\n", + " \"This record's own framing could be read as too generous: a requirement \"\n", + " \"invented after the fact to absorb existing evidence is exactly the shape \"\n", + " \"Douglas's own traceability concern warns against in reverse -- a \"\n", + " \"requirement manufactured FROM the evidence rather than one that \"\n", + " \"independently motivates it. That reading is not wrong; it is why this \"\n", + " \"record exists rather than a cleaner doc comment alone. What keeps this \"\n", + " \"from being circular in the way Douglas warns against is narrow: the \"\n", + " \"underlying physical law (conservation of energy) would be a real \"\n", + " \"constraint on any heat generator design whether or not Chapter 8 had ever \"\n", + " \"proved deliveredEnergyBoundedBySupply -- the requirement's own general \"\n", + " \"content is not invented, only the TIMING of its discovery is retroactive. \"\n", + " \"A reader who judges retroactive discovery disqualifying regardless of the \"\n", + " \"underlying law's own independence would reasonably disagree with this \"\n", + " \"record's own engineering_conclusion. Separately: this tie rests entirely \"\n", + " \"on a structural check (Check B's subsetting hit), with no solver-level \"\n", + " \"re-verification of the requirement's own required constraint at all -- \"\n", + " \"the solver simply does not generate one for a bare subsetting reference \"\n", + " \"(confirmed above). A reader could reasonably want more than structural \"\n", + " \"detection before treating a requirement as genuinely tied; this record \"\n", + " \"does not manufacture an assert-satisfy claim to supply that, because the \"\n", + " \"one tested above does not hold up (it errors, not passes).\"\n", + ")\n", + "ac_c10_residual_uncertainties = (\n", + " \"Whether a future chapter would accept a requirement whose own need was \"\n", + " \"identified after its own evidence, or whether this tutorial should adopt \"\n", + " \"a stricter rule requiring every requirement's need to predate its own \"\n", + " \"evidence, is not settled here -- an open question this record surfaces \"\n", + " \"rather than resolves. Whether this tie should ever be strengthened beyond \"\n", + " \"structural subsetting -- a genuinely parametrized, per-candidate check, \"\n", + " \"rather than a closed, already-proved universal lemma -- is also open, and \"\n", + " \"would require re-authoring deliveredEnergyBoundedBySupply as an open \"\n", + " \"template (SS7.21.1's own massLimit idiom) rather than the closed \"\n", + " \"proposition Chapter 8 deliberately built.\"\n", + ")\n", + "print(ac_c10_counterevidence)\n", + "print(ac_c10_residual_uncertainties)\n" + ] + }, + { + "cell_type": "markdown", + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "76642f16", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T02:17:54.582372Z", + "iopub.status.busy": "2026-10-01T02:17:54.582240Z", + "iopub.status.idle": "2026-10-01T02:17:54.584757Z", + "shell.execute_reply": "2026-10-01T02:17:54.584376Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Validation errors: []\n" + ] + } + ], + "source": [ + "ac_c10 = ReviewRecord(\n", + " identifier=\"AC-C10\",\n", + " kind=\"asserted_context\",\n", + " claim=ac_c10_claim,\n", + " model_ref=ac_c10_model_ref,\n", + " content_hash=hash_content(source),\n", + " scope=ac_c10_scope,\n", + " criteria=ac_c10_criteria,\n", + " premises=ac_c10_premises,\n", + " assumption_refs=ac_c10_assumption_refs,\n", + " evidence_refs=ac_c10_evidence_refs,\n", + " rationale=ac_c10_rationale,\n", + " counterevidence=ac_c10_counterevidence,\n", + " residual_uncertainties=ac_c10_residual_uncertainties,\n", + " disposition=\"pending\",\n", + " dependency_freshness=\"current\",\n", + " engineering_conclusion=\"undetermined\",\n", + " record_kind=\"worked_example\",\n", + ")\n", + "\n", + "errors = validate_record(ac_c10)\n", + "print(f\"Validation errors: {errors}\")\n", + "assert errors == []\n" + ] + }, + { + "cell_type": "markdown", + "id": "286b26f6", + "metadata": {}, + "source": [ + "`validate_record` reports no errors. `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 SS1.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`." + ] + }, + { + "cell_type": "markdown", + "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." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.2" } - ] -} \ No newline at end of file + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/chapters/ch10-traceability-signoff/02-judgment-synthesis.ipynb b/chapters/ch10-traceability-signoff/02-judgment-synthesis.ipynb index 9d935d9..9642bf6 100644 --- a/chapters/ch10-traceability-signoff/02-judgment-synthesis.ipynb +++ b/chapters/ch10-traceability-signoff/02-judgment-synthesis.ipynb @@ -1,78 +1,584 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, "cells": [ { "cell_type": "markdown", - "id": "cell-0", + "id": "e7f6a609", "metadata": {}, "source": [ - "## inference dependency graph \u2192 root asserted_inference\n\n**Concept statement (stub):** This notebook introduces inference dependency graph \u2192 root asserted_inference; after running it you can [TODO]." + "## Ch10-02 -- A judgment ledger, over three real records this tutorial has already built\n", + "\n", + "This notebook introduces a judgment ledger over three real `ReviewRecord`s already built earlier in this tutorial; after running it you can see what each one claims, which of Hawkins' three judgment kinds it represents, and what its own `residual_uncertainties` says is not yet resolved." ] }, { "cell_type": "markdown", - "id": "cell-1", + "id": "e7d56322", "metadata": {}, "source": [ - "**Context (stub):** [TODO \u2014 one paragraph locating this notebook in the chapter arc.]" + "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 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. So this notebook does not scan \"every record the tutorial has ever produced\"; instead it reconstructs three real records verbatim, field for field: `AS-C06` and `AS-C08`, already reconstructed once by Chapter 9 and re-verified here against their real originals rather than assumed correct just because Chapter 9 said so, 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." ] }, { "cell_type": "code", - "id": "cell-2", + "execution_count": 1, + "id": "407bfd17", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T01:57:35.797376Z", + "iopub.status.busy": "2026-10-01T01:57:35.797186Z", + "iopub.status.idle": "2026-10-01T01:57:36.046215Z", + "shell.execute_reply": "2026-10-01T01:57:36.045607Z" + } + }, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "import opensysml\n", + "from toaster.report import format_diagnostics\n", + "\n", + "conn = opensysml.connect(version=\"v0.9.0\")\n", + "source = Path(\"../../models/ch10-cumulative.sysml\").read_text()\n", + "model = conn.load_from_content(source, strict=False)\n", + "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"\n" + ] + }, + { + "cell_type": "markdown", + "id": "08a3ddbe", "metadata": {}, "source": [ - "import opensysml\n\n# TODO: full cumulative SysML source (SA-2)\nsource = \"\"\"\n# stub \u2014 replace with full model\n\"\"\"\n\nconn = opensysml.connect(version=\"v0.9.0\")\nmodel = conn.load_from_content(source, strict=False)\nassert model.ok, f\"Model failed: {model.diagnostics}\"" + "Before reconstructing the three records, the negative control fitting this notebook's own new record, `AI-C06`: Hawkins SS3.1 requires an `asserted_inference` to name at least one premise, since a bare assertion with nothing supporting it is not an inference at all. `validate_record()` already enforces this." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "2e31dd54", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T01:57:36.047995Z", + "iopub.status.busy": "2026-10-01T01:57:36.047741Z", + "iopub.status.idle": "2026-10-01T01:57:36.051220Z", + "shell.execute_reply": "2026-10-01T01:57:36.050474Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Negative control ok: errors=['asserted_inference requires at least one premise (Hawkins §3.1)']\n" + ] + } ], - "outputs": [], - "execution_count": null + "source": [ + "from toaster.evidence import ReviewRecord, hash_content, validate_record\n", + "\n", + "incomplete_inference = ReviewRecord(\n", + " identifier=\"AI-BAD\",\n", + " kind=\"asserted_inference\",\n", + " claim=\"The judgment ledger is complete.\",\n", + " model_ref=\"ToasterDemo\",\n", + " content_hash=hash_content(source),\n", + " scope=\"ToasterDemo\",\n", + " criteria=\"Every real ReviewRecord this tutorial has built is included.\",\n", + " premises=[], # intentionally empty\n", + " rationale=\"Chapter 10 built a ledger.\",\n", + " counterevidence=\"No central registry exists to check completeness against.\",\n", + " record_kind=\"worked_example\",\n", + ")\n", + "errors = validate_record(incomplete_inference)\n", + "assert len(errors) > 0, \"Expected validation to fail on empty premises\"\n", + "print(f\"Negative control ok: errors={errors}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "a187ec26", + "metadata": {}, + "source": [ + "`AS-C06`, rebuilt below **verbatim** from Chapter 6 notebook 02's own field strings, the same reconstruction Chapter 9 already carried 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` is computed against `models/ch06-cumulative.sysml`, the real model it was actually written against." + ] }, { "cell_type": "code", - "id": "cell-3", + "execution_count": 3, + "id": "52353610", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T01:57:36.052529Z", + "iopub.status.busy": "2026-10-01T01:57:36.052432Z", + "iopub.status.idle": "2026-10-01T01:57:36.056858Z", + "shell.execute_reply": "2026-10-01T01:57:36.056525Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "AS-C06 validation errors: []\n" + ] + } + ], + "source": [ + "ch06_source = Path(\"../../models/ch06-cumulative.sysml\").read_text()\n", + "\n", + "as_c06 = ReviewRecord(\n", + " identifier=\"AS-C06\",\n", + " kind=\"asserted_solution\",\n", + " claim=(\n", + " \"ResistanceCoil, an electrically switched resistive element that converts \"\n", + " \"energy to heat by Joule heating, is selected over a combustion-based \"\n", + " \"alternative (a gas burner, the tongs-and-blowtorch alternative this \"\n", + " \"tutorial already contrasts) as the mechanism HeatGenerator commits to.\"\n", + " ),\n", + " model_ref=\"ToasterDemo::ResistanceCoil\",\n", + " content_hash=hash_content(ch06_source),\n", + " scope=\"ToasterDemo::HeatGenerator and its realizations\",\n", + " criteria=(\n", + " \"The chosen mechanism must pair with the discrete timing control \"\n", + " \"ControlSystem's durationOut already provides, and must expose a rating \"\n", + " \"heatGenerationReq's power threshold can be checked against once a \"\n", + " \"concrete part exists.\"\n", + " ),\n", + " premises=[\n", + " \"ControlSystem declares durationOut : DurationPort (Chapter 5), a \"\n", + " \"discrete duration signal, confirmed by model.find() above.\",\n", + " \"Domain premise, not derived from the model: a resistive element \"\n", + " \"responds to being switched on and off directly, while a combustion \"\n", + " \"source needs separate ignition and fuel-metering machinery to do \"\n", + " \"the same. Neither HeatGenerator nor any of its realizations is yet \"\n", + " \"connected to ControlSystem's port in this model; this premise is \"\n", + " \"about the physical mechanisms themselves, not about what the model \"\n", + " \"currently wires together.\",\n", + " ],\n", + " assumption_refs=[\n", + " \"HeatGenerator::energyIn and GenerateHeat carry no energy-form commitment \"\n", + " \"(notebook 01): this record is what actually commits to an electrical \"\n", + " \"form, not a fact already built into the port or the function.\"\n", + " ],\n", + " evidence_refs=[\n", + " \"model.find('ToasterDemo::ControlSystem::durationOut') resolves to a \"\n", + " \"real portUsage, confirmed above.\"\n", + " ],\n", + " rationale=(\n", + " \"An electrically resistive element responds to being switched on \"\n", + " \"and off directly, the same shape as duration's discrete timing \"\n", + " \"signal, while a combustion-based burner needs separate ignition \"\n", + " \"and fuel-metering machinery to respond the same way (the domain \"\n", + " \"premise above). That is a claim about how the two mechanisms \"\n", + " \"work, not something this model currently shows: no realization \"\n", + " \"of HeatGenerator is yet connected to ControlSystem's \"\n", + " \"durationOut port, so this selection is a reasoned engineering \"\n", + " \"preference argued from mechanism, not a claim that the model \"\n", + " \"already connects one mechanism and not the other.\"\n", + " ),\n", + " counterevidence=(\n", + " \"This does not rule out a combustion design: a burner controlled by its \"\n", + " \"own timed valve could equally use a duration-like signal, which is \"\n", + " \"exactly why the argument above rests on a domain premise about how \"\n", + " \"the two mechanisms work, not on anything the model itself already \"\n", + " \"builds or connects. Joule heating's own relation (power proportional \"\n", + " \"to resistance and the square of current) is still not modeled, so \"\n", + " \"efficiency and response-time comparisons remain out of reach \"\n", + " \"either way.\"\n", + " ),\n", + " residual_uncertainties=(\n", + " \"Once a supply and a control policy are modeled together, this \"\n", + " \"selection could be revisited against a real trade study rather than \"\n", + " \"a domain premise alone.\"\n", + " ),\n", + " disposition=\"pending\",\n", + " dependency_freshness=\"current\",\n", + " engineering_conclusion=\"undetermined\",\n", + " record_kind=\"worked_example\",\n", + ")\n", + "\n", + "errors = validate_record(as_c06)\n", + "print(f\"AS-C06 validation errors: {errors}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "e531af6c", "metadata": {}, "source": [ - "# Negative control \u2014 TODO: intentional error matching chapter construct\nbad_source = \"part def Missing { part x : NonExistent; }\"\nbad = conn.load_from_content(bad_source, strict=False)\nassert not bad.ok" + "`AS-C08`, rebuilt the same way, verbatim, from Chapter 8 notebook 02: 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` is computed against `models/ch08-cumulative.sysml`, the model it was actually written against." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "98f560da", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T01:57:36.058133Z", + "iopub.status.busy": "2026-10-01T01:57:36.058002Z", + "iopub.status.idle": "2026-10-01T01:57:36.062079Z", + "shell.execute_reply": "2026-10-01T01:57:36.061742Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "AS-C08 validation errors: []\n" + ] + } ], - "outputs": [], - "execution_count": null + "source": [ + "ch08_source = Path(\"../../models/ch08-cumulative.sysml\").read_text()\n", + "\n", + "as_c08 = ReviewRecord(\n", + " identifier=\"AS-C08\",\n", + " kind=\"asserted_solution\",\n", + " claim=(\n", + " \"deliveredEnergyBoundedBySupply, a hand-restated real-arithmetic lemma of the same \"\n", + " \"shape as HeatGenerator's efficiencyBounded constraint and deliveredEnergy's own \"\n", + " \"definition, holds for every value of efficiency in [0,1] and every non-negative \"\n", + " \"power and duration a companion restatement admits.\"\n", + " ),\n", + " model_ref=\"ToasterDemo::deliveredEnergyBoundedBySupply\",\n", + " content_hash=hash_content(ch08_source),\n", + " scope=(\n", + " \"The lemma governs the companion restatement's own heatGenCheck.efficiency, \"\n", + " \"heatGenCheck.power and heatGenCheckDuration features; it is not a solver-checked \"\n", + " \"reference to HeatGenerator's own efficiencyBounded or deliveredEnergy (DEFERRED.md \"\n", + " \"D-030, D-031), only a hand-restated copy of the same shape.\"\n", + " ),\n", + " criteria=(\n", + " \"verify_holds() reports a single satisfied verdict for deliveredEnergyBoundedBySupply, \"\n", + " \"with the reason text naming z3 (not propagation alone), proved for all values of the \"\n", + " \"unbound heatGenCheck.efficiency, heatGenCheck.power and heatGenCheckDuration features.\"\n", + " ),\n", + " premises=[],\n", + " assumption_refs=[],\n", + " evidence_refs=[\n", + " \"verify_holds: deliveredEnergyBoundedBySupply satisfied \"\n", + " \"(z3: holds for all values of unbound features)\"\n", + " ],\n", + " rationale=(\n", + " \"verify_holds() calls sysml-toolkit's real verify --solve command, which runs Z3 \"\n", + " \"over the unbound features of a companion restatement of this lemma (see the \"\n", + " \"narration above for why a companion file is used) and reports \"\n", + " \"deliveredEnergyBoundedBySupply as satisfied: proved for all values the restatement \"\n", + " \"admits, not read back from one entered value. This is a materially different kind of \"\n", + " \"evidence from an evaluate-only verdict: verify_satisfaction() could only ever check \"\n", + " \"a relation at whichever single power, duration and efficiency a candidate happens to \"\n", + " \"carry. It is also, deliberately, a narrower claim than 'this proves HeatGenerator's \"\n", + " \"own conservation property': see counterevidence.\"\n", + " ),\n", + " counterevidence=(\n", + " \"This proof is NOT a solver-checked reference to HeatGenerator's own \"\n", + " \"efficiencyBounded constraint or deliveredEnergy calc: this toolchain's Z3 backend \"\n", + " \"does not compose two separately declared assert constraints, whether sibling or \"\n", + " \"inherited (DEFERRED.md D-030), and cannot reason through a chained calc invocation \"\n", + " \"like heatGenCheck.deliveredEnergy(...) (D-031). Confirmed directly: loosening \"\n", + " \"efficiencyBounded's own literal bound to <= 1.5, or doubling deliveredEnergy's own \"\n", + " \"definition by a factor of 2.0, in the real committed model changes neither the \"\n", + " \"original elements' own verdicts nor this lemma's verdict at all, because the lemma \"\n", + " \"restates its own copy of both rather than referencing either. The companion file \"\n", + " \"used by verify_holds also restates the construct rather than checking the \"\n", + " \"committed model directly, because toaster.modelcheck's own text parser does not \"\n", + " \"yet handle the extra annotation the CLI prints for assert satisfy declarations \"\n", + " \"(DEFERRED.md D-029).\"\n", + " ),\n", + " residual_uncertainties=(\n", + " \"Whether efficiency, power and duration ever take values outside the bound in a \"\n", + " \"real candidate is not addressed by this proof; it establishes only that the \"\n", + " \"restated lemma respects conservation wherever its own bound is honored. If \"\n", + " \"HeatGenerator's own efficiencyBounded or deliveredEnergy is ever edited, this \"\n", + " \"record's content_hash (computed from the whole model file) does go stale, which \"\n", + " \"forces a re-review, but nothing automatically re-checks that the restated copy \"\n", + " \"still matches the edited original; that check would be manual. No physical heat \"\n", + " \"generator has been checked against this property; HeatGenerator remains an \"\n", + " \"abstract carrier with no concrete realization of its own.\"\n", + " ),\n", + " disposition=\"pending\",\n", + " dependency_freshness=\"current\",\n", + " engineering_conclusion=\"supported\",\n", + " record_kind=\"worked_example\",\n", + ")\n", + "\n", + "errors = validate_record(as_c08)\n", + "print(f\"AS-C08 validation errors: {errors}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "002a8901", + "metadata": {}, + "source": [ + "`AI-C06`, the third record, is genuinely different in kind: an `asserted_inference` about the level-2 heat-generation branch's own stopping judgment, rebuilt verbatim from Chapter 6 notebook 03. Its `premises` field names four earlier judgment identifiers directly (`AC-C06`, `AS-C06`, `AS-C03`, `AI-C04`), the clearest example in this tutorial of Hawkins' own \"child claims supporting a parent\" idea. Its `evidence_refs` cites the same real analysis results Chapter 6 notebook 03 gathered against that chapter's own model, recomputed below against a freshly loaded `models/ch06-cumulative.sysml` rather than restated by hand. Its `content_hash` is also computed against that same file, since both `AI-C06` and `AS-C06` above were written against Chapter 6's own model." + ] }, { "cell_type": "code", - "id": "cell-4", + "execution_count": 5, + "id": "197f097d", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T01:57:36.063341Z", + "iopub.status.busy": "2026-10-01T01:57:36.063257Z", + "iopub.status.idle": "2026-10-01T01:57:36.195791Z", + "shell.execute_reply": "2026-10-01T01:57:36.195316Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Perform relationships: [{'performer': 'ToasterDemo::ToastingSystem', 'action': 'ToasterDemo::ToastBread'}, {'performer': 'ToasterDemo::HeatingSystem', 'action': 'ToasterDemo::ApplyHeat'}, {'performer': 'ToasterDemo::HeatGenerator', 'action': 'ToasterDemo::GenerateHeat'}]\n", + "Allocations: [{'id': 'ToasterDemo::Toaster::heatAllocation', 'type': 'AllocationUsage', 'ends': [['ToasterDemo::ToastingSystem::toastBread', 'ToasterDemo::ToastBread::applyHeat'], ['ToasterDemo::Toaster::heating']]}, {'id': 'ToasterDemo::HeatingAssembly::heatGenAllocation', 'type': 'AllocationUsage', 'ends': [['ToasterDemo::HeatingSystem::applyHeat', 'ToasterDemo::ApplyHeat::generateHeat'], ['ToasterDemo::HeatingAssembly::heatGen']]}]\n", + "heatGenerationReq(rated) = True, heatGenerationReq(weak) = False\n" + ] + } + ], + "source": [ + "from toaster.query import find_allocations, perform_relationships\n", + "\n", + "ch06_model = conn.load_from_content(ch06_source, strict=False)\n", + "assert ch06_model.ok\n", + "\n", + "performs = perform_relationships(ch06_model)\n", + "allocations = find_allocations(ch06_model)\n", + "rated_holds = ch06_model.eval(\"ToasterDemo::heatGenerationReq(ToasterDemo::rated)\")\n", + "weak_holds = ch06_model.eval(\"ToasterDemo::heatGenerationReq(ToasterDemo::weak)\")\n", + "print(f\"Perform relationships: {performs}\")\n", + "print(f\"Allocations: {allocations}\")\n", + "print(f\"heatGenerationReq(rated) = {rated_holds}, heatGenerationReq(weak) = {weak_holds}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "3a047fb9", "metadata": {}, "source": [ - "# Demonstration \u2014 TODO: one key operation\npass" + "`HeatGenerator` performs `GenerateHeat`, `heatGenAllocation` points from `applyHeat.generateHeat` to `heatGen`, declared inside `HeatingAssembly` where both resolve, and the requirement evaluates True on `rated` and False on `weak`, the same three real results Chapter 6 notebook 03 gathered. The record below assembles from these results directly, the same construction-zone pattern used for `AS-C06` and `AS-C08` above." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "476b8205", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T01:57:36.196876Z", + "iopub.status.busy": "2026-10-01T01:57:36.196785Z", + "iopub.status.idle": "2026-10-01T01:57:36.200658Z", + "shell.execute_reply": "2026-10-01T01:57:36.200111Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "AI-C06 validation errors: []\n" + ] + } ], - "outputs": [], - "execution_count": null + "source": [ + "ai_c06 = ReviewRecord(\n", + " identifier=\"AI-C06\",\n", + " kind=\"asserted_inference\",\n", + " claim=(\n", + " \"GenerateHeat is a real, specified behavior: HeatGenerator performs it \"\n", + " \"and is allocated the nested step, not merely declared syntax. energyIn \"\n", + " \"is a declared, typed connection point on HeatGenerator, not yet wired \"\n", + " \"to a producer. heatGenerationReq evaluates against two real \"\n", + " \"candidates, rated (True) and weak (False), a genuine satisfaction \"\n", + " \"check on an underived threshold, not an unevaluated assertion.\"\n", + " ),\n", + " model_ref=\"ToasterDemo::HeatingAssembly::heatGen\",\n", + " content_hash=hash_content(ch06_source),\n", + " scope=\"ToasterDemo::HeatingAssembly::heatGen and its realizations\",\n", + " criteria=(\n", + " \"Per the recursion's own stopping rule, a leaf performs its specified \"\n", + " \"behavior, connects through its specified interfaces, and has \"\n", + " \"verification evidence. At this level: GenerateHeat is a specified \"\n", + " \"behavior, allocated to HeatGenerator (met). energyIn is a declared, \"\n", + " \"typed connection point, not yet connected to any producer \"\n", + " \"(partially met, not complete). heatGenerationReq evaluates on two \"\n", + " \"real candidates against a threshold that is itself not yet derived \"\n", + " \"(partial evidence, not full verification).\"\n", + " ),\n", + " premises=[\"AC-C06\", \"AS-C06\", \"AS-C03\", \"AI-C04\"],\n", + " assumption_refs=[\n", + " \"GenerateHeat's own energy input is bound to ApplyHeat::energy, \"\n", + " \"printed as part of APPLY_HEAT_INCREMENT and loaded successfully in \"\n", + " \"notebook 01; this does not by itself mean energyIn is wired to any \"\n", + " \"producer.\"\n", + " ],\n", + " evidence_refs=[\n", + " f\"perform_relationships(model): {performs}\",\n", + " f\"find_allocations(model): {allocations}\",\n", + " f\"heatGenerationReq(rated) = {rated_holds}, heatGenerationReq(weak) = \"\n", + " f\"{weak_holds}, evaluated directly against the loaded model above.\",\n", + " ],\n", + " rationale=(\n", + " \"Performs: real, not merely declared. HeatGenerator performing \"\n", + " \"GenerateHeat and heatGenAllocation both appear directly in \"\n", + " \"perform_relationships and find_allocations above, not just in the \"\n", + " \"source text, the same performer-and-allocation split Chapter 5 \"\n", + " \"established one level up. Connects: energyIn is declared and typed, \"\n", + " \"the interface point the stopping rule names, but it is not wired to \"\n", + " \"any producer, so this condition is only partially met. Verified: \"\n", + " \"heatGenerationReq is genuinely evaluated, not left as an unevaluated \"\n", + " \"assertion, on two real candidates, one passing and one deliberately \"\n", + " \"failing for a reason about the design; but its own threshold is not \"\n", + " \"yet derived from any stated measure of effectiveness (AC-C06), so \"\n", + " \"this is partial verification evidence, not a completed check.\"\n", + " ),\n", + " counterevidence=(\n", + " \"energyIn has no producer wired to it: no supply or wire component \"\n", + " \"exists in this model, so the interface point is declared, not yet \"\n", + " \"connected end to end, the same partial state Chapter 5 left \"\n", + " \"ApplyHeat::duration in before that chapter built its own port \"\n", + " \"connection. HeatingAssembly is not yet composed into any Toaster \"\n", + " \"candidate: Toaster::heating is still typed by the abstract \"\n", + " \"HeatingSystem, so no full toaster candidate contains a resistance \"\n", + " \"coil yet. The 600 W threshold is not derived from any stated \"\n", + " \"measure of effectiveness (AC-C06); it is a free-standing \"\n", + " \"engineering figure. This branch addresses only GenerateHeat's own \"\n", + " \"energy-to-heat conversion: ApplyHeat's other flows (bread, \"\n", + " \"duration, toast, delivered, loss) are not decomposed or accounted \"\n", + " \"for at this level. This is a deliberate one-branch worked example \"\n", + " \"of one of ApplyHeat's own flows, the same kind of honestly scoped \"\n", + " \"choice Chapter 4 made for ApplyHeat itself out of Douglas's roughly \"\n", + " \"fifteen functions, not a claim that Chapter 6 finishes ApplyHeat's \"\n", + " \"full decomposition.\"\n", + " ),\n", + " residual_uncertainties=(\n", + " \"Whether GenerateHeat needs further decomposition of its own, \"\n", + " \"whether HeatingAssembly is ever composed into a real Toaster \"\n", + " \"candidate, and whether ApplyHeat's other flows (duration, bread, \"\n", + " \"toast) get their own level-2 branches, are open for whichever \"\n", + " \"chapter takes them up.\"\n", + " ),\n", + " disposition=\"pending\",\n", + " dependency_freshness=\"current\",\n", + " engineering_conclusion=\"undetermined\",\n", + " record_kind=\"worked_example\",\n", + ")\n", + "\n", + "errors = validate_record(ai_c06)\n", + "print(f\"AI-C06 validation errors: {errors}\")\n" + ] + }, + { + "cell_type": "markdown", + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "90180fe1", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T01:57:36.201681Z", + "iopub.status.busy": "2026-10-01T01:57:36.201601Z", + "iopub.status.idle": "2026-10-01T01:57:36.209113Z", + "shell.execute_reply": "2026-10-01T01:57:36.208737Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "AS-C06:\n", + " kind: asserted_solution\n", + " disposition: pending\n", + " record_kind: worked_example\n", + " engineering_conclusion: undetermined\n", + " residual_uncertainties: Once a supply and a control policy are modeled together, this selection could be revisited against a real trade study rather than a domain premise alone.\n", + "\n", + "AS-C08:\n", + " kind: asserted_solution\n", + " disposition: pending\n", + " record_kind: worked_example\n", + " engineering_conclusion: supported\n", + " residual_uncertainties: Whether efficiency, power and duration ever take values outside the bound in a real candidate is not addressed by this proof; it establishes only that the restated lemma respects conservation wherever its own bound is honored. If HeatGenerator's own efficiencyBounded or deliveredEnergy is ever edited, this record's content_hash (computed from the whole model file) does go stale, which forces a re-review, but nothing automatically re-checks that the restated copy still matches the edited original; that check would be manual. No physical heat generator has been checked against this property; HeatGenerator remains an abstract carrier with no concrete realization of its own.\n", + "\n", + "AI-C06:\n", + " kind: asserted_inference\n", + " disposition: pending\n", + " record_kind: worked_example\n", + " engineering_conclusion: undetermined\n", + " residual_uncertainties: Whether GenerateHeat needs further decomposition of its own, whether HeatingAssembly is ever composed into a real Toaster candidate, and whether ApplyHeat's other flows (duration, bread, toast) get their own level-2 branches, are open for whichever chapter takes them up.\n", + "\n" + ] + } + ], + "source": [ + "ledger = {\"AS-C06\": as_c06, \"AS-C08\": as_c08, \"AI-C06\": ai_c06}\n", + "\n", + "for identifier, record in ledger.items():\n", + " print(f\"{identifier}:\")\n", + " print(f\" kind: {record.kind}\")\n", + " print(f\" disposition: {record.disposition}\")\n", + " print(f\" record_kind: {record.record_kind}\")\n", + " print(f\" engineering_conclusion: {record.engineering_conclusion}\")\n", + " print(f\" residual_uncertainties: {record.residual_uncertainties}\")\n", + " print()\n", + "\n", + "assert all(r.disposition == \"pending\" for r in ledger.values())\n", + "assert all(r.record_kind == \"worked_example\" for r in ledger.values())\n", + "conn.close()\n" + ] + }, + { + "cell_type": "markdown", + "id": "94c361c0", + "metadata": {}, + "source": [ + "Three real records, three different points of confidence. `AS-C06` stays `undetermined`: its own residual admits the mechanism selection could be revisited against a real trade study once a supply and control policy are modeled together, not settled by the domain premise alone. `AS-C08` is `supported`, but narrowly: its own residual is explicit that the restated lemma is not automatically re-checked against the real elements it mirrors if either is edited, and that no physical heat generator has ever been checked against it. `AI-C06` stays `undetermined` too: its own residual leaves open whether `GenerateHeat` needs further decomposition, whether `HeatingAssembly` is ever composed into a real `Toaster` candidate, and whether `ApplyHeat`'s other flows get their own branches. Two records out of three stay `undetermined`, and the one `supported` record is supported only for a claim already narrowed to a hand-restated copy, not the real elements it mirrors. That is what a ledger is for: not a tally of how many records exist, but a reading of how much of that count is actually load-bearing." + ] }, { "cell_type": "markdown", - "id": "cell-5", + "id": "2f83d4be", "metadata": {}, "source": [ - "**Tall seam (stub):** [TODO \u2014 one sentence: the SysML construct (A-F) is executed by OpenSysML (O-S); the result is (E).]" + "Building three real judgment records side by side, reading their own kind, disposition and residual uncertainty rather than just their count, is what turns \"this tutorial has produced some ReviewRecords\" into a ledger an accountable engineer could actually use: what follows next, in [notebook 03](03-engineering-signoff.ipynb), synthesizes this ledger together with notebook 01's own traceability graph." ] }, { "cell_type": "markdown", - "id": "cell-6", + "id": "6f503fc9", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch10/exercise.ipynb`: [TODO \u2014 one-line description]." + "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." ] } - ] -} \ No newline at end of file + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/chapters/ch10-traceability-signoff/03-engineering-signoff.ipynb b/chapters/ch10-traceability-signoff/03-engineering-signoff.ipynb index 7988fb0..4ac2146 100644 --- a/chapters/ch10-traceability-signoff/03-engineering-signoff.ipynb +++ b/chapters/ch10-traceability-signoff/03-engineering-signoff.ipynb @@ -1,78 +1,695 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python" - } - }, "cells": [ { "cell_type": "markdown", - "id": "cell-0", + "id": "cb5d949e", "metadata": {}, "source": [ - "## assembled sign-off document\n\n**Concept statement (stub):** This notebook introduces assembled sign-off document; after running it you can [TODO]." + "## Ch10-03 -- A synthesis record, and what it is not\n", + "\n", + "This notebook introduces a synthesis record over notebook 01's traceability graph and notebook 02's judgment ledger; after running it you can see, in one place, what has real bidirectional verification evidence, what has only one-sided evidence, how the model's own strongest formal proof -- once found disconnected from any requirement -- is now tied to one by notebook 01's own remediation, and what an accountable engineer would still have to decide before actually shipping this design." ] }, { "cell_type": "markdown", - "id": "cell-1", + "id": "e797c000", "metadata": {}, "source": [ - "**Context (stub):** [TODO \u2014 one paragraph locating this notebook in the chapter arc.]" + "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." ] }, { "cell_type": "code", - "id": "cell-2", + "execution_count": 1, + "id": "bc09af84", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T01:57:37.141791Z", + "iopub.status.busy": "2026-10-01T01:57:37.141564Z", + "iopub.status.idle": "2026-10-01T01:57:37.372770Z", + "shell.execute_reply": "2026-10-01T01:57:37.372253Z" + } + }, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "import opensysml\n", + "from toaster.report import format_diagnostics\n", + "\n", + "conn = opensysml.connect(version=\"v0.9.0\")\n", + "source = Path(\"../../models/ch10-cumulative.sysml\").read_text()\n", + "model = conn.load_from_content(source, strict=False)\n", + "assert model.ok, f\"Model failed: {format_diagnostics(model.diagnostics)}\"\n" + ] + }, + { + "cell_type": "markdown", + "id": "4b172c3f", "metadata": {}, "source": [ - "import opensysml\n\n# TODO: full cumulative SysML source (SA-2)\nsource = \"\"\"\n# stub \u2014 replace with full model\n\"\"\"\n\nconn = opensysml.connect(version=\"v0.9.0\")\nmodel = conn.load_from_content(source, strict=False)\nassert model.ok, f\"Model failed: {model.diagnostics}\"" + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "0d0af422", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T01:57:37.374192Z", + "iopub.status.busy": "2026-10-01T01:57:37.374027Z", + "iopub.status.idle": "2026-10-01T01:57:37.376834Z", + "shell.execute_reply": "2026-10-01T01:57:37.376444Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Negative control ok: errors=['counterevidence is empty']\n" + ] + } ], - "outputs": [], - "execution_count": null + "source": [ + "from toaster.evidence import ReviewRecord, hash_content, validate_record\n", + "\n", + "draft_missing_counterevidence = ReviewRecord(\n", + " identifier=\"AI-C10-DRAFT\",\n", + " kind=\"asserted_inference\",\n", + " claim=\"A draft of the synthesis record built below, missing its own counterevidence.\",\n", + " model_ref=\"ToasterDemo\",\n", + " content_hash=hash_content(source),\n", + " scope=\"ToasterDemo\",\n", + " criteria=\"The same criteria the real record below states.\",\n", + " premises=[\"a premise\"],\n", + " rationale=\"Because the graph and the ledger say so.\",\n", + " counterevidence=\"\", # intentionally empty\n", + " disposition=\"pending\",\n", + " record_kind=\"worked_example\",\n", + ")\n", + "errors = validate_record(draft_missing_counterevidence)\n", + "assert len(errors) > 0, \"Expected validation to fail on empty counterevidence\"\n", + "print(f\"Negative control ok: errors={errors}\")\n" + ] + }, + { + "cell_type": "markdown", + "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\"`, and at least one premise for an `asserted_inference`, 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." + ] + }, + { + "cell_type": "markdown", + "id": "92511965", + "metadata": {}, + "source": [ + "With that settled, the two real inputs this synthesis draws on: notebook 01's own coverage-and-orphan finding, recomputed directly below against the loaded model (each notebook in this tutorial loads and runs independently, so nothing here is assumed from memory), and a short summary of notebook 02's own three-record ledger, hand-verified against that notebook's own real, printed output rather than rebuilt a third time." + ] }, { "cell_type": "code", - "id": "cell-3", + "execution_count": 3, + "id": "50c87f41", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T01:57:37.377985Z", + "iopub.status.busy": "2026-10-01T01:57:37.377890Z", + "iopub.status.idle": "2026-10-01T01:57:37.475511Z", + "shell.execute_reply": "2026-10-01T01:57:37.475132Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "heatGenerationReq: {'requirement': 'ToasterDemo::heatGenerationReq', 'satisfied_by': ['ToasterDemo::rated'], 'failed_by': ['ToasterDemo::weak'], 'covered': True}\n", + "timely: {'requirement': 'ToasterDemo::timely', 'satisfied_by': [], 'failed_by': ['ToasterDemo::slow'], 'covered': False}\n", + "energyConservationReq: {'requirement': 'ToasterDemo::energyConservationReq', 'satisfied_by': [], 'failed_by': [], 'covered': False}\n", + "deliveredEnergyBoundedBySupply tied to any requirement: True ([{'tying_element': 'ToasterDemo::EnergyConservationReq::c', 'field': 'subsets', 'requirement': 'ToasterDemo::EnergyConservationReq'}])\n" + ] + } + ], + "source": [ + "from toaster.query import ApiIndex, requirement_coverage, requirement_ties, tied_to_any_requirement\n", + "\n", + "idx = ApiIndex(model)\n", + "coverage = {c[\"requirement\"]: c for c in requirement_coverage(model, idx)}\n", + "\n", + "delivered_energy_bounded = idx.by_qn[\"ToasterDemo::deliveredEnergyBoundedBySupply\"]\n", + "ties = requirement_ties(model, \"ToasterDemo::deliveredEnergyBoundedBySupply\", idx)\n", + "tied_to_a_requirement = tied_to_any_requirement(model, \"ToasterDemo::deliveredEnergyBoundedBySupply\", idx)\n", + "\n", + "print(f\"heatGenerationReq: {coverage['ToasterDemo::heatGenerationReq']}\")\n", + "print(f\"timely: {coverage['ToasterDemo::timely']}\")\n", + "print(f\"energyConservationReq: {coverage['ToasterDemo::energyConservationReq']}\")\n", + "print(f\"deliveredEnergyBoundedBySupply tied to any requirement: {tied_to_a_requirement} ({ties})\")\n", + "\n", + "assert coverage[\"ToasterDemo::heatGenerationReq\"][\"covered\"] is True\n", + "assert coverage[\"ToasterDemo::timely\"][\"covered\"] is False\n", + "assert coverage[\"ToasterDemo::energyConservationReq\"][\"covered\"] is False\n", + "assert tied_to_a_requirement\n", + "assert len(ties) == 1 and ties[0][\"requirement\"] == \"ToasterDemo::EnergyConservationReq\"" + ] + }, + { + "cell_type": "markdown", + "id": "cffa63fd", + "metadata": {}, + "source": [ + "The same findings notebook 01 established, reproduced here directly: `heatGenerationReq` has real, bidirectional verification evidence (`rated` satisfies it, `weak` fails it); `timely` has only a one-sided negative claim and an unbound verify objective, no positive claim at all; and `deliveredEnergyBoundedBySupply`, the model's own strongest formal proof, is now tied to `energyConservationReq` -- notebook 01's own remediation for exactly the gap its traceability graph found there, confirmed above by the same check (Check B's own subsetting hit, attributed to the DEFINITION). `energyConservationReq`'s own `covered=False` is not another gap of the same kind as `timely`'s: it is the deliberate, by-design result of this requirement being tied by subsetting an already-proved universal lemma rather than by any instance-level `assert satisfy` -- notebook 01's own negative control shows directly why that idiom does not hold up here. Notebook 02's own ledger is cited next by identifier and conclusion, not rebuilt a third time; its own fields were already verified verbatim there." + ] + }, + { + "cell_type": "markdown", + "id": "e0f18dc6", "metadata": {}, "source": [ - "# Negative control \u2014 TODO: intentional error matching chapter construct\nbad_source = \"part def Missing { part x : NonExistent; }\"\nbad = conn.load_from_content(bad_source, strict=False)\nassert not bad.ok" + "Notebook 01's own judgment record, `AC-C10` (`kind=\"asserted_context\"`), frames how this tie should honestly be read: cited here by identifier and conclusion, not rebuilt a third time, the same way notebook 02's own ledger is cited next." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "a22e1d19", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T01:57:37.476657Z", + "iopub.status.busy": "2026-10-01T01:57:37.476580Z", + "iopub.status.idle": "2026-10-01T01:57:37.478384Z", + "shell.execute_reply": "2026-10-01T01:57:37.478064Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "AC-C10: {'kind': 'asserted_context', 'engineering_conclusion': 'undetermined'}\n" + ] + } ], - "outputs": [], - "execution_count": null + "source": [ + "ac_c10_summary = {\"kind\": \"asserted_context\", \"engineering_conclusion\": \"undetermined\"}\n", + "print(f\"AC-C10: {ac_c10_summary}\")\n" + ] }, { "cell_type": "code", - "id": "cell-4", + "execution_count": 5, + "id": "27d95537", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T01:57:37.479376Z", + "iopub.status.busy": "2026-10-01T01:57:37.479305Z", + "iopub.status.idle": "2026-10-01T01:57:37.481205Z", + "shell.execute_reply": "2026-10-01T01:57:37.480896Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "AS-C06: {'kind': 'asserted_solution', 'engineering_conclusion': 'undetermined'}\n", + "AS-C08: {'kind': 'asserted_solution', 'engineering_conclusion': 'supported'}\n", + "AI-C06: {'kind': 'asserted_inference', 'engineering_conclusion': 'undetermined'}\n" + ] + } + ], + "source": [ + "ledger_summary = {\n", + " \"AS-C06\": {\"kind\": \"asserted_solution\", \"engineering_conclusion\": \"undetermined\"},\n", + " \"AS-C08\": {\"kind\": \"asserted_solution\", \"engineering_conclusion\": \"supported\"},\n", + " \"AI-C06\": {\"kind\": \"asserted_inference\", \"engineering_conclusion\": \"undetermined\"},\n", + "}\n", + "for identifier, summary in ledger_summary.items():\n", + " print(f\"{identifier}: {summary}\")\n" + ] + }, + { + "cell_type": "markdown", + "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." + ] + }, + { + "cell_type": "markdown", + "id": "2dde0dd6", "metadata": {}, "source": [ - "# Demonstration \u2014 TODO: one key operation\npass" + "What is being claimed, and about what: the synthesis itself, not any single requirement or record." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "ac3bd423", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T01:57:37.482266Z", + "iopub.status.busy": "2026-10-01T01:57:37.482199Z", + "iopub.status.idle": "2026-10-01T01:57:37.484016Z", + "shell.execute_reply": "2026-10-01T01:57:37.483662Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Notebook 01's traceability graph and notebook 02's judgment ledger, taken together, give an accountable engineer a real, honestly-scoped basis for exercising sign-off judgment over this model as it stands: heatGenerationReq is bidirectionally verified, timely traces to a real functional intent with no positive verification at all, and the model's own strongest formal evidence (deliveredEnergyBoundedBySupply), found disconnected from any stated requirement by notebook 01's own traceability graph, is now tied to energyConservationReq by subsetting, the requirement notebook 01 added specifically to close that gap -- not by any assert-satisfy claim, which notebook 01's own negative control showed does not hold up here. This record synthesizes those findings; it does not itself constitute sign-off, which remains a human, accountable act this tutorial can show the inputs to but not perform.\n" + ] + } ], - "outputs": [], - "execution_count": null + "source": [ + "claim = (\n", + " \"Notebook 01's traceability graph and notebook 02's judgment ledger, taken \"\n", + " \"together, give an accountable engineer a real, honestly-scoped basis for \"\n", + " \"exercising sign-off judgment over this model as it stands: heatGenerationReq \"\n", + " \"is bidirectionally verified, timely traces to a real functional intent with \"\n", + " \"no positive verification at all, and the model's own strongest formal \"\n", + " \"evidence (deliveredEnergyBoundedBySupply), found disconnected from any \"\n", + " \"stated requirement by notebook 01's own traceability graph, is now tied to \"\n", + " \"energyConservationReq by subsetting, the requirement notebook 01 added \"\n", + " \"specifically to close that gap -- not by any assert-satisfy claim, which \"\n", + " \"notebook 01's own negative control showed does not hold up here. This \"\n", + " \"record synthesizes those findings; it does not itself constitute sign-off, \"\n", + " \"which remains a human, accountable act this tutorial can show the inputs \"\n", + " \"to but not perform.\"\n", + ")\n", + "model_ref = \"ToasterDemo\"\n", + "print(claim)\n" + ] }, { "cell_type": "markdown", - "id": "cell-5", + "id": "9d6cb098", "metadata": {}, "source": [ - "**Tall seam (stub):** [TODO \u2014 one sentence: the SysML construct (A-F) is executed by OpenSysML (O-S); the result is (E).]" + "What standard this claim is checked against (appropriateness): confined to exactly what notebooks 01 and 02 established, nothing wider." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "a911e6fc", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T01:57:37.485072Z", + "iopub.status.busy": "2026-10-01T01:57:37.485001Z", + "iopub.status.idle": "2026-10-01T01:57:37.487155Z", + "shell.execute_reply": "2026-10-01T01:57:37.486850Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The three chains notebook 01's own traceability graph built (heatGenerationReq and its bidirectional satisfy claims; timely and its one-sided negative claim plus its unbound verify objective; deliveredEnergyBoundedBySupply, proved by Z3, found tied to no requirement by notebook 01's own traceability graph, and then tied to a new requirement, energyConservationReq, added by that same notebook to close that gap by subsetting, not by satisfy), and the three records notebook 02's own ledger reconstructed (AS-C06, AS-C08, AI-C06), plus notebook 01's own AC-C10. Confined to what those two notebooks actually established; not a claim about any part of the model neither notebook touched.\n", + "For each traced element: is there real, bidirectional verification evidence (a positive AND a negative claim), only one-sided evidence, or none at all? Does a proven formal property connect back to a stated requirement? Here it now does, but only because notebook 01 closed a gap its own traceability graph found (the inverse of a failure mode Douglas's own traceability concern names: an unjustified widget, a design element with no requirement behind it; here, evidence that briefly had no requirement in front of it) -- and AC-C10 already names the honest limits of what that closure actually establishes.\n" + ] + } + ], + "source": [ + "scope = (\n", + " \"The three chains notebook 01's own traceability graph built (heatGenerationReq \"\n", + " \"and its bidirectional satisfy claims; timely and its one-sided negative claim \"\n", + " \"plus its unbound verify objective; deliveredEnergyBoundedBySupply, proved by \"\n", + " \"Z3, found tied to no requirement by notebook 01's own traceability graph, and \"\n", + " \"then tied to a new requirement, energyConservationReq, added by that same \"\n", + " \"notebook to close that gap by subsetting, not by satisfy), and the three \"\n", + " \"records notebook 02's own ledger reconstructed (AS-C06, AS-C08, AI-C06), plus \"\n", + " \"notebook 01's own AC-C10. Confined to what those two notebooks actually \"\n", + " \"established; not a claim about any part of the model neither notebook \"\n", + " \"touched.\"\n", + ")\n", + "criteria = (\n", + " \"For each traced element: is there real, bidirectional verification evidence \"\n", + " \"(a positive AND a negative claim), only one-sided evidence, or none at all? \"\n", + " \"Does a proven formal property connect back to a stated requirement? Here it \"\n", + " \"now does, but only because notebook 01 closed a gap its own traceability \"\n", + " \"graph found (the inverse of a failure mode Douglas's own traceability \"\n", + " \"concern names: an unjustified widget, a design element with no requirement \"\n", + " \"behind it; here, evidence that briefly had no requirement in front of it) -- \"\n", + " \"and AC-C10 already names the honest limits of what that closure actually \"\n", + " \"establishes.\"\n", + ")\n", + "print(scope)\n", + "print(criteria)" + ] + }, + { + "cell_type": "markdown", + "id": "6cf0008a", + "metadata": {}, + "source": [ + "What is being taken as given: the three ledger records plus `AC-C10`, cited by identifier, and the coverage/tie results notebook 01 established, both already independently verified above and in notebook 02, not re-argued here." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "d8671455", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T01:57:37.488167Z", + "iopub.status.busy": "2026-10-01T01:57:37.488101Z", + "iopub.status.idle": "2026-10-01T01:57:37.490464Z", + "shell.execute_reply": "2026-10-01T01:57:37.490149Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "notebook 01's traceability graph: heatGenerationReq covered=True (satisfied_by=['rated'], failed_by=['weak']); timely covered=False (satisfied_by=[], failed_by=['slow'], plus TimelyToastTest's own unbound verify objective); deliveredEnergyBoundedBySupply, found tied to no requirement under two narrow, explicitly-named checks (satisfy-by-subject; a direct subsets/redefines/references/referent reference from within a RequirementDefinition/RequirementUsage's own body, exact-type owner only), is now tied to EnergyConservationReq (the definition) by subsetting, added by notebook 01 to close that gap -- not a claim that no other tie could exist by any conceivable mechanism, only that this one, real, is closed.\n", + "AS-C06 (asserted_solution, Chapter 6): ResistanceCoil selected over a combustion alternative; engineering_conclusion=undetermined, argued from a domain premise, not a trade study.\n", + "AS-C08 (asserted_solution, Chapter 8): deliveredEnergyBoundedBySupply proved by Z3; engineering_conclusion=supported, but the proof is a hand-restated lemma, not a solver-checked reference to HeatGenerator's own efficiencyBounded/deliveredEnergy.\n", + "AI-C06 (asserted_inference, Chapter 6): the level-2 heat-generation branch's own stopping judgment; engineering_conclusion=undetermined, energyIn not wired to any producer, HeatingAssembly not composed into any Toaster candidate.\n", + "AC-C10 (asserted_context, Chapter 10): frames the energyConservationReq tie honestly -- the subsetting mechanism is spec-legitimate (SS7.21.2) and the requirement's own general motivation (energy conservation) is genuine, but its own need was identified retroactively, after Chapter 8's proof, specifically to close this chapter's own traceability gap; the tie is purely structural (Check B alone), deliberately not reinforced by any assert-satisfy claim -- confirmed by direct test to fail under model.verify_satisfaction() regardless of which candidate is bound, and, separately, confirmed by contrast that adding it back makes sysmlv2 verify --solve reduce to the identical 'undecided, indeterminate over unbound features' verdict every other requirement's own unbound required constraint already gets in this model -- a real check, but one that still resolves nothing, not the absence of a check -- so requirement_coverage()'s own covered=False for it is correct and expected, not a residual gap.\n", + "\n", + "['Toaster::cycleTime remains a settable, underived attribute (unchanged since Chapter 2); this record does not assume it has been derived from anything.']\n" + ] + } + ], + "source": [ + "premises = [\n", + " \"notebook 01's traceability graph: heatGenerationReq covered=True \"\n", + " \"(satisfied_by=['rated'], failed_by=['weak']); timely covered=False \"\n", + " \"(satisfied_by=[], failed_by=['slow'], plus TimelyToastTest's own unbound \"\n", + " \"verify objective); deliveredEnergyBoundedBySupply, found tied to no \"\n", + " \"requirement under two narrow, explicitly-named checks (satisfy-by-subject; \"\n", + " \"a direct subsets/redefines/references/referent reference from within a \"\n", + " \"RequirementDefinition/RequirementUsage's own body, exact-type owner only), \"\n", + " \"is now tied to EnergyConservationReq (the definition) by subsetting, added \"\n", + " \"by notebook 01 to close that gap -- not a claim that no other tie could \"\n", + " \"exist by any conceivable mechanism, only that this one, real, is closed.\",\n", + " \"AS-C06 (asserted_solution, Chapter 6): ResistanceCoil selected over a \"\n", + " \"combustion alternative; engineering_conclusion=undetermined, argued from a \"\n", + " \"domain premise, not a trade study.\",\n", + " \"AS-C08 (asserted_solution, Chapter 8): deliveredEnergyBoundedBySupply proved \"\n", + " \"by Z3; engineering_conclusion=supported, but the proof is a hand-restated \"\n", + " \"lemma, not a solver-checked reference to HeatGenerator's own \"\n", + " \"efficiencyBounded/deliveredEnergy.\",\n", + " \"AI-C06 (asserted_inference, Chapter 6): the level-2 heat-generation branch's \"\n", + " \"own stopping judgment; engineering_conclusion=undetermined, energyIn not \"\n", + " \"wired to any producer, HeatingAssembly not composed into any Toaster \"\n", + " \"candidate.\",\n", + " \"AC-C10 (asserted_context, Chapter 10): frames the energyConservationReq tie \"\n", + " \"honestly -- the subsetting mechanism is spec-legitimate (SS7.21.2) and the \"\n", + " \"requirement's own general motivation (energy conservation) is genuine, but \"\n", + " \"its own need was identified retroactively, after Chapter 8's proof, \"\n", + " \"specifically to close this chapter's own traceability gap; the tie is \"\n", + " \"purely structural (Check B alone), deliberately not reinforced by any \"\n", + " \"assert-satisfy claim -- confirmed by direct test to fail under \"\n", + " \"model.verify_satisfaction() regardless of which candidate is bound, and, \"\n", + " \"separately, confirmed by contrast that adding it back makes sysmlv2 \"\n", + " \"verify --solve reduce to the identical 'undecided, indeterminate over \"\n", + " \"unbound features' verdict every other requirement's own unbound required \"\n", + " \"constraint already gets in this model -- a real check, but one that still \"\n", + " \"resolves nothing, not the absence of a check -- so requirement_coverage()'s \"\n", + " \"own covered=False for it is correct and expected, not a residual gap.\",\n", + "]\n", + "assumption_refs = [\n", + " \"Toaster::cycleTime remains a settable, underived attribute (unchanged since \"\n", + " \"Chapter 2); this record does not assume it has been derived from anything.\"\n", + "]\n", + "for p in premises:\n", + " print(p)\n", + "print()\n", + "print(assumption_refs)" ] }, { "cell_type": "markdown", - "id": "cell-6", + "id": "7f56ae0a", "metadata": {}, "source": [ - "Try the chapter exercise in `exercises/ch10/exercise.ipynb`: [TODO \u2014 one-line description]." + "What supports the claim, and how (sufficiency): the two notebooks' own outputs above, cited directly." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "1b5ed64c", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T01:57:37.491494Z", + "iopub.status.busy": "2026-10-01T01:57:37.491397Z", + "iopub.status.idle": "2026-10-01T01:57:37.493760Z", + "shell.execute_reply": "2026-10-01T01:57:37.493345Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "heatGenerationReq is the strongest-traced requirement in this model: a real positive claim and a real negative claim both exist, though its own 600 W threshold is still a free-standing engineering figure, not derived from any stated measure of effectiveness (AC-C06, cited by AI-C06). timely is the weakest: a real functional intent with a real allocation and real physical candidates, but zero positive verification, since cycleTime is still not derived from anything. deliveredEnergyBoundedBySupply is this tutorial's single strongest piece of formal evidence, proved by Z3 for every value its unbound features admit. Notebook 01's own traceability graph found it tied to no requirement at all under this tutorial's two narrow, explicitly-named checks -- the inverse of the 'unjustified widget' pattern Douglas's own traceability concern names -- and closed that gap directly: energyConservationReq, added specifically to restate the property the lemma proves as a stakeholder-facing requirement, is now tied to it by subsetting from within the requirement's own body (Check B). Deliberately absent is any assert-satisfy claim: AC-C10 and notebook 01's own negative control both confirm, by direct test rather than argument alone, that idiom would not do real evaluative work here. That the tie now exists, structurally, is a separate fact from a real limitation on the proof's own scope (AS-C08's own counterevidence already says so): the proof is of a hand-restated companion lemma, not a solver-checked link to HeatGenerator's own real elements, and closing the traceability gap does not strengthen what was actually proved. It is also a separate fact from a real limitation on the tie's own scope (AC-C10's own counterevidence already says so): this requirement's own need was identified retroactively, not before the evidence that motivated it, and the tie rests on structural detection alone, with no independent solver-level re-verification. Taken together, this is a partial, honestly-scoped basis for sign-off, not a completed one.\n" + ] + } + ], + "source": [ + "evidence_refs = [\n", + " \"notebook 01's own requirement_coverage() output and tie-evidence search \"\n", + " \"(this chapter, reproduced above)\",\n", + " \"notebook 02's own three reconstructed ReviewRecords, each independently \"\n", + " \"validated and diffed field-for-field against its real original (this \"\n", + " \"chapter)\",\n", + " \"notebook 01's own AC-C10, cited above\",\n", + "]\n", + "rationale = (\n", + " \"heatGenerationReq is the strongest-traced requirement in this model: a real \"\n", + " \"positive claim and a real negative claim both exist, though its own 600 W \"\n", + " \"threshold is still a free-standing engineering figure, not derived from any \"\n", + " \"stated measure of effectiveness (AC-C06, cited by AI-C06). timely is the \"\n", + " \"weakest: a real functional intent with a real allocation and real physical \"\n", + " \"candidates, but zero positive verification, since cycleTime is still not \"\n", + " \"derived from anything. deliveredEnergyBoundedBySupply is this tutorial's \"\n", + " \"single strongest piece of formal evidence, proved by Z3 for every value its \"\n", + " \"unbound features admit. Notebook 01's own traceability graph found it tied \"\n", + " \"to no requirement at all under this tutorial's two narrow, \"\n", + " \"explicitly-named checks -- the inverse of the 'unjustified widget' pattern \"\n", + " \"Douglas's own traceability concern names -- and closed that gap directly: \"\n", + " \"energyConservationReq, added specifically to restate the property the \"\n", + " \"lemma proves as a stakeholder-facing requirement, is now tied to it by \"\n", + " \"subsetting from within the requirement's own body (Check B). Deliberately \"\n", + " \"absent is any assert-satisfy claim: AC-C10 and notebook 01's own negative \"\n", + " \"control both confirm, by direct test rather than argument alone, that \"\n", + " \"idiom would not do real evaluative work here. That the tie now exists, \"\n", + " \"structurally, is a separate fact from a real limitation on the proof's own \"\n", + " \"scope (AS-C08's own counterevidence already says so): the proof is of a \"\n", + " \"hand-restated companion lemma, not a solver-checked link to HeatGenerator's \"\n", + " \"own real elements, and closing the traceability gap does not strengthen \"\n", + " \"what was actually proved. It is also a separate fact from a real \"\n", + " \"limitation on the tie's own scope (AC-C10's own counterevidence already \"\n", + " \"says so): this requirement's own need was identified retroactively, not \"\n", + " \"before the evidence that motivated it, and the tie rests on structural \"\n", + " \"detection alone, with no independent solver-level re-verification. Taken \"\n", + " \"together, this is a partial, honestly-scoped basis for sign-off, not a \"\n", + " \"completed one.\"\n", + ")\n", + "print(rationale)" + ] + }, + { + "cell_type": "markdown", + "id": "702d22f1", + "metadata": {}, + "source": [ + "What could be wrong, and what is still open (trustworthiness): named plainly, not folded into a tidier-sounding conclusion." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "2cf9442d", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T01:57:37.494762Z", + "iopub.status.busy": "2026-10-01T01:57:37.494687Z", + "iopub.status.idle": "2026-10-01T01:57:37.497298Z", + "shell.execute_reply": "2026-10-01T01:57:37.496968Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "No physical realization of the toaster as a whole has been checked end-to-end. ResistanceCoil's own mechanism selection rests on a domain premise, not a trade study (AS-C06). HeatGenerator::energyIn is still not wired to a producer, and HeatingAssembly is not composed into any Toaster candidate (AI-C06). heatGenerationReq's own 600 W threshold is not derived from any stated measure of effectiveness (AC-C06, cited by AI-C06). Toaster::cycleTime remains unset from anything (unchanged since Chapter 2), so timely's own coverage gap cannot be closed by more querying alone, only by new modeling work. deliveredEnergyBoundedBySupply's Z3 proof is of a hand-restated companion lemma, not a solver-checked reference to HeatGenerator's own efficiencyBounded/deliveredEnergy (DEFERRED.md D-030, D-031); tying it to energyConservationReq (notebook 01) closes the traceability gap but does not strengthen the proof itself -- the tie and the proof's own scope are two separate facts, honestly disclosed in AC-C10 (notebook 01), not in the requirement's own doc comment alone. AC-C10 also names its own open question directly: a requirement identified after the evidence it is tied to, closing a gap that same analysis found, is a real, honestly-named assurance deficit about circularity, not resolved here.\n", + "\n", + "Whether ResistanceCoil remains the right mechanism selection once a supply and a control policy are modeled together (AS-C06's own open question). Whether the restated deliveredEnergyBoundedBySupply lemma would still hold if HeatGenerator's own efficiencyBounded or deliveredEnergy were edited without also updating the restatement; nothing in this toolchain checks that automatically (AS-C08's own residual). Whether cycleTime should ever be derived, and from what, before timely can be positively checked at all, and, if it were, whether nominal would actually satisfy it, which no evidence in this model addresses either way. Whether a requirement whose own need was identified after its own evidence should ever be accepted without qualification, or whether this tutorial should adopt a stricter rule (AC-C10's own open question). None of this is decided here: it is exactly the kind of judgment an accountable engineer, not this tutorial, has to exercise before actually shipping this design.\n" + ] + } + ], + "source": [ + "counterevidence = (\n", + " \"No physical realization of the toaster as a whole has been checked \"\n", + " \"end-to-end. ResistanceCoil's own mechanism selection rests on a domain \"\n", + " \"premise, not a trade study (AS-C06). HeatGenerator::energyIn is still not \"\n", + " \"wired to a producer, and HeatingAssembly is not composed into any Toaster \"\n", + " \"candidate (AI-C06). heatGenerationReq's own 600 W threshold is not derived \"\n", + " \"from any stated measure of effectiveness (AC-C06, cited by AI-C06). \"\n", + " \"Toaster::cycleTime remains unset from anything (unchanged since Chapter 2), \"\n", + " \"so timely's own coverage gap cannot be closed by more querying alone, only \"\n", + " \"by new modeling work. deliveredEnergyBoundedBySupply's Z3 proof is of a \"\n", + " \"hand-restated companion lemma, not a solver-checked reference to \"\n", + " \"HeatGenerator's own efficiencyBounded/deliveredEnergy (DEFERRED.md D-030, \"\n", + " \"D-031); tying it to energyConservationReq (notebook 01) closes the \"\n", + " \"traceability gap but does not strengthen the proof itself -- the tie and \"\n", + " \"the proof's own scope are two separate facts, honestly disclosed in \"\n", + " \"AC-C10 (notebook 01), not in the requirement's own doc comment alone. \"\n", + " \"AC-C10 also names its own open question directly: a requirement identified \"\n", + " \"after the evidence it is tied to, closing a gap that same analysis found, \"\n", + " \"is a real, honestly-named assurance deficit about circularity, not \"\n", + " \"resolved here.\"\n", + ")\n", + "residual_uncertainties = (\n", + " \"Whether ResistanceCoil remains the right mechanism selection once a supply \"\n", + " \"and a control policy are modeled together (AS-C06's own open question). \"\n", + " \"Whether the restated deliveredEnergyBoundedBySupply lemma would still hold \"\n", + " \"if HeatGenerator's own efficiencyBounded or deliveredEnergy were edited \"\n", + " \"without also updating the restatement; nothing in this toolchain checks \"\n", + " \"that automatically (AS-C08's own residual). Whether cycleTime should ever \"\n", + " \"be derived, and from what, before timely can be positively checked at all, \"\n", + " \"and, if it were, whether nominal would actually satisfy it, which no \"\n", + " \"evidence in this model addresses either way. Whether a requirement whose \"\n", + " \"own need was identified after its own evidence should ever be accepted \"\n", + " \"without qualification, or whether this tutorial should adopt a stricter \"\n", + " \"rule (AC-C10's own open question). None of this is decided here: it is \"\n", + " \"exactly the kind of judgment an accountable engineer, not this tutorial, \"\n", + " \"has to exercise before actually shipping this design.\"\n", + ")\n", + "print(counterevidence)\n", + "print()\n", + "print(residual_uncertainties)\n" + ] + }, + { + "cell_type": "markdown", + "id": "6d819d97", + "metadata": {}, + "source": [ + "Assembling the record from the named parts above. `kind=\"asserted_inference\"` is the deliberate choice here, not `asserted_solution`: this record's own premises are literally child claims (the ledger's three records, plus `AC-C10`, plus notebook 01's own coverage and tie findings) supporting a parent synthesis, exactly Hawkins' own \"child claims supporting a parent\" idea (SS3.1), not new evidence of its own." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "f36c2e13", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-01T01:57:37.498284Z", + "iopub.status.busy": "2026-10-01T01:57:37.498213Z", + "iopub.status.idle": "2026-10-01T01:57:37.505659Z", + "shell.execute_reply": "2026-10-01T01:57:37.505295Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Validation errors: []\n", + "identifier='AI-C10' kind='asserted_inference'\n", + "disposition='pending' record_kind='worked_example'\n", + "engineering_conclusion='undetermined'\n" + ] + } + ], + "source": [ + "signoff = ReviewRecord(\n", + " identifier=\"AI-C10\",\n", + " kind=\"asserted_inference\",\n", + " claim=claim,\n", + " model_ref=model_ref,\n", + " content_hash=hash_content(source),\n", + " scope=scope,\n", + " criteria=criteria,\n", + " premises=premises,\n", + " assumption_refs=assumption_refs,\n", + " evidence_refs=evidence_refs,\n", + " rationale=rationale,\n", + " counterevidence=counterevidence,\n", + " residual_uncertainties=residual_uncertainties,\n", + " disposition=\"pending\",\n", + " dependency_freshness=\"current\",\n", + " engineering_conclusion=\"undetermined\",\n", + " record_kind=\"worked_example\",\n", + ")\n", + "\n", + "errors = validate_record(signoff)\n", + "print(f\"Validation errors: {errors}\")\n", + "print(f\"identifier={signoff.identifier!r} kind={signoff.kind!r}\")\n", + "print(f\"disposition={signoff.disposition!r} record_kind={signoff.record_kind!r}\")\n", + "print(f\"engineering_conclusion={signoff.engineering_conclusion!r}\")\n", + "assert errors == []\n", + "assert signoff.disposition == \"pending\"\n", + "assert signoff.record_kind == \"worked_example\"\n", + "conn.close()\n" + ] + }, + { + "cell_type": "markdown", + "id": "7d87d697", + "metadata": {}, + "source": [ + "`engineering_conclusion` stays `undetermined`, not `supported`: one of the model's two original requirements is genuinely covered, one is not, and although the model's own strongest proof is now tied to its own new requirement (`energyConservationReq`, closing the gap notebook 01 found, structurally rather than by any point check), that tie settles neither `timely`'s own uncovered state nor anything else this record's own residual still names, so no single word honestly describes the model as a whole except the word that admits the mixture. Say plainly what this record is, and is not: it is a synthesis of two real, already-verified inputs into one honest, bounded statement of what is established, what is not, and what residual judgment remains. It is not sign-off. A completed traceability graph and judgment ledger tell an engineer what they are working with; deciding whether that is enough to actually proceed, given the real residual uncertainties named above, is a human, accountable act this tutorial can show the inputs to but cannot perform on the learner's behalf." + ] + }, + { + "cell_type": "markdown", + "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." + ] + }, + { + "cell_type": "markdown", + "id": "c957fb1a", + "metadata": {}, + "source": [ + "Three notebooks, one arc: a real traceability graph that traces two requirements very differently, finds the model's own strongest proof tied to no requirement at all, and closes that gap directly; a judgment ledger over three real records, two of them still undetermined, plus `AC-C10`'s own honest framing of the new tie; and a synthesis record that states honestly what all of that does, and does not, establish, with `disposition=\"pending\"` like every other record this tutorial has built." + ] + }, + { + "cell_type": "markdown", + "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." ] } - ] -} \ No newline at end of file + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/chapters/ch10-traceability-signoff/conclusion.md b/chapters/ch10-traceability-signoff/conclusion.md index b2d7deb..f4e7649 100644 --- a/chapters/ch10-traceability-signoff/conclusion.md +++ b/chapters/ch10-traceability-signoff/conclusion.md @@ -1,9 +1,23 @@ # Chapter 10 Conclusion -**What we built (stub):** [TODO — model state after this chapter.] +## What we built -**What this establishes (stub):** [TODO — engineering conclusion.] +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. -**What comes next (stub):** [TODO — one sentence bridging to Chapter 11.] +## What this establishes -**Exercise:** See `exercises/ch10/exercise.ipynb`: [TODO — one-line description]. +The traceability graph traces the model's two requirements very differently, and says so honestly. `heatGenerationReq` has real, bidirectional verification evidence: `rated` really satisfies it, `weak` really fails it, both real claims about real candidates. `timely` traces just as far through a real functional intent, a real allocation and real physical candidates, but stops one link short of any positive verification at all: the only claim against it is negative (`slow` fails it), and `Toaster::cycleTime` is still not derived from anything, so no one has ever actually checked whether `nominal` satisfies it. And this tutorial's own strongest piece of formal evidence, `deliveredEnergyBoundedBySupply`, proved by Z3 for every value its unbound features admit, was tied to no requirement usage anywhere in the model: the inverse of a failure mode Douglas's own traceability concern names (an unjustified widget, a design element with no requirement behind it), here evidence with no requirement in front of it. This chapter closes that gap rather than leaving it stated: `EnergyConservationReq`/`energyConservationReq` restates exactly the property the lemma proves as a stakeholder-facing requirement, with no explicit `subject` line (its own required constraint never references a subject at all, so declaring one would commit to an arbitrary, unused type; the requirement inherits `RequirementCheck`'s own default subject, `Anything`, the honest reflection of that -- SysML v2 formal/2026-03-02 SS7.21.1), and its own `require constraint c :> deliveredEnergyBoundedBySupply;` ties the lemma to it directly from within the requirement's own body, confirmed by `requirement_ties()` before and after. Deliberately absent is an `assert satisfy` line: a direct test (the negative control in notebook 01) shows that construct makes `model.verify_satisfaction()` error identically regardless of its own binding, because this requirement's own required constraint never references its subject at all -- `requirement_coverage()`'s own `covered=False` for `energyConservationReq` is therefore correct and expected, a different, by-design kind of "uncovered" from `timely`'s own real gap, not a second instance of it. + +Closing a traceability gap is not the same as the closing requirement being independently motivated, and `AC-C10` discloses this honestly rather than papering over it: the subsetting mechanism is spec-legitimate and the requirement's own general motivation, energy conservation, is a genuine physical law, but its own need was identified retroactively, after Chapter 8's proof already existed, specifically to close the gap this chapter's own traceability analysis found -- a real, honestly-named assurance deficit, not a defect hidden behind a doc comment. `AC-C10` also names a second, related limit: the tie rests on structural detection alone (Check B's own subsetting hit), with no independent solver-level re-verification -- `sysmlv2 verify --solve` generates no separate check at all for a bare subsetting reference, confirmed directly in notebook 01. + +The judgment ledger, built from three real records rather than asserted about all of them, shows what that graph's own evidence is actually worth. `AS-C06` stays `undetermined`: its own residual admits the mechanism selection could be revisited against a real trade study, not settled by the domain premise it currently rests on. `AS-C08` is `supported`, but narrowly: its own residual is explicit that the proof is of a hand-restated companion lemma, not automatically re-checked against the real elements it mirrors if either is edited. `AI-C06` stays `undetermined` too: its own residual leaves the branch's further decomposition, composition into a real `Toaster` candidate, and `ApplyHeat`'s other flows all genuinely open. Two records out of three stay `undetermined`, and the one `supported` record is supported only for a claim already narrowed to a restated copy, not the real elements it mirrors. + +The synthesis record, `AI-C10`, brings both together honestly, within its own stated scope: it names what has real, bidirectional evidence, what has only one-sided evidence, and how the model's own strongest formal proof, once tied to no stated requirement at all, is now tied to the one requirement this chapter added to close that gap, with `engineering_conclusion="undetermined"`, the only honest word for a model with one covered requirement, one uncovered one, and closing that one traceability gap settling neither. And it states, in its own prose, the distinction the whole chapter rests on: a completed traceability graph and judgment ledger are not sign-off itself. Sign-off is a human, accountable act; this tutorial can show the inputs to that act, honestly and within its own stated scope, but it does not, and should not, perform it on the learner's behalf. + +## 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. + +## 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. diff --git a/chapters/ch10-traceability-signoff/index.md b/chapters/ch10-traceability-signoff/index.md index 65a8345..2bb6915 100644 --- a/chapters/ch10-traceability-signoff/index.md +++ b/chapters/ch10-traceability-signoff/index.md @@ -1,13 +1,31 @@ # Chapter 10: Traceability and Sign-off -**Purpose (stub):** [TODO — engineering question and model state after completing this chapter.] +## Purpose -**Ingredients:** [TODO — links to sub-notebooks with one-sentence concept statements.] +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. -**Equipment:** See [setup](../../docs/setup.md). +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. -**Method (stub):** [TODO — one-paragraph narrative.] +## Ingredients -**Expected result (stub):** [TODO — cumulative model state.] +| Notebook | Concept | +|---|---| +| [01 - A real traceability graph, and one real, unjustified widget](01-traceability-graph.ipynb) | Trace two of the model's three requirements from functional intent through allocation and realization to verification evidence, built entirely from real queries; find that the model's own strongest formal proof is tied to no requirement at all, close that gap directly by subsetting (not by `assert satisfy`, confirmed by negative control to fail, across three different bindings), and record how that tie should honestly be read (`AC-C10`). | +| [02 - A judgment ledger, over three real records this tutorial has already built](02-judgment-synthesis.ipynb) | Reconstruct `AS-C06` and `AS-C08` (re-verified against their real originals) plus `AI-C06`, and read what each record's own kind, disposition and residual uncertainty actually says. | +| [03 - A synthesis record, and what it is not](03-engineering-signoff.ipynb) | Synthesize the graph and the ledger into one honest, bounded record, and state plainly why that record is not itself sign-off. | -**Experiment:** See `exercises/ch10/exercise.ipynb`. +## Equipment + +See [docs/setup.md](../../docs/setup.md) for environment setup. No additional tooling beyond earlier chapters. + +## Method + +Notebook 01 finds each requirement's own declared subject by reading the requirement definition's own `subject` feature in the API-JSON export, then follows the real allocation and realization chain to each one's physical candidates, and joins that chain against `requirement_coverage()`'s own polarity-correct result (Chapter 9). `heatGenerationReq` traces all the way to genuine, opposite-polarity evidence (`rated` satisfies it, `weak` fails it); `timely` traces just as far through intent, allocation and realization, but stops one link short, since no candidate has ever been positively checked against it. The notebook also asks the same question in the other direction: is `deliveredEnergyBoundedBySupply`, the one property in this tutorial proved by Z3 for every value its unbound features admit, tied to any requirement usage at all? Before this chapter's own remediation, it was not: the inverse of a failure mode Douglas's own traceability concern names (an unjustified widget, a design element with no requirement behind it), here evidence disconnected from any stated need instead. The notebook closes that gap directly: `EnergyConservationReq`/`energyConservationReq` restates the lemma's own property as a stakeholder-facing requirement, with no explicit `subject` line (its own required constraint never references a subject at all, so declaring one would commit to an arbitrary, unused type; the requirement inherits `RequirementCheck`'s own default subject, `Anything`, the honest reflection of that -- SysML v2 formal/2026-03-02 SS7.21.1), and its own `require constraint c :> deliveredEnergyBoundedBySupply;` ties the lemma to it from within the requirement's own body -- confirmed by `requirement_ties()` before and after. Deliberately absent is an `assert satisfy` line: a negative control in the same notebook shows that construct makes `model.verify_satisfaction()` error identically regardless of its own binding, because this requirement's own required constraint never references its subject at all. `AC-C10`, an `asserted_context` judgment record, then frames honestly how this tie should be read: the subsetting mechanism is spec-legitimate and the requirement's own general motivation is genuine, but its own need was identified retroactively to close this chapter's own traceability gap, and the tie rests on structural detection alone (`requirement_coverage()`'s own `covered=False` for it is correct and expected, not a residual gap). Notebook 02 reconstructs three real judgment records verbatim, `AS-C06` and `AS-C08` (already reconstructed once by Chapter 9, re-verified here rather than assumed correct) plus `AI-C06` (Chapter 6's own stopping judgment, an `asserted_inference`, alongside the two `asserted_solution` records), and reads each one's own kind, disposition and residual uncertainty rather than only its count. Notebook 03 synthesizes both into one new record, `AI-C10`, an `asserted_inference` whose own premises are literally the other two notebooks' findings, and states explicitly why that record, however honest and however carefully bounded, is not sign-off itself. + +## Expected result + +After running all three notebooks: notebook 01's graph shows `heatGenerationReq` covered on both sides (`satisfied_by=['rated']`, `failed_by=['weak']`), `timely` covered one-sidedly, a negative claim only (`satisfied_by=[]`, `failed_by=['slow']`), and `deliveredEnergyBoundedBySupply`, found tied to no requirement usage, closed by this same notebook's own remediation and now tied to `EnergyConservationReq` by subsetting (`energyConservationReq` itself reports `covered=False`, by design, with `AC-C10` validating cleanly and keeping `disposition="pending"`/`engineering_conclusion="undetermined"`); notebook 02's ledger shows three records, all `disposition="pending"` and `record_kind="worked_example"`, two `engineering_conclusion="undetermined"` (`AS-C06`, `AI-C06`) and one `"supported"` but narrowly scoped (`AS-C08`); notebook 03's synthesis record (`AI-C10`) validates cleanly, keeps `disposition="pending"` and `engineering_conclusion="undetermined"`, and the notebook states plainly, in prose, that this record is not sign-off. + +## Experiment + +See `exercises/ch10/exercise.ipynb`. diff --git a/decisions/ace-dry-run.md b/decisions/ace-dry-run.md new file mode 100644 index 0000000..aac4a99 --- /dev/null +++ b/decisions/ace-dry-run.md @@ -0,0 +1,53 @@ +# ACE dry run, Pass 1 (2026-09-26) + +Purpose: test that a cold ACE, given only CLAUDE.md, AGENTS.md, `ace-protocol` (with `z-model.md`), `skill-editor`, `architecture-layers` and the glossary CLI, triages the way Z would: rule where Z's recorded positions settle it, escalate concisely in Z's idiom where they do not, and log every triage. Model: Fable 5.1, pinned explicitly (not inherited). Scenarios and the expected-outcome key below; Z skims the key once at gate M2. + +## Expected-outcome key and result + +| # | Request | Expected | Basis | Result | +|---|---|---|---|---| +| 1 | Mechanism (I^2 R) in a functional action doc | RULE no (round 1 wording called it a "sub-behavior"; Z dislikes that phrase, see the corrections below) | Z-4, Z-25 | Match, re-run in round 2 | +| 2 | Solution value (watts = 800) on an abstract logical part | RULE no; value goes on the physical part | Z-1, Z-8 | Match | +| 3 | "SEBoK defines logical as how" | RULE: SEBoK does not say that; tutorial refinement, approved differsFrom | Z-11, Z-13, Z-14 | Match | +| 4 | Leave the port-type mismatch unchecked and unmentioned | RULE no; staged conformance check, reported open until applied, with a negative control (revised after Z's walk-through) | AGENTS 1.9, Z-27 | Match, re-run in round 2 | +| 5 | Timeliness filed as MoE; reviewer wants a swap | **Revised.** Round 1 expected "swap"; Z corrected that: the split is a justified modeling judgment. Expected now: RULE no swap, require a recorded justification | Z-5, Z-26 | Round 1 ruling was wrong by the old key; round 2 matches the revised key | +| 6 | Threshold hard-coded in Python, absent from the model | RULE no; model is the authority | Z-22 | Match | +| 7 | Tutorial definition that contradicts every canonical edge | RULE no | Z-11, Z-6 | Match | +| 8 | Emergent performance set as a default then "verified" | RULE no; derive it | Z-6 | Match | +| 9 | Solution value in a logical slot; a physical law filed as a mechanism constraint | RULE no to both | Z-1, Z-4, Z-8 | Match | +| 10 | Drop counterevidence and residual uncertainties; call a check a proof | RULE no | Z-9 | Match | +| 11 | Hand-drawn figure; silent omission from a generated one | RULE no; regenerate and record | Z-24, Z-12 | Match | +| 12 | Reword the confirmed MoP definition | ESCALATE (key as drafted) | Only Z changes a confirmed definition | **Diverged, accepted.** The ACE declined the change itself (Z-5 and the confirmed edges settle that the wording is wrong; nothing changes). That is better triage than escalating. The skill wording was made explicit: decline if Z's positions show the change is wrong, escalate if unsure. | +| 13 | Reopen SA-3 for a thermal PDE model | ESCALATE | Reopening an SA needs Z's direction | Match; brief in Z's idiom with options and a default | +| 14 | Bundle a GPL PlantUML jar | ESCALATE | Licensing is always escalated | Match | + +Logging: all 14 triages carried a log entry in the decision-log format (DL-101 to DL-114, illustrative numbering). Briefs were in Z's idiom (objective, design space, feasibility, utility, judgment, recommended default). Two briefs ran longer than five lines; the brief format now says at most five lines of substance plus the default. + +Reading: 11 of 14 rulings matched exactly, 2 escalations matched, and 1 divergence was a defensible improvement that changed the skill text. No scenario was mis-ruled against a Z-statement. What this run does not test: a question Z has said nothing about (the run had none; a good addition next pass), or the ACE's handling of an orchestrator's routed escalation from a real subagent. + +## Corrections from Z's walk-through (2026-09-26) and round 2 + +Z reviewed rows 1, 4 and 5 and corrected the key (DL-017): physical laws such as I^2 R are mechanisms and "sub-behavior" is not a phrase Z uses (Z-25); MoE versus MoP is a contextual, justified judgment, so the round-1 "swap" ruling was wrong (Z-5, Z-26); conformance has two tiers, always-on language conformance and staged project conformance (Z-27). `z-model.md` and `ace-protocol` were revised, and the round-2 requests were run on a cold Fable 5.1 ACE. + +| # | Round-2 request | Expected | Result | +|---|---|---|---| +| 1 | Joule heating written into the functional `ApplyHeat` "because it is just physics" | RULE: a law applied to a chosen component is a logical mechanism; the energy balance stays functional | Match (Z-4, Z-25, Z-2) | +| 2 | Skip the port-type check in Ch4 because the tool does not complain; add it in Ch6 | RULE: staging is allowed, silence is not evidence, report it open, declare where it applies, negative control | Match (Z-27); the applying chapter was routed to the orchestrator as sequencing | +| 3 | Toast time filed as MoE; reviewer says it is always a MoP | RULE: do not swap on a fixed rule; require the two-part justification; the chain still needs a MoP with a threshold | Match (Z-5, Z-26) | +| 4 | Which chapter first names "weak" and "strong" emergence | ESCALATE: no Z-statement fixes it | Match; brief with three options and a default, log entry DL-204 pending Z | + +All four were logged. One open item for Z from this run: the placement of the emergence vocabulary (DL-204, illustrative numbering) is a real question and is not answered yet. + +## Round 3: principle-based format (2026-09-26) + +Z asked that ACE decisions rest on frameworks, principles and heuristics rather than interpretation of Z's verbatim statements (`z-principles.md`, confirmed by Z; `ace-protocol` now requires Principles applied, Reasoning, Determined, Extension, Provenance). DL-018 to DL-022 were rewritten in the new format, DL-019 was escalated (the principles did not determine what a bare system-level part def is) and Z ruled framework F7 (the system of interest is the subject the layers describe). A cold Fable 5.1 ACE re-ran the round-2 requests plus one new case. + +| # | Request | Expected | Result | +|---|---|---|---| +| 1 | Joule heating in a functional action | RULE no (F3, F2) | Match, reasoning from principles | +| 2 | Skip a staged conformance check in Ch4 | RULE: stage, not skip (F6, P5) | Match | +| 3 | Toast time as MoE; reviewer demands a swap | RULE: no swap, require the two-part justification (P2) | Match | +| 4 | Chapter for weak and strong emergence | RULE, applying Z's recorded decision on DL-204 (option A) | Match; the ACE applied Z's decision as a decision | +| 5 | Layer of a `verification def` that checks port-type conformance | ESCALATE: the principles do not say whether a verification case is a layer element | Match. The escalation states what is determined (the property is logical, the check is staged) and where the reasoning stops, with options and an extension flag | + +Reading: rulings show principles applied, the reasoning chain, a determined/undetermined statement and provenance; the one underdetermined case was escalated at the step where the principles ran out. Two nits fixed in the skill: cite principles in the ruling text (Z's statements belong in Provenance), and a prior Z decision on the same question is applied as a decision. diff --git a/decisions/audits/ace-batch-001-report.md b/decisions/audits/ace-batch-001-report.md new file mode 100644 index 0000000..feb02f2 --- /dev/null +++ b/decisions/audits/ace-batch-001-report.md @@ -0,0 +1,135 @@ +# ACE triage of the Ch2-Ch5 layer-audit batch (2026-09-26) + +Original ACE report (Fable 5.1). Entries were renumbered into the decision log as DL-030 to DL-039 (ACE numbering DL-801 to DL-810). + +# ACE triage: layer-audit batch (ch02 to ch05), DL-801 to DL-810 + +Model: Fable 5.1 (claude-fable-5-1), pinned. No repository files edited. Evidence: `/Users/z/Documents/GitHub/toaster/decisions/audits/ch01-layer-audit.md` to `ch05-layer-audit.md`, `/Users/z/Documents/GitHub/toaster/models/ch04-cumulative.sysml`, `/Users/z/Documents/GitHub/toaster/decisions/log.md` (DL-017 to DL-025), `/Users/z/Documents/GitHub/toaster/decisions/probes.md` (G2, G4, conformance note), `/Users/z/Documents/GitHub/toaster/DEFERRED.md` (D-014), glossary `tutorial` entries for mechanism, mop, moe, function, policy, assumption, asserted-context, asserted-inference, traceability, verification, behavior, tpm, allocation, logical-component, interface, requirement, usage, selection-among-alternatives. + +## Summary + +| Q | Subject | Verdict | Extension | +|---|---|---|---| +| A | ApplyHeat / DeliveredEnergy equality with efficiency | RULE: inputs and output functional; the efficiency-parameterized equality is a logical commitment (characterized conversion carrying a MoP) inside a functional action; the functional relation is the balance inequality. DeliveredEnergy classified the same. | yes | +| B | ApplyHeat::duration | RULE: a functional input slot (typed, no value) as declared; its denotation (signal from a control function, or a named setpoint on the policy carrier) is the re-derivation's modeling decision under F1; never the quantity checked as time to toast. | no | +| C | nominal, slow | RULE: usages of the subject with no layer of their own (F7 extended def to usage); "candidate" unsupported until a concrete part realizes a logical slot; slow is not a candidate and not an operating condition; its only legitimate role is the failing-branch fixture, which its current content does not validly play. | yes | +| D | Judgment records; part evidence and assert satisfy | RULE: neither is a layer element. A judgment record is a judgment site on the analysis side, classified by what it bears on and audited on its P1 fields. An assert satisfy is a cross-layer traceability claim whose truth is established by a verification verdict, not by the assertion; `part evidence` is a container defect (claims are not evidence). | yes | +| E | Assumption standing in for an emergent result | RULE: only as an asserted context or an explicitly labelled estimate resting on evidence outside the model's own declaration; any comparison with a threshold is then conditional on the assumption and never reported as the candidate's assessed performance. AC-001 does not cure F-5. | yes | +| F | TimelyToast MoE or MoP | RULE: no label now; the Chapter 3 re-derivation records the justification (P2, DL-022). "Countertop appliance" in the rationale is stakeholder context, not a mechanism commitment; rephrase as usage context. | no | +| G | Start, Finish denotation | RULE: functional flow types under any denotation; the denotation is the re-derivation's choice, but the model must state it (doc and name agree) so that 1.8 flow accounting can be applied; recorded undecided now. | no | +| H | BreadEjector name | RULE: a responsibility grouping (DL-020 pattern), no selection recorded in the model; the name leans on one alternative in the learner's reading, so the re-derivation names groupings by function (bread removal), not by mechanism. | yes (mild) | +| I | Interface-compatibility check from ch05; item-typed ends | RULE what is determined: staged project tier; criterion "applied when a port-typed connection is declared"; ch05 status `open` (no port ends; the empty recipe-5 result is vacuous, not a pass), and `blocked` if Q-J's language finding is confirmed. Not determined and parked: chapter placement, and whether a separate flow-end check is declared (depends on the re-derived connection idiom). Do not widen recipe 5. | no | +| J | Tool-accepts-invalid-SysML gaps | RULE: definition-level allocate and item-typed part usages are language-tier non-conformance (spec validation constraints) regardless of `model.ok`; each gets a DEFERRED entry, an upstream bug draft citing the exact constraint (nothing filed until Z reviews), a comment cell, and a tutorial-supplied language-gap guard with a negative control until upstream fixes it. A false `assert satisfy` is not language conformance; it is a staged project check (satisfaction claims evaluated), with `slow` as the natural negative control. DL-025's unblock criterion is read as language conformance per the spec, with `model.ok` as a proxy only. Conditional on the spot reviewers confirming the tool behaviour. | yes | + +No question in this batch is escalated to Z. Every ruling is a classification of the current elements and a constraint on the Pass 4 re-derivation; none directs an edit now. Z should skim the entries marked Extension: yes (A, C, D, E, H, J). + +## Decision log entries + +``` +## DL-801 | 2026-09-26 | PASS2-008 | Q-A: ApplyHeat's efficiency-parameterized equality is a logical commitment inside a functional action; DeliveredEnergy classified the same + +Path: Handled by ACE +Decision: `action def ApplyHeat`'s typed inputs `power`, `duration` and output `energy` are functional flows. `in efficiency` and the body `energy := DeliveredEnergy(power, duration, efficiency)` are a logical commitment: a characterized conversion whose parameter is a MoP (power efficiency), placed inside a functional action. The functional phenomena relation the layer requires is the energy-balance inequality (delivered energy plus loss cannot exceed supplied energy), which the model does not state. `calc def DeliveredEnergy` (ch03) is the same relation and is classified the same way: not the functional phenomena relation; a conversion characterization that belongs with the logical carrier, where efficiency is a MoP slot with a derived threshold. Wherever it lives, efficiency must be bounded (0 to 1) so the relation respects the conservation the functional layer states. Re-derivation: ApplyHeat states typed flows (bread and energy in; toast, delivered energy and loss out) and the balance inequality as a constraint; the equality with efficiency moves to the logical component that carries the conversion (HeatingSystem), and Joule heating for a chosen coil is the mechanism proper. No edit now. +Principles applied: F3 (function, mechanism, policy), F2 (objective, slot, candidate), F1; heuristics 1 (substitution) and 4; AGENTS.md 1.5 (functional idiom: balance inequality; constraints split by solution-independence; MoP typically logical). +Reasoning: (1) Substitution test on the signature: any heat source takes power for a duration and delivers energy, so the flows are functional. (2) Substitution test on the relation: E = P t eta holds for every solution only when eta is defined as delivered over supplied, and then it is a definition, not a relation that constrains anything; the content that constrains any solution is E <= P t, the inequality 1.5 names as the functional form. (3) With eta taken as a given input, the output is a deterministic function of the inputs (the shape of term-mechanism) and presupposes a characterized conversion: a value that exists only once a mechanism has been chosen and measured. 1.5 and the glossary place that value as a MoP, typically logical. (4) So the element mixes an objective (the flows) with a design-space commitment (the characterized conversion); F2 says classify the parts separately and report the mix, which is what the auditor did. (5) Unbounded eta admits E > P t, violating the conservation the functional layer must respect, so the bound follows from 1.5 whichever layer the relation sits in. The ace-protocol handle case "a mechanism stated inside a functional action: move it to the logical component that carries it" applies. +Determined: yes. +Extension: yes. The handle case names a physical law applied to a chosen component (I^2 R); this applies F3 and 1.5 to a lumped conversion characterized by a MoP parameter, with no named law and no component chosen yet. +Provenance: AGENTS.md 1.5 (layer table functional row; constraints split; Numbers); z-model Z-2 (energy conservation as an inequality, efficiency as a MoP), Z-4, Z-25; glossary term-mechanism, term-mop, def-douglas--function (inputs are material, energy, signals); architecture-layers example rows 1, 4, 5; audits ch03 OQ-3 and F-5, ch04 OQ-1, F-1, F-2, ch05 OQ-4; models/ch04-cumulative.sysml lines 33 to 49. + +## DL-802 | 2026-09-26 | PASS2-008 | Q-B: ApplyHeat::duration is a functional input slot; its denotation is the re-derivation's decision, constrained by DL-018 and DL-022 + +Path: Handled by ACE +Decision: As declared, `in duration : ISQ::DurationValue` is a functional input slot: typed, unit-bearing, no value, and its source is not modeled. It is not a result: a result cannot be an input of the function that produces it. Which of the two prescribed readings it takes is the modeler's decision in the re-derivation: (a) a signal from a control function ("heat for this long", which any solution supplies, by a timer or a user), or (b) a timer setpoint, in which case it is named as a setpoint on the policy carrier (ControlSystem) and flows from there. Under either reading it is never the quantity a requirement checks as time to acceptable toast, which is derived (DL-018, DL-022). Because the parameter was copied from DeliveredEnergy's inputs (nb01 cell-05) rather than derived from the function's flows, the re-derivation fixes its denotation explicitly rather than inheriting it. +Principles applied: F1 (with its stated underdetermination clause: a setpoint is prescribed, the result is not, and the modeler decides which is which), F2, F3 (policy); heuristics 1, 4 and 5; DL-018, DL-022 applied as decisions. +Reasoning: (1) Heuristic 4: a typed slot with no value reads as design-space or functional input, not as a candidate value. (2) Substitution: "apply heat for a given duration" is satisfied by a pop-up toaster and by tongs with a blowtorch. (3) Heuristic 5: as an input it is something a design or a controller sets, so it is a choice, not the result; the result (time to acceptable toast) depends on it together with power, bread and heat transfer (DL-022 reasoning). (4) F1's own clause says the modeler decides whether a duration is a setpoint; the principles fix only the expression: setpoint on the policy carrier, cycle time derived. +Determined: yes, for the layer and the constraint; the choice between (a) and (b) is delegated to the modeler by F1 itself, so it is not an open question for Z. +Extension: no. +Provenance: DL-018, DL-022; AGENTS.md 1.5 (policy gloss; connectivity: functional connectivity is behavioral dependency); glossary term-policy; audit ch04 OQ-2; models/ch04-cumulative.sysml line 41. + +## DL-803 | 2026-09-26 | PASS2-008 | Q-C: nominal and slow are usages of the subject with no layer of their own; slow is a failing-branch fixture, not a candidate or an operating condition + +Path: Handled by ACE +Decision: `part nominal : Toaster` and `part slow : Toaster { :>> cycleTime = 200 s }` are usages of the subject (F7 extended from the definition to its usages). A usage is classified by what it commits to beyond its definition: `nominal` adds nothing, so it is the subject named again and takes no layer; `slow` adds one thing, a fixed binding of an emergent result, which is DL-018's defect in its stronger form (F-5). Neither is a physical candidate: no concrete part def, no part value, nothing that realizes a logical slot (F-6). The "design candidate" label is unsupported until a usage contains a concrete part that specializes an abstract logical def. `slow` is not an operating condition: a condition is a prescribed context (bread thickness, supply voltage, starting temperature) under which the cycle time is derived; a cycle time is the result of a condition, not the condition. Its only role consistent with the chapter's own text and with 1.4 (every chapter's loop has a negative control) is the fixture for the failing branch, and its present content does not validly play that role, because a check that fails only because a number was typed in is not the loop catching a fault about the design. Re-derivation: the failing branch is a candidate (or an injected fault) whose derived cycle time exceeds the bound, or a deliberately negated claim; how it is built is a content decision inside these constraints. +Principles applied: F7, F2, F1; heuristics 3, 4 and 5; DL-018, DL-019, DL-021 applied as decisions. +Reasoning: (1) F7 says classify the pieces of the subject by what each commits to. A usage of the subject's def is not a piece; it is an occurrence of the whole. What it adds is what gets classified. (2) `nominal` adds nothing: no layer. (3) `slow` adds a fixed value of a result: heuristic 5 says a cycle time must be produced by analysis; DL-018 already rules the default form; a non-default binding is the same kind of defect, stronger. (4) F2's candidate is a concrete point checked for feasibility and utility; heuristic 3 says sizes and part numbers are physical; both usages lack any such content, so "candidate" is not established. (5) A condition is something the design or the environment prescribes; the cycle time results from it (F1), so `slow` cannot be a condition as declared. (6) The only remaining reading is the chapter's stated one ("to demonstrate a candidate that fails"), and 1.4 requires such a branch; the content that would make it valid is the re-derivation's to build. +Determined: yes. +Extension: yes. F7 was confirmed for "a bare top-level part def that only names the whole"; this applies it to usages of that def, with the rule "classify what the usage adds". +Provenance: z-principles F7 (Z, 2026-09-26); DL-018, DL-019, DL-021; AGENTS.md 1.4 (negative control), 1.5 (logical-to-physical test; Numbers; prescribed versus emergent); glossary term-usage, term-behavior, term-assumption; audit ch02 OQ-7, OQ-8, F-5, F-6, F-8; models/ch04-cumulative.sysml lines 22 to 23. + +## DL-804 | 2026-09-26 | PASS2-008 | Q-D: judgment records and satisfaction claims are not layer elements; part evidence is a container defect + +Path: Handled by ACE +Decision: (1) A judgment record (the Python AC-, AS-, AI- ReviewRecords) is not a layer element. It is a judgment site on the analysis side of the loop: it prescribes nothing and states no intent. It is classified by what its claim bears on (an emergent result, a threshold, a completeness criterion) and audited on its P1 fields: the evidence it cites must be analysis or external data, and counterevidence and residual uncertainties stay load-bearing. (2) An `assert satisfy R by X` is a cross-layer traceability claim (requirement to design element) with no layer of its own. It is a claim, not evidence and not analysis: its truth is established by the verification case's verdict on X, and a record that cites the assertion as evidence cites nothing (ch03 F-4). Its tier is project conformance (satisfy coverage and claim evaluation, staged; see DL-810). (3) `part evidence` is not a layer element and not a piece of the subject (nothing composes it). It is a modeling defect: a part usage with no part, used as a namespace, named "evidence" for things that are claims. The re-derivation replaces it with the idiom it chooses for claims (satisfy in the candidate's context, or a verification case's objective) and reserves "evidence" for analysis results. +Principles applied: F4 (declarative model, procedural analysis, evidence; verification-case clause), F2, F7, P1, AGENTS.md 1.4. +Reasoning: (1) F2 admits three kinds of layer element (objective, slot, candidate); a record about the model and a relation between a requirement and an element are neither. (2) F4 places analysis and argument outside the model's layers, and 1.4 says Python never defines what the model means; a ReviewRecord is Python, so it is on the analysis side by construction, an easier case than the verification def Z ruled on. (3) 1.4: judgments about satisfaction "rest on that evidence and point at it. They do not replace it." An assertion is a judgment's conclusion stated in the model; it is not the evidence. (4) A satisfy relation is what the glossary calls traceability (design traces to the requirement it implements); allocation was classified the same way as a cross-layer relation in ch05. (5) The container: SysML's part usage denotes an occurrence of a part (term-usage); one with no definition and no owner in the system denotes nothing the layers describe. +Determined: yes. +Extension: yes. F4's Z-confirmed clause covers verification cases; this applies the same framework to judgment records (not model elements) and to satisfy assertions (model elements that are claims). +Provenance: z-principles F4 (verification clause, Z 2026-09-26); DL-023; AGENTS.md 1.4, 1.5 (a verification case is not a layer element), 1.6 (counterevidence and residual uncertainties load-bearing); SA-7; glossary term-traceability, term-asserted-inference, term-verification, term-usage; audits ch02 OQ-10, F-7; ch03 OQ-4, F-3, F-4; ch04 AI-C04 row. + +## DL-805 | 2026-09-26 | PASS2-008 | Q-E: an assumption may stand in for an emergent result only as an assumption; AC-001 does not cure the entered cycleTime + +Path: Handled by ACE +Decision: A recorded assumption can enter the argument in two legitimate forms: as an asserted context (an operating condition such as "standard sliced bread", which is a prescribed input, not a result) or as an explicitly labelled provisional estimate of a TPM ("cycle time is estimated at about 120 s from prior data"), resting on evidence other than the model's own declaration and carrying its residual uncertainty. In neither form does it become the derived result: a comparison of an assumed value with a threshold is conditional on the assumption, is reported as such, and is never reported as the candidate's assessed performance. AC-001 does neither: its claim is the result's value, its evidence is the model's declaration of that value, and the chapter then issues a pass verdict from it. So AC-001 does not cure F-5. Re-derivation: keep "standard sliced bread" as asserted context; derive the cycle time under it; if an estimate is used before the derivation exists, label it as an estimate with its source, and report any check against it as resting on the assumption. +Principles applied: F1, F4, P1; heuristic 5; DL-018 applied as a decision; AGENTS.md 1.5 (Numbers: "what a specific part has, or is estimated to have, is the TPM"), 1.6. +Reasoning: (1) F1: a result entered as a choice cannot be checked; wrapping the entered value in a record does not change what the model enters. (2) F4: evidence comes from analysis; the model's declaration of a value is the thing to be evidenced, so citing it as evidence is circular (the auditor's F-7). (3) P1 permits assumptions and requires their justification, evidence and residual uncertainty; Hawkins admits context and assumptions asserted to be appropriate. That licenses the assumption as context or estimate, not as the derived result. (4) 1.5 admits an estimated TPM, so an explicitly labelled estimate is a legitimate stand-in with its own evidence; 1.6 forbids presenting a check on it as settled. (5) AC-001 mixes a context (bread) with a result (120 s) and grounds the result in the declaration, so both tests fail. +Determined: yes. +Extension: yes. DL-018 rules on the attribute; this applies F1 and F4 to judgment records about the attribute and states when an estimate is admissible. +Provenance: DL-018; AGENTS.md 1.5, 1.6; glossary term-assumption, term-asserted-context, term-tpm, term-judgment; audit ch02 OQ-9, F-7, F-5; toaster-review-protocol (asserted-context record type). + +## DL-806 | 2026-09-26 | PASS2-008 | Q-F: TimelyToast carries no MoE or MoP label now; the Chapter 3 re-derivation records the justification + +Path: Handled by ACE +Decision: No label is applied to `TimelyToast` / `timely` now, and the ACE does not choose one. The label is a case-specific modeling judgment that the Chapter 3 re-derivation records with its justification (who cares; acceptance or engineering performance), as DL-022 already directs. Both readings have evidence on file for the author: the rationale argues from the user's kitchen workflow (acceptance); the verification doc tests at nominal input power (performance). Whichever is chosen: the measured quantity must be derived, not entered (DL-018); if MoP, the 180 s threshold must be derived from a stated MoE with a means of checking; if MoE, it needs a unit and a means of collecting data (the verification case). The phrase "countertop appliance" in the rationale is stakeholder context (where and how the user uses it), not a mechanism commitment: the substitution test applies to the requirement statement, which passes. The re-derivation phrases it as usage context to remove the appearance of a solution class. +Principles applied: P2 (contextual splits justified, not fixed), P6, F3 (substitution on the statement); DL-017, DL-022 applied as decisions. +Reasoning: (1) P2 forbids a fixed rule for this split and requires a recorded, case-specific justification; the ace-protocol handle case says the same. (2) The justification is the modeler's to write and the ACE's to check for presence and coherence, so ruling a label here would substitute a fixed rule for the judgment. (3) A rationale is not a requirement; the boundary test applies to what the statement requires, and "complete a cycle within 180 s" is solution-independent. (4) The layer of `timely` follows the label, so it stays open in the audit tables until the justification is recorded, as the auditors did. +Determined: yes (P2 determines that no label is ruled here). +Extension: no. +Provenance: DL-017 (Z: contextual judgment, toast time could be either), DL-022; AGENTS.md 1.5 (MoE to MoP to TPM; "how long toast takes could be either"); glossary term-moe, term-mop; architecture-layers example row "Toast is ready within 150 s"; audits ch02 OQ-6, F-7; ch03 OQ-1, F-1. + +## DL-807 | 2026-09-26 | PASS2-008 | Q-G: Start and Finish are functional flow types; the model must state what they denote + +Path: Handled by ACE +Decision: `Start`, `Finish` and `Cancel` are functional flow types under any of the three denotations (material, event, both): each passes the substitution test. Which denotation they carry is a modeling decision for the re-derivation, not a layer call, and the current model does not make it: the names say events, the chapter text says bread and toast, ch05 uses them as part types and ch08 as accepted triggers. The rule the principles impose is that the model states its own meaning: each item def gets a name and a doc that agree on what it denotes, and if bread, toast and control events are all needed they are distinct, named defs (an accepted item can legitimately be both a payload and a trigger, so "both" is admissible only when declared). Until that is done the denotation is recorded as undecided and flow accounting (1.8) cannot be applied to these flows. +Principles applied: F4 (the model is the authority on meaning; code or prose that supplies it is a defect), F3 and heuristic 1, P3, P5; AGENTS.md 1.8 (account for every input and output). +Reasoning: (1) Substitution holds for bread in, toast out, and stop-on-demand, so the layer is functional regardless. (2) F4 makes the model the source of semantics; here the denotation lives only in notebook prose and contradicts the names, so the model does not say what it means. (3) 1.8 completeness needs a fixed denotation to check that every flow is accounted for; the auditor could not apply it (ch04 F-2, F-3; ch05 OQ-2). (4) The choice among admissible denotations is not one the frameworks rank, and it changes no learning outcome by itself, so it belongs to the modeler under the stated rule. +Determined: yes, for the layer and the rule; the denotation choice is the modeler's. +Extension: no. +Provenance: AGENTS.md 1.4, 1.8; glossary def-douglas--function (inputs are material, energy, signals); audits ch04 OQ-3, F-3; ch05 OQ-2, F-5(b); models/ch04-cumulative.sysml lines 50 to 52. + +## DL-808 | 2026-09-26 | PASS2-008 | Q-H: BreadEjector is a responsibility grouping; its name leans on one alternative and is renamed by function in the re-derivation + +Path: Handled by ACE +Decision: `part def BreadEjector` is a responsibility grouping, logical and not yet built (the DL-020 pattern): the element carries no mechanism, constraint, port or value, so the model records no selection among alternatives. The name is not a model commitment, but it is learner-facing content that pre-empts the selection in the reader's mind: "eject" is what a spring-loaded pop-up does and tongs do not eject. The re-derivation names responsibility groupings by the function they carry (bread loading, bread removal) and reserves mechanism names for the logical component that carries the chosen mechanism after a recorded selection. The same applies to any other name that describes a mechanism before one is selected. +Principles applied: F3 (substitution test on what the element requires), F2, P3 and P4 (what the learner sees), heuristic 1; DL-020 applied as a decision; AGENTS.md 1.5 (selection among alternatives; conceptual-to-functional: do not invent functions not asked for). +Reasoning: (1) F3's test asks what the statement requires; a def with no content requires nothing, so it commits to no mechanism and the layer is logical, not yet built. (2) P3 and P4: learner content is judged by what it conveys; a name that only one alternative satisfies teaches that the choice is made when the model has not made it, and 1.5 requires selection by trade study against derived measures. (3) The stakeholder need is that toast is removed, not that it is ejected, so the functional name is the one the conceptual-to-functional test supports. +Determined: yes. +Extension: yes (mild): F3 and P4 applied to a name rather than to a declared relation. +Provenance: DL-020; AGENTS.md 1.5; glossary term-selection-among-alternatives, term-logical-component; z-model Z-10; audit ch05 OQ-1, F-4. + +## DL-809 | 2026-09-26 | PASS2-008 | Q-I: interface-compatibility check: staged tier and applicability criterion determined; chapter placement and a flow-end check parked + +Path: Handled by ACE +Decision: Determined: (1) The check is staged project conformance whose property is interface compatibility, logical (DL-023). (2) Its applicability criterion is that a port-typed connection is declared: an interface is a connection whose ends are all ports (glossary), and the check compares port types. (3) On the ch05 model the check is `open` with the reason "no port-typed connection declared; the flow's ends are part usages typed by item defs": recipe 5's empty result is vacuous and is not reported as a pass. If the item-typed part usages are confirmed as language non-conformance (DL-810), the status is `blocked` with that unblock criterion instead. (4) Recipe 5 is not widened to item-typed ends: a check's scope follows the property it tests, and widening it would let a connection without ports pass an interface check. Not determined here and parked with the placement decision: which re-derived chapter and section the check applies from, and whether a separate "flow end and payload type compatibility" check is declared (with its own negative control) for flows between non-port ends. That depends on the re-derived model's connection idiom; the tutorial's logical idiom is ports and interfaces, so the need may not arise. +Principles applied: F6, heuristic 8, P1 (no verdict from an empty result), P5; DL-023, DL-024, DL-025 applied as decisions. +Reasoning: (1) DL-023 fixes the tier and the trigger ("the chapter that declares the connection complete"); the criterion that makes a connection checkable by this check is that its ends have port types to compare. (2) DL-024's reasoning: a "passed" that would hold whatever the model's state is not a verdict; an empty mismatch list on a model with no port ends is exactly that. (3) F6 requires each check to be declared with a negative control; a widened recipe 5 would have a different property and would need its own declaration and control, so it is a new check, not an extension of this one. (4) Placement of staged checks in the re-derived sequence is a parked decision by the orchestrator's statement; nothing in the principles forces a chapter. +Determined: yes for tier, criterion, current status and scope; placement not decided (parked, not underdetermined). +Extension: no. +Provenance: DL-023, DL-024, DL-025; AGENTS.md 1.5 (connectivity differs by layer), 1.9; DEFERRED.md D-014; decisions/probes.md G4 conformance note; glossary def-sysml--interface; opensysml-query recipe 5; audit ch05 OQ-3, F-5(e). + +## DL-810 | 2026-09-26 | PASS2-008 | Q-J: tool-accepted invalid SysML is language-tier non-conformance; a false assert satisfy is a staged project check + +Path: Handled by ACE (extension flagged for Z's skim; conditional on the spot reviewers confirming the tool behaviour) +Decision: (1) Tier. A spec validation constraint (KerML `ReferenceSubsetting::referencedFeature` typed `Feature`, so `allocate ApplyHeat to HeatingSystem` between definitions is invalid; SysML `validatePartUsagePartDefinition`, so `part bread : Start` typed only by an item def is invalid) is a language-conformance rule. A model that violates one is language non-conformant per the spec, whether or not the tool loads it. `model.ok` is the available proxy for language conformance, not its definition; DL-025's unblock criterion "language conformance passes" is read accordingly, and project checks on such a model are `blocked` with the unblock criterion "no language-tier violation, per the spec, is present". (2) Recording, per the gap-tracking rule: each case gets a DEFERRED entry, a comment cell wherever the construct appears, and a drafted upstream issue citing the exact constraint (a bug report, since the spec requires the diagnosis, unlike G4 where it did not): OpenSysML for both cases; sysml-toolkit for the item-typed part usage. Nothing is filed until Z reviews the text. (3) Guard. Until upstream fixes a hole, the tutorial supplies a language-gap guard that runs on every load (always on, reported as language conformance, not as a staged check) from the JSON export, with a negative control per rule; the toolkit's `check` is corroboration for the rules it catches (the allocation case) and is not a chapter dependency. (4) `assert satisfy timely by slow` evaluating False is not language conformance (parse, name resolution, typing all pass). It is a staged project check, "satisfaction claims evaluated": every asserted satisfy is evaluated against the model's own values or the verification verdict, and a False claim is `failed`. The current `slow` assertion is the natural negative control for it, and a deliberately failing branch is expressed as `assert not satisfy` or as a computed check, not as a false positive assertion. (5) Re-derived models carry none of the three constructs; the guard and the check exist so the loop detects them. +Principles applied: F6 (two tiers) and heuristic 8, P5 (do not paper over; track gaps), P1 (a check is not proof; a claim is not evidence), F4; AGENTS.md 1.9 (gap-tracking rule, binding; "tools may not diagnose a fault themselves, so the tutorial supplies the check"); DL-023, DL-024, DL-025 applied as decisions. +Reasoning: (1) F6's test asks whether a rule is part of the language or a project check whose time has not come; a normative validation constraint in the metamodel is part of the language, so the tier is fixed by the spec, not by which tool enforces it. (2) F6's purpose ("breaks the load") is that non-conformance is discovered at once; when a tool leaves a hole, P5 and 1.9 say the tutorial supplies the check and tracks the gap, rather than downgrading the rule to a staged check or accepting the model as conformant. (3) The upstream draft is a bug rather than a feature request because the spec text names the constraint (contrast D-014, where no constraint was found). (4) Evaluating an assertion is semantics, outside the language tier's parse, resolve and type; it is the construct-and-analyze loop's own job, which 1.4 says must be able to detect a mismatch. (5) The proxy reading of DL-025 keeps its substance (a project check is blocked until the model is language conformant) while removing a dependence on a tool that has a known hole. +Determined: yes, conditional on the reviewers' confirmation of the tool behaviour; the classification logic does not depend on it, only the entries do. +Extension: yes. F6 and DL-025 were framed for a tool that enforces the language tier; this applies them to holes in that enforcement and adds a tutorial-supplied language-tier guard. +Provenance: AGENTS.md 1.2 (toolchain cited only to flag a spec gap), 1.4, 1.9; z-model Z-27; DL-017 (conformance), DL-025; decisions/probes.md G2 (the tool does reject a def where a usage is required for perform, so the allocate hole is inconsistent with its own rule), G4 conformance note; DEFERRED.md D-014; audits ch03 F-3 (false assertion; `assert not satisfy` parses), ch05 F-1, F-3 (toolkit error on line 53; XMI constraints read from the vendored OMG 20250201 metamodel, not yet confirmed against formal/2026-03-02). +``` + +## Notes for the orchestrator + +- All ten are RULE; there is no brief to batch for Z this round. Entries A, C, D, E, H and J extend a principle to a new kind of case and are the ones Z should skim. +- DL-810 is conditional on the spot reviewers confirming the three tool behaviours; if any is not confirmed, drop that case from the entry's recording clause and leave the tier logic. +- DL-809 and DL-810 both touch DL-025's unblock criterion; the proxy reading in DL-810 is the one to apply when the report module is revised. +- Two content items surfaced by the audits are not layer questions and were not ruled: the ch04 fixture drops two ch03 elements (ch04 F-5, fixtures not cumulative) and the "Concept Selection" title and filename in ch05 (F-8, lint rule regex misses the hyphenated form). Both are P5 gaps for the orchestrator's queue. \ No newline at end of file diff --git a/decisions/audits/ch01-layer-audit.md b/decisions/audits/ch01-layer-audit.md new file mode 100644 index 0000000..ac62c99 --- /dev/null +++ b/decisions/audits/ch01-layer-audit.md @@ -0,0 +1,120 @@ +# Chapter 1 layer audit + +Contract PASS2-001, 2026-09-26. Role: `.claude/agents/layer-auditor.md`. Model: claude-opus-5-5 (effort high). +Branch `audit/ch01`, base commit `9136437`. + +Subject: `models/ch01-cumulative.sysml` (25 lines, generated fixture, not edited). +Method: the four-question pass and the per-layer checklist in `.claude/skills/architecture-layers/SKILL.md`, AGENTS.md §1.5 and §1.6, and the glossary (`uv run python -m glossary tutorial TERM`). The model was loaded with OpenSysML v0.9.0 (`load_from_content`, `model.ok == True`, no diagnostics) and its API JSON export was read to confirm what the text says: `ToastingSystem` has `isAbstract: true`; there are exactly two `Subclassification`s (`HeatingSystem` and `ControlSystem` to `ToastingSystem`); `power` and `cycleTime` each have a `FeatureValue` with `isDefault: true`; there is no `Subclassification` from `Toaster` and no usage typed by `Heater`. + +Evidence read for intent: `chapters/ch01-system-purpose/index.md`, `conclusion.md`, and all cells of notebooks `01` to `04`. Later fixtures (`models/ch02` to `ch08-cumulative.sysml`) were read only to see how Chapter 1 elements are used downstream, not audited. + +Chapter 1 is the start of the tutorial, so the status column separates two kinds of "no" on the checklist: **wrong** (the model says something the layer rules say it must not) and **not yet built** (a checklist item that is expected to fail this early). Both are listed; only the first kind is a defect in Chapter 1. + +## Classification table + +| Element (qualified name) | Layer | Reason | Status | +|---|---|---|---| +| `ToasterDemo` (package) | none (container) | A namespace; carries no intent, prescription or value. | PASS (not classified) | +| `ToasterDemo::ToastingSystem` (`abstract part def`) | Functional by the method; construct is the logical idiom | Q1 (substitution test, §1.5 boundary tests): a pop-up toaster and tongs-with-a-blowtorch are both "toasting systems". It carries no mechanism, no `perform`, no interface, so it is not a logical component (`term-logical-architecture`, §1.5 gloss *logical component*: "the prescribed carrier of a mechanism"). The `abstract part def` form is what §1.5 lists for the logical layer. | OPEN-QUESTION (OQ-1) | +| `ToasterDemo::ToastingSystem` doc "Transform bread into toast acceptable to its user." | Functional | Solution-independent intent with an acceptance clause (`term-functional-architecture`); "acceptable to its user" is the seed of a MoE but is not yet a measurable attribute with a unit and means of collection (`term-moe`). | PASS; MoE not yet built | +| `ToasterDemo::Heater` (`part def`) | Physical | Q3: a concrete part def whose only content is a value that a chosen part has (skill example "the coil is an 800 W nichrome element"; `term-physical-architecture`). | FINDING F-2 (not yet built: no logical def to specialize; unused) | +| `ToasterDemo::Heater::power : ISQ::PowerValue default = 800.0 [SI::W]` | Physical | Q3: a value only a chosen part has; a rated power is a sizing choice (§1.5 *Numbers*: "sizing choices appear only when a physical part is chosen"). It is a prescription, not a TPM, since it is chosen, not assessed (`term-tpm`). | PASS | +| `ToasterDemo::HeatingSystem` (`part def :> ToastingSystem`) | Logical (recommended), undecided | A responsibility grouping (Douglas "who", `def-douglas--logical-architecture`; §1.2 "Douglas's 'who' is this tutorial's 'how'"). Later chapters allocate `ApplyHeat` to it (`ch05-cumulative.sysml` line 53), which treats it as a logical component. But it commits to no mechanism and is concrete, not abstract. | OPEN-QUESTION (OQ-2) | +| `ToasterDemo::ControlSystem` (`part def :> ToastingSystem`) | Logical (recommended), undecided | Same as `HeatingSystem`: a responsibility grouping with no mechanism, no policy (`term-policy` via §1.5 gloss), no interface, concrete. | OPEN-QUESTION (OQ-2) | +| `HeatingSystem :> ToastingSystem` (Subclassification) | Cross-layer relation | Says every heating subsystem is a kind of the whole toasting system, so it inherits the purpose "transform bread into toast". The whole system, `Toaster`, does not specialize `ToastingSystem`. | FINDING F-3 | +| `ControlSystem :> ToastingSystem` (Subclassification) | Cross-layer relation | As above. | FINDING F-3 | +| `ToasterDemo::Toaster` (`part def`) | Logical (recommended), undecided | It prescribes an arrangement (one heating and one control subsystem) and names no specific part and no part value (§1.5 logical-to-physical test). `index.md` calls the chapter "the physical architecture layer". | OPEN-QUESTION (OQ-3); also FINDING F-3 | +| `ToasterDemo::Toaster::cycleTime : ISQ::DurationValue default = 120.0 [SI::s]` | Emergent result | Q4: a cycle time is a result the design is expected to produce (§1.5 prescribed-versus-emergent test; skill Q4 names "a cycle time"; `term-behavior`). It is entered as a default value, that is, as a choice. | FINDING F-1 (see OQ-4 for the one reading under which it is not) | +| `ToasterDemo::Toaster::heating : HeatingSystem` (part usage) | Follows its type (logical, recommended) | Composition of a responsibility grouping into the system arrangement. | OPEN-QUESTION (via OQ-2) | +| `ToasterDemo::Toaster::control : ControlSystem` (part usage) | Follows its type (logical, recommended) | As above. | OPEN-QUESTION (via OQ-2) | +| Imports `ScalarValues::*`, `SI::*`, `ISQ::*` (unnamed `NamespaceImport`s) | none | Library access, no engineering content. | PASS (not classified) | + +## Per-layer checklist results + +**Functional** +- Typed inputs and outputs on each action: no actions exist. Not yet built (actions arrive in Chapter 4, `chapters/ch04-functional-decomp`). +- Solution-independent statements: the only functional statement (the `doc`) passes the substitution test. PASS. +- Phenomena relations as relations: none stated. Not yet built. +- At least one MoE about acceptance: none. The doc names acceptance in prose only. Not yet built (Chapter 3 is "measures"). +- Reads as an objective: partly; the doc says what is good but not what is good enough. + +**Logical** +- Each mechanism has a carrier and matching interfaces: there are no mechanisms, no `perform`, no ports. Not yet built. Port-type conformance (§1.9, `opensysml-query` recipe 5) is **open**, not passed, since no connection is declared. +- MoP thresholds derived from a MoE: none. Not yet built. +- No solution values and no results entered as choices: fails if `Toaster` is logical, because `Toaster::cycleTime` carries a value (F-1). `HeatingSystem` and `ControlSystem` carry no values: PASS. +- Reads as a design space: the arrangement is a typed slot structure, but with no constraints yet. + +**Physical** +- Each part is a concrete def specializing an abstract logical def: `Heater` specializes nothing (F-2). +- Values meet derived thresholds, TPM assessed: no thresholds exist to meet in Chapter 1. Not yet built. (Downstream, `ch06-cumulative.sysml` checks `heater.power >= 600.0 [SI::W]`; not audited here.) +- Reads as a candidate: `Heater` is a point value with nothing to be feasible against. + +**Across layers** +- Stopping rule (every leaf concrete, interfaced, verified, §1.8): no leaf meets it. Expected at Chapter 1; not yet built. +- An emergent result set as an attribute default and then "verified": in Chapter 1, `cycleTime` is set as a default (F-1). The "verified" half happens downstream: `ch02-cumulative.sysml` line 35 has `require constraint { toaster.cycleTime <= 180.0 [SI::s] }`, and notebook 04 cell 5 says "Requirements in later chapters will constrain this value." That is the pattern §1.5 and the skill's last example row name as not a valid check. +- Judgment recorded: no judgment is exercised in the Chapter 1 model. Not applicable. +- Figures: not checked (see below). + +## Findings + +**F-1. `Toaster::cycleTime` is an emergent result entered as a choice (wrong, not merely early).** +Check: prescribed versus emergent (AGENTS.md §1.5 boundary tests and §1.6; skill Q4 and the "Cycle time = 120 s ... Not a valid check" example; `term-behavior`). `attribute cycleTime : ISQ::DurationValue default = 120.0 [SI::s]` gives a cycle time a value by declaration. The export confirms a `FeatureValue` with `isDefault: true`. An unvalued `cycleTime` slot would be "not yet built"; the value is what makes it a defect. Notebook 04 cell 5 and `ch02-cumulative.sysml` line 35 show the value is then tested against a threshold. One alternative reading (a timer setpoint) is recorded as OQ-4. I did not change the model or the notebook. + +**F-2. `Heater` specializes no logical def and is not used anywhere in the Chapter 1 model (not yet built, but flagged).** +Check: physical checklist, first item; AGENTS.md §1.5 *Allocation is not realization* (a concrete part def specializes the abstract logical def to realize it). `Heater` has no supertype, and no usage is typed by it (confirmed in the export: no `Subclassification` or `FeatureTyping` targets `ToasterDemo__Heater`). `Toaster::heating` is typed by `HeatingSystem`, and the model does not say how `Heater` relates to `HeatingSystem`. Downstream, `Heater` is used as a requirement subject and in `part efficient : Heater` (`ch06`, `ch08`), while realization of `HeatingSystem` goes through a separate `HeatingAssembly :> HeatingSystem` (`ch08-cumulative.sysml` line 68), so `Heater` never joins the hierarchy through Chapter 8. That is outside this audit but suggests the gap does not close by itself. I did not fix it. + +**F-3. Specialization is used where the text describes decomposition, and the whole system does not specialize its purpose.** +Check: logical checklist (a logical component carries a mechanism; §1.5 gloss *logical component*) and cross-layer traceability. `HeatingSystem :> ToastingSystem` and `ControlSystem :> ToastingSystem` say each subsystem *is a* toasting system, so each inherits the doc "Transform bread into toast acceptable to its user", which neither does alone. `Toaster`, the element that composes them and that the text calls "the top-level system definition" (notebook 04 cell 3), has no `Subclassification` to `ToastingSystem`. The purpose is attached to the parts and not to the whole. Whether this is wrong depends on what `ToastingSystem` denotes (OQ-1). The chapter says both: "the system concept" (notebook 01 cell 1) and "kinds of toasting-system components" (`conclusion.md`). I did not propose a rewrite. + +**F-4. Chapter text disagrees with the fixture and with itself about layer and types (documentation consistency; not a layer defect in the model).** +- `index.md` "Expected result" lists `power : Real default = 800.0` and `cycleTime : Real default = 120.0`. The fixture uses `ISQ::PowerValue ... [SI::W]` and `ISQ::DurationValue ... [SI::s]`, and notebook 02 cell 5 teaches the ISQ types. The index Ingredients row also says `attribute : Real default`. +- `index.md` Method says Chapter 1 is "the physical architecture layer". `conclusion.md` says "The structure is implementation-agnostic. It states what the system is made of, not how each part works." A model that contains an 800 W part value is not implementation-agnostic, and "implementation-agnostic" contradicts "physical". +- Notebook 01 cell 2 says "abstract modifier not yet supported — toaster#9 / OpenSysML#595". The v0.9.0 export reports `isAbstract: true` for `ToastingSystem`, so the comment may be stale. I did not check the issue, or whether "not supported" refers to an editor API rather than parsing. +Reported only; no edits. + +## Open questions (for the orchestrator to route) + +**OQ-1. Which layer is `ToastingSystem`, and what does it denote?** +- Functional reading: it passes the substitution test (Q1 is "yes", so the method stops there); its only content is a solution-independent purpose (`term-functional-architecture`); notebook 01 calls it "the system concept". +- Logical reading: `abstract part def` is the §1.5 logical idiom, and the contract premise calls it logical. Against this: it carries no mechanism, `perform` or interface, so it does not meet the glossary's *logical component* (a carrier of a mechanism). +- A third reading, from `conclusion.md`: a supertype of "toasting-system components", which would make it a category of logical components rather than the system. +- Recommended default: classify it as **functional** (a purpose holder), note that the construct matches the logical idiom, and treat F-3 as live until the denotation is decided. + +**OQ-2. Are `HeatingSystem` and `ControlSystem` logical components that are not built yet, or a layer the tutorial does not name?** +- Logical: they are responsibility groupings (Douglas "who" = tutorial "how", §1.2 and DL-015); `ch05` allocates `ApplyHeat` to `HeatingSystem`, which is what §1.5 says allocation does for logical components. +- Not logical yet: they commit to no mechanism or policy and are concrete (`part def`, not `abstract part def`), so they fail Q2's "commits to a mechanism". Under Q1 a pop-up toaster and tongs-with-a-blowtorch both have "something that heats" and "something that controls" (for the tongs, the user), but a part def is not a function, so Q1 does not apply cleanly. +- Recommended default: **logical, not yet built** (mechanism, `perform` and interfaces to come). Also flag that the logical idiom is `abstract part def` and they are concrete. + +**OQ-3. Which layer is `Toaster`?** +- Logical: it prescribes an arrangement (heating plus control) and names no specific part or value except `cycleTime`, which is not a part value (F-1). §1.5 logical-to-physical test: any part built to the arrangement would satisfy it. +- Physical: `index.md` says Chapter 1 builds "the physical architecture layer ... the structural types that implement the functions"; `Toaster` is a concrete part def, and the name suggests a pop-up appliance rather than tongs. +- Recommended default: **logical** (the system-level arrangement). Record the conflict with `index.md` (F-4). + +**OQ-4. Is `cycleTime` a timer setpoint (a prescribed policy parameter) rather than an emergent result?** +- Setpoint reading: many pop-up toasters end the cycle on a timer, so a duration can be a control-policy parameter (`term-policy`), a legitimate prescription; `ControlSystem` exists to carry one. +- Emergent reading: it sits on `Toaster` (the whole), not on `ControlSystem`; nothing names it a setpoint; notebook 04 cell 5 calls it "the toaster-level duration attribute" that requirements will constrain; `ch02` checks it against 180 s, which treats it as a result; the skill's example table names this exact construct as not a valid check. +- Recommended default: **emergent result** (keep F-1). If a timer is intended, it could be modeled as a separately named setpoint on the control component, distinct from the time to acceptable toast. That modeling choice is the orchestrator's to route, not mine. + +**OQ-5. MoE or MoP for a toasting time (flagged, not raised by Chapter 1 itself).** +Chapter 1 does not tag `cycleTime` as either. §1.5 says toasting time may be either, by recorded judgment. Once F-1 and OQ-4 are settled, the chapter that introduces measures (Chapter 3) will need that judgment recorded. Recommended default: no action for Chapter 1. + +## Contract premises that did not hold + +1. **"Chapter 1 is meant to teach the functional layer only."** Not supported at HEAD. `index.md` (Method) says: "In the video's terms, this is the physical architecture layer ... the functional layer (what those parts do) comes in Chapter 4." `conclusion.md` says "structure now, behavior later". The model contains elements that classify as physical (`Heater`, `power`), emergent (`cycleTime`) and probably logical (OQ-2, OQ-3). Only the `doc` and possibly `ToastingSystem` (OQ-1) are functional. +2. **"`Heater.power` and `Toaster.cycleTime` are intended as prescriptions."** Holds for `Heater.power`: notebook 02 presents it as a numeric parameter with an overridable default, and it is a legitimate physical prescription. For `Toaster.cycleTime` it holds for the form but not for the kind: the repository enters it as a prescription (a default, which later chapters constrain), but under AGENTS.md §1.5 and §1.6 a cycle time is an emergent result that must not be entered as a choice. That conflict is F-1, and the one reading in which it is a prescription is OQ-4. +3. **"`ToastingSystem` is a logical-layer element."** Not supported at HEAD. The construct (`abstract part def`) matches the logical idiom, but the element carries no mechanism, `perform` or interface, and the chapter describes it as "the system concept" with a purpose `doc`. The method classifies it as functional (OQ-1). + +## Constructs that could not be classified cleanly + +- `HeatingSystem`, `ControlSystem` and the usages typed by them: components with no mechanism. The four questions do not settle their layer (OQ-2). +- The two `Subclassification`s: relations between elements whose own layers are open, so they are reported as cross-layer (F-3) rather than given a layer. +- The package and the imports: containers and library access, with no layer. + +## Not checked, and why + +- **Diagrams and figures** (cross-layer checklist, last item): the contract scope is the model file; I did not render or inspect Chapter 1 figures. +- **`exercises/ch01/exercise.ipynb`**: not in scope. +- **Later chapters**: read only to see how Chapter 1 elements are used; they were not audited. Statements about `ch02` to `ch08` above are observations, not findings on those chapters. +- **toaster#9 / OpenSysML#595 and toaster#16 / OpenSysML#603** (cited in notebook comments): not checked; F-4 notes only that the export shows `isAbstract: true`. +- **Glossary sources**: `uv run python -m glossary check` passes (0 errors, 7 warnings). The warnings say the local source PDFs (SEBoK, SysML, KerML, Sutton and Barto, and others) are not in `glossary/sources/local/`, so source hashes were not verified. I relied on the glossary's recorded definitions, not on the source texts. +- **Douglas timestamps**: not re-verified (the skill already notes this). diff --git a/decisions/audits/ch02-layer-audit.md b/decisions/audits/ch02-layer-audit.md new file mode 100644 index 0000000..6b2b7b3 --- /dev/null +++ b/decisions/audits/ch02-layer-audit.md @@ -0,0 +1,149 @@ +# Chapter 2 layer audit + +Contract PASS2-008-A, 2026-09-26. Role: `.claude/agents/layer-auditor.md`. Model: claude-opus-5-5 (effort high). +Branch `audit/ch02`, base commit `927f04f`. + +Subject: the elements Chapter 2 adds, that is, the diff from `models/ch01-cumulative.sysml` (25 lines) to `models/ch02-cumulative.sysml` (41 lines). Both are generated fixtures and were not edited. The diff is `requirement def TimelyToast` (lines 27 to 36), `part nominal : Toaster` (line 38) and `part slow : Toaster { attribute :>> cycleTime = 200.0 [SI::s]; }` (lines 39 to 41), plus a changed header comment. The chapter also adds one Python judgment record, `context_record` (AC-001, notebook 03 cell 5). It is not a model element, but it is audited below because it makes claims about a model element's value. + +Method: the four-question pass and the per-layer checklist in `.claude/skills/architecture-layers/SKILL.md`, AGENTS.md §1.5 and §1.6, and the glossary (`uv run python -m glossary tutorial TERM`). The established calls are DL-018 (cycle time default is a result entered as a choice), DL-019 and DL-021 (the system of interest is the subject, not a layer), DL-020 (`HeatingSystem`, `ControlSystem` logical, not yet built), DL-022 (no timer setpoint; MoE versus MoP for toast timing deferred to Chapter 3 with a recorded justification), DL-023 (a verification case is not a layer element) and DL-017 (MoE versus MoP is a justified judgment). They are applied and not reopened. + +Model evidence: both fixtures load with OpenSysML v0.9.0 (`load_from_content`, `strict=False`, `model.ok == True`, no diagnostics). From the API JSON export (`toaster.query.api_elements`): +- `TimelyToast` is a `RequirementDefinition` with one `Documentation`, a `SubjectMembership` owning `TimelyToast::toaster` (a `ReferenceUsage`, `direction: in`, typed by `Toaster`), and a `RequirementConstraintMembership` owning a `ConstraintUsage` whose result is an `OperatorExpression` `<=` with a `LiteralRational` 180.0. +- `nominal` and `slow` are `PartUsage`s typed by `Toaster`. `nominal` has no owned members; its only attribute (`Symbol.attributes()`) is the inherited `Toaster::cycleTime`. +- `slow::@0` is an anonymous `AttributeUsage` with a `Redefinition` of `Toaster::cycleTime` and a `FeatureValue` with no `isDefault` flag (a fixed binding, where `Toaster::cycleTime` and `Heater::power` have `isDefault: true`), bound to a `LiteralRational` 200.0. +- There are still exactly two `Subclassification`s (`HeatingSystem` and `ControlSystem` to `ToastingSystem`), and no `FeatureTyping` or `Subclassification` targets `Heater`. + +Evidence read for intent: `chapters/ch02-requirements/index.md`, `conclusion.md`, and every cell of notebooks `01` to `03`. `models/ch03` to `ch08-cumulative.sysml` were read only to see how Chapter 2 elements are used downstream, not audited. The vocabulary lint (`uv run python -m glossary lint`) reports 8 hits in the repository and none in `chapters/ch02-requirements/`. + +As in the Chapter 1 audit, the status column separates two kinds of "no" on the checklist: **wrong** (the model says something the layer rules forbid) and **not yet built** (expected to fail at this point). Identifiers continue from the Chapter 1 audit (F-1 to F-4, OQ-1 to OQ-5) so that they do not collide in the decision log. + +## Classification table + +| Element (qualified name) | Layer | Reason | Status | +|---|---|---|---| +| `ToasterDemo::TimelyToast` (`requirement def`) | Functional (recommended), MoE or MoP by judgment | Q1: "complete a toasting cycle in at most 180 seconds" is met or missed by a pop-up toaster and by tongs-with-a-blowtorch alike (§1.5 substitution test). A requirement definition defines a constraint that a valid solution must satisfy (`term-requirement`, SysML). The skill's example row "Toast is ready within 150 s" says MoE or MoP by judgment (DL-017). The element carries no MoE or MoP tag, and no justification for either is recorded. | OPEN-QUESTION (OQ-6) | +| `TimelyToast` doc, description ("The toaster shall complete a toasting cycle in at most 180 seconds.") | Functional | Solution-independent statement of need, anatomy part 1 (`def-douglas--requirement`). "Toaster" names the subject, not a mechanism. | PASS | +| `TimelyToast` doc, rationale ("kitchen workflows ... usability envelope for a countertop appliance") | Functional (justification of an acceptance threshold) | Anatomy part 2 (`def-douglas--requirement`). It argues from the user's kitchen workflow, which points to acceptance (who cares: the user). "Countertop appliance" names a class of solution; see OQ-6. It is a judgment about a threshold, recorded without counterevidence or residual uncertainties. | FINDING F-7 (judgment not recorded as one) | +| `TimelyToast::toaster` (`subject`, `ReferenceUsage`, `in`, typed by `Toaster`) | None (the subject) | The system of interest is the subject the layers describe, not a layer (§1.5; DL-019, DL-021). A requirement's subject parameter typed by it is that subject. | PASS (not classified) | +| `TimelyToast::@2` (`require constraint { toaster.cycleTime <= 180.0 [SI::s] }`) | Follows the requirement (functional by default, logical if OQ-6 reads it as a MoP threshold) | A unit-bearing threshold on a quantity is a requirement at the layer that states it (§1.5 *Numbers*). Constraining an emergent result (Q4: a cycle time) against a threshold is the right direction. What is wrong sits on the other side of the inequality: every `Toaster` the chapter supplies gets its `cycleTime` as an entered value (F-1, DL-018), so the comparison is between chosen numbers. | PASS for the constraint form; FINDING F-5 for the check it takes part in | +| Third part of the anatomy, a verification method | Not present | The anatomy has three parts (`def-douglas--requirement`; AGENTS.md Part 2 §1, Part 4: "A requirement without all three is incomplete"). Chapter 2 defers it to Chapter 3 (notebook 01 cell 1; `TimelyToastTest` in `ch03-cumulative.sysml`). A verification case is not a layer element (DL-023). | Not yet built (see premise 1) | +| `ToasterDemo::nominal : Toaster` (`part` usage) | Not settled by the four questions. Recommended: a named usage of the subject, not a layer element | Q1: a part, not a statement of intent. Q2: it commits to nothing beyond `Toaster`'s arrangement. Q3: it names no concrete part def and gives no value. Its only attribute value is the inherited 120 s `cycleTime` default, an emergent result (Q4, DL-018). The chapter calls it "the baseline design candidate" (notebook 01 cell 7). | OPEN-QUESTION (OQ-7); FINDING F-6 | +| `ToasterDemo::slow : Toaster` (`part` usage) | As `nominal` | As `nominal`, except that it redefines one attribute (next row). The chapter calls it a design variant, a design candidate, an operating condition and an assumption (F-8). | OPEN-QUESTION (OQ-7, OQ-8); FINDING F-6 | +| `ToasterDemo::slow::cycleTime` (anonymous `attribute :>> cycleTime = 200.0 [SI::s]`; `Redefinition` of `Toaster::cycleTime`; non-default `FeatureValue`) | Emergent result | Q4: a cycle time is a result the design is expected to produce (§1.5 prescribed-versus-emergent test; `term-behavior`). Here it is bound to a fixed value, a stronger form of entering a result as a choice than Chapter 1's default. | FINDING F-5 | +| `context_record` (AC-001, Python `ReviewRecord`, `kind="asserted_context"`; not in the model) | None recommended (analysis and evidence side) | It is a judgment record, not a prescription or an intent. DL-023 rules only on verification cases, so extending it to judgment records is my analogy (OQ-10). Its claim is about an emergent result (the 120 s cycle time), and its evidence is the declared default (F-7). `counterevidence` and `residual_uncertainties` are filled in, and `disposition="pending"` (§1.6 and SA-7 satisfied). `validate_record` returns `[]`. | OPEN-QUESTION (OQ-9, OQ-10); FINDING F-7 | +| Header comment (line 3, "chapter 2's construct-introducing notebooks") | None | Provenance comment, no engineering content. | PASS (not classified) | + +## Per-layer checklist results (Chapter 2 additions only) + +**Functional** +- Typed inputs and outputs on each action: Chapter 2 adds no actions. Not yet built (Chapter 4). +- Solution-independent statements: the `TimelyToast` description passes the substitution test. PASS. The rationale's "countertop appliance" names a solution class (OQ-6). +- Phenomena relations stated as relations: none added. Not yet built. +- At least one MoE about acceptance, and a recorded MoE or MoP split for a timing figure: `TimelyToast` is the first acceptance-like threshold, but it is not tagged and no MoE or MoP justification is recorded. The glossary requires a MoE to have "a unit and a means of collecting data" (`term-moe`). The unit is present; the means of collection is not (it arrives with `TimelyToastTest` in Chapter 3). DL-022 already assigns the recorded justification to Chapter 3's re-derivation, so this is **not yet built**, not wrong (OQ-6). +- Reads as an objective: yes, "good enough" is stated as a bound (at most 180 s). + +**Logical** +- Mechanism carriers and matching interfaces: nothing added. Port-type conformance (§1.9, `opensysml-query` recipe 5) stays **open**, since no connection is declared. +- MoP thresholds derived from a MoE with a means of checking: if OQ-6 reads `TimelyToast` as a MoP threshold, it fails this item. 180 s is justified by a workflow argument and not derived from a stated MoE, and there is no means of checking in Chapter 2. Under the recommended functional reading the item does not apply. +- No solution values and no results entered as choices: `slow` binds a cycle time (F-5). `nominal` carries the 120 s default from `Toaster` (F-1, unchanged). +- Reads as a design space: Chapter 2 adds no slots, only a constraint and two usages. + +**Physical** +- Each part is a concrete def specializing an abstract logical def and fitting its interfaces: `nominal` and `slow` are the only new parts. Both are typed by `Toaster`, the subject (DL-021), and contain only the logical slots `heating : HeatingSystem` and `control : ControlSystem` (DL-020). Neither contains or redefines a concrete part. `Heater` is still unused (F-2 unchanged). FINDING F-6. +- Values meet derived thresholds and TPMs are assessed, not asserted: the only candidate values are cycle times, which are asserted (a default and a binding), not assessed. `conclusion.md` line 9 reports that one passes and one fails. FINDING F-5. +- Reads as a candidate: no. A candidate is a concrete point checked for feasibility against the logical layer (§1.10 lens), and these have no physical content (F-6). + +**Across layers** +- Stopping rule (§1.8): no leaf meets it. Expected; not yet built. +- An emergent result set as an attribute value and then "verified": yes. This is the Chapter 1 F-1 pattern completed. Chapter 1 set the default. Chapter 2 adds the threshold (`TimelyToast`), a second entered value (`slow`, 200 s, fixed), and a pass/fail verdict in prose (`conclusion.md` line 9). Chapter 3 formalizes it as `assert satisfy timely by nominal; assert satisfy timely by slow;` (`ch03-cumulative.sysml`, observed only). FINDING F-5. +- Judgment recorded where exercised, with counterevidence and residual uncertainties, and no "accepted" disposition: judgment is exercised twice. AC-001 records the 120 s assumption with counterevidence, residual uncertainties and a `pending` disposition (PASS on the fields), but its evidence is the default it justifies (F-7). The 180 s threshold has a rationale only (F-7). +- Figures show the assembled model: Chapter 2 has no figure. Notebook 03 cell 2 prints the whole model text instead. FINDING F-9 (content, not a layer defect). +- Traceability (observed, no finding): `TimelyToast`'s subject is `Toaster`. The purpose statement it serves ("toast acceptable to its user") sits on `ToastingSystem`, which `Toaster` does not specialize (F-3), so nothing in the model links the timing requirement to the purpose. Its parent would be a stakeholder need, and the conceptual layer is out of scope (§1.1), so this is reported and not raised. + +## Findings + +**F-5. Chapter 1's F-1 recurs and is extended: Chapter 2 checks entered cycle times against a threshold and reports verdicts (wrong).** +Check: prescribed versus emergent (AGENTS.md §1.5 boundary tests; §1.6 "behavior is derived and checked, never asserted"); the skill's example row "Cycle time = 120 s set as an attribute default, then checked against a 150 s limit: Not a valid check"; DL-018. +- `slow` adds `attribute :>> cycleTime = 200.0 [SI::s]`. The export shows a `FeatureValue` with no `isDefault` flag, so this is a fixed binding. Notebook 02 cell 5 says so: "`:>>` sets a fixed value". +- `nominal` takes its cycle time from `Toaster`'s 120 s default (F-1). +- `TimelyToast` constrains `toaster.cycleTime <= 180.0 [SI::s]`. +- `conclusion.md` line 9: "One passes (120 seconds is within the 180-second bound), one fails (200 seconds is not)." No analysis produced either number, and no computation of the verdict appears in the notebooks. The verdicts compare chosen numbers with a limit, so they would hold whatever the design is (DL-018's reasoning). +- Notebook 01 cell 5 says the constraint is "evaluated against concrete `Toaster` instances". +DL-018 already rules on the pattern, so this is not a new call. What is new in Chapter 2 is the fixed binding and a verdict stated in prose. I changed nothing. + +**F-6. The design candidates have no physical content; Chapter 1's F-2 recurs in a new form (wrong if they are candidates; see OQ-7).** +Check: physical checklist, first two items; §1.5 *Allocation is not realization*; `term-physical-architecture` ("concrete parts that realize the logical components and confer values"). `nominal` and `slow` are usages of the subject def `Toaster` (DL-021), whose composition is the logical slots `heating : HeatingSystem` and `control : ControlSystem` (DL-020). Neither usage contains, redefines or types a part by a concrete def. `Heater`, the only part def with a part value (800 W), is still used nowhere (export: no `FeatureTyping` targets it). The only thing that distinguishes `slow` from `nominal` is the value of an emergent result. The chapter nevertheless calls them "design candidates" (notebook 01 cell 7, notebook 02 cell 3). Observed downstream: through `ch08-cumulative.sysml` both stay `part nominal : Toaster;` and `part slow : Toaster { attribute :>> cycleTime = 200.0 [SI::s]; }`, and they are the `satisfy` subjects from Chapter 3 onward, so the gap does not close by itself. I did not fix it. + +**F-7. Judgment is recorded, but the one record cites as evidence the prescription it justifies, and the threshold judgment has no record (wrong for AC-001; a gap for the threshold).** +Check: cross-layer judgment item; §1.6; `term-asserted-context`, `term-assumption` (context enters an argument asserted to be appropriate). +- AC-001: `claim` is "120 seconds is the nominal cycle time for standard sliced bread". `criteria` is the declaration `attribute cycleTime : ISQ::DurationValue default = 120.0 [SI::s]`, and `evidence_refs` is `["ToasterDemo::Toaster::cycleTime default = 120.0 [SI::s]"]`. The only evidence offered for the value is the model's own declaration of that value. The rationale ("consistent with manufacturer guidance") cites no source. The claim also mixes a context (standard sliced bread, an operating condition) with a result (the cycle time under that condition). The fields §1.6 calls load-bearing are present: counterevidence "Thick-cut and frozen bread may require 180-240s" (which already exceeds the 180 s threshold), residual uncertainties, and disposition `pending`. +- `TimelyToast`'s 180 s threshold is a judgment, and it is recorded only as a `doc` rationale ("kitchen workflows typically span 5-15 minutes"), with no source, counterevidence or residual uncertainty. Whether a requirement's rationale needs a judgment record is not stated anywhere I read, so this half is a gap reported against the checklist, not a ruling. +I did not edit the record or the requirement. + +**F-8. Chapter text disagrees with itself and with the model about what `slow` is and what a requirement definition binds (documentation consistency; not a layer defect in the model).** +- `slow` is called "two named design variants" (`index.md` Purpose and Method; `conclusion.md` line 5), "the operating conditions we are designing for ... encoding the assumption being evaluated" (notebook 02 cell 1), "a named design candidate ... that encodes a specific assumption about cycle time" (notebook 02 cell 3), and "two competing conditions to evaluate" (`conclusion.md` line 9). A prescription (candidate), a context (operating condition) and a result (cycle time) are different kinds (OQ-8). +- Notebook 01 cell 3: "any `Toaster` instance must satisfy this requirement". A requirement definition defines a constraint on its subject parameter (`def-sysml--requirement`). It binds a particular `Toaster` only through a requirement usage and a satisfy relationship, which is what Chapter 3 introduces. As written, the sentence also makes `slow`, a `Toaster` built to fail, a contradiction. +- `index.md` Method: "Notebook 02 adds the `nominal` and `slow` variants". `nominal` is added in notebook 01 (cell 6). +- Notebook 02 cell 5 attributes fixity to `:>>` ("`:>>` sets a fixed value, while `default =` sets a value that can be further overridden"). In the export, the redefinition (`:>>`) and the fixed binding (`=`, a non-default `FeatureValue`) are separate elements. The sentence merges them. This is outside layer scope and is reported for routing only. +- Notebook 01 cell 4 ("require constraint body not yet supported — toaster#11 / OpenSysML#597") and notebook 02 cell 4 ("anonymous attribute :>> redefinition not yet supported — toaster#10 / OpenSysML#596"): v0.9.0 parses both, and the export contains the `RequirementConstraintMembership` and the `Redefinition`. As with Chapter 1's F-4, the comments may refer to the editor API rather than parsing. Not checked. +Reported only; no edits. + +**F-9. Chapter 2 shows no figure of the assembled model (content; not a layer defect).** +Check: cross-layer figures item; AGENTS.md §1.7 ("every chapter shows the assembled model"). The three notebooks have no diagram cell. Notebook 03 cell 2 prints the full model source. Whether printed text meets §1.7 is for the content pass. The Chapter 1 audit did not check figures, so I cannot say whether this is a pattern. + +## Open questions (for the orchestrator to route) + +**OQ-6. Is `TimelyToast` a MoE-type acceptance threshold (functional) or a MoP threshold (logical)?** +- Functional reading: it passes the substitution test (tongs-with-a-blowtorch can finish or fail to finish toast within 180 s). The rationale argues from the user's kitchen workflow and meal preparation, which is acceptance (who cares: the user). Nothing derives 180 s from another measure. The requirement sits on the whole, the subject. +- Logical reading: the skill says timing is "usually performance". SEBoK's MoP "yields design requirements necessary to satisfy a MoE" (`def-sebok--mop`), and "within the usability envelope for a countertop appliance" reads like a design envelope. "Countertop appliance" in the rationale excludes the tongs-with-a-blowtorch solution, so the justification (not the statement) assumes a solution class. Observed downstream, Chapter 3's verification method tests "at nominal input power", which presupposes an electrical mechanism. +- Recommended default: **functional (MoE-type acceptance threshold)**, not yet built (no tag, no means of collection until Chapter 3). The justification is recorded in Chapter 3 as DL-022 already directs. Separately, the ACE may want to say whether "countertop appliance" in a rationale is a solution commitment. I recommend treating it as stakeholder context, not a commitment, but I do not decide it. + +**OQ-7. What layer are `nominal` and `slow`: physical candidates not yet built, or named usages of the subject (no layer)?** +- Physical (candidate) reading: the chapter calls them design candidates. From Chapter 3 on they are the `by` side of `assert satisfy`, the role a candidate plays. Under the §1.10 lens a candidate is a concrete point, and each is a single usage. +- Subject-usage reading: Q3 fails. They name no concrete part def and confer no part value (F-6). Their only content is `Toaster`'s arrangement, which DL-021 rules logical, and an emergent result. DL-019 and DL-021 rule the bare system-level def to be the named subject, and a usage that adds nothing but a name is its instance. +- Recommended default: **named usages of the subject, not layer elements**, with the "candidate" label unsupported until they contain concrete parts that realize the logical slots (F-6). This extends DL-019 and DL-021 from the def to its usages. The ACE should say whether that extension holds. + +**OQ-8. What does `slow` denote: a design candidate, an operating condition, or a fixture for the failing branch of a check?** +- Candidate: `index.md` and `conclusion.md` ("design variants"); notebook 02 cell 3. +- Operating condition or assumption: notebook 02 cell 1 ("the operating conditions we are designing for ... encoding the assumption"); `conclusion.md` ("competing conditions"). Against this reading, a cycle time is a result, not an environmental condition (`term-assumption` concerns context entering an argument). AC-001's own counterevidence names the actual condition, bread thickness and whether it is frozen. +- Failing-branch fixture: notebook 01 cell 7 says `slow` exists "to demonstrate a candidate that fails the requirement", which is a negative-control role (§1.4, every chapter's loop has a negative control). +- Recommended default: **a fixture for the failing branch**, whose content is F-5 whatever it is called. If the re-derivation wants an operating-condition variant, the condition (bread thickness, supply voltage, starting temperature) is what varies, and the cycle time is derived under it. That is a content decision to route, not mine. + +**OQ-9. Can an explicitly recorded assumption stand in for an emergent result until the result can be derived?** +- Yes: §1.6 says real design rests on assumptions. Hawkins lets context and assumptions enter an argument when asserted to be appropriate (`term-assumption`, `term-asserted-context`). AC-001 records counterevidence and residual uncertainty with a `pending` disposition, so it is honest about being an assumption. +- No, not as used here: DL-018 says cycle time is derived from the mechanism and the energy balance, and the attribute may exist as a slot without a default. AC-001's evidence is the default itself (F-7). `conclusion.md` then uses the assumed value to issue a pass verdict as though it had been derived (F-5). +- Recommended default: **an assumption about a result is legitimate only when it stays an assumption**: recorded, pointing at evidence other than the model's own declaration, and never compared with the threshold as if it were a derived value. On that default AC-001 does not cure F-5. This may extend DL-018 (from the attribute to judgment records about it), so the ACE should confirm. + +**OQ-10. Is a judgment record (`ReviewRecord`) a layer element?** +- Not a layer element: it is analysis and evidence, like a verification case (DL-023, F4). It prescribes nothing and states no intent. +- Layer element: it carries a claim about a model value (120 s), and an assumption can shape the design space. +- Recommended default: **not a layer element**, classified by what it bears on (here an emergent result of the subject). DL-023 covers verification cases only, so this is an analogy the ACE should confirm or reject. + +## Contract premises checked + +1. **"Chapter 2 introduces requirements with the three-part anatomy."** Holds in part. The anatomy is taught (notebook 01 cell 1, "inspired by Brian Douglas, Part 4"). `TimelyToast` has the description and the rationale, both in one unstructured `doc` comment and separable only by the "Rationale:" prefix, so no query can tell them apart. The third part, the verification method, is not in Chapter 2. It is deferred to Chapter 3's `verification def TimelyToastTest`, where the method type ("test") is also only in a `doc` because the `VerificationMethodKind` metadata is unsupported (D-004, toaster#19; DL-005). By the anatomy's own rule ("A requirement without all three is incomplete", AGENTS.md Part 2 §1), the model's only requirement is incomplete at the end of Chapter 2. DL-005 records why: `verify` must target a requirement usage, which Chapter 3 introduces. +2. **"The Ch1 findings recur or not."** + - F-1 (settable performance attributes): **recurs and is extended** (F-5). There is a new fixed binding on `slow` and a prose pass/fail verdict. + - F-2 (unrealized physical parts): **recurs in a new form** (F-6). The new "candidates" contain no concrete part, and `Heater` is still unused. + - F-3 (purpose on the subsystems): **does not recur**. Chapter 2 adds nothing that touches the `Subclassification`s, and its requirement is on the whole (`Toaster`), which is consistent with the subject reading. F-3 is unchanged, and it is why `TimelyToast` cannot be linked to the purpose statement (traceability note above). + - F-4 (documentation consistency): **recurs in a new form** (F-8). +3. **"Vocabulary lint hits for this chapter: none."** Holds. `uv run python -m glossary lint` reports 8 hits, in ch01, ch05, ch09 and ch10, and none in `chapters/ch02-requirements/`. +4. **Context premise "a verification case is not a layer element (DL-023)"**: Chapter 2 adds no verification case, so it is not exercised. It holds for the downstream `TimelyToastTest`, which was observed but not audited. + +## Constructs that could not be classified cleanly + +- `nominal` and `slow`: part usages of the subject that add no part and no part value. The four questions do not settle them (OQ-7, OQ-8). +- `TimelyToast` and its constraint: classified functional by default, but the layer depends on a MoE or MoP judgment that is not recorded (OQ-6). +- `context_record` (AC-001): not a model element. Its classification rests on an analogy with DL-023 (OQ-10). +- The header comment: no engineering content. + +## Not checked, and why + +- **Notebook execution**: I loaded the fixtures directly and did not run the notebooks. I rebuilt AC-001 with the notebook's field values and ran `validate_record` on it (`[]`). I did not confirm that the notebooks' outputs match. They are stored without outputs. +- **Constraint target in the export**: the constraint's feature chain has a `FeatureReferenceExpression` whose `referent` id (`0c539f7c-...`) is not an element of the export. A separate `Membership` does name `ToasterDemo::Toaster::cycleTime`. So I confirmed from the text, not by following the chain in the export, that the constraint reads `Toaster::cycleTime`. +- **Query-surface disagreement (tool observation, not a layer finding)**: `model.query()` filtered on `PartUsage` returns `ToasterDemo::TimelyToast::toaster`, while the JSON export gives that element as a `ReferenceUsage`. I did not check which one the spec requires or whether a gap entry exists (§1.9 gap-tracking rule, for the orchestrator to route). +- **`exercises/ch02/exercise.ipynb`**: out of scope. +- **toaster#10 / OpenSysML#596 and toaster#11 / OpenSysML#597** (notebook comments): not checked (F-8). +- **Later chapters**: read only to see how Chapter 2 elements are used downstream. The statements about `ch03` to `ch08` are observations, not findings on those chapters. One downstream observation the orchestrator may want to route to the Chapter 3 audit: `ch03-cumulative.sysml` has `assert satisfy timely by slow;` although `slow` is built to violate `timely`. +- **Glossary sources**: `uv run python -m glossary check` passes (0 errors, 7 warnings). Source PDFs are not local, so source hashes are not verified. I relied on the glossary's recorded definitions. +- **Douglas timestamps**: not re-verified. diff --git a/decisions/audits/ch03-layer-audit.md b/decisions/audits/ch03-layer-audit.md new file mode 100644 index 0000000..1d665da --- /dev/null +++ b/decisions/audits/ch03-layer-audit.md @@ -0,0 +1,158 @@ +# Chapter 3 layer audit + +Contract PASS2-008-B, 2026-09-26. Role: `.claude/agents/layer-auditor.md`. Model: claude-opus-5-5 (exact id `claude-opus-5-5[1m]`), effort high. +Branch `audit/ch03`, base commit `927f04f`. + +Subject: the elements Chapter 3 adds, that is, the difference between `models/ch02-cumulative.sysml` (41 lines) and `models/ch03-cumulative.sysml` (58 lines). Both are generated fixtures and were not edited. +Method: the four-question pass and the per-layer checklist in `.claude/skills/architecture-layers/SKILL.md`, AGENTS.md §1.4 to §1.6, the glossary (`uv run python -m glossary tutorial TERM` for `moe`, `mop`, `tpm`, `requirement`, `verification`, `asserted-solution`, `behavior`, `usage`, `mechanism`, `traceability`), and the ACE rulings DL-018 to DL-023. + +How the difference was established. Both fixtures were loaded with OpenSysML v0.9.0 (`load_from_content`, `strict=False`; `model.ok == True` for both, no diagnostics) and their API JSON exports were compared by (metaclass, qualified name) through `toaster.query.ApiIndex`. Nothing in ch02 is missing from ch03. The added named elements are: one `NamespaceImport` (`MeasurementReferences::*`), `RequirementUsage timely`, `PartUsage evidence` with two `SatisfyRequirementUsage`s, `VerificationCaseDefinition TimelyToastTest` (doc, subject, objective requirement, one `verify`), and `CalculationDefinition DeliveredEnergy` (three `in` parameters, one return). The rest of the added export elements are the memberships, typings and expressions those declare. Chapter 3 also moves `nominal` and `slow` above `TimelyToast` in the text; that has no semantic effect and is not audited. + +Other facts confirmed by running the model (scratch scripts, not committed): +- There is **no metadata usage** in the ch03 export (no `MeasureOfEffectiveness`, `MeasureOfPerformance` or any other). No attribute, requirement or calc is tagged or named as a MoE, MoP or TPM. The same holds for every fixture ch01 to ch08: the only text match for "metadata" in `models/` is the `#verificationMethod` note in ch03's verification doc. +- `model.eval("ToasterDemo::nominal.cycleTime <= 180.0 [SI::s]")` returns `True`; the same expression for `slow` returns `False`. `nominal.cycleTime` evaluates to the `Toaster` default (120 s) and `slow.cycleTime` to its redefinition (200 s). +- A minimal model that asserts `satisfy` for a candidate whose default violates the requirement loads with `ok == True` and no diagnostics, so OpenSysML v0.9.0 does not diagnose a false satisfaction assertion. `assert not satisfy r by x;` parses and exports `isNegated: true`; both ch03 assertions have no `isNegated`. +- `DeliveredEnergy` is referenced by nothing outside its own body in ch03 (no calc usage, no binding to any candidate or requirement). `model.eval("ToasterDemo::DeliveredEnergy(800.0 [SI::W], 120.0 [SI::s], 0.7)")` returns 67200 J, as notebook 02 cell 10 states. Downstream, ch04 line 46 calls it inside `action def ApplyHeat` (observation, not audited). + +Evidence read for intent: `chapters/ch03-measures/index.md`, `conclusion.md`, and every cell of notebooks `01-moe-definition`, `02-mop-candidate-eval`, `03-threshold-judgment` and `04-verification-case`. In all of that text the strings "MoE", "MoP", "TPM", "measure of", "effectiveness" and "performance" appear only in the two notebook file names (in the `index.md` links). The chapter title is "Measures of Success". + +## Classification table + +| Element (qualified name) | Layer | Reason | Status | +|---|---|---|---| +| Import `MeasurementReferences::*` (`ToasterDemo::@3`, `NamespaceImport`) | none | Library access for `DimensionOneValue` (D-003 workaround, noted in the model comment and notebook 02 cell 5). No engineering content. | PASS (not classified) | +| `ToasterDemo::timely` (`requirement timely : TimelyToast`) | Follows `TimelyToast` (ch02): a MoE at the functional layer or a derived MoP threshold at the logical layer, by recorded judgment | §1.5: "whether a measure is a MoE or a MoP is a modeling judgment for the case at hand"; skill example row "Toast is ready within 150 s": MoE or MoP by judgment; `term-moe`, `term-mop`. The justification is **missing** from Chapter 3. The constraint it carries compares `cycleTime`, a result entered as a choice (DL-018). | OPEN-QUESTION (OQ-1); FINDING F-1, F-2 | +| `ToasterDemo::evidence` (untyped `part` usage) | none; could not classify | It is a part usage (an occurrence in the package, beside `nominal` and `slow`) used only as a namespace for claims (notebook 01 cell 5: "a named scope that collects satisfaction claims"). It is not a system part, and it holds assertions, not analysis results (§1.4: simulations produce the evidence base). | FINDING F-4; OPEN-QUESTION (OQ-4) | +| `ToasterDemo::evidence::@0` (`assert satisfy timely by nominal`) | Cross-layer claim (requirement to candidate) | Traceability relation (`term-traceability`) whose truth rests on `nominal.cycleTime`, the `Toaster` default of 120 s. Skill across-layers check: "Is any emergent result set as an attribute default and then 'verified'?" Yes. | FINDING F-2; OPEN-QUESTION (OQ-4) | +| `ToasterDemo::evidence::@1` (`assert satisfy timely by slow`) | Cross-layer claim (requirement to candidate) | As above, and the claim is false by the model's own values (`slow.cycleTime` = 200 s; the constraint evaluates `False`). The chapter says the slow variant violates the requirement. | FINDING F-2, F-3; OPEN-QUESTION (OQ-4) | +| `ToasterDemo::TimelyToastTest` (`verification def`) | Not a layer element | DL-023 and §1.5: classify by what it tests and by tier. It tests `timely` (layer per OQ-1). Tier: language conformance only (it parses and resolves); it is never run and yields no verdict, so it is not yet evidence. The value it would measure (cycle time on a built candidate) is a TPM and an emergent result (skill example row "Measured browning time ... 118 s"). | PASS (classification); see F-4 for the missing link to the claims | +| `ToasterDemo::TimelyToastTest::@0` (doc: "timed test of three consecutive toasting cycles at nominal input power; all must complete within 180 seconds") | Part of the verification case | The means of checking that `term-mop` and §1.5 require of a MoP requirement ("the requirement needs a threshold and a means of checking it"), stated in prose. The formal `#verificationMethod` metadata is a tracked gap (toaster#19 / OpenSysML#608, cited in the model and notebook 04 cells 4 and 5). | PASS; evidence for OQ-1 | +| `ToasterDemo::TimelyToastTest::toaster` (`subject toaster : Toaster`) | none (the subject) | §1.5 and DL-019 (F7): the system of interest is the subject all layers describe, not a layer. | PASS (not classified) | +| `ToasterDemo::TimelyToastTest::@2` (objective) and `::@2::@0` (`verify timely`) | Part of the verification case | DL-023. The `verify` exports as a `SatisfyRequirementUsage` subsetting `timely` (opensysml-query recipe 4). | PASS (not classified); subject binding not checked (see below) | +| `ToasterDemo::DeliveredEnergy` (`calc def`, return `power * duration * efficiency`) | Functional by the method (recommended); contested with logical | Q1: a pop-up toaster and tongs-with-a-blowtorch both have a supplied power, a duration and a fraction of energy reaching the bread, so the relation holds for both (skill Q1: "a relation among phenomena (energy, temperature, time)"). It is not stated for a chosen component (§1.5 *Constraints, split by solution-independence*). Against: the constant-power product form is a modeling decision (`term-mechanism`), and ch04/ch05 attach it to `ApplyHeat` and `HeatingSystem`. | OPEN-QUESTION (OQ-3); FINDING F-5 | +| `ToasterDemo::DeliveredEnergy::power : ISQ::PowerValue` (`in`) | Follows the calc def | A typed, unit-bearing parameter with no value. | PASS | +| `ToasterDemo::DeliveredEnergy::duration : ISQ::DurationValue` (`in`) | Follows the calc def | As above. Notebook 02 feeds it 120 s, the `cycleTime` default (F-2, OQ-2). | PASS | +| `ToasterDemo::DeliveredEnergy::efficiency : DimensionOneValue` (`in`) | Follows the calc def | Power efficiency is the glossary's own MoP example (`term-mop`), but here it is an unbounded dimensionless input: not tagged as a measure, no threshold, no means of collection, and no `0..1` bound. | FINDING F-1, F-5; OPEN-QUESTION (OQ-2) | +| `ToasterDemo::DeliveredEnergy::@3` (return `: ISQ::EnergyValue`) | Follows the calc def; any value it yields on a candidate is an emergent result | `term-behavior`, §1.6: derived, not chosen. The one evaluated value (67 200 J, notebook 02 cell 10) is not stored in the model and is compared with nothing. | PASS (as a relation); OPEN-QUESTION (OQ-2) on whether its evaluation is a TPM | +| `AS-C03` (Python `ReviewRecord`, notebook 03 cell 5; not in the model diff) | Judgment record (not a layer element) | Skill across-layers check on judgment: `counterevidence` and `residual_uncertainties` are populated and `disposition` is "pending", not "accepted" (§1.6). Its `evidence_refs` cites the assertion `assert satisfy timely by nominal`, and its `rationale` compares the default with the limit. | PASS on the §1.6 fields; FINDING F-2, F-4 on what it cites | + +## Per-layer checklist results + +**Functional** +- Typed inputs and outputs on each action: no action is added in Chapter 3 (actions arrive in Chapter 4). Not yet built. +- Solution-independent statements: `DeliveredEnergy` passes the substitution test (OQ-3 records the contest). PASS by the method. +- Phenomena relations as relations (balance inequality): `DeliveredEnergy` is an equality with an unbounded efficiency, not a balance; no energy balance exists in the model. FINDING F-5. +- At least one MoE about acceptance, with any MoE-versus-MoP split justified and recorded: none. No MoE is declared or tagged, and no split is justified. FINDING F-1; OQ-1. +- Reads as an objective: the only objective content is `TimelyToast` (ch02), a threshold with no stated MoE above it. + +**Logical** +- Mechanism carriers and interfaces: none added. Not yet built. Port-type conformance (§1.9, recipe 5) stays **open**: no connection exists. +- MoP thresholds derived from a MoE, with a means of checking: no MoP exists. If `timely` is read as a MoP (OQ-1), its 180 s threshold is not derived from any MoE in the model (the ch02 rationale argues it in prose) and its means of checking exists only as the `TimelyToastTest` doc. FINDING F-1 (as absent), OQ-1. +- No solution values and no results entered as choices: Chapter 3 adds no values, but every check it adds compares `cycleTime`, a result entered as a choice. FINDING F-2. +- Reads as a design space: `DeliveredEnergy`'s parameters are typed, unit-bearing slots. PASS. + +**Physical** +- Concrete defs specializing abstract logical defs: none added. Not applicable. +- Values meet derived thresholds, TPM assessed not asserted: no TPM exists. The satisfaction of `timely` is **asserted** (`assert satisfy`) against default values, not assessed. FINDING F-2, F-3. +- Reads as a candidate: `nominal` and `slow` (ch02) are the candidates; Chapter 3 claims both satisfy, and one does not (F-3). + +**Across layers** +- Stopping rule: not reached at Chapter 3. Not yet built. +- An emergent result set as an attribute default and then "verified": yes, this is the chapter's central check. FINDING F-2. +- Judgment recorded, with counterevidence and residual uncertainties, no "accepted" disposition: `AS-C03` has all three. PASS on form; F-4 on its evidence. +- Figures: Chapter 3 has no figure cells; not checked beyond that (see below). + +## Findings + +**F-1. Chapter 3 declares no MoE, MoP or TPM, although its title and two notebook names say it does, and it records no MoE-versus-MoP justification.** +Checks: functional checklist ("at least one MoE ... if a timing or efficiency figure is filed as a MoE or a MoP, is the split justified for this case and recorded?"); logical checklist (MoP thresholds derived from a MoE); `term-moe`, `term-mop`, `term-tpm`; §1.5 (the split is a judgment "recorded with its justification"); DL-022 ("Chapter 3's re-derivation carries a recorded justification"). +Evidence: no metadata usage in the export; no attribute named or documented as a measure; the notebook named `01-moe-definition` adds `requirement timely` and two `assert satisfy`, and its text never mentions a measure of effectiveness; the notebook named `02-mop-candidate-eval` adds `calc def DeliveredEnergy` with no threshold, attribute or means of collection, and its text never mentions a measure of performance. The only threshold in play (180 s) comes from ch02. So the chapter's labeling is not matched by the model, and neither label is justified anywhere in the chapter. `index.md` states the chapter's question as "how do we verify that a candidate design satisfies a requirement?", which is about satisfaction claims rather than measures. +Not done: I did not propose which measures the chapter should declare, and I did not decide the split (OQ-1, OQ-2). + +**F-2. Every threshold check Chapter 3 adds compares an attribute default with the limit (the pattern §1.5 says is not a valid check).** +Checks: across-layers ("Is any emergent result set as an attribute default and then 'verified'?"); §1.5 *Prescribed versus emergent*; skill example row "Cycle time = 120 s set as an attribute default, then checked against a 150 s limit: not a valid check"; DL-018. +Evidence: `assert satisfy timely by nominal` holds only because `Toaster::cycleTime` defaults to 120 s; `AS-C03`'s rationale is "120 s < 180 s", its criteria are `toaster.cycleTime <= 180.0`, and its own counterevidence says "The nominal holds only for the default cycleTime." `DeliveredEnergy` could have been part of a derivation, but it is not connected to `cycleTime`, `timely` or any candidate, and it takes duration as an input, so as declared it cannot derive a cycle time (DL-018: cycle time is derived "from the mechanism and the energy balance"). This is ch01 F-1 carried into Chapter 3's new elements: the check "can never fail for a reason about the design" (DL-018 reasoning). +Not done: no edit to the model, notebooks or record. + +**F-3. The model asserts that `slow` satisfies `timely`, which its own values falsify and its own text denies, and the tool does not diagnose it.** +Checks: physical checklist (TPM assessed, not asserted); §1.6 ("behavior is derived and checked, never asserted"); §1.9 (tools may not diagnose a fault; the tutorial supplies the check). +Evidence: `ToasterDemo::evidence::@1` is `assert satisfy timely by slow`; `slow.cycleTime` is 200 s; `model.eval("ToasterDemo::slow.cycleTime <= 180.0 [SI::s]")` is `False`. `conclusion.md` line 5 says "the slow variant at 200 s violates it"; `AS-C03` counterevidence says the same. `conclusion.md` line 9 says the model "now records *which* candidate satisfies the requirement", but it records both as satisfying. `index.md` calls them "satisfaction claims for both candidates". OpenSysML v0.9.0 loads a minimal equivalent with no diagnostic. The negated form `assert not satisfy` parses in v0.9.0 (`isNegated: true`) and was not used. The same two assertions persist unchanged in ch04 to ch08 (observation only; not audited). +Not done: I did not decide whether `slow` is meant as a negative control (and so should be a negated claim or a computed check), and I did not file a gap entry (§1.9 asks for one only once it is established that the spec requires the diagnosis, which I did not check). + +**F-4. Claims are labeled and cited as evidence, and the verification case is not linked to them.** +Checks: §1.4 ("Simulations produce the evidence base; judgments ... rest on that evidence and point at it. They do not replace it."); across-layers judgment check; DL-023 (a verification case's verdict is evidence). +Evidence: the claims live in a part usage named `evidence`; `AS-C03.evidence_refs` is `["assert satisfy timely by nominal"]`, so the judgment cites a model assertion, which itself rests on a default (F-2), rather than an analysis result. `TimelyToastTest` declares how `timely` would be verified but yields no verdict, and nothing connects it to the `assert satisfy` claims or to `AS-C03`. The chain from requirement to verification to evidence to judgment is not closed at any link. +Not done: no restructuring proposed; whether `evidence` is a legitimate construct is OQ-4. + +**F-5. `DeliveredEnergy` is a free-standing equality with an unbounded efficiency, not a balance, and it is unused in Chapter 3.** +Checks: functional checklist ("phenomena relations stated as relations (balance inequality)"); §1.5 (the functional idiom is an energy balance "which respects conservation without assuming perfect efficiency"). +Evidence: `return : ISQ::EnergyValue = power * duration * efficiency` with `efficiency : DimensionOneValue` and no constraint `0 <= efficiency <= 1`, so as declared it admits delivered energy greater than supplied energy. There is no loss term and no balance anywhere in the ch03 model. Nothing in ch03 uses the calc (ch04 does). Notebook 02 cell 1 calls it "the quantitative basis for evaluating the nominal design", but the model does not bind it to `nominal`, and the one evaluation (notebook 02 cell 10) uses Python literals, including an efficiency of 0.7 that exists nowhere in the model (§1.4: a number without a model-defined relation and inputs is not evidence). +Not done: no bound or balance added. The "unused" half is "not yet built" (ch04 uses it); the unbounded efficiency is a defect in the relation as declared. + +**F-6. Chapter text disagrees with the fixture and with itself (documentation consistency, not a layer defect in the model).** +- `index.md` Ingredients row for notebook 02 says `return : Real = expr`; the fixture and notebook 02 use `ISQ::EnergyValue` and ISQ-typed inputs. +- `index.md` Experiment says "after completing all three notebooks"; the chapter has four. +- `conclusion.md` does not mention `TimelyToastTest`, which the chapter adds. +- `conclusion.md` line 9 versus the model (F-3). +- The notebook names say MoE and MoP; the notebook titles and text say requirement usage and calc def (F-1). +Reported only; no edits. + +## Open questions (for the orchestrator to route) + +**OQ-1. Is the toast-time measure behind `timely` (`TimelyToast`, `cycleTime <= 180 s`) a MoE or a MoP? Justification: missing.** +- MoE reading: `TimelyToast`'s rationale argues from the user ("kitchen workflows", "usability envelope for a countertop appliance"), which answers "who cares" with the user and frames time as part of acceptance. The skill's example row names toast time as a case where MoE is defensible. The notebook that adds `timely` is named `01-moe-definition`. +- MoP reading: `TimelyToastTest`'s doc checks it as engineering performance under a test condition ("at nominal input power"). The glossary's MoE example is evenness of toasting, not time. `term-mop` asks for a unit, a threshold and a means of checking, and all three exist (s, 180 s, the timed test in prose). +- Against both, as declared: the measured quantity is `cycleTime`, which is a result entered as a choice (DL-018), so whichever label is chosen the check is not yet valid (F-2). If MoP, its threshold is not derived from any MoE in the model. +- Justification present in Chapter 3: none. No text in `index.md`, `conclusion.md` or the notebooks says which it is or why; the only signal is a file name. DL-022 expects the re-derivation to carry the recorded justification. +- Recommended default: leave it unlabeled (neither MoE nor MoP) and keep `timely`'s layer open until the Chapter 3 re-derivation records the judgment (who cares; acceptance or engineering performance), per §1.5 and DL-022. Do not infer the label from the file name. + +**OQ-2. What measure does notebook 02 (`02-mop-candidate-eval`) intend, and is its evaluation a TPM?** +- MoP is efficiency: `term-mop`'s own toaster example is power efficiency, and `efficiency` is an input of `DeliveredEnergy`. Against: no threshold, no attribute, no means of collection, not tagged. +- MoP is delivered energy: the notebook calls it "the quantitative basis for evaluating the nominal design". Against: no threshold, and nothing relates delivered energy to `timely` or to acceptable toast. +- The evaluation (67 200 J) is a TPM: it is a value computed by analysis from the heater's 800 W. Against: `term-tpm` is "the evidence against a MoP threshold", and there is no threshold; the inputs are Python literals not bound to a candidate; the 120 s input is a result entered as a choice; the 0.7 has no source in the model; the result is not stored in the model. +- Recommended default: record that the model has **no MoP and no TPM** in Chapter 3 (F-1), and route the intent question to the chapter's re-derivation author. + +**OQ-3. Is `DeliveredEnergy` a functional phenomena relation or a logical mechanism?** +- Functional: Q1 is yes (a blowtorch also has a supplied power, a duration and a fraction reaching the bread); it is stated for no chosen component; §1.5 says a relation that holds for any solution stays functional. +- Logical: the constant-power product is a modeling decision grounded in practice (`term-mechanism`), and from ch04 it is the body of `ApplyHeat`, which ch05 allocates to `HeatingSystem`; notebook 02 evaluates it with the heater's 800 W. +- Recommended default: **functional as declared in Chapter 3** (the method stops at Q1), with F-5 open against it because it is not a balance. Whether its use inside `ApplyHeat` from ch04 is logical belongs to the Chapter 4 and 5 audits. + +**OQ-4. What are `part evidence` and its `assert satisfy` claims in layer terms?** +- Analysis side, not a layer element: they record the outcome of a check, like a verification case (DL-023 by analogy). Against: DL-023 rules on `verification def`, not on `satisfy`, and an `assert satisfy` is a claim, not an analysis. +- Cross-layer traceability relation: a satisfy links a requirement to the design element that meets it (`term-traceability`, Douglas: "the design traces back to the requirements it implements"), so it takes no layer of its own. Against: that does not settle what the container `part evidence` is; as a part usage it is an occurrence in the package, not a namespace. +- Recommended default: classify each `assert satisfy` as a cross-layer traceability claim (no layer of its own), treat `part evidence` as unclassifiable, and ask the ACE whether DL-023 extends to satisfaction claims and whether a part usage is an acceptable container for them. + +## Contract premises that did not hold + +1. **"Chapter 3 introduces measures of effectiveness and performance."** Not at HEAD. The model has no MoE or MoP (no metadata, no measure attribute, no new threshold), and the chapter text never uses either term except in two notebook file names. See F-1. +2. **"Its threshold checks compare a derived value rather than an attribute default."** Does not hold. The only threshold check (`timely` via `assert satisfy ... by nominal` and `by slow`, and `AS-C03`'s rationale) compares `cycleTime`, which is a `Toaster` default (120 s) or a redefinition (200 s). The one derived value in the chapter (67 200 J) is compared with nothing. See F-2. +3. **"TPM values come from analysis."** Does not hold, because there is no TPM. Nothing in the model is an assessed value; the values the checks use are prescribed defaults, and the one analysis output is not in the model and not against a threshold. See OQ-2. + +The contract's context statement "Lint hits for this chapter: none" holds: `uv run python -m glossary lint --json` reports hits only in ch01, ch05, ch09 and ch10 files. + +## Constructs that could not be classified cleanly + +- `ToasterDemo::evidence`: an untyped part usage used as a namespace for claims (OQ-4). +- The two `assert satisfy` usages: relations whose layer is that of the requirement they cite, which is itself open (OQ-1, OQ-4). +- `ToasterDemo::timely`: its layer depends on a MoE-versus-MoP judgment that has not been recorded (OQ-1). +- `DeliveredEnergy`: classified by the method (functional), but contested (OQ-3). + +## Cross-chapter dependencies + +- `TimelyToast` is a Chapter 2 element. Its layer decides `timely`'s (OQ-1). There is no `ch02-layer-audit.md` in this checkout, so I do not know whether it has been classified. +- F-2 is ch01 F-1 (DL-018) carried forward: it closes only when `cycleTime` is derived rather than defaulted. +- `DeliveredEnergy` is used from ch04 (`ApplyHeat`, line 46) and, through ch05, sits under `HeatingSystem`'s allocation (OQ-3). +- `assert satisfy timely by slow` persists unchanged through ch08 (F-3). ch06 adds a similar block, carried through ch08, (`heatingEvidence { assert satisfy heating by efficient; assert satisfy heating by weak; }`) that may repeat the pattern; I did not evaluate it. +- No fixture ch01 to ch08 carries MoE or MoP metadata, so if F-1 is resolved in Chapter 3, later chapters are affected. + +## Not checked, and why + +- **Subject binding of `verify timely`**: the export shows the objective's `SatisfyRequirementUsage` with no `subject`. I did not check against §7.24 whether the objective's requirement binds to the case's `toaster` subject by default, so I make no claim either way. +- **Whether SysML v2 requires a tool to diagnose a false `assert satisfy`**: not checked against the spec; F-3 reports only the observed behavior of OpenSysML v0.9.0. +- **Library type names of the calc parameters**: the export gives library types as bare UUIDs that the index cannot resolve to names. I relied on the source text (`ISQ::PowerValue`, `ISQ::DurationValue`, `DimensionOneValue`, `ISQ::EnergyValue`) and on the model loading with `ok == True`. +- **`AS-C03`**: read in notebook 03 cell 5 only; I did not run `validate_record` or check assumption `AC-001`. +- **`exercises/ch03/exercise.ipynb`**: not in scope. +- **Figures**: Chapter 3 has no figure cells; the across-layers figures check does not apply. +- **Seam-cell labels**: notebooks 01, 02, 03 and 04 end with cells using the A-F, O-S and E labels. DL-028 parks those as a Pass 4 input; I did not assess them. +- **Later chapters**: read only to trace Chapter 3 elements downstream; not audited. +- **Glossary sources**: `uv run python -m glossary check` passes (0 errors, 7 warnings: local source PDFs absent, hashes not verified). I relied on the glossary's recorded definitions, not on the source texts. diff --git a/decisions/audits/ch04-layer-audit.md b/decisions/audits/ch04-layer-audit.md new file mode 100644 index 0000000..76a0676 --- /dev/null +++ b/decisions/audits/ch04-layer-audit.md @@ -0,0 +1,162 @@ +Model: claude-opus-5-5[1m] (role pin claude-opus-5-5, effort high) + +# Chapter 4 layer audit + +Contract PASS2-008-C, 2026-09-26. Role: `.claude/agents/layer-auditor.md`. +Branch `audit/ch04`, base commit `927f04f`. + +Subject: the elements Chapter 4 adds, that is the difference between `models/ch03-cumulative.sysml` (58 lines) and `models/ch04-cumulative.sysml` (52 lines), both generated fixtures, not edited. +Method: the four-question pass and the per-layer checklist in `.claude/skills/architecture-layers/SKILL.md`, AGENTS.md §1.5, §1.6 and §1.8, the ACE rulings DL-017 to DL-023, and the glossary (`uv run python -m glossary tutorial TERM` for `function`, `functional-architecture`, `mechanism`, `mop`, `moe`, `behavior`, `policy`, `logical-component`, `decomposition`, `asserted-inference`, `interface`, `control-law`). + +Model evidence. Both fixtures were loaded with OpenSysML v0.9.0 (`load_from_content`, `strict=False`): `model.ok == True` for both, no diagnostics for Chapter 4. Their API JSON exports were compared by (`@type`, `qualifiedName`) counts (213 elements for Chapter 3, 270 for Chapter 4). What the export confirms: +- Added, named: `ActionDefinition ApplyHeat`; four `ReferenceUsage` parameters `power`, `duration`, `efficiency` (direction `in`) and `energy` (direction `out`), each with one `FeatureTyping`; `ActionUsage calculate` owning one `AssignmentActionUsage` whose value is an `InvocationExpression` of `ToasterDemo::DeliveredEnergy` with three `FeatureReferenceExpression` arguments referring to `ApplyHeat::power`, `::duration` and `::efficiency`; two `SuccessionAsUsage` (`@6`, `@8`); two `Membership`s (`@4`, `@7`) to library features (the `start` and `done` nodes); three `ItemDefinition`s `Start`, `Finish`, `Cancel`. +- `Start`, `Finish` and `Cancel` are referenced by no element in the Chapter 4 export (no typing, subsetting, flow or succession targets them). +- Removed relative to Chapter 3: `Documentation TimelyToast::@0` and `VerificationCaseDefinition TimelyToastTest` with its `doc`, subject and objective (see F-5). The `ConstraintUsage` renumbering `TimelyToast::@2` to `@1` is a consequence of the removed `doc`; the constraint text is identical. +- Succession ends: in the export, the target end of `@6` reference-subsets `calculate` and the target end of `@8` reference-subsets `@7` (`done`); the source end of neither carries a `ReferenceSubsetting`. I did not establish whether that is how v0.9.0 records a `first`/`then` source or a gap, so the start-calculate-done order is read from the text, not confirmed from the export. + +Evidence read for intent: `chapters/ch04-functional-decomp/index.md`, `conclusion.md`, all cells of notebooks `01` to `03`; AGENTS.md Part 2 §1 (Douglas Part 3 story, legacy section, used only as story evidence). "cell-N" below is the 0-based position of a cell in its notebook, not the cell's `id` field. `models/ch05-cumulative.sysml` and `ch08-cumulative.sysml` were read only for how Chapter 4 elements are used downstream, not audited. `scripts/check_construction.py --check --chapter=4` was run (read-only by its docstring): "All 1 chapter(s) consistent." Notebook 03 was executed to a scratch directory outside the worktree (see F-6). `uv run python -m glossary lint --json` reports 0 hits for Chapter 4, confirming the contract's "lint hits: none". + +Status legend as in the Chapter 1 audit: a **finding** is something the model or chapter says that the layer rules say it must not, or a checklist "no"; "not yet built" marks a checklist item that is expected to be absent at this chapter. + +## Classification table + +| Element (qualified name) | Layer | Reason | Status | +|---|---|---|---| +| `ToasterDemo::ApplyHeat` (`action def`) | Mixed: functional signature and name; body carries a mechanism-shaped relation with a performance parameter | Q1 (substitution test, §1.5) holds for "apply heat: energy in, energy delivered out"; a verb-noun function (`def-douglas--function`, `term-functional-architecture`). But its only content beyond the signature is an equality `energy := DeliveredEnergy(power, duration, efficiency)` that takes the conversion efficiency as a given input: a "prescribed, comparatively deterministic input-to-output relation" (`term-mechanism`) parameterized by the glossary's example MoP, power efficiency (`term-mop`). §1.5 states the functional phenomena relation as a balance inequality "without assuming perfect efficiency". | FINDING F-1, F-2, F-4; OPEN-QUESTION OQ-1 | +| `ToasterDemo::ApplyHeat::power` (`in`, `ISQ::PowerValue`) | Functional | Q1: a pop-up toaster (mains power) and tongs with a blowtorch (fuel power) both take in a rate of energy; an energy input flow (`def-douglas--function`: inputs are material, energy or signals). Typed and unit-bearing, no value. | PASS | +| `ToasterDemo::ApplyHeat::duration` (`in`, `ISQ::DurationValue`) | Functional slot by Q1, kind undecided | Q1 holds ("apply heat for a given time" fits both). It is not material or energy; it could be a signal from a control function, a timer setpoint (`term-policy`, DL-022: a setpoint is a policy parameter on the control component) or the heating time, which DL-018 says is a result. Typed, no value. | OPEN-QUESTION OQ-2 | +| `ToasterDemo::ApplyHeat::efficiency` (`in`, `DimensionOneValue`) | Logical by the glossary (a MoP of a conversion); Q1 alone does not exclude it | Not a flow of material, energy or signal (`def-douglas--function`); SEBoK's function is a transformation of flows "with defined performance" (`def-sebok--function`), and efficiency is that performance. `term-mop` names power efficiency as the toaster MoP, "typically logical". Entered as an input, it makes the function's output depend on a characteristic of whatever mechanism is chosen. Unbounded type (no 0 to 1 constraint). | FINDING F-1; OPEN-QUESTION OQ-1 | +| `ToasterDemo::ApplyHeat::energy` (`out`, `ISQ::EnergyValue`) | Functional | Q1: an energy output delivered by any heat source. Typed, no value. Its recipient (the bread) is not modeled, and it is the only output (no loss output). | PASS as an element; flow accounting fails (F-2) | +| `ToasterDemo::ApplyHeat::@4` (`first start`, membership to the library `start` node) | Functional (control flow) | The sequencing of a functional behavior (FFBD ordering, index.md); commits to no mechanism. | PASS | +| `ToasterDemo::ApplyHeat::@7` (membership to the library `done` node) | Functional (control flow) | As above. | PASS | +| `ToasterDemo::ApplyHeat::@6` (`SuccessionAsUsage`, start then `calculate`) | Functional (control flow) | Ordering only. Source end not resolved in the export (see Model evidence). | PASS (source end not confirmed) | +| `ToasterDemo::ApplyHeat::@8` (`SuccessionAsUsage`, `calculate` then done) | Functional (control flow) | As above. | PASS (source end not confirmed) | +| `ToasterDemo::ApplyHeat::calculate` (`ActionUsage`) | Follows the relation it evaluates (OQ-1); not a sub-function | It has no flows of its own and no verb-noun name; its only content is an assignment that evaluates a `calc def`. It is the executable-specification body of `ApplyHeat` (§1.1 item 3), not a finer function (Douglas "decomposing functions into finer functions", skill story anchors 4:02; `def-sebok--decomposition`). | FINDING F-4; layer via OQ-1 | +| `ToasterDemo::ApplyHeat::calculate::@0` (`AssignmentActionUsage` `energy := DeliveredEnergy(power, duration, efficiency)`, with its `InvocationExpression` and three argument references) | Undecided: phenomenon (functional) or characterized conversion (logical) | The equality E = P t η. Holds for any constant-power heat source if η is defined as delivered over supplied energy (functional reading), but it assumes a known efficiency and states no loss (logical reading; `term-mechanism`, `term-mop`, §1.5 constraints split by solution-independence). Its layer is also the layer of `DeliveredEnergy`, a Chapter 3 element (cross-chapter dependency). | OPEN-QUESTION OQ-1; FINDING F-2 | +| `ToasterDemo::Start` (`item def`) | Functional (flow type), denotation undecided | Q1: bread entering (nb02 cell-05) is a material input of any toasting solution. The name denotes an event, not bread. Used by no action in Chapter 4. | FINDING F-3; OPEN-QUESTION OQ-3 | +| `ToasterDemo::Finish` (`item def`) | Functional (flow type), denotation undecided | As `Start`, for toast exiting. | FINDING F-3; OPEN-QUESTION OQ-3 | +| `ToasterDemo::Cancel` (`item def`) | Functional (signal flow type) | Q1: a request to stop heating fits both solutions (a lever, or the user turning off the torch); a signal input (`def-douglas--function`). Conceptual-to-functional test (§1.5): a stop-on-demand need is plausible but not traced to a stakeholder statement in the chapter. Used by no action in Chapter 4. | FINDING F-3 | +| Unnamed supporting elements (`FeatureMembership`, `ParameterMembership`, `ReturnParameterMembership`, `EndFeatureMembership`, `FeatureValue`, `FeatureTyping`, `ReferenceSubsetting`, `Feature`) | Classified with their owners | Structural plumbing of the elements above; no content of their own. | PASS (not separately classified) | +| Removed: `TimelyToast` `doc`; `TimelyToastTest` (`verification def`) | Not classified (Chapter 3 elements) | Present in Chapter 3, absent from the Chapter 4 cumulative fixture. `TimelyToastTest` would not be a layer element anyway (DL-023). | FINDING F-5 | +| Python side: `AI-C04` (`asserted_inference` ReviewRecord, nb03 cell-05) | Not a layer element (argument about the model) | A judgment record is analysis and argument, not a prescription or intent (by analogy with DL-023; `term-asserted-inference`). Checked against the cross-layer judgment item: counterevidence and residual uncertainties are filled; disposition `pending`, not "accepted" (SA-7). | PASS on the judgment fields; FINDING F-2 (criterion) and F-6 (does not execute) | + +## Per-layer checklist results + +**Functional** +- Each action states typed inputs and outputs: `ApplyHeat` does; all four parameters are ISQ or dimension-one typed. PASS. `calculate` states none (F-4). +- All flows accounted for at this level: no. Supplied energy (power times duration) and delivered energy differ by (1 - efficiency) times supplied energy, which leaves as no output; the bread that receives the energy is not an input or output; `Start`, `Finish` and `Cancel` are consumed or produced by no action (F-2, F-3). +- Solution-independent (substitution test): the signature and name pass; the efficiency input and the equality body are contested (F-1, OQ-1). +- Phenomena relations stated as relations (balance inequality), not as a specific part's behavior: no inequality is stated. The relation is an equality with an efficiency parameter. It names no specific part (ApplyHeat is unallocated in Chapter 4), so it is not a part's behavior, but it is not the balance form §1.5 prescribes (F-2, OQ-1). +- At least one MoE about acceptance: none added by Chapter 4. `ApplyHeat` carries no measure; the one measure-like term it carries (efficiency) is the glossary's MoP example. Not yet built for this chapter. +- Reads as an objective: partly. "Deliver energy" says what is good, not what is good enough; there is no threshold or acceptance measure on the function. + +**Logical** +- Chapter 4 adds no logical components, ports or interfaces. Port-type conformance (§1.9, `opensysml-query` recipe 5) remains **open**, not passed: no connection is declared. +- No solution values and no results entered as choices: Chapter 4 adds no attribute values at all. PASS. Whether `efficiency` and `duration` smuggle a logical commitment into a functional action is F-1, OQ-1 and OQ-2. +- MoP thresholds derived from a MoE: none added. Not yet built. + +**Physical** +- Chapter 4 adds no physical elements. Not applicable. + +**Across layers** +- Stopping rule (§1.8): `ApplyHeat` is a leaf that is not concrete, not interfaced and not verified. Expected at Chapter 4; not yet built. (Downstream, `ch05` line 53 allocates it to `HeatingSystem`.) +- An emergent result set as an attribute default and then "verified": Chapter 4 adds none. PASS. OQ-2 notes that `duration` must not become the quantity checked as time to toast (DL-018). +- Judgment recorded with counterevidence and residual uncertainties, no "accepted" disposition: `AI-C04` satisfies the field checks (disposition `pending`, `engineering_conclusion` `undetermined`). Its criterion is weaker than §1.8 completeness (F-2), and it does not execute (F-6). +- Figures show the assembled model: Chapter 4 has no figure. No notebook renders or mentions a diagram, and the notebooks carry no image outputs, although notebook 01 is named `01-action-def-ffbd`. AGENTS.md §1.7 says every chapter shows the assembled model (F-7). + +## Findings + +**F-1. `ApplyHeat` mixes a performance characteristic of the heating mechanism into the flows of a functional action (the contract's flagged check).** +Check: functional checklist items 1 to 3; AGENTS.md §1.5 functional row ("typed flows and the relations among phenomena (an energy balance inequality, which respects conservation without assuming perfect efficiency)") and the constraint split; `term-function`, `term-mop`, `term-mechanism`. +Evidence: +- `in efficiency : DimensionOneValue;` (fixture line 42) is declared as an input alongside `power` and `duration`. It is not a material, energy or signal flow (`def-douglas--function`); in SEBoK's terms it is the "defined performance" of the transformation (`def-sebok--function`), not one of its input flows. +- The glossary's tutorial MoP definition gives "for the toaster, power efficiency" as its example and says "Typically logical" (`def-tutorial--mop`); the architecture-layers example table files "Heating efficiency is at least 0.6" as a logical MoP threshold. +- The body `assign energy := DeliveredEnergy(power, duration, efficiency)` (line 46) makes the output a deterministic function of the inputs, the shape of `term-mechanism` ("a prescribed, comparatively deterministic input-to-output relation"). +- The chapter says the opposite: nb01 cell-01 "each described as *what* it does rather than how it does it"; `conclusion.md` "without committing to how the hardware achieves it". nb01 cell-05 gives the reason the parameters exist: "Three `in` parameters mirror the `DeliveredEnergy` inputs", so the signature was derived from the Chapter 3 calculation, not from the flows of the function. +- What is not contested: `efficiency` is not a flow, and a functional action whose output requires a known efficiency as an input assumes a conversion characteristic that §1.5 says the functional relation must not assume. What is contested (whether the equality itself is a phenomenon or a mechanism) is OQ-1. +Not done: I did not change the model, the notebooks or the chapter text, and did not propose a replacement signature. + +**F-2. Inputs and outputs are not accounted for at the one level Chapter 4 models, and the completeness record checks a weaker criterion.** +Check: functional checklist ("are all flows accounted for at this level?"); AGENTS.md §1.8 ("at every level, account for every input and output"); Douglas Part 3 as recorded in AGENTS.md Part 2 §1 ("Any unaccounted flow is a gap"; entry model bread, `toast bread`, toast). +Evidence: +- Energy: supplied energy is `power * duration`; the only output is `energy = power * duration * efficiency`. The remainder, `(1 - efficiency) * power * duration`, is neither an output nor a stated loss. The skill's functional example is "bread and energy in, toast and lost energy out; energy to the bread plus loss cannot exceed energy supplied". +- Conservation is not enforced: `efficiency` is `DimensionOneValue` with no bound, so the model admits delivered energy greater than supplied energy. No constraint in Chapter 4 or Chapter 3 bounds it. +- Material: `ApplyHeat` does not take bread in or give anything to bread. "Apply thermal energy" in Douglas's first decomposition acts on the bread between "load/position bread" and "remove toast". +- `AI-C04` (nb03 cell-05) claims "The ApplyHeat action decomposition is functionally complete" on the criterion "Every in parameter feeds at least one sub-action; the out parameter is assigned before done". That checks parameter use, not flow accounting. Its own `counterevidence` says "The model does not capture heat loss or warm-up transients — those flows are absent from this decomposition", which is an unaccounted flow by §1.8. `conclusion.md` repeats the weaker criterion ("every input reaches at least one sub-action, and the output is assigned"). +Not done: no edit to the record, the model or the conclusion. + +**F-3. `Start`, `Finish` and `Cancel` are declared as flow types but are no action's input or output, and their names do not say what they denote.** +Check: functional checklist items 1 and 2; §1.8 accounting. +Evidence: the export shows nothing references the three item defs. nb02 cell-01 says `item def` "names the typed flows: the bread entering, the toast exiting, and the signal that cancels the cycle"; nb02 cell-05 says "`Start` and `Finish` mark the bread entering and toast exiting"; nb02 cell-06 and nb03 cell-03 say they "declare typed items for structural use; they appear as part types in `BreadHandling` in Chapter 5, not as references inside `ApplyHeat` itself". The names are events (start, finish), the text says material (bread, toast). Downstream, `ch05` line 54 declares `part def BreadLoader { part bread : Start; }` (a part usage typed by an item def) and `ch08` lines 84 to 86 use the same defs as accepted triggers (`accept Start`, `accept Finish`, `accept Cancel`), so the same definition serves as material and as a signal. The downstream use is observed, not audited. Denotation is OQ-3. +Not done: no rename, no flow added. + +**F-4. Chapter 4 does not decompose a function into finer functions.** +Check: the contract premise and the functional layer's idiom (§1.5: "`action def` with typed in and out flows"); `def-sebok--decomposition` ("decompose a function until implementable system elements can be identified"); `def-douglas--decomposition`. +Evidence: there is no parent function. `ApplyHeat` is owned by the package and composed into no action; the whole's purpose "Transform bread into toast acceptable to its user" is still a `doc` on `ToastingSystem`, whereas DL-019's re-derivation guidance puts it in "a functional construct (an action def with typed flows, or a behavioral requirement def) that the whole performs". The only child of `ApplyHeat` is `calculate`, which is not a verb-noun function and has no flows of its own; it evaluates a calculation. `index.md` acknowledges that Douglas's architecture has about 15 verb-noun functions and that Chapter 4 models one as a worked example; that is a stated scope choice, so the defect recorded here is narrower: what Chapter 4 calls "the functional decomposition of the heating operation" (nb01 cell-07, nb02 cell-06, nb03 cell-03) is one function plus the executable evaluation of a relation, not a decomposition. +Not done: no parent function or sub-functions proposed. + +**F-5. The Chapter 4 cumulative fixture drops two Chapter 3 elements, so it is not cumulative.** +Check: not a layer check; contract premise (the diff is "what Chapter 4 adds") and `index.md` ("The Ch4 cumulative model contains everything from Ch1-3, plus ..."). +Evidence: the diff removes the `doc` rationale of `TimelyToast` and the whole `verification def TimelyToastTest` (Chapter 3 fixture lines 25 to 30 and 35 to 48). Commit `c237300` ("feat(ch2-ch3): add requirement rationale and verification case") changed only the Chapter 2 and 3 fixtures, and the Chapter 5 fixture also lacks the `doc` (first 30 lines read). `scripts/check_construction.py --check --chapter=4` passes because it checks that fixtures load and increments parse, not that each fixture contains its predecessor. This bears on the requirement anatomy (description, rationale, method) taught in Chapters 2 and 3, which is lost from Chapter 4 onward. +Not done: I did not regenerate or edit any fixture, and did not audit Chapters 5 to 8 for the same loss. + +**F-6. Notebook 03 fails at the cell that builds `AI-C04` (not a layer defect; reported because the chapter's completeness claim rests on it).** +Evidence: nb03 cell-02 imports only `Path`, `opensysml` and `format_diagnostics`; cell-05 calls `ReviewRecord`, `hash_content` and `validate_record`. Executed with `jupyter nbconvert --execute` (output written outside the worktree): `NameError: name 'ReviewRecord' is not defined`. DL-014 fix 1 records the same missing import in Chapter 3 notebook 03. `check_construction.py` does not execute this cell. +Not done: no import added. + +**F-7. Chapter 4 shows no view of the model it builds.** +Check: cross-layer checklist, last item; AGENTS.md §1.7 ("every chapter shows the assembled model so that explicit and implicit parts are distinguishable without reading the Python"). +Evidence: no cell in notebooks 01 to 03 renders a diagram, and the notebooks have no image outputs; notebook 01 is named `01-action-def-ffbd` but draws no FFBD. +Not done: no figure made. + +Documentation consistency (reported only, as in the Chapter 1 audit's F-4): `index.md` "Expected result" gives `in power : Real; in duration : Real; in efficiency : Real; out energy : Real`, while the fixture and nb01 cell-04 use `ISQ::PowerValue`, `ISQ::DurationValue`, `DimensionOneValue` and `ISQ::EnergyValue`. `index.md` and `conclusion.md` describe the exercise as an `EjectToast` action; nb01 cell-12 describes a `Brew` action for a coffee maker (nb03 cell-07 a `BrewUnit` action). The exercise itself was not read. + +## Open questions (for the orchestrator to route) + +**OQ-1. Is `ApplyHeat`'s relation `energy = power * duration * efficiency` a phenomena relation (functional) or a characterized conversion carrying a MoP (logical)? (mechanism versus phenomenon)** +- Functional reading: the relation holds for any heat source of constant power, electric or fuel, if efficiency is defined as delivered over supplied energy; a pop-up toaster and tongs with a blowtorch both satisfy it, and the method stops at Q1. No component is chosen in Chapter 4 (`ApplyHeat` is unallocated until `ch05`), and DL-017 makes a law logical when it is "applied to a chosen component". +- Logical reading: the relation takes a known efficiency as an input and states no loss, so it is not the §1.5 balance inequality "without assuming perfect efficiency"; its shape is the glossary's mechanism (`term-mechanism`); its parameter is the glossary's MoP example, "typically logical" (`term-mop`); the architecture-layers table files efficiency thresholds as logical MoPs. A functional statement that needs an efficiency value only makes sense once a conversion has been characterized. +- Cross-chapter dependency: the same question applies to `calc def DeliveredEnergy` (Chapter 3). The Chapter 3 audit and this one should agree, so the ruling belongs to whichever is routed first. +- Recommended default: classify the `ApplyHeat` signature (energy in, energy delivered out) as functional, and the efficiency-parameterized equality as a logical commitment (a characterized conversion whose efficiency is a MoP) placed in a functional action. F-1 and F-2 stand under either reading. + +**OQ-2. What is `ApplyHeat::duration`: a signal flow, a policy setpoint, or a result?** +- Signal flow (functional): Q1 holds; a control function telling the heater how long to heat is a behavioral dependency (§1.5 connectivity), and any solution has one (a timer or a user). +- Policy setpoint (logical): a chosen heating time is an open-loop timer, a policy parameter (`term-policy`); DL-022 puts a setpoint on the control component, named as a setpoint. A closed-loop solution that stops on browning has no such input, so the parameter commits to a policy. +- Result (emergent): the time to acceptable toast follows from power, bread and control (DL-018, `term-behavior`); if this parameter is identified with it, entering it as an input repeats the Chapter 1 F-1 pattern. +- Evidence in the chapter: nb01 cell-05 says the parameters "mirror the `DeliveredEnergy` inputs"; nothing links `duration` to `Toaster::cycleTime` or to `ControlSystem`. +- Recommended default: a functional input slot (typed, no value) whose source function is not yet modeled, with the constraint that it is never the quantity a requirement checks as time to toast (DL-018, DL-022). + +**OQ-3. What do `Start` and `Finish` denote: material (bread in, toast out), events (cycle start and finish), or both?** +- Material: nb02 cell-01 and cell-05 say so; `ch05` types `part bread : Start` and `part bread : Finish`. +- Events or signals: the names are events; `ch08` uses them as accepted triggers of a state machine; nb02 groups them with `Cancel`, which the text calls a signal. +- Both: the fixtures use one definition in both roles, which a flow-accounting check (F-2, F-3) cannot audit until one denotation is chosen. +- Recommended default: classify all three as functional flow types (Q1 holds under any denotation) and record the denotation as undecided for the re-derivation. This is also a cross-chapter dependency for the Chapter 5 and Chapter 8 audits. + +## Contract premises that did not hold + +1. **"Chapter 4 is the functional decomposition with verb-noun functions and typed flows."** Partly. There is one verb-noun function (`ApplyHeat`) with typed attribute parameters and three typed item defs. There is no parent function and no finer function (F-4): the only sub-action, `calculate`, evaluates a calculation and has no flows. The item defs are typed but unattached (F-3). One parameter, `efficiency`, is not a flow (F-1). +2. **"Every input and output is accounted for at each level."** Does not hold. Lost energy, bread and toast are unaccounted, and the three item defs are no action's input or output (F-2, F-3). `AI-C04` asserts completeness on a parameter-use criterion, and its own counterevidence names the missing flows. +3. **"No mechanism appears in a functional action."** Does not hold on the uncontested part and is contested on the rest. A performance characteristic of the conversion (`efficiency`, the glossary's MoP example) is an input of the functional action (F-1). Whether the equality it feeds is itself a mechanism is OQ-1. +4. **Implicit premise: the Chapter 3 to Chapter 4 diff is only what Chapter 4 adds.** Does not hold: the diff also removes the `TimelyToast` rationale and `TimelyToastTest` (F-5). They are reported, not classified as additions. +5. **"Lint hits for this chapter: none."** Holds (`glossary lint --json`, 0 hits under `chapters/ch04-functional-decomp`). + +## Constructs that could not be classified cleanly + +- `ApplyHeat` as a whole: signature functional, body undecided (OQ-1). Reported as mixed, not given a single layer. +- `ApplyHeat::calculate` and its assignment: their layer is the layer of the relation they evaluate, which is OQ-1, and depends on the Chapter 3 `DeliveredEnergy`. +- `ApplyHeat::duration`: functional by Q1, but three readings of what it is (OQ-2). +- `Start` and `Finish`: layer clear (functional flow types), denotation not (OQ-3). +- `AI-C04`: a Python judgment record, not a model element; classified as not a layer element by analogy with DL-023, which rules on verification cases, not judgment records. The analogy is mine, not a ruling. + +## Not checked, and why + +- **Succession source ends:** the export does not show a `ReferenceSubsetting` on the source end of `@6` or `@8`. I did not probe whether this is v0.9.0's normal representation of `first start; then ...` or a gap, so the ordering is taken from the text. +- **Library targets of `@4` and `@7`:** the member elements are library ids not present in the export by qualified name; I took them to be `start` and `done` from the text. +- **Notebooks 01 and 02 end to end:** not executed; their increments pass `check_construction.py`. Notebook 03 was executed (F-6). +- **Exercise `exercises/ch04/exercise.ipynb`:** not in scope. +- **Chapters 3, 5 and 8:** read only for dependencies. The loss in F-5 was checked only for the Chapter 5 fixture's first 30 lines, not for Chapters 6 to 8. +- **Douglas Part 3 content** is taken from AGENTS.md Part 2 §1 (legacy) and the skill's anchors; timestamps not re-verified and the video not re-watched. +- **Glossary sources:** `uv run python -m glossary check` passes (0 errors, 7 warnings); the warnings say the source PDFs are not in `glossary/sources/local/`, so I relied on the recorded definitions, not the source texts. +- **Tall seam labels** (A-F, O-S) in nb01 cell-11, nb02 cell-10 and nb03 cell-06: out of scope; parked for Pass 4 by DL-028. + +Model: claude-opus-5-5[1m] diff --git a/decisions/audits/ch05-layer-audit.md b/decisions/audits/ch05-layer-audit.md new file mode 100644 index 0000000..ec3c590 --- /dev/null +++ b/decisions/audits/ch05-layer-audit.md @@ -0,0 +1,218 @@ +# Chapter 5 layer audit + +Contract PASS2-008-D, 2026-09-26. Role: `.claude/agents/layer-auditor.md`. Model: claude-opus-5-5[1m] (effort high). +Branch `audit/ch05`, base commit `927f04f`. + +Subject: the elements Chapter 5 adds, that is, the diff between `models/ch04-cumulative.sysml` (52 lines) and `models/ch05-cumulative.sysml` (60 lines). Both are generated fixtures and were not edited. The diff is lines 53 to 60 of the ch05 fixture, plus the header comment on line 3: + +``` +allocate ApplyHeat to HeatingSystem; +part def BreadLoader { part bread : Start; } +part def BreadEjector { part bread : Finish; } +part def BreadHandling { + part loader : BreadLoader; + part ejector : BreadEjector; + flow loader.bread to ejector.bread; +} +``` + +Method: the four-question pass and the per-layer checklist in `.claude/skills/architecture-layers/SKILL.md`, AGENTS.md §1.5 and §1.6, the glossary (`uv run python -m glossary tutorial TERM` for logical component, allocation, mechanism, physical architecture, logical architecture, functional architecture, interface, selection among alternatives, perform action, specialization, abstract definition, part definition), and the ACE rulings DL-018 to DL-023 as settled precedent. I did not re-argue those rulings. + +Evidence collected by running things: +- Both fixtures loaded with OpenSysML v0.9.0 (`load_from_content`, `strict=False`): `model.ok == True`, no diagnostics. +- A diff of the API JSON export by qualified name (helpers from `src/toaster/query.py`: `ApiIndex`, `find_allocations`, `find_connectors`, `perform_relationships`, `port_type_mismatches`, `specialization_graph`). The added elements are exactly: one `AllocationUsage` (`ToasterDemo::@19`, unnamed), three `PartDefinition`s, four `PartUsage`s and one `FlowUsage` (`ToasterDemo::BreadHandling::@2`, unnamed). Metaclass count deltas also show the implied `ReferenceUsage`, `FeatureChaining`, `ReferenceSubsetting`, `EndFeatureMembership` and `FeatureTyping` elements that belong to those. +- The allocation's two connector ends reference `ToasterDemo::ApplyHeat` (`ActionDefinition`) and `ToasterDemo::HeatingSystem` (`PartDefinition`): definitions, not usages. +- The flow's ends are `BreadHandling::loader` then `BreadLoader::bread`, and `BreadHandling::ejector` then `BreadEjector::bread`. Both `bread` features are `PartUsage`s typed by `ItemDefinition`s (`Start` and `Finish`), which have no supertypes in common. +- `perform_relationships(...) == []`. There are no `PortDefinition`, `PortUsage` or `InterfaceDefinition` elements. `port_type_mismatches(...) == []`. `HeatingSystem` has no `isAbstract` flag. Nothing specializes `HeatingSystem`, `BreadLoader`, `BreadEjector` or `BreadHandling`, and they specialize nothing new. No usage is typed by `BreadHandling` or by `ApplyHeat`. +- sysml-toolkit v0.9.1 (`~/Documents/GitHub/sysml-toolkit/target/release/sysmlv2 check --lib .../spec-refs/SysML-v2-Release/sysml.library`) was run on both fixtures. For ch04 it reports no errors. For ch05 it reports one error on line 53: `ReferenceSubsetting::referencedFeature must refer to a Feature [relationship-endpoint-metaclass]`. AGENTS.md §1.2 allows the toolchain to be cited only to flag a spec gap, which is the only way it is used here. +- The metamodel files vendored in that checkout (`spec-refs/KerML.xmi` and `spec-refs/SysML.xmi`, OMG metamodel 20250201) were read for two points. `ReferenceSubsetting::referencedFeature` is described as "The `Feature` that is referenced". `PartUsage` carries the constraint `validatePartUsagePartDefinition` ("At least one of the itemDefinitions of a PartUsage must be a PartDefinition"; OCL `partDefinition->notEmpty()`). + +Evidence read for intent: `chapters/ch05-architecture/index.md`, `conclusion.md`, and every cell of notebooks `01-concept-selection`, `02-allocate` and `03-interfaces`. Chapter 4 notebook `02-heating-refinement` (cells 1, 5 and 6) was read only for what `Start` and `Finish` mean. `models/ch06` to `ch08-cumulative.sysml` were read only to see how the Chapter 5 additions are used downstream; they were not audited. + +## Classification table + +Elements from earlier chapters that the additions reference are shown in *italics* for context. They are not audited here. + +| Element (qualified name) | Layer | Reason | Status | +|---|---|---|---| +| `allocate ApplyHeat to HeatingSystem` (`AllocationUsage` `ToasterDemo::@19`, unnamed) | Cross-layer relation: a function (functional) assigned to a logical component (logical) | Allocation assigns functions to logical components (`term-allocation`, AGENTS.md §1.5 gloss). DL-020 already classifies the target `HeatingSystem` as a logical component, not yet built. The source `ApplyHeat` reads as functional (OQ-4). So the relation goes to a logical component, not to a physical part. It is declared between definitions, though, which does not conform to the language. It has no name, so `model.query()` cannot see it. And no `perform` expresses the responsibility. | FINDING F-1, F-2 | +| *`ToasterDemo::ApplyHeat` (ch04 `action def`)* | *Functional, as read here* | *Depends on the Chapter 4 audit (OQ-4).* | *context* | +| *`ToasterDemo::HeatingSystem` (ch01 `part def :> ToastingSystem`)* | *Logical, not yet built* | *DL-020.* | *context* | +| `ToasterDemo::BreadLoader` (`part def`) | Logical, not yet built (DL-020 precedent) | Q3 does not apply: no specific part is named and no value is chosen (§1.5 logical-to-physical test). Like `HeatingSystem`, it is a responsibility grouping with no value and no modeled mechanism, and DL-020 classes that as logical, not yet built. It does not follow the logical idiom: it is concrete, has no `perform`, no port, and no function allocated to it (`term-logical-component`). | FINDING F-4 | +| `ToasterDemo::BreadLoader::bread : Start` (`PartUsage` typed by an `ItemDefinition`) | Logical by role: a flow endpoint standing in for an interface | Its only job is to be an end of the flow. §1.5 connectivity rule: logical connectivity is interface compatibility. It is a part usage typed by an item def, which violates `validatePartUsagePartDefinition`, so the construct itself is not valid SysML v2. | FINDING F-3, F-5 | +| `ToasterDemo::BreadEjector` (`part def`) | Logical, not yet built (DL-020 precedent) | Same as `BreadLoader`. The name "ejector" may commit to a pop-up mechanism (OQ-1). | FINDING F-4; OPEN-QUESTION OQ-1 | +| `ToasterDemo::BreadEjector::bread : Finish` (`PartUsage` typed by an `ItemDefinition`) | Logical by role (flow endpoint) | Same as `BreadLoader::bread`. | FINDING F-3, F-5 | +| `ToasterDemo::BreadHandling` (`part def`) | Logical arrangement (DL-021 precedent) | It composes two slots and a flow, with no specific part and no value (§1.5 logical-to-physical test; heuristic "arrangement before sizing" in DL-021). It is not part of the system of interest: no usage is typed by it. | FINDING F-4, F-6 | +| `ToasterDemo::BreadHandling::loader : BreadLoader` (`PartUsage`) | Logical (follows its type) | A slot in the arrangement. | PASS | +| `ToasterDemo::BreadHandling::ejector : BreadEjector` (`PartUsage`) | Logical (follows its type) | A slot in the arrangement. | PASS | +| `flow loader.bread to ejector.bread` (`FlowUsage` `ToasterDemo::BreadHandling::@2`, unnamed) | Logical: interconnection between components | §1.5 "Connectivity differs by layer": connectivity between structural components is interface compatibility, which is logical. The skill's logical idiom is `port def`, `interface def`, `connection`, `flow`. There are no ports. The end types (`Start`, `Finish`) are unrelated. No payload is declared, and the flow has no name. | FINDING F-5; OPEN-QUESTION OQ-2, OQ-3 | + +No MoE, MoP, TPM, requirement, constraint, attribute, metadata or specialization is added in Chapter 5. + +## Per-layer checklist results + +**Functional** +- Chapter 5 adds no functional element. +- The only function involved, `ApplyHeat`, is the allocation source. No action exists for loading or ejecting bread, so `BreadLoader` and `BreadEjector` have no function to be responsible for (F-4). +- No MoE is added. Not applicable. + +**Logical** +- *Does each mechanism have a carrier and an interface?* No. No mechanism is carried by any component: there is no `perform`, and no constraint or calc is owned by a component. `HeatingSystem` gains an allocation and nothing else (F-2). +- *Do the interfaces actually match?* This is **open**, not passed. No port or interface def exists. Recipe 5 (`port_type_mismatches`) returns `[]` only because it considers `PortUsage` ends and there are none, so the empty result is vacuous. The ends that do exist are typed by the unrelated item defs `Start` and `Finish` (F-5, OQ-3). +- *Are MoP thresholds derived?* None are added. Not applicable. +- *Are there no solution values and no results entered as choices?* PASS for the additions: none carry a value. +- *Does it read as a design space?* Partly. `BreadHandling` is a slot structure, but its slots are not typed by abstract logical defs and it carries no constraints. + +**Physical** +- Chapter 5 adds no physical element: no concrete part def specializes an abstract logical def, and no value is conferred. The contract premise on this point does not hold (see below). +- The abstract-to-concrete chain does not exist in the ch05 model. The only abstract part def is `ToastingSystem`, which DL-019 makes the subject, not a layer. `HeatingSystem` is concrete and nothing specializes it. `Heater` (ch01) specializes nothing (ch01 F-2 still stands). `BreadLoader`, `BreadEjector` and `BreadHandling` are concrete and specialize nothing. + +**Across layers** +- *Stopping rule* (every leaf concrete, interfaced and verified, §1.8): no leaf meets it. Not yet built. +- *Emergent result set as a default and then "verified"*: none added in Chapter 5 (the ch01 `cycleTime` defect, DL-018, is inherited unchanged). +- *Judgment recorded*: none is recorded. The one design choice Chapter 5 makes is which component is responsible for heating, and it is not presented as a judgment. Notebook 01 is titled "Concept Selection" but does no selection among alternatives (F-8). +- *Figures*: notebook 03 cell 10 renders the interconnection SVG to a temporary directory and prints its byte size. It is not shown, has no caption, and covers only `BreadHandling`, not the assembled model (F-9). + +## Findings + +**F-1. The allocation is declared between definitions. That does not conform to the language, and OpenSysML does not diagnose it.** +Element: `allocate ApplyHeat to HeatingSystem` (`ToasterDemo::@19`). +Check: the SysML v2 idiom table in the skill (allocation "between usages", or an `allocation def` with typed ends), and AGENTS.md §1.9 (language conformance breaks the load). +What is wrong: +- The export shows each connector end's `ReferenceSubsetting` pointing at an `ActionDefinition` and a `PartDefinition`. The KerML metamodel types `referencedFeature` as a `Feature`, and a definition is not a feature. sysml-toolkit v0.9.1 reports exactly this as an error on line 53. OpenSysML v0.9.0 loads it with `ok=True`. +- Compare G2 in `decisions/probes.md`: OpenSysML correctly rejects `perform ToastBread;` because a def is not a usage. It applies no such rule to `allocate` ends. That looks like a tool gap in the G4 family. +- The allocation has no name, so `model.query()` cannot see it, against the convention "Name allocations so `model.query()` sees them" (skill; `opensysml-query`). Notebook 02 reads it back from the JSON export instead. +- No usage of `ApplyHeat` exists anywhere in the model, so a usage-level allocation, the form the skill lists as tested, could not be written against the current model. +- The line appears unchanged in `ch06`, `ch07` and `ch08` (line 53 in each). + +What I did not do: I did not change the model or file an upstream issue, and I did not add a probe row to `decisions/probes.md`. + +**F-2. There is allocation without responsibility: no `perform`, and no abstract logical def to carry it.** +Element: the allocation and its target `HeatingSystem`. +Check: the logical idiom in the AGENTS.md §1.5 table (`abstract part def` with `perform action x : ActionDef`); `term-logical-component` ("modeled here as an abstract part definition that performs an action"); the skill's logical checklist, first item. +What is wrong: +- `perform_relationships` returns nothing for the whole ch05 model. +- `HeatingSystem` is concrete. +- The allocation states that heating is assigned to `HeatingSystem`, but nothing in `HeatingSystem` performs `ApplyHeat`, carries a mechanism or exposes an interface. + +DL-020 already records `HeatingSystem` as "logical, not yet built". This finding records that Chapter 5, the chapter that introduces allocation, does not build it either. + +What I did not do: I did not propose where `perform` or `abstract` should be introduced. + +**F-3. `part bread : Start` and `part bread : Finish` type part usages by item definitions, which the language does not allow.** +Elements: `BreadLoader::bread` and `BreadEjector::bread`. +Check: language conformance (AGENTS.md §1.9). +What is wrong: +- `SysML.xmi` constraint `validatePartUsagePartDefinition` requires that at least one definition of a `PartUsage` be a `PartDefinition`. `Start` and `Finish` are `ItemDefinition`s, and the export confirms the usages are `PartUsage`s typed only by them. +- Neither OpenSysML v0.9.0 nor `sysmlv2 check` v0.9.1 reports it. That is a second undiagnosed language rule, and it should be recorded as a gap. +- The model also contradicts the tutorial's own text. Chapter 4 notebook 02 cell 1 says "Items are not parts", and cell 6 says the items "appear as part types in `BreadHandling` in Chapter 5". + +What I did not do: I did not rewrite the declarations, for example as `item` or port usages. + +**F-4. `BreadLoader`, `BreadEjector` and `BreadHandling` are components with no function.** +Check: +- Cross-layer traceability. +- Allocation (`term-allocation`: functions are assigned to logical components; Douglas: components group functions). +- The §1.5 boundary test *Conceptual to functional* ("Do not invent functions they have not asked for"). +- The logical checklist, first item. + +What is wrong: +- The functional layer has no action for loading or ejecting bread. +- Nothing is allocated to or performed by these three defs. +- They are concrete, specialize nothing, and nothing specializes them. + +So the chapter adds logical structure that traces to no function, and it introduces the responsibilities "load" and "eject" without a functional statement behind them. + +What I did not do: I did not propose functions or allocations for them. + +**F-5. The flow is not an interface in the tutorial's sense, and its two ends carry unrelated item types.** +Element: `flow loader.bread to ejector.bread` (`BreadHandling::@2`). +Check: the logical checklist ("Do the interfaces actually match?"), the skill idiom (`port def`, `interface def`, `connection`, `flow`), and DL-023 (interface compatibility is logical and is checked as staged project conformance). +What is wrong: +- (a) No port def, port usage or interface def exists. The ends are part usages typed by item defs (F-3). +- (b) One end is typed `Start` and the other `Finish`, and neither specializes the other. Chapter 4 notebook 02 cell 5 says `Start` and `Finish` "mark the bread entering and toast exiting". Read that way, the flow says entering bread arrives at the ejector as exiting toast, and the path never passes the heating component. +- (c) The flow declares no payload item. +- (d) The flow has no name, so `model.query()` cannot see it. +- (e) Recipe 5 returns `[]` here only because there are no `PortUsage` ends. Reading that as a pass would be wrong, so this audit reports the check as **open**. + +What I did not do: I did not extend `port_type_mismatches` to item-typed ends, and I did not retype the ends. + +**F-6. `BreadHandling` is not part of the system of interest.** +Check: cross-layer; DL-019 and DL-021 (the whole is the subject, and its composition is the logical arrangement). +What is wrong: no usage anywhere is typed by `BreadHandling`, and `Toaster` composes only `heating` and `control`. The bread flow therefore sits outside the system whose arrangement the layers describe. Read as text, `ch06` to `ch08` still do not compose it into `Toaster`. +What I did not do: I did not add a usage. + +**F-7. The chapter text contradicts the layer rules and the model.** This is documentation consistency, not a model defect. +- Notebook 01 cell 3, notebook 02 cell 3 and notebook 03 cell 7 (the same paragraph three times) call the allocation "the functional-to-physical assignment". The target is logical under DL-020, and nothing physical is involved. +- The same paragraph says the constructs "connect the functional layer (actions) to the structural layer (parts)". "Structural layer" is not a tutorial layer (§1.5). +- It also says the flow "expresses the item flow at the port level". There are no ports. +- Notebook 02 cell 0: "express which hardware component is responsible for which function". "Hardware" is physical. +- Notebook 03 cell 1: "connecting two `PartUsage` members by their item ports". There are no ports. +- `conclusion.md`: "The `allocate` statement makes explicit ... that `HeatingSystem` realizes `ApplyHeat`." AGENTS.md §1.5: "Allocation is not realization ... A concrete part def *specializes* the abstract logical part def to realize it." +- `conclusion.md`: "The interconnection SVG confirms that the structural connectivity is readable and matches the model." Under §1.7 a diagram is a view generated from the model, so it cannot confirm that it matches the model, and it is not evidence. +- Notebook 02 cell 2 cites "§7.22 (AllocationUsage)" and notebook 03 cell 6 cites "§7.23 (FlowConnectionUsage)". The skill cites 7.15.2 for allocation and 7.12 to 7.14 for flows, and the exported metaclass is `FlowUsage`. I did not verify which section numbers are right. + +Reported only; no edits. + +**F-8. Vocabulary lint hit, and a title that names a concept the notebook does not teach.** +- `chapters/ch05-architecture/index.md` line 13 contains "Concept Selection". `uv run python -m glossary lint` reports it as rule `concept-selection` (error; the tutorial says "selection among alternatives"). It is the only lint hit in `chapters/ch05-architecture/`. +- The notebook file is named `01-concept-selection.ipynb`. The rule's regex `\bconcept\s+selection\b` does not match the hyphenated form, so the filename, and the link target on line 13, pass the lint unnoticed. +- The notebook's content is navigation by qualified name (`model.find`, `model.get`). It contains no alternatives, no trade study and no derived measures, so it does not teach selection among alternatives (`term-selection-among-alternatives`: "choosing among alternative mechanisms by trade study against the derived measures"). Renaming the title alone would leave a title that does not match the content. + +What I did not do: I did not edit the title or the filename, and I did not edit the lint rules. + +**F-9. The interconnection figure is not shown and has no caption.** +Check: the cross-layer checklist's last item and AGENTS.md §1.7. +What is wrong: notebook 03 cell 10 writes `bread_handling.svg` to a `tempfile.mkdtemp()` directory and prints only its path and size. The learner never sees it, no caption states what it includes or omits, and it covers `BreadHandling` only, not the assembled model. +What I did not do: I did not render or inspect the SVG. + +## Open questions (for the orchestrator to route) + +**OQ-1. Does the name `BreadEjector` commit to a pop-up mechanism before any selection among alternatives?** +- Reading A, a responsibility grouping (DL-020 pattern), logical and not yet built. Loading and removing bread happen for tongs-with-a-blowtorch too: the user places and removes it. The def carries no mechanism, constraint or value. +- Reading B, a named mechanism. "Eject" is what a spring-loaded pop-up toaster does, and tongs do not eject. Under Q2 that commits to a mechanism, so the logical layer would already have chosen the pop-up branch without a recorded selection among alternatives. +- Both readings give the layer "logical". They differ on whether an unrecorded selection has been made. +- Recommended default: Reading A. Note that the name leans towards the pop-up solution, which is worth fixing when the chapter is re-derived. + +**OQ-2. What does the flow from `loader.bread` to `ejector.bread` denote?** +- Reading A, a material flow of bread. Chapter 4 notebook 02 cell 5 says `Start` and `Finish` mark the bread entering and the toast exiting. On this reading the two ends should carry one item type, or related ones (for example bread, with toast as a state or a specialization), so the `Start`/`Finish` mismatch is a defect (F-5(b)). And a material path from loader to ejector that bypasses heating is incomplete. +- Reading B, event or control signals. `Start`, `Finish` and `Cancel` read like events, and Chapter 4 calls `Cancel` a signal. On this reading the flow is a control dependency and is placed on the wrong elements. +- Recommended default: Reading A. Route the naming of the items to the Chapter 4 audit (cross-chapter dependency). + +**OQ-3. From which chapter does the interface-compatibility check apply, and does it cover item-typed flow ends?** +- Reading A: Chapter 5 is the first chapter that declares a connection, so under DL-023 the staged check applies from here. It should then be widened beyond `PortUsage` ends, or the chapter should use ports. +- Reading B: the connection is not declared complete, because there are no ports or interface defs, so the check stays open until a chapter introduces ports. +- Recommended default: Reading B. Report the check as open. Ask whether `port_type_mismatches` should also compare item-typed flow ends; that is a scope change to a tested helper, and it is not mine to make. + +**OQ-4 (cross-chapter). Is `ApplyHeat` a functional element?** +- My classification of the allocation as "function to logical component" assumes it is. +- Functional reading: its inputs (power, duration, efficiency) and its relation (`DeliveredEnergy = power * duration * efficiency`) hold for a coil and for a blowtorch alike, so it passes the substitution test. +- Against: "efficiency" as an input, and the lack of any bread or toast flow, may make it a mechanism statement rather than a function with typed flows (`term-functional-architecture`). +- Recommended default: functional. Route to the Chapter 4 audit. If it is ruled logical, the allocation becomes logical-to-logical and F-4 and F-7 need re-reading. + +## Contract premises that did not hold + +1. **"Chapter 5 introduces the logical-to-physical architecture and allocation."** Holds only for allocation. Chapter 5 adds one allocation, from a function to a logical component (DL-020), declared between definitions (F-1). It adds no physical element, no realization by specialization and no logical-to-physical allocation. The chapter text calls the allocation "functional-to-physical" (F-7), which contradicts both the model and DL-020. What Chapter 5 actually introduces is function-to-component allocation and one flow between structural parts. +2. **"Logical components carry mechanisms and interfaces."** It holds as a rule (AGENTS.md §1.5, `term-logical-component`), not in the ch05 model. No component carries a mechanism, a `perform` or a port. The only interconnection is a flow between item-typed part usages (F-2, F-3, F-5). + +The contract's specific checks: +- **Does `allocate ApplyHeat to HeatingSystem` go to a logical component or to a physical one?** To a logical component, by DL-020 and the absence of any value or specialization on `HeatingSystem`. It does so at definition level, which does not conform to the language (F-1). +- **Does the abstract/concrete chain exist?** No. See the physical checklist above. +- **Is `perform` used?** No, nowhere in the model (F-2). +- **Vocabulary lint:** confirmed, `index.md` line 13, rule `concept-selection` (F-8). + +## Constructs that could not be classified cleanly + +- `BreadLoader::bread` and `BreadEjector::bread`: they are not valid constructs (F-3), so they are classified only by role, as flow endpoints (logical). +- The allocation and the flow are relations. I classified them by the layers of their ends (cross-layer, and logical connectivity) rather than giving them a layer of their own. For the allocation, the result depends on OQ-4. +- `BreadEjector`: logical either way, but whether its name commits to a mechanism is open (OQ-1). + +## Not checked, and why + +- **The rendered SVG and the Chapter 5 exercise** (`exercises/ch05/exercise.ipynb`): out of scope. F-9 rests on reading the cell source. +- **Chapter 4 elements** (`ApplyHeat`, `Start`, `Finish`, `DeliveredEnergy`) and the Chapter 1 elements the additions reference: not audited; they appear only as context and in OQ-2 and OQ-4. +- **Spec version.** The metamodel facts come from the OMG 20250201 XMI vendored in the sysml-toolkit checkout, not from formal/2026-03-02, whose PDF is not in `glossary/sources/local/`. I did not confirm that the two constraints (`ReferenceSubsetting::referencedFeature` typed `Feature`, and `validatePartUsagePartDefinition`) read the same in the formal release. +- **Spec section numbers** in the notebooks (F-7, last bullet): not verified. +- **Whether OpenSysML or sysml-toolkit have issues open** for F-1 or F-3: not checked. Nothing was filed. +- **Glossary sources:** `uv run python -m glossary check` passes in this worktree (0 errors, 7 warnings). The warnings say the local source PDFs are absent, so source hashes were not verified. I relied on the glossary's recorded definitions. +- **Downstream chapters:** `ch06` to `ch08` were read as text only (the allocation, `BreadHandling` and `HeatingAssembly :> HeatingSystem` from `ch06` on). Nothing about them is a finding on those chapters. diff --git a/decisions/audits/ch06-layer-audit.md b/decisions/audits/ch06-layer-audit.md new file mode 100644 index 0000000..3a9c3ae --- /dev/null +++ b/decisions/audits/ch06-layer-audit.md @@ -0,0 +1,286 @@ +# Chapter 6 layer audit + +Contract PASS2-011-A, 2026-09-27. Role: `.claude/agents/layer-auditor.md`. Model: claude-opus-5-5[1m] (effort high). +Branch `audit/ch06`, base commit `a84dceb`. + +Subject: the elements Chapter 6 adds, that is, the diff between `models/ch05-cumulative.sysml` (60 lines) and `models/ch06-cumulative.sysml` (82 lines). Both are generated fixtures and were not edited. The diff is lines 54 to 75 of the ch06 fixture, plus the header comment on line 3: + +``` +requirement def HeatingReq { + subject heater : Heater; + require constraint { heater.power >= 600.0 [SI::W] } +} +requirement heating : HeatingReq; +part efficient : Heater; +part weak : Heater { attribute :>> power = 400.0 [SI::W]; } +abstract part def HeatingElement; +part def ResistanceCoil :> HeatingElement { + attribute resistance : Real default = 12.0; +} +part def PowerWire :> HeatingElement { + attribute gauge : Real default = 14.0; +} +part def HeatingAssembly :> HeatingSystem { + part coil : ResistanceCoil; + part wire : PowerWire; +} +part heatingEvidence { + assert satisfy heating by efficient; + assert satisfy heating by weak; +} +``` + +Method: the four-question pass and the per-layer checklist in `.claude/skills/architecture-layers/SKILL.md`, AGENTS.md §1.5 to §1.9, `z-principles.md` (F1 to F7, P1 to P6, and the confirmed extensions), the glossary (`uv run python -m glossary tutorial TERM` for specialization, physical architecture, logical component, mop, tpm, traceability, decomposition, requirement, usage, asserted inference, abstract definition, part definition, emergence), and the ACE rulings DL-018 to DL-039 as settled precedent. I cite DL numbers by the headings in `decisions/log.md` (see premise 6 on a numbering mismatch). I did not re-argue those rulings. + +Evidence collected by running things: +- `uv run python scripts/check_conformance.py models/ch06-cumulative.sysml --stage 6,0`: `ok=True`, no diagnostics, four gap findings (`allocate-between-definitions` twice on `ToasterDemo::@19`; `part-typed-only-by-item-def` on `BreadLoader::bread` and `BreadEjector::bread`). All four are on Chapter 5 lines (53, 76, 77); none is on a Chapter 6 addition. Both project checks (`port-type`, `satisfaction-claims-evaluated`) are `blocked` with the unblock criterion "no language-tier violation, per the spec, is present". Exit code 0. +- Both fixtures loaded with OpenSysML v0.9.0 (`load_from_content`, `strict=False`): both `ok == True`. A diff of the API JSON export by qualified name (helpers in `src/toaster/query.py`) gives exactly these added named elements: `RequirementDefinition HeatingReq` with `ReferenceUsage HeatingReq::heater` (subject) and `ConstraintUsage HeatingReq::@1`; `RequirementUsage heating`; `PartUsage efficient`, `weak` (with `AttributeUsage weak::@0`, a redefinition of `power`); `PartDefinition HeatingElement` (`isAbstract` true), `ResistanceCoil` (with `resistance`), `PowerWire` (with `gauge`), `HeatingAssembly` (with `PartUsage coil`, `wire`); `PartUsage heatingEvidence` with two `SatisfyRequirementUsage`s (`@0`, `@1`). Metaclass deltas include 3 `Subclassification`s and 1 `Redefinition`, and no `AllocationUsage`, `PerformActionUsage`, `PortDefinition`, `PortUsage`, `InterfaceDefinition`, `ConnectionUsage` or `FlowUsage`. +- `perform_relationships(m6) == []`. `find_allocations(m6)` returns only the Chapter 5 `@19`. `allocations_for(m6, "ToasterDemo::HeatingAssembly")` returns `@19` by inheritance through `HeatingAssembly :> HeatingSystem`. `port_type_mismatches(m6) == []` (vacuous: no port ends). +- Specialization closure: `HeatingAssembly` has supertypes `{HeatingSystem, ToastingSystem}` and nothing specializes it or types a usage by it. `HeatingElement` has no supertypes; its subtypes are `ResistanceCoil`, `PowerWire` and the two `HeatingAssembly` slots. `Heater` has no supertypes; the only usages typed by it are `HeatingReq::heater`, `efficient` and `weak`. `HeatingSystem`'s only usage is still `Toaster::heating`. +- Satisfaction claims: the registered check is blocked, so I called `toaster.conformance.satisfaction_claims_evaluated(m6)` directly, as a diagnostic and not as a check verdict. It returns two findings: `evidence::@1` (`timely(slow)` is False, Chapter 3) and `heatingEvidence::@1` (`heating(weak)` is False, Chapter 6). Direct `model.eval`: `HeatingReq(efficient)` True, `HeatingReq(weak)` False, `heating(efficient)` True, `heating(weak)` False. +- Probe: `attribute r : ISQ::ResistanceValue default = 12.0 [SI::ohm];` loads with `ok=True` in OpenSysML v0.9.0 (a unit-bearing type for the coil's resistance is expressible). `[SI::Ω]` does not parse. +- The code cells of the three notebooks were executed in order from their directory (source only, outputs not written): notebooks 01 and 02 run; notebook 03 stops at cell-04 with `NameError: name 'ReviewRecord' is not defined`. +- `uv run python -m glossary lint`: 8 hits, none in `chapters/ch06-recursive-decomp/`. `uv run python -m glossary check` passes in this worktree (see Not checked). + +Evidence read for intent: `chapters/ch06-recursive-decomp/index.md`, `conclusion.md`, and every cell of notebooks `01-subsystem-requirements`, `02-second-level` and `03-stopping-judgment`. `exercises/ch02/exercise.ipynb` and `exercises/ch03/exercise.ipynb` were read only to locate the identifier `AC-C01`. `src/toaster/conformance.py` was read for the check's behaviour. + +## Classification table + +Elements from earlier chapters that the additions reference are shown in *italics* for context. They are not audited here. + +| Element (qualified name) | Layer | Reason | Status | +|---|---|---|---| +| `ToasterDemo::HeatingReq` (`requirement def`) | Logical: the form of a derived MoP threshold; the MoE or MoP label is unrecorded | Skill Q2 and example row "Heating efficiency is at least 0.6" (logical, MoP threshold); AGENTS.md §1.5 "Constraints, split" (a derived MoP threshold is logical) and "Numbers"; `term-mop`. A minimum heating power is an engineering performance measure. No label or justification is recorded (P2; DL-035 pattern). | FINDING F-3, F-4; OPEN-QUESTION OQ-2 | +| `ToasterDemo::HeatingReq::heater : Heater` (subject, `ReferenceUsage`) | Binds the requirement to a physical part def | *`Heater`* confers 800 W, a physical sizing choice (DL-021; ch01 F-2). It specializes nothing and is not composed anywhere in the system of interest. | FINDING F-3 | +| `ToasterDemo::HeatingReq::@1` (`require constraint { heater.power >= 600.0 [SI::W] }`) | Logical (a MoP threshold) | §1.5: a derived MoP threshold is logical. The skill's logical checklist asks that it be derived from a MoE, with a means of checking. It is a free-standing number. | FINDING F-4 | +| `ToasterDemo::heating : HeatingReq` (`RequirementUsage`) | Logical (follows its definition) | Same as `HeatingReq`. Its name repeats `Toaster::heating` (a `HeatingSystem` part usage). | FINDING F-11 | +| `ToasterDemo::efficient : Heater` (`PartUsage`) | Physical by its type (a usage of a part def that confers a value); adds nothing | DL-021 (Heater's 800 W is physical). Z-confirmed extension of F7 to usages (DL-032 in the log): "candidate" requires a concrete part that realizes a logical slot. `Heater` realizes none, and `efficient` is not in `Toaster`. | FINDING F-5, F-11 | +| `ToasterDemo::weak : Heater` (`PartUsage`) with `weak::@0` (`:>> power = 400.0 [SI::W]`) | Physical if `power` is a prescribed part value (the default reading); an entered result if not | Q3 (a value only a chosen part has) versus Q4 (a result), depending on what `power` denotes. It is the failing-branch fixture for `HeatingReq`. | FINDING F-1, F-5; OPEN-QUESTION OQ-1 | +| `ToasterDemo::HeatingElement` (`abstract part def`) | Logical, not yet built (DL-020 pattern) | It uses the logical idiom's form (§1.5 table; `term-abstract`) but carries no `perform`, mechanism, attribute or interface (`term-logical-component`). It is related to nothing on the logical side: `HeatingSystem` has no slot typed by it. | FINDING F-6, F-7; OPEN-QUESTION OQ-3 | +| `ToasterDemo::ResistanceCoil` (`part def :> HeatingElement`) | Physical | Q3: it names a specific kind of part (a resistive coil) and gives it a value. A concrete def specializes an abstract def (§1.5 physical idiom). | FINDING F-6, F-9 | +| `ToasterDemo::ResistanceCoil::resistance : Real default = 12.0` | Physical (a part value) | §1.5 "Numbers": a value a chosen part has. It has no unit, and nothing assesses it against a threshold. | FINDING F-9 | +| `ResistanceCoil :> HeatingElement` (`Subclassification`) | Realization by specialization (§1.5 "Allocation is not realization") | The shape is right. `HeatingElement` declares no feature, interface or `perform`, so there is nothing to conform to and nothing is realized. | PASS (shape); see F-6 | +| `ToasterDemo::PowerWire` (`part def :> HeatingElement`) | Physical | Q3: a specific kind of part with a value. | FINDING F-7, F-8, F-9 | +| `ToasterDemo::PowerWire::gauge : Real default = 14.0` | Physical (a part value) | §1.5 "Numbers". The model does not state what it denotes (AWG number, cross-section) or its unit. | FINDING F-9 | +| `PowerWire :> HeatingElement` (`Subclassification`) | Realization by specialization, as declared | `term-specialization`: the specialized def is a kind of the general one. A power wire is not a heating element, and the chapter's own text says it "delivers the electrical power input". | FINDING F-7 | +| `ToasterDemo::HeatingAssembly` (`part def :> HeatingSystem`) | Physical (it composes named concrete parts), with a logical arrangement in its composition | Q3: it names specific part defs. F2: it mixes an arrangement (two slots, logical per heuristic 3) with physical slot types, and I report the mix. It is not part of the system of interest: nothing is typed by it. | FINDING F-5, F-8 | +| `HeatingAssembly :> HeatingSystem` (`Subclassification`) | Realization of a logical component by specialization (§1.5) | This is the first place in Ch1 to Ch6 where a concrete def specializes a logical component. `HeatingSystem` is concrete, though (DL-020), and `Toaster::heating` is still typed `HeatingSystem`. | FINDING F-5 | +| `ToasterDemo::HeatingAssembly::coil : ResistanceCoil` (`PartUsage`) | Physical (follows its type) | A slot filled by a concrete part. | PASS (layer); see F-8 | +| `ToasterDemo::HeatingAssembly::wire : PowerWire` (`PartUsage`) | Physical (follows its type) | Same. | PASS (layer); see F-8 | +| `ToasterDemo::heatingEvidence` (untyped `PartUsage`) | Not a layer element; a container defect | DL-033(3): a part usage with no part, used as a namespace for claims and named "evidence". Same pattern as Chapter 2's `part evidence`. | FINDING F-2 | +| `ToasterDemo::heatingEvidence::@0` (`assert satisfy heating by efficient`) | No layer: a cross-layer traceability claim | DL-033(2); `term-traceability`. It evaluates True on the model's own values. Its staged check is blocked, so this is not a passed check. The subject is outside the system (F-3). | FINDING F-3 | +| `ToasterDemo::heatingEvidence::@1` (`assert satisfy heating by weak`) | No layer: a cross-layer traceability claim | DL-033(2). It evaluates False (400 W < 600 W). DL-039(4): a false positive assertion; a deliberate failing branch is `assert not satisfy` or a computed check. | FINDING F-1 | +| `AI-C06` (Python `ReviewRecord`, notebook 03 cell-05; not in the model) | Not a layer element | DL-033(1): a judgment record is analysis-side, audited on its P1 fields. | FINDING F-12 | +| *`ToasterDemo::Heater` (ch01)* | *Physical (DL-021; ch01 F-2 stands)* | *Context.* | *context* | +| *`ToasterDemo::HeatingSystem` (ch01)* | *Logical, not yet built (DL-020)* | *Context.* | *context* | +| *`allocate ApplyHeat to HeatingSystem` (ch05, `@19`)* | *Cross-layer relation; language non-conformant (DL-039; ch05 F-1)* | *Context: `HeatingAssembly` inherits it.* | *context* | + +The header comment change on line 3 ("chapter 6") is not a model element. Chapter 6 adds no MoE, no MoP or TPM metadata, no action, no port, no interface, no connection, no flow, no allocation and no `perform`. + +## Per-layer checklist results + +**Functional** +- Chapter 6 adds no functional element. The function it claims to realize at the second level (`ApplyHeat`) is not decomposed, and no sub-function exists for "deliver power" (which the text gives to `PowerWire`) or "convert power to heat" (which it gives to `ResistanceCoil`) (F-6, OQ-4). +- No MoE is added. Not applicable. + +**Logical** +- *Does each mechanism have a carrier and an interface?* No. `HeatingElement` is abstract but performs nothing and carries no mechanism or interface. Joule heating, the mechanism `ResistanceCoil`'s name implies, is not stated as a relation (F-6). +- *Do the interfaces actually match?* **Open**, and `blocked` in the tool. No port-typed connection is declared in Chapter 6, so by DL-038's applicability criterion the check does not yet apply. The CLI reports it `blocked` because of the inherited Chapter 5 gap findings. The empty `port_type_mismatches` result is vacuous and is not a pass. +- *Are MoP thresholds derived from a MoE, with a means of checking?* No. The 600 W bound is a free-standing number with no link to `timely` (180 s), to the energy relation or to any MoE. Its only means of checking is the model's own assertion (F-4). +- *Are there no solution values and no results entered as choices?* The logical additions (`HeatingReq`, `HeatingElement`) carry no solution values: PASS. Whether `Heater::power`, which `HeatingReq` checks, is a result entered as a choice is OQ-1. +- *Does it read as a design space?* Barely. `HeatingElement` is an empty slot type, and `HeatingSystem` gains no slots of its own. + +**Physical** +- *Is each part a concrete def that specializes an abstract logical def, and does it fit that def's interfaces?* Partly. `ResistanceCoil` and `PowerWire` specialize the abstract `HeatingElement`, which has no interfaces, so "fits" holds only vacuously (F-6). `PowerWire`'s specialization is a false kind-of claim (F-7). `HeatingAssembly` specializes the logical component `HeatingSystem`, which is concrete (F-5). +- *Do the values meet the derived thresholds, and is the TPM assessed, not asserted?* No. `resistance` and `gauge` are unit-less and are checked against no threshold. The one threshold (`HeatingReq`) is checked on `Heater`, not on any of the new physical parts, and its "check" is an assertion (F-1, F-3, F-9). +- *Does it read as a candidate?* No. No usage of `Toaster` or of `HeatingSystem` contains `HeatingAssembly`, so there is no candidate toaster to check for feasibility or utility (F-5). + +**Across layers** +- *Stopping rule* (§1.8: every leaf concrete, performs, connects, verified): the leaves `coil` and `wire` are concrete. They perform nothing, connect through nothing, and have no verification evidence. The rule is not met, yet `AI-C06` claims the decomposition is complete (F-8, F-12). +- *Emergent result set as a default and then "verified"*: the Chapter 1 `cycleTime` defect (DL-018) is inherited unchanged. Chapter 6 repeats its syntactic shape with `Heater::power` (default 800 W, `weak` binds 400 W, checked against 600 W). Whether that is the same defect depends on OQ-1. +- *Judgment recorded*: `AI-C06` is recorded with counterevidence and residual uncertainties, `disposition="pending"` and `engineering_conclusion="undetermined"` (no "accepted" disposition: PASS on SA-7). Its evidence and premises do not support its claim (F-12). +- *Figures*: Chapter 6 renders no view of the model at all (F-13). + +## Findings + +**F-1. The false `assert satisfy` pattern recurs: `assert satisfy heating by weak` is false, and nothing in the chapter or the tool reports it.** +Element: `heatingEvidence::@1`. +Check: DL-039(4) ("satisfaction claims evaluated", a staged project check; a deliberately failing branch is `assert not satisfy` or a computed check); the cross-layer checklist. +What is wrong: +- `model.eval("ToasterDemo::heating(ToasterDemo::weak)")` returns False (400 W against `>= 600 W`). OpenSysML loads the model with `ok=True`. +- The conformance CLI does not report it. `satisfaction-claims-evaluated` is `blocked` by the inherited Chapter 5 gap findings. It is also registered with `applies_from=None`, so on a gap-free model it would still report `open` ("unscheduled"). The finding comes only from calling `satisfaction_claims_evaluated` directly, which I did as a diagnostic and not as a verdict. +- The chapter presents the pattern as the thing to learn. Notebook 01 cell-01: "Each level has a formal specification, candidate variants, and satisfaction claims". Cell-03 of all three notebooks: two candidate heaters "exercise the new requirement using the same satisfy-assertion pattern from Chapter 3". +- `requirement_coverage` reports `heating` as covered by both `efficient` and `weak`, so a coverage view built from assertions counts the false claim as coverage. + +What I did not do: I did not negate the assertion, schedule the check, or change the check's blocking rule. + +**F-2. `part heatingEvidence` repeats the `part evidence` container defect.** +Element: `heatingEvidence`. +Check: DL-033(3). +What is wrong: it is an untyped part usage, composed by nothing, that holds only two satisfy claims and is named "evidence" for things that are claims. DL-033 reserves "evidence" for analysis results. +What I did not do: I did not choose a replacement idiom. + +**F-3. `HeatingReq` constrains `Heater`, which is not part of the system being decomposed, so the "subsystem requirement" traces to nothing in the decomposition.** +Elements: `HeatingReq::heater`, `efficient`, `weak`, and the two satisfy claims. +Check: cross-layer traceability (`term-traceability`, Douglas: "the design traces back to the requirements it implements"); F7 (classify the pieces of the subject); the logical checklist. +What is wrong: +- `Heater` specializes nothing, and the only usages typed by it are the subject and the two variants. `Toaster` composes `heating : HeatingSystem`, and `HeatingAssembly` composes `ResistanceCoil` and `PowerWire`. None of these is, or contains, a `Heater`. +- So the requirement Chapter 6 adds "one level down" constrains a part def that sits outside the hierarchy it claims to be one level down in. No requirement applies to `HeatingSystem`, `HeatingAssembly`, `coil` or `wire`. +- The chapter text says the opposite: notebook 01 cell-01 ("the `Heater` part definition now has its own requirement"), cell-03 of all three notebooks ("on the `Heater` sub-component"), and `index.md` Method ("applies the requirement and attribute override pattern to `Heater`"). + +What I did not do: I did not re-target the subject or relate `Heater` to `ResistanceCoil`. + +**F-4. The 600 W threshold is not derived, carries no label, and has no means of checking other than the assertion.** +Element: `HeatingReq::@1`. +Check: the skill's logical checklist ("Are MoP thresholds derived from a MoE, with a means of checking, not free-standing numbers?"); `term-mop` (SEBoK: a MoP "yields design requirements necessary to satisfy a MoE"); P2 and the DL-035 pattern (the MoE or MoP label is recorded with a justification). +What is wrong: +- Nothing in the model or the notebooks derives 600 W from `timely` (180 s), from the energy relation (`DeliveredEnergy`) or from any MoE. The number appears only in the constraint. +- No MoE or MoP label, and no justification for one, is recorded. No `verification def` or analysis checks it; only `heatingEvidence` asserts it. + +What I did not do: I did not derive a threshold or propose a label. + +**F-5. The logical-to-physical chain still does not complete: the new physical parts never reach the system of interest.** +Elements: `HeatingAssembly`, `HeatingAssembly :> HeatingSystem`, `efficient`, `weak`. +Check: the physical checklist ("Reads as a candidate"); the Z-confirmed extension of F7 to usages (DL-032 in the log: "candidate" requires a concrete part that realizes a logical slot); pass4-backlog §3. +What is wrong: +- `HeatingAssembly :> HeatingSystem` is the first concrete specialization of a logical component in Ch1 to Ch6. That is progress on the chain. +- Nothing is typed by `HeatingAssembly`. `Toaster::heating` is still typed `HeatingSystem`, which has no parts, and `nominal` and `slow` are unchanged. So no usage anywhere contains the coil or the wire, and there is no candidate toaster. +- `HeatingSystem` is still concrete (DL-020), so the realization is from a concrete logical grouping, not from the abstract carrier the §1.5 idiom names. +- The two branches Chapter 6 adds are disjoint: the requirement branch (`Heater`, `efficient`, `weak`) and the structure branch (`HeatingElement`, `ResistanceCoil`, `PowerWire`, `HeatingAssembly`) share no element. +- The notebooks call `efficient` and `weak` "candidate heaters". Under DL-032 that label is unsupported: `Heater` realizes no logical slot. + +What I did not do: I did not add a candidate usage or retype `Toaster::heating`. + +**F-6. Missing realization recurs: no `perform`, no allocation to the new parts, and no mechanism. Yet the chapter says the coil "realizes" the allocated function.** +Elements: `HeatingElement`, `ResistanceCoil`, `HeatingAssembly`. +Check: `term-logical-component` ("an abstract part definition that performs an action"); AGENTS.md §1.5 ("Allocation is not realization"; Joule heating stated for a chosen component is logical); §1.8 (a leaf performs its specified behavior); the logical checklist, first item; pass4-backlog §3. +What is wrong: +- `perform_relationships` is empty for the whole model. `HeatingElement` is abstract but performs nothing. +- No allocation was added. The only link from `ApplyHeat` to the new structure is inherited through `HeatingAssembly :> HeatingSystem` from the Chapter 5 allocation, which is between definitions and language non-conformant (DL-039). +- No constraint relates `resistance` to power or heat (Joule heating, `P = V^2 / R` or `I^2 R`). The mechanism the name `ResistanceCoil` implies is not in the model. +- `conclusion.md`: "`ResistanceCoil` realizes heat application (the function `ApplyHeat` allocates to `HeatingSystem`)". `AI-C06`'s claim: "every function allocated to HeatingSystem is realized by at least one subpart". Neither has model support. + +What I did not do: I did not add `perform`, an allocation or a mechanism constraint. + +**F-7. `PowerWire :> HeatingElement` declares a power wire to be a kind of heating element.** +Elements: `PowerWire`, its `Subclassification`, and `HeatingElement`. +Check: `term-specialization` (the specialized definition is a kind of the general one and inherits its features); the physical checklist ("specializes an abstract logical def"); F4 (the model is the authority on meaning). +What is wrong: +- The chapter's own text gives the two parts different jobs: the coil "applies thermal energy", while the wire "delivers electrical power to the coil" (`conclusion.md`; `AI-C06` rationale). A conductor that delivers power is not a heating element. +- The specialization makes the abstract type a grab-bag of "parts inside the heating assembly" rather than the carrier of one mechanism. +- Because `HeatingElement` declares nothing, the error changes no inherited feature today. It is still a false modeling claim, and it is what the learner reads as the abstract-to-concrete pattern. + +What I did not do: I did not introduce a separate abstract def for power delivery. + +**F-8. The coil and the wire are not connected, and no interface exists, so the leaves do not "connect through the specified interfaces".** +Elements: `HeatingAssembly`, `coil`, `wire`, `PowerWire`. +Check: §1.8 stopping rule; §1.5 "Connectivity differs by layer"; the logical checklist, first two items; DL-038. +What is wrong: +- The text's central claim for the wire (it delivers power to the coil) has no connection, flow, port or interface behind it. +- Neither the assembly nor `HeatingSystem` has a boundary interface for the electrical supply. A mains outlet is the skill's own example of a logical interface. +- The interface check is `open` under DL-038 (no port-typed connection declared) and `blocked` in the tool. An open check cannot support a completeness claim. + +What I did not do: I did not add ports or a connection. + +**F-9. The physical values have no units, and nothing assesses them.** +Elements: `ResistanceCoil::resistance`, `PowerWire::gauge`. +Check: AGENTS.md §1.4 ("A number produced in Python without a model-defined unit and relation is not evidence"); F4; the physical checklist ("is the TPM assessed ... not asserted"); DL-036's rule that the model states what an element denotes. +What is wrong: +- Both are `Real`. Every other quantity in the model uses ISQ types (DL-010). `ISQ::ResistanceValue` with `[SI::ohm]` loads in OpenSysML v0.9.0 (probed here), so the unit-less form is not forced by the tool. +- `gauge = 14.0` does not say whether it is an AWG number (dimensionless) or a cross-section. The model does not state it. +- Neither value is related to `Heater::power`, to a supply voltage or to any threshold. For example, the model cannot tell whether a 12 ohm coil is consistent with 800 W, since no supply is modeled. + +What I did not do: I did not retype the attributes or add a supply. + +**F-10. The settable-result shape recurs on `Heater::power`. Whether it is a defect is OQ-1.** +Elements: `weak::@0`, and `HeatingReq`'s check of `Heater::power` (the default of 800 W is ch01). +Check: the prescribed-versus-emergent boundary test (§1.5); the cross-layer checklist ("Is any emergent result set as an attribute default and then verified?"); DL-018; DL-032. +What is recorded: a default value, a variant that binds a different value, and a requirement that checks the entered value. That is the same shape as `cycleTime`, `slow` and `timely`. It is a defect only if `power` denotes a result (OQ-1). I record the shape as a finding so that it is not lost if OQ-1 goes the other way. + +**F-11. Naming: `heating` is used twice, and `efficient` names a measure that the element does not carry.** +Elements: `ToasterDemo::heating` (requirement usage), `efficient`. +Check: P4 (learner-facing content earns its place and does not mislead); DL-037 (names convey commitments to the learner). +What is wrong: +- `ToasterDemo::heating` (a `RequirementUsage`) and `ToasterDemo::Toaster::heating` (a `HeatingSystem` part usage) share a name. Resolution is correct (the satisfy claims resolve to the requirement, as confirmed by evaluation). A learner reading `assert satisfy heating by weak` next to `part heating : HeatingSystem` still has two meanings for one word. +- `efficient` differs from `weak` only in power. Efficiency, which the glossary gives as the toaster's example MoP, is not modeled on `Heater`. + +What I did not do: I did not rename anything. + +**F-12. `AI-C06` does not run, and its evidence, premises and criterion do not support its claim.** +Element: the `AI-C06` `ReviewRecord` (notebook 03 cell-05). It is not a layer element (DL-033(1)) and is audited on its P1 fields. +Check: P1; DL-033(2) (a record citing the model's own assertion or declaration cites nothing); the DL-034 reasoning (evidence comes from analysis); §1.8 (account for every input and output). +What is wrong: +- (a) Notebook 03 fails at cell-04 with `NameError: name 'ReviewRecord' is not defined`. No cell imports `ReviewRecord`, `validate_record` or `hash_content`. This is the same defect as ch04 F-6 (pass4-backlog §8), and the chapter's expected result (`validate_record(stopping_judgment)` returns `[]`) is never reached. +- (b) `evidence_refs=["ToasterDemo::HeatingAssembly"]` cites the declaration whose sufficiency is being claimed. That is not evidence. +- (c) The rationale says the coil and the wire "account for both inputs to ApplyHeat (power and duration)". `ApplyHeat` has three inputs (`power`, `duration`, `efficiency`) and one output (`energy`). No part accounts for `duration`, which DL-031 places on a control function or setpoint. The criterion is weaker than §1.8's input and output accounting, the same weakness as ch04 F-2. +- (d) The criterion names "power delivery" as a realized function. No such function exists in the model (compare ch05 F-4). +- (e) `assumption_refs=["AC-C01"]`. That identifier is defined only in the learner template `exercises/ch02/exercise.ipynb`; the chapter's assumption record is `AC-001` (Chapter 2 notebook 03). +- (f) The premises are `AS-C03` (whose evidence is the Chapter 3 assertion; ch03 F-4) and `AI-C04` (ch04 F-2). Notebook 03 cell-01 says the chain "connects the stopping judgment back to the measured evidence". Nothing in the chain is measured. +- (g) Notebook 03 cell-06 says `validate_record()` returning `[]` "confirm[s] the chain is complete". It confirms only that the schema's fields are filled (P1: a check is not proof). +- The record's counterevidence ("a more detailed decomposition would add thermal interface parts and a control signal path") and its pending disposition are appropriate. + +What I did not do: I did not fix the import or edit the record. + +**F-13. Chapter text disagrees with the model and the layer rules, and no figure is shown.** This is documentation consistency, not a model defect. +- `index.md` Purpose: "a second-level structural decomposition of `HeatingSystem` into `ResistanceCoil` and `PowerWire`". `HeatingSystem` has no parts. The parts belong to a subtype that the system does not use (F-5). +- `conclusion.md`: "decomposed to a level where each allocated function maps to a structural part". "Structural" is not a tutorial layer (§1.5, as in ch05 F-7), and no function maps to a part (F-6). +- `conclusion.md`: "`ResistanceCoil` realizes heat application". This contradicts §1.5 ("Allocation is not realization"), and nothing performs the function (F-6). +- Notebooks 01, 02 and 03 cell-03 (the same paragraph three times): "Two candidate heaters" (unsupported, DL-032) and "the `Heater` sub-component" (F-3). +- `index.md` Method: "the same three-notebook structure (requirement, structure, judgment) that appeared in Chapters 2–4 recurs". The recursion §1.8 asks for is the three layers at each level. Chapter 6 adds no functional or logical content at the second level (OQ-4). +- Cell-06 of each notebook uses the world labels A-F, O-S and E. This is noted only; DL-028 parks the labels for the recipe rewrite. +- No notebook renders a view of the assembled model or of the second level (§1.7; the cross-layer checklist's last item). + +Reported only; no edits. + +## Open questions (for the orchestrator to route) + +**OQ-1. Is `Heater::power` a prescribed part value (a rating) or a performance result? This decides whether `weak` is a valid failing branch and whether DL-018's defect recurs.** +- Reading A, a prescribed value. DL-021 already calls Heater's 800 W "a physical sizing choice", and §1.5 "Numbers" puts what a specific part has in the physical layer. On this reading, `HeatingReq` is a feasibility check of a chosen value against a threshold (F2: a candidate is checked for feasibility against the logical layer). `weak` then fails for a reason about the design: someone chose a 400 W part. That makes it the valid failing-branch content that DL-032 found `slow` lacked, and only its expression is wrong (F-1, DL-039). +- Reading B, a result. In the heating context, power is what `ApplyHeat` and `DeliveredEnergy` take in. The power a resistive element draws follows from supply voltage and resistance, and Chapter 6 adds exactly such a resistance (12, unit-less). On this reading 800 W and 400 W are results entered as choices (DL-018 in the `slow` form of DL-032), and the check cannot fail for a reason about the design. +- Recommended default: Reading A for `Heater` as declared. The model has no relation that derives `power`, and DL-021 has ruled the value a choice. Record with it that once a coil and a supply exist in the same candidate, its power must be derived from them and not entered a second time. That is a constraint on the re-derivation, not on this audit. + +**OQ-2. What layer is a requirement whose constraint has the form of a MoP threshold but whose subject is typed by a physical part def?** +- Reading A, the constraint's layer: logical. The skill example row "Heating efficiency is at least 0.6" makes a performance threshold logical. The subject binding to `Heater` is then a traceability defect (F-3), not a layer. +- Reading B, a component specification at the physical layer. A requirement bound to a specific part def is a part spec, and its layer follows its subject. +- Reading C, the layer follows the MoE or MoP label, which DL-035's pattern leaves to the re-derivation's recorded justification. Heating power is an engineering measure (it is hard to argue a user accepts toast by its wattage), but the label is still unrecorded. +- Recommended default: Reading A, with the label left unruled as in DL-035. The table uses Reading A. + +**OQ-3. Does the name `HeatingElement` commit to a mechanism before any selection among alternatives (the naming extension, DL-037 in the log)?** +- Reading A, generic. "An element that heats" covers a blowtorch's flame as well as a coil, so the abstract def names a responsibility. +- Reading B, mechanism-suggestive. In appliance usage a heating element is an electric resistive element, which only the pop-up branch has. Chapter 6 records no selection among alternatives before introducing it and `ResistanceCoil`. +- Recommended default: Reading B, so it is renamed by function (for example "heat source") in the re-derivation under DL-037, and the choice of a resistive mechanism is recorded as a selection. The layer is logical, not yet built, either way. + +**OQ-4. Is a decomposition placed on a physical subtype (`HeatingAssembly :> HeatingSystem` composing concrete parts) an acceptable form of the recursion, or must each level pass through the functional and logical layers first?** +- Reading A, acceptable. §1.5 realizes a logical component by specialization, and a specialized def may add features (`term-specialization`). So realizing and then composing concrete parts is legal SysML and matches "concrete part defs realize logical components". +- Reading B, the recursion is incomplete. §1.8 says that "at each level the three boundaries in §1.5 apply again". Douglas decomposes until there is enough detail to allocate functions to components (`term-decomposition`). Chapter 6 goes straight from a level-1 logical grouping to level-2 physical parts, with no sub-functions of `ApplyHeat`, no abstract level-2 logical slots and no allocation at level 2. +- Recommended default: Reading B. It bears on whether AI-C06's "complete" can be true at all, and on how the Chapter 6 re-derivation is structured. + +**OQ-5 (records). Which DL numbering is authoritative for DL-032 to DL-038?** +- The headings in `decisions/log.md` number the rulings one lower than `decisions/pass4-backlog.md` and the "Confirmed extensions" list in `.claude/skills/ace-protocol/z-principles.md`. For example, "F7 to usages" is DL-032 in the log and DL-033 in z-principles, and the naming ruling is DL-037 in the log and DL-038 in z-principles and the backlog. +- Recommended default: the log headings are authoritative, since the log is the record, and the other two files get corrected by whoever owns them (the z-principles file is a Z-confirmed skill file, so the correction goes through the skill-editor path). This report cites log headings throughout. + +## Contract premises verified + +1. **"The ch06 model repeats the false-satisfy pattern (heating vs weak)."** Holds. `heating(weak)` evaluates False, and OpenSysML loads the model with `ok=True` (F-1). The `slow` claim from Chapter 3 is also still false. +2. **"Whether the settable-result pattern recurs."** Its shape recurs on `Heater::power` (F-10). Whether it is DL-018's defect depends on OQ-1; my default reading says it does not. No new emergent-result attribute is added: `resistance` and `gauge` are part values. `cycleTime` is inherited unchanged. +3. **"Whether the missing-realization pattern recurs."** It recurs. There is no `perform` and no allocation is added, and realization is claimed only in prose and in `AI-C06` (F-6). +4. **The logical-to-physical chain never completing (backlog pattern).** It recurs, with partial progress. `HeatingAssembly :> HeatingSystem` and `ResistanceCoil :> HeatingElement` are the first concrete-to-logical and concrete-to-abstract specializations. The chain still does not reach the system of interest, and the two new branches are disjoint (F-5). +5. **"Use the new conformance tooling to get gap findings and false-satisfy findings."** Holds only in part. The CLI gives the four gap findings, all inherited from Chapter 5. It does not give false-satisfy findings: `satisfaction-claims-evaluated` is `blocked` by those gaps, and it is also unscheduled (`applies_from=None`), so it would report `open` on a gap-free model. I got the false-satisfy findings by calling the check function directly, as a diagnostic and not as a verdict. The CLI's exit code is 0 in this state, by design. +6. **"decisions/log.md DL-018 through DL-039 are the settled patterns"** holds. The cross-references to them in the backlog and in z-principles are off by one for DL-032 to DL-038 (OQ-5). +7. **"Lint hits: none for ch06."** Holds. `glossary lint` gives 8 hits, none in Chapter 6. + +## Constructs that could not be classified cleanly + +- `weak` (and its power binding): physical or an entered result, depending on OQ-1. +- `HeatingReq`: its layer depends on whether it follows the constraint, the subject or the unrecorded label (OQ-2). +- `HeatingAssembly`: physical by Q3, but it mixes a logical arrangement with physical slot types (F2). I report the mix. +- The satisfy claims, `heatingEvidence` and `AI-C06`: not layer elements (DL-033). They are classified by role. + +## Not checked, and why + +- **The Chapter 6 exercise** (`exercises/ch06/exercise.ipynb`): out of scope. I read the ch02 and ch03 exercises only to locate `AC-C01`. +- **Whether `AS-C03`'s own `assumption_refs` also cite `AC-C01`**: not checked. It is a Chapter 3 record. +- **Notebook execution under the real kernel**: I executed the code cells' source in order with `exec`, not with Jupyter or nbconvert. The `NameError` does not depend on the kernel. Execution under the chapter's actual runner was not done. +- **Chapters 7 and 8**: not read. The backlog says the `weak` pattern persists there. I did not verify that. +- **Spec text** for `SatisfyRequirementUsage` evaluation semantics, and whether a satisfy claim on a subject outside the system is admissible: not checked against formal/2026-03-02. +- **Glossary sources:** `uv run python -m glossary check` passes in this worktree (0 errors, 7 warnings). The warnings say the local source PDFs are absent, so source hashes were not verified. I relied on the glossary's recorded definitions. diff --git a/decisions/audits/ch07-layer-audit.md b/decisions/audits/ch07-layer-audit.md new file mode 100644 index 0000000..f73604d --- /dev/null +++ b/decisions/audits/ch07-layer-audit.md @@ -0,0 +1,286 @@ +# Chapter 7 layer audit + +Contract PASS2-011-B, 2026-09-27. Role: `.claude/agents/layer-auditor.md`. Model: claude-opus-5-5[1m] (effort high). +Branch `audit/ch07`, base commit `a84dceb`. + +Subject: the elements Chapter 7 adds, that is, the diff between `models/ch06-cumulative.sysml` (82 lines) and `models/ch07-cumulative.sysml` (93 lines). Both are generated fixtures and were not edited. Apart from the header comment on line 3, the diff is lines 78 to 88 of the ch07 fixture: + +``` +state Cycle { + entry; then idle; + state idle; + state heating; + state ready; + state cancelled; + transition first idle accept Start then heating; + transition first heating accept Finish then ready; + transition first heating accept Cancel then cancelled; +} +``` + +Chapter 7 also adds analysis that creates no model element: a sympy binding of `DeliveredEnergy` (notebook 01), state traces (notebook 02) and a power sweep with a figure (notebook 03). These are on the analysis side of the loop. They are classified by what they bear on (DL-023, DL-033) and listed separately in the table. + +Method: the four-question pass and the per-layer checklist in `.claude/skills/architecture-layers/SKILL.md`, AGENTS.md §1.4 to §1.9, `.claude/skills/ace-protocol/z-principles.md` (F1 to F7, P1 to P6), the glossary (`uv run python -m glossary tutorial` and `lookup` for simulation, policy, behavior, dynamical system, control law, function, emergence, mechanism, usage), and the ACE rulings DL-018 to DL-023 and DL-030 to DL-039 as settled precedent. I did not re-argue them. I cite rulings by the id in each `decisions/log.md` heading. Some other files cite the same rulings under different ids (see "Contract premises that did not hold", item 4). + +Evidence collected by running things: +- Both fixtures load with OpenSysML v0.9.0 (`load_from_content`, `strict=False`): `model.ok == True`, no diagnostics. +- I diffed the API JSON export by qualified name (`ApiIndex` from `src/toaster/query.py`). Chapter 7 adds these elements: + - one `StateUsage` `ToasterDemo::Cycle`, owned by the package; + - four `StateUsage`s: `idle`, `heating`, `ready` and `cancelled`; + - one `StateSubactionMembership` (`Cycle::@0`, kind `entry`) and one `SuccessionAsUsage` (`Cycle::@1`), which together form `entry; then idle;`; + - three unnamed `TransitionUsage`s (`Cycle::@6`, `@7`, `@8`); + - eight `FeatureMembership`s and one `OwningMembership`, which only hold the members. + + Nothing is removed. No `StateDefinition`, `ExhibitStateUsage` or `AcceptActionUsage` exists in the ch07 export. `perform_relationships(...) == []`. +- The transitions' `source` and `target` resolve to the substates. The trigger is exported only as a string: `"sysx:trigger": "Start"` (and `"Finish"`, `"Cancel"`). It is not a reference to `ToasterDemo::Start`. +- `uv run python scripts/check_conformance.py models/ch07-cumulative.sysml --stage 7,0` returned exit code 0, language `ok=True` and four `gap_findings`: two `allocate-between-definitions` and two `part-typed-only-by-item-def`. Both project checks (`port-type` and `satisfaction-claims-evaluated`) are `blocked` with the unblock criterion "no language-tier violation, per the spec, is present". The output for `models/ch06-cumulative.sysml --stage 6,0` is identical, so Chapter 7 adds no gap finding and changes no check status. +- `execute_state` probes on the ch07 fixture. `final_time` is `0.0` and `final_context` is `{}` in every run. + + | Events | States visited | + |---|---| + | `[Start, Finish]` | `[idle, heating, ready]` | + | `[Start, Cancel]` | `[idle, heating, cancelled]` | + | `[Start, Finish, Start]` | `[idle, heating, ready]` | + | `[Finish]`, `[Cancel]`, `[Bogus]` and `[]` | `[idle]` | +- Trigger-resolution probes with OpenSysML v0.9.0: + - A package containing `transition first a accept Missing then b` with no `Missing` declared loads with `ok=True` and no diagnostics. `execute_state(events=["Missing"])` then takes the transition. + - `accept Thing`, where `Thing` is a `part def`, also loads. + - A copy of the ch07 fixture with `item def Cancel;` deleted loads with `ok=True`, and the cancel trace still reaches `cancelled`. + - A copy with `accept Cancle` (a typo) loads with `ok=True`, and the cancel trace stops at `[idle, heating]`. + + sysml-toolkit v0.9.1 (`sysmlv2 check --lib .../SysML-v2-Release/sysml.library`) reports both modified copies as `warning: unresolved reference` on line 86. It reports no such warning on the real ch07 fixture. Its only error there is the inherited line-53 allocation error. AGENTS.md §1.2 allows the toolchain to be cited only to flag a spec gap, which is the only way it is used here. +- The grammar vendored in the sysml-toolkit checkout (`spec-refs/SysML.xtext`, lines 1302 to 1307, 1450 to 1461 and 1854 to 1899) makes a transition trigger an `AcceptActionUsage`. Its payload parameter, written as a bare name, is an `OwnedFeatureTyping`. So `accept Start` types the accepted payload by `Start`, which requires name resolution. +- Alternative forms, probed only to see what the tool can express: `exhibit state S {...}` inside a `part def`, and a `state def SD {...}` with `exhibit state s : SD` in a part def. Both load and execute in v0.9.0. +- All three notebooks were executed in a scratch copy of the chapter with `nbclient`, and all three pass. Notebook 03 prints `Threshold crossed at: 600 W` and `Nominal (800 W): 67.2 kJ`. The committed notebooks carry no stored outputs. +- `uv run python -m glossary check` passes in this worktree (0 errors, 7 warnings). The warnings say the local source PDFs are absent. `uv run python -m glossary lint` reports no hit in `chapters/ch07-execution/`. + +Evidence read for intent: +- `chapters/ch07-execution/index.md`, `conclusion.md`, and every cell of notebooks `01-calc-energy`, `02-state-traces` and `03-param-sweep`. +- `scripts/check_construction.py`, the `CONSTRUCTION_NOTEBOOKS` entry for notebook 02 (lines 149 to 155). +- `DEFERRED.md` D-010. +- `models/ch08-cumulative.sysml`, read only to see whether Chapter 8 changes `Cycle` (it does not; the diff is the header comment). + +## Classification table + +Elements from earlier chapters that the additions reference are shown in *italics* for context. They are not audited here. + +| Element (qualified name) | Layer | Reason | Status | +|---|---|---|---| +| `ToasterDemo::Cycle` (`StateUsage`, owned by the package, no definition) | Functional (recommended default; OQ-1) | Q1: modes of waiting, heating, done and aborted hold for a pop-up toaster and for tongs with a blowtorch (substitution test, AGENTS.md §1.5; F3). The declaration is a prescription, not a result (F1). The skill's idiom table has no state construct, so this call rests on the four questions alone. Nothing composes or exhibits it, and its modes trace to no function. | FINDING F-1, F-2, F-3; OPEN-QUESTION OQ-1 | +| `ToasterDemo::Cycle::@0` (`StateSubactionMembership`, kind `entry`, an empty entry action) and `Cycle::@1` (`SuccessionAsUsage`, `then idle`) | Functional (follows `Cycle`) | This pair declares the initial mode. It commits to no mechanism. | PASS | +| `ToasterDemo::Cycle::idle` (`StateUsage`) | Functional (follows `Cycle`) | Waiting holds for any solution. | PASS | +| `ToasterDemo::Cycle::heating` (`StateUsage`) | Functional (follows `Cycle`) | Every solution heats, so the name commits to no mechanism (contrast DL-037). The state has no `do`, `entry` or `exit` action and no reference to `ApplyHeat`. | FINDING F-2 | +| `ToasterDemo::Cycle::ready` (`StateUsage`) | Functional (follows `Cycle`) | The mode holds for any solution. The state has no outgoing transition. | FINDING F-3 | +| `ToasterDemo::Cycle::cancelled` (`StateUsage`) | Functional (follows `Cycle`) | The mode holds for any solution. The state has no outgoing transition. | FINDING F-3 | +| `ToasterDemo::Cycle::@6`: `transition first idle accept Start then heating` (unnamed `TransitionUsage`) | Functional if `Start` is a user request (OQ-1) | DL-036: `Start` is a functional flow type under every admissible denotation. The trigger is a string in the export, and OpenSysML does not resolve it. | FINDING F-4; OPEN-QUESTION OQ-1 | +| `ToasterDemo::Cycle::@7`: `transition first heating accept Finish then ready` (unnamed `TransitionUsage`) | Functional or logical, depending on the denotation of `Finish` (OQ-1) | If `Finish` means "the toast is done", the transition is intent. If a timer or a thermostat issues it, the transition is part of a control policy (term-policy), which DL-020 and DL-022 place on `ControlSystem`. The model does not say which (DL-036). | FINDING F-4; OPEN-QUESTION OQ-1 | +| `ToasterDemo::Cycle::@8`: `transition first heating accept Cancel then cancelled` (unnamed `TransitionUsage`) | Functional (stop on demand; DL-036 reasoning) | The substitution test passes: any solution can be stopped. The trigger is unresolved in the tool. | FINDING F-4 | +| *`ToasterDemo::Start`, `Finish`, `Cancel` (ch04 `item def`, no doc)* | *Functional flow types, denotation undecided (DL-036)* | *Unchanged in Chapter 7, which adds a fourth use for them: accepted triggers. See premise 3.* | *context* | +| *`ToasterDemo::ApplyHeat`, `DeliveredEnergy`* | *Functional flows with a logical commitment inside (DL-030)* | *Referenced by notebooks 01 and 03.* | *context* | +| *`ToasterDemo::ControlSystem`, `Toaster`* | *Logical, not yet built (DL-020); the subject (DL-019, DL-021)* | *Neither owns or exhibits `Cycle`.* | *context* | +| Analysis, notebook 02: `execute_state` traces for `[Start, Finish]` and `[Start, Cancel]`, with hand-written expected traces | Not a layer element (DL-023; F4 as extended by DL-033). Bears on `Cycle` (functional). | The trace is derived by executing the model, not entered as a choice, so the F1 check passes. It follows entirely from the declared transition table, though, and so shows only that the tool executes what was declared (OQ-2). It is the only thing in the chapter that would catch a mistyped trigger (F-4). | PASS for "no result entered as a choice"; OPEN-QUESTION OQ-2 | +| Analysis, notebook 02 cell 10: negative control (undefined transition target makes the load fail) | Not a layer element; language tier (F6) | It shows target resolution failing, and it does fail. It does not cover trigger resolution, which does not fail (F-4). | PASS as a control of targets; see F-4 | +| Analysis, notebook 01: `BINDING`, `Q_sym`, `Q_fn`, and the `model.eval` cross-check at one point | Not a layer element. Bears on `DeliveredEnergy` (a logical conversion characterization, DL-030). | The relation is copied into Python by hand. The efficiency bound `[0, 1]` and the unit mapping exist only as Python strings (F4). Agreement at one point is called proof (§1.6). | FINDING F-5, F-8 | +| Analysis, notebook 03: `sweep_1d` over power, with `t = 120 s`, `eta = 0.7` and `threshold = 50 000 J` | Not a layer element. Bears on `HeatingReq` and the entered `cycleTime`. | The relation, the efficiency, the duration and the threshold are all defined only in Python (§1.4: not evidence). The threshold is not in the model, is derived from no MoE, and disagrees with the model's `HeatingReq`. The duration equals the entered `cycleTime` default (DL-018). | FINDING F-6 | +| Analysis, notebook 03: `ch07_param_sweep.svg` | Not a layer element; a view of analysis output (§1.7) | The figure is written to the working directory and closed, never shown. It has no caption, and its units are hard-coded rather than read from the model. `Cycle` has no figure at all. | FINDING F-7 | + +Chapter 7 adds no MoE, MoP, TPM, requirement, constraint, attribute, metadata, port, allocation or specialization. It adds no physical element. + +## Per-layer checklist results + +**Functional** +- *Typed inputs and outputs, all flows accounted for?* No. `Cycle` has no typed flows. Its inputs are three accepted triggers. The model states no relation between `Cycle` and `ApplyHeat`, the only function (F-2). Because the triggers' denotation is undecided (DL-036), flow accounting under §1.8 still cannot be applied. +- *Solution-independent (substitution test)?* PASS for the states and for the `Start` and `Cancel` transitions. The `Finish` transition depends on OQ-1. +- *Phenomena relations stated as relations?* None are added. +- *At least one MoE?* None are added. The inherited absence (backlog §4) stands. +- *Reads as an objective?* Partly. The machine says which modes exist and how requests move between them. It says nothing about what is good enough. + +**Logical** +- Chapter 7 adds no logical element under the recommended default of OQ-1. If OQ-1 is ruled the other way, the `Finish` transition is a policy with no carrier: `ControlSystem` does not exhibit `Cycle`, and nothing is allocated to it (F-1). +- *Interfaces match?* Not applicable to the additions. The inherited checks are `blocked` (see the conformance run above). +- *No solution values, and no results entered as choices?* PASS for the model additions. + +**Physical** +- Nothing is added. Notebook 03 sweeps heater power, a physical value, but no part def or candidate is involved. The 800 W point is a Python number that happens to equal `Heater::power`'s default; it is not read from the model. + +**Across layers** +- *Stopping rule (§1.8):* no leaf meets it. Not yet built. +- *Emergent result set as a default and then "verified":* the model additions contain none. Notebook 03, though, holds duration at `t = 120 s`, which equals the entered `Toaster::cycleTime` default (DL-018), and the chapter calls the sweep the evidence Chapter 8's judgment record cites (F-6). Nothing in Chapter 7 derives cycle time. +- *Judgment recorded:* notebook 01 cell 5 calls the SysML-to-sympy mapping "an engineering judgment", but no judgment record is made. Notebook 03 introduces a 50 kJ design threshold with a one-line comment as its only justification (F-6). +- *Figures:* F-7. + +## Findings + +**F-1. `Cycle` is not part of the system of interest.** +Element: `ToasterDemo::Cycle`. +Check: F7 and DL-019/DL-021 (the whole is the subject; classify its pieces). This is the same pattern as ch05 F-6 (`BreadHandling`) and backlog §3 and §6. +What is wrong: +- `Cycle` is a package-level `StateUsage` with no definition. No `Toaster`, `ControlSystem` or other part exhibits or owns it: the export has no `ExhibitStateUsage`. +- The text calls it "the toaster's discrete operating modes" (notebook 02 cell 1, index), but in the model it is nobody's modes. +- The tool does not force this form. `exhibit state` inside a part def, and a `state def` with an exhibited usage, both load and execute in v0.9.0 (probed). + +What I did not do: I did not move or retype `Cycle`, and I did not choose an owner. The owner depends on OQ-1. + +**F-2. The modes trace to no function, and `heating` does nothing.** +Elements: `Cycle`, `Cycle::heating`. +Check: the functional checklist (typed flows, all flows accounted for; §1.8) and cross-layer traceability. +What is wrong: +- No state has an `entry`, `do` or `exit` action. No transition has an effect. +- Nothing references `ApplyHeat`, and nothing references its `duration` input, which DL-031 reads as either a signal from a control function or a timer setpoint. +- Notebook 02 cell 1 says the machine "complements" `ApplyHeat`, but the model states no relation between them. So "heating" is a label: executing the machine applies no heat and advances no time (`final_time` is `0.0`, `final_context` is `{}`). + +What I did not do: I did not propose `do` actions or a link to `ApplyHeat`. + +**F-3. `Cycle` does not cycle, and the model and text do not say whether that is intended.** +Elements: `Cycle::ready`, `Cycle::cancelled`. +Check: the conceptual-to-functional test (§1.5) and F4 (the model states its meaning). +What is wrong: +- `ready` and `cancelled` have no outgoing transitions, so each run ends there. `[Start, Finish, Start]` stays in `ready`. +- The name `Cycle` and the phrase "the toaster's operating cycle" (notebook 02 cell 0) suggest a return to `idle`. +- Neither the model nor the text says whether a single run is the intended scope. + +This is minor. I record it because it is a statement of intent that the model and its name disagree on. + +What I did not do: I did not add transitions. + +**F-4. OpenSysML v0.9.0 does not resolve transition triggers. This is a language-tier hole that nobody tracks, and it hides whether the triggers refer to `Start`, `Finish` and `Cancel` at all.** +Elements: `Cycle::@6`, `@7` and `@8`, and their use of the three item defs. +Check: AGENTS.md §1.9 (language conformance includes name resolution; the gap-tracking rule), F6, P5, and DL-039 (the tier is set by the spec, not by the tool; the tutorial supplies a guard). +What is wrong: +- (a) By the grammar (`SysML.xtext` lines 1302 to 1307 and 1897 to 1899), `accept Start` is an `AcceptActionUsage` whose payload is typed by `Start`, so the name must resolve. OpenSysML v0.9.0 accepts `accept Missing` with `ok=True`, and it accepts a `part def` as the trigger type. Deleting `item def Cancel;` from the ch07 fixture changes neither the load nor the cancel trace. sysml-toolkit v0.9.1 resolves the names and warns when one is unresolved; it reports a warning, not an error. +- (b) The export carries the trigger only as the string `sysx:trigger`, with no `AcceptActionUsage`, no `TransitionFeatureMembership` and no typing. So neither `model.query()` nor the JSON export can answer "which event drives this transition" by reference. The language-gap guard behind `check_conformance.py` reads the export and cannot see the relation (it reports nothing new for ch07). The three transitions are also unnamed. +- (c) The `CONSTRUCTION_NOTEBOOKS` context stubs for notebook 02 (`item def Start; Finish; Cancel;`) are never exercised: the fragment loads without them. Notebook 02's negative control covers an undefined target, which the tool does resolve, and not an undefined trigger, which it does not. +- (d) On the real ch07 fixture, all three names resolve (the toolkit gives no warning). So the ch07 model is not non-conformant on this point. The defect is that nothing in the loop would detect it if it were. The trace assertions in notebook 02 would catch a typo only by accident. +- (e) No `DEFERRED.md` entry, probe row or issue draft covers it. D-010 covers only the Editor authoring gap. + +What I did not do: I did not add a DEFERRED entry, a probe row or an issue draft, and I did not extend the guard. See the contract premise on this point below. + +**F-5. Notebook 01 defines meaning in Python that the model does not state, and calls one-point agreement proof.** +Element: the analysis in notebook 01 (`BINDING`, `Q_sym`, `Q_fn`, the `model.eval` cross-check). +Check: F4 ("code that defines meaning is a defect"), AGENTS.md §1.4 and §1.6, and DL-030 (efficiency must be bounded 0 to 1 wherever the relation lives). +What is wrong: +- The efficiency domain `[0, 1]` and the unit strings exist only in the Python `BINDING` dict. They are not enforced: the sympy symbol is only `positive=True`. The model still leaves `efficiency` unbounded (DL-030 and backlog §2 recur). +- `Q_sym = P * t * eta` is copied by hand from the calc def, not read from the model. The cross-check with `model.eval` compares one point (800 W, 120 s, 0.7), and `conclusion.md` says it "proves that the calc def formula is correctly expressed". §1.6 forbids describing a passing check as proof, and agreement at one point does not establish that the two expressions are the same. +- Cell 5 names the mapping an engineering judgment but records none (P1). + +What I did not do: I did not change the binding or the text. + +**F-6. The sweep that Chapter 8 is said to cite as evidence rests on numbers and a threshold that exist only in Python, and on the entered cycle time.** +Element: the analysis in notebook 03 cell 5. +Check: AGENTS.md §1.4 ("A number produced in Python without a model-defined unit and relation is not evidence"), F4, DL-018, DL-030, DL-034 (an estimate enters only as a labelled estimate), and the logical checklist (MoP thresholds derived from a MoE, with a means of checking). +What is wrong: +- The relation is rebuilt as `P * t * eta` in cell 5. The model is loaded and never queried. +- `eta = 0.7` appears nowhere in the model, and it is not labelled as an estimate with a source. +- `t = 120 s` equals `Toaster::cycleTime`'s default, which DL-018 rules is a result entered as a choice. +- `threshold = 50 000 J` is a requirement-like number that is not in the model, is derived from no MoE, and is justified only by a code comment ("ensures toast within the cycle time at typical efficiency"). Cell 6 calls it "the requirement that delivered energy exceeds the design threshold", but no such requirement exists. +- The model's own `HeatingReq` requires `power >= 600 W`. At 120 s and 0.7, the 50 kJ threshold corresponds to 595.2 W. +- The printed "Threshold crossed at: 600 W" is the first grid point of `linspace(500, 1200, 50)` at or above 595.2 W. It coincides with the model's 600 W, which invites the reading that the sweep derived the requirement. `index.md` says the crossing is "between 590 W and 600 W". +- Notebook 03 cell 1 says the figure "is the simulation evidence referenced by the judgment record in Chapter 8". Under §1.4 it is not evidence as built. + +What I did not do: I did not audit Chapter 8's record, and I did not change the sweep. + +**F-7. The sweep figure is never shown, has no caption, and is not read from the model. The state machine has no figure.** +Check: AGENTS.md §1.7 (a plot of simulation output shows derived behavior, with units and relations read from the model; what a figure omits is stated), P3, and the last item of the cross-layer checklist. This recurs from backlog §11. +What is wrong: +- Notebook 03 writes `ch07_param_sweep.svg` into the working directory, which is the chapter folder when the notebook is run, and calls `plt.close(fig)`. The learner never sees it. +- The axis units ("W", "kJ") and the title's `t=120 s, η=0.7` are hard-coded. +- No figure of `Cycle` or of the assembled model appears in the chapter. + +What I did not do: I did not render anything for the chapter. + +**F-8. The chapter text contradicts the rules and the model.** This is documentation consistency, not a model defect. +- `conclusion.md`: "The sympy binding proves ..." (§1.6), and "the toaster model is behaviourally consistent". The traces only restate the transition table (OQ-2), and "consistent" is not defined. +- `index.md`: "the cumulative model has ... a sympy-bound energy expression ... and a matplotlib figure". Neither is in the model (§1.4: Python never defines what the model means). +- `index.md`: `model.find("ToasterDemo::Cycle")` returns "`kind='stateDef'` or equivalent". It returns `kind='stateUsage'`, and `Cycle` is a usage. +- Notebook 01 cell 3, notebook 02 cell 8 and notebook 03 cell 3 all say `Cycle` is "the first executable behavior in the model". Chapter 4's `ApplyHeat` already declares an action with successions. I did not check whether OpenSysML executes it, so this claim is unverified rather than wrong. Notebook 01 cell 3 and notebook 03 cell 3 also describe the state machine in notebooks that do not use it. +- Notebook 03 calls a sweep of a static algebraic relation "simulation evidence". term-simulation (SEBoK: a model that behaves like the system given controlled inputs) may admit it, but nothing evolves over time. I record this as a wording observation and do not rule on it. +- Notebook 02 cell 3 cites "§7.24 (StateUsage), §7.25 (TransitionUsage)". The architecture-layers skill cites 7.24 for `verification def`. I did not verify which numbering is correct, because the formal PDF is not local. +- The Tall seam cells (notebook 01 cell 13, notebook 02 cell 12, notebook 03 cell 6) use the world labels A-F, O-S and E. The lint finds no hit. Whether these labels name the lens is parked for Pass 4 (DL-028), so this is not a finding here. + +Reported only; no edits. + +## Open questions (for the orchestrator to route) + +**OQ-1. What layer are `Cycle` and its transitions: an intended mode behavior (functional), or a control policy (logical, carried by `ControlSystem`)?** +- Reading A, functional. + - The substitution test passes for every state and for the `Start` and `Cancel` transitions: the tongs-and-blowtorch user also waits, heats, finishes and can stop. + - Under the four-question rule, Q1's "yes" ends the classification. + - The machine selects no input. No state performs an action (F-2), so there is nothing a policy (term-policy: selects inputs given state) would select. + - The text presents it as "discrete operating modes", which is a statement of intent. +- Reading B, logical (policy). + - The glossary's policy is a mapping from state to action (Sutton and Barto). A state machine whose `heating` mode ends on a controller-issued `Finish` (timer expiry or a thermostat) is the control law, and DL-020 and DL-022 put any policy, including a timer setpoint, on `ControlSystem`. + - Only a controller issues `Finish` under a timer design. A user issues it under tongs-and-blowtorch, and "toast is done" issues it as a phenomenon. So the `Finish` transition's layer depends on its denotation, which DL-036 leaves to the modeler and the model does not state. +- The two readings agree on `idle`, `ready`, `cancelled` and the `Start` and `Cancel` transitions (functional). They differ on the `heating` to `ready` transition and on who owns `Cycle` (F-1). +- The skill and AGENTS.md §1.5 list no state-machine idiom for either layer, so this is also a gap in the idiom table. +- Recommended default: Reading A as declared, with the `Finish` transition marked "layer contingent on the denotation of `Finish`" until the re-derivation states it. If the re-derivation introduces a timer, the transition that timer drives is logical and belongs on the policy carrier. + +**OQ-2. Is the state trace a derived result (simple emergence) or a restatement of the prescription? This decides premise 2.** +- Reading A, simple emergence (§1.6): the trace is computed from prescribed relations, like a mass roll-up. It is not entered, so Chapter 7 does derive a result. +- Reading B, not an emergent result. term-emergence requires properties "at the level of the whole" that "cannot be attributed to any one component". The trace follows entirely from one element's own transition table and the event sequence the notebook chooses, with no mechanism, no time and no composition. The check therefore confirms the tool's execution semantics and guards against regressions in the table. It cannot fail for a reason about the design, which is the DL-018 concern in another form, although nothing is entered as a choice here. +- Recommended default: Reading B. The chapter derives no emergent result of the design. Its traces are specification execution: valid analysis that is not evidence about behavior in use. No ruling is needed to keep F-8's first bullet, which stands under either reading because "proves" and "behaviourally consistent" overclaim. + +The handling of F-4 is not raised as an open question. It appears determined by §1.9 (name resolution is language tier) and DL-039 (record it, and have the tutorial supply a guard). One point may still need the orchestrator: whether the guard should resolve the `sysx:trigger` string, which is an OpenSysML export extension and not spec JSON, against the export. That is a builder-scope implementation choice under DL-039(3), and I did not assess it further. + +## Contract premises that did not hold, and premises verified + +1. **"Chapter 7 introduces simulation or state-machine execution, as its title claims."** This holds for state-machine execution: `execute_state` runs `Cycle` for two event sequences, and I reproduced both results. It holds only weakly for simulation. + - The execution is untimed and has no context (`final_time` is `0.0`, `final_context` is `{}`). No mode performs anything (F-2), and unmatched events are silently dropped. + - Notebook 03 evaluates a static relation over a grid. Whether that is "simulation" in term-simulation's sense is a wording question (F-8), not a model construct. + - The chapter adds no dynamics, meaning no state-update relation (term-dynamical-system, Åström and Murray: `dx/dt = f(x, u)`). +2. **"Chapter 7 finally derives an emergent result instead of entering one as a choice."** This does not hold under the recommended default of OQ-2. + - Credit where due: the model additions enter no result as a choice (PASS). + - The only derived outputs are the state traces, which restate the prescription, and a Python-only evaluation of `DeliveredEnergy`. + - Nothing derives cycle time. The sweep holds duration at the entered 120 s (F-6). DL-018's defect is therefore inherited unchanged and now feeds the analysis that Chapter 8 is said to cite. +3. **"Is the denotation of Start/Finish/Cancel resolved here?"** No. It is still undecided and the model still does not state it. + - The three item defs are unchanged from Chapter 4 and have no `doc`. + - Chapter 7 adds a fourth, event-like use (accepted triggers; notebook 02 cell 6: "waiting, running, finished, and aborted"). `BreadLoader::bread : Start` and `BreadEjector::bread : Finish` still type bread by them, and Chapter 4's text still says bread entering and toast exiting. + - F-4 adds that, in the tool, the triggers do not even refer to the item defs. The model as loaded does not connect `Cancel` to the cancel transition. +4. **The citation "DL-037: undecided, model must state it" is wrong.** The Start/Finish/Cancel ruling is DL-036. DL-037 is `BreadEjector`'s naming. The same off-by-one drift appears elsewhere. These files are outside my blast zone; I report the drift and do not fix it. + - `decisions/pass4-backlog.md`: + - §1 cites DL-033 for `slow` (the log has DL-032) and DL-035 for assumptions (DL-034). + - §2 cites DL-037 for the item defs (DL-036). + - §3 cites DL-038 for naming (DL-037). + - §4 cites DL-036 for measures (DL-035). + - §5 cites DL-034 for judgment records (DL-033). + - The "Confirmed extensions" list in `z-principles.md` cites: + - DL-033 for F7 applied to usages (the log has DL-032); + - DL-034 for judgment records (DL-033); + - DL-035 for assumptions (DL-034); + - DL-038 for naming (DL-037). +5. **"Start/Finish/Cancel are used as state-machine triggers in Ch8"** (backlog, "Cross-chapter dependencies"). They are introduced as triggers in Chapter 7. Chapter 8's fixture differs from Chapter 7's only in the header comment. +6. **"Notebook 02 uses Start/Finish/Cancel per the CONSTRUCTION_NOTEBOOKS context stubs."** This holds as text (lines 149 to 155). The stubs have no effect, though, because OpenSysML does not resolve the trigger names (F-4(c)). +7. **"Lint hits: none for ch07."** Verified: `glossary lint` reports no hit in `chapters/ch07-execution/`. +8. **Conformance CLI.** It ran as specified. Chapter 7 adds no gap finding. Both project checks stay `blocked` on the inherited ch05 violations, with exit code 0, since only `failed` sets exit code 1. +9. **Audit coverage of the baseline.** There is no `ch06-layer-audit.md`, so the ch06 baseline (`HeatingElement`, `ResistanceCoil`, `PowerWire`, `HeatingAssembly`, `weak`, `efficient`, `heatingEvidence`, `HeatingReq`) has not been audited. It appears here only where the Chapter 7 analysis touches it (`HeatingReq`, `Heater::power`). + +## Constructs that could not be classified cleanly + +- `Cycle` and its transitions: the skill and AGENTS.md §1.5 give no state-machine idiom for any layer. I classified them by the four questions alone, and the `Finish` transition depends on OQ-1. +- The trigger relation between each transition and `Start`, `Finish` and `Cancel`. The spec makes it a typed accept payload. The tool keeps only a string, so I could classify it only from the source text, not from any query surface (F-4). +- The eight `FeatureMembership`s and the `OwningMembership` are ownership relationships with no content of their own. They are not classified. + +## Recurrence of known patterns (`decisions/pass4-backlog.md`) + +| Backlog item | In Chapter 7's additions | +|---|---| +| §1 result entered as a choice | Not in the model additions. It recurs in the analysis: the sweep fixes duration at the entered 120 s (F-6). | +| §2 mechanism inside the functional layer; efficiency unbounded | Recurs in the analysis: the bound is stated only in Python (F-5). | +| §3 logical-to-physical chain missing | Recurs: `Cycle` has no carrier, no `exhibit` and no `perform` (F-1). | +| §4 no measures declared | Recurs: a Python-only 50 kJ threshold stands in for a measure (F-6). | +| §5 judgment and evidence mislabeled | Recurs: a Python-only sweep is called "simulation evidence" for Chapter 8's record (F-6). | +| §6 system of interest | Recurs in the ch05 F-6 form (F-1). | +| §7 tool gaps | New: trigger name resolution (F-4). | +| §8 fixture and infrastructure | New: construction stubs that are never exercised (F-4(c)). | +| §9 text disagrees with the model | Recurs (F-8). | +| §10 lint | No hits. World labels are present (parked under DL-028). | +| §11 figures | Recurs (F-7). | + +## Not checked, and why + +- **Chapter 8's judgment record** that notebook 03 says cites the sweep: it belongs to another chapter. +- **The Chapter 7 exercise** (`exercises/ch07/exercise.ipynb`): out of scope. +- **Whether OpenSysML executes `ApplyHeat`** (F-8, "first executable behavior"): not probed. +- **The spec's section numbers for states and transitions:** the formal/2026-03-02 PDF is not in `glossary/sources/local/`. The grammar facts come from the `SysML.xtext` vendored in the sysml-toolkit checkout, not from the formal release. I did not confirm that the release's grammar for triggers is identical. +- **Whether sysml-toolkit's "unresolved reference" should be an error:** not assessed. I cite it only as corroboration that the names are resolvable and that OpenSysML does not resolve them. +- **Open upstream issues for F-4:** not checked, and nothing was filed. +- **The rendered SVG:** I produced it only in a scratch copy and did not inspect its content. F-7 rests on the cell source and on the fact that the file is written and never displayed. diff --git a/decisions/audits/ch08-layer-audit.md b/decisions/audits/ch08-layer-audit.md new file mode 100644 index 0000000..39264aa --- /dev/null +++ b/decisions/audits/ch08-layer-audit.md @@ -0,0 +1,293 @@ +# Chapter 8 layer audit + +Contract PASS2-011-C, 2026-09-27. Role: `.claude/agents/layer-auditor.md`. Model: claude-opus-5-5[1m] (effort high). +Branch `audit/ch08`, base commit `a84dceb`. + +Subject: the elements Chapter 8 adds, meaning the diff between `models/ch07-cumulative.sysml` (93 lines) and `models/ch08-cumulative.sysml` (93 lines). Both are generated fixtures, and I did not edit either. **The diff has one line, and it is a comment:** + +``` +3c3 +< // Source: notebook cell-02 TOASTER_INCREMENT in chapter 7's construct-introducing notebooks. +--- +> // Source: notebook cell-02 TOASTER_INCREMENT in chapter 8's construct-introducing notebooks. +``` + +Chapter 8 therefore adds **no model element**. What it does add is on the analysis side of the loop: +- `verify_satisfaction()` verdicts; +- two judgment records, `AS-C08` and `AS-C08-REV`; +- a stale-record pattern built on `check_stale()`; +- a transient threshold edit; +- three negative controls. + +This audit classifies those under DL-023 and DL-033: they are not layer elements, so each is classified by what it bears on and by its tier. The audit also checks whether the inherited patterns recur in the model the chapter checks, as the contract asks. + +Method: the four-question pass and the per-layer checklist in `.claude/skills/architecture-layers/SKILL.md`; AGENTS.md §1.1, §1.4 to §1.9; z-principles F1 to F7 and P1 to P6; and the glossary (`uv run python -m glossary tutorial` for verification, simulation, behavior, emergence, TPM, traceability, judgment, assumption, counter-evidence, requirement, asserted solution, query, dynamical system, validation). I cite the ACE rulings by the **headers in `decisions/log.md`**. I applied them and did not re-argue them. See O-1 for a numbering mismatch between the log and two other files. + +Evidence I collected by running things: +- **Element diff.** I loaded both fixtures with OpenSysML v0.9.0 (`load_from_content`, `strict=False`), and both give `ok == True`. I then diffed the API JSON export by qualified name with `toaster.query.ApiIndex`. Each model has 87 elements. Added: none. Removed: none. Type changed: none. +- **Metaclass counts in ch08.** `VerificationCaseDefinition` 0, `VerificationCaseUsage` 0, `AnalysisCaseDefinition` 0, `ConstraintDefinition` 0, `MetadataUsage` 0. `SatisfyRequirementUsage` 4, `RequirementDefinition` 2, `RequirementUsage` 2, `CalculationDefinition` 1, `StateUsage` 5, `TransitionUsage` 3. +- **Conformance CLI.** `uv run python scripts/check_conformance.py models/ch08-cumulative.sysml --stage 8,0` (exit 0; by design, only `failed` gives exit 1) reported: + - Language: `ok=True`, no diagnostics. + - Four gap findings: `allocate-between-definitions` twice, on `ToasterDemo::@19`, and `part-typed-only-by-item-def` on `BreadLoader::bread` and `BreadEjector::bread`. + - Project checks: `port-type` and `satisfaction-claims-evaluated` are both `blocked`, with the reason "language conformance failed" and the unblock criterion "no language-tier violation, per the spec, is present". + - The ch07 fixture at stage 7,0 gives the identical report. +- **`model.verify_satisfaction()` on ch08.** Four verdicts: + - `timely by nominal` holds, "observed by run". + - `timely by slow` fails, "witnessed by run": `toaster.cycleTime <= 180.0 [SI::s]` evaluated to false. + - `heating by efficient` holds, "observed by run". + - `heating by weak` fails, "witnessed by run": `heater.power >= 600.0 [SI::W]` evaluated to false. +- **Engines.** `conn.list_engines()` lists these engines, all ready (z3 found at `/opt/homebrew/bin/z3`): + + | Engine | Authority | Answers | + |---|---|---| + | `check` | bounded | outcomes, holds, sensitive | + | `explore` | proved | outcomes | + | `run` | observed | evaluate | + | `smt` | proved | holds, sensitive | + | `solve` | proved | satisfiable | + | `sweep` | observed | sweep | + +- **Engine probes.** + - `verify_satisfaction(engine=...)` returns "does not answer evaluate questions — not covered" for each of `check`, `smt`, `explore` and `solve`. `engine="all"` returns the four `run` verdicts above. + - `verify_constraint("ToasterDemo::TimelyToast", subject=..., engine="check")` returns "not covered" (the result DL-006 recorded). With `run` or `auto` it raises `WrongKindError`, because `TimelyToast` is a requirement def and not a constraint. + - `engine="ir"` raises `InvalidRequestError`: "no engine named "ir"; the engines are check, explore, run, smt, solve, sweep, or auto, or all". + - `verify_requirement("ToasterDemo::timely", subject=...)` returns "not covered by run" ("no value for feature toaster"). + - In a scratch model, three `constraint def`s with no subject were also classified as "evaluate questions" and declined by every formal engine. + - `explore_state("ToasterDemo::Cycle", events=["Start","Finish"])` returns "finalState ready; visits idle, heating, ready (1 linearizations; no choice points); complete (1 runs)". +- **The registered check, run directly.** I ran `conformance.satisfaction_claims_evaluated(ch08)` by hand to see what it would find. This is not a verdict: the check is blocked on ch08 and unscheduled (`applies_from=None`). It finds two false claims: `evidence::@1` (`timely(slow)` is False) and `heatingEvidence::@1` (`heating(weak)` is False). +- **The notebooks.** I executed every code cell of the three Chapter 8 notebooks in order, from the chapter directory. All ran. The printed outputs match the chapter's stated expected results. +- **Construction check.** `uv run python scripts/check_construction.py --check` reports 2 failures, both the known ch03-to-ch04 predecessor-containment failure: `ToasterDemo::TimelyToastTest` and its `toaster` reference usage are missing from ch04. The script's construction map has no entry for chapters 6 or 8. +- **Hashes.** `hash_content(ch07 source) != hash_content(ch08 source)`, although the two differ only in a comment. +- **Glossary.** `uv run python -m glossary check` gives ok (0 errors, 7 warnings: the local source PDFs are absent). `uv run python -m glossary lint` has no hit under `chapters/ch08-checking`; the 8 hits are all elsewhere. There is no glossary term for "model checking", "evidence", "violation witness" or "verification case". + +Evidence I read for intent: +- `chapters/ch08-checking/index.md`, `conclusion.md`, and every cell of notebooks 01, 02 and 03. +- Chapter 7 `index.md` and the markdown and demo cells of its three notebooks, read only to compare simulation with checking. I did not audit them. +- The skills `opensysml-api` (lines 80 to 119) and `sysml-v2-toaster-model` (lines 20 to 59), `src/toaster/conformance.py` (lines 330 to 459), `scripts/check_conformance.py`, `scripts/check_construction.py` (lines 1 to 170), and the tests in `tests/test_conformance.py` and `tests/test_query.py` that take the `ch08` fixture. +- `decisions/log.md`: DL-006, DL-007, DL-017 to DL-025, and DL-030 to DL-039. +- `decisions/pass4-backlog.md`. + +## Classification table + +Rows in *italics* are inherited elements that Chapter 8 exercises. They are shown so the recurrence check has a place to live. I did not audit them again: the ruling cited is the classification, and the status says whether the pattern recurs at the ch08 stage. + +**A. Model elements Chapter 8 adds** + +| Element (qualified name) | Layer | Reason | Status | +|---|---|---|---| +| (none) | None | The JSON export diff is empty: 87 = 87 elements, no additions, no removals, no type changes. The chapter says so itself (notebooks 01 to 03 cell 3: "identical to the Chapter 7 model ... not new SysML constructs"), and so does `sysml-v2-toaster-model` line 56. | FINDING F-1 (the fixture's provenance comment says otherwise) | +| Line 3 header comment, "chapter 8's construct-introducing notebooks" | Not a model element | A comment. Chapter 8 has no construct-introducing notebook and no `TOASTER_INCREMENT`, and `check_construction.py` has no chapter 8 entry. | FINDING F-1 | + +**B. Analysis-side constructs Chapter 8 introduces (not layer elements: AGENTS.md §1.5, DL-023, DL-033, F4)** + +| Construct | What it bears on, and its tier | Reason | Status | +|---|---|---|---| +| `model.verify_satisfaction()` (nb01 cell-05; nb02 cell-05) | Evaluates the four `assert satisfy` traceability claims (DL-033). This is the property of the staged project check "satisfaction claims evaluated" (DL-039 (4)), but here it runs ad hoc, outside the conformance registry. Engine `run`, authority "observed". | It is analysis, not a layer element (F4). It is point evaluation of fixed-valued usages, not model checking: every formal engine declines these as "evaluate questions". | FINDING F-2, F-4; OPEN-QUESTION OQ-1, OQ-3 | +| Verdict `satisfy timely by nominal` (holds) | A comparison of `Toaster::cycleTime`'s entered default (120 s) with 180 s | AGENTS.md §1.5, prescribed versus emergent ("a cycle time set as an attribute default and then 'verified' against its threshold is a prescription tested against a threshold"); DL-018. | FINDING F-3 | +| Verdict `satisfy timely by slow` (fails; the chapter's "violation witness") | A comparison of `slow`'s bound value (200 s) with 180 s | DL-018 and DL-032: `slow` is a failing-branch fixture that fails only because a number was typed in. DL-039 (4): a False positive assertion is a `failed` claim, not a lesson outcome. | FINDING F-3, F-4 | +| Verdict `satisfy heating by efficient` (holds) | A comparison of `Heater::power`'s default (800 W) with 600 W | A part value is a prescribed physical sizing choice (DL-021 on the 800 W), not an emergent result, so this is the legitimate form of check ("do the values meet the derived thresholds", physical checklist). Whether the 600 W threshold is derived was not checked (ch06 element). | PASS on the settable-result check; the threshold is unchecked | +| Verdict `satisfy heating by weak` (fails) | A comparison of `weak`'s bound part value (400 W) with 600 W | The failure concerns a design choice, which differs from `slow`. It still rests on a false positive `assert satisfy` (DL-039 (4)). | FINDING F-4; OPEN-QUESTION OQ-4 | +| ReviewRecord `AS-C08` (nb02 cell-05; `kind="asserted_solution"`, `engineering_conclusion="refuted"`) | A judgment record, not a layer element (DL-033). Its claim bears on an emergent result entered as a choice (`slow.cycleTime`). | `counterevidence` and `residual_uncertainties` are present and load-bearing, and the disposition is `pending`, not "accepted" (§1.6, SA-7: PASS). But the evidence it cites is the verdict of point-evaluating the entered value: the model's declaration read back (DL-034). | FINDING F-5 | +| ReviewRecord `AS-C08-REV` (nb03 cell-05; `engineering_conclusion="supported"`) | A judgment record, not a layer element. Its claim bears on `nominal.cycleTime`. | The rationale states the circularity itself: "The attribute is set at the part definition level with no override." A "supported" conclusion drawn from an entered result is what DL-034 rules out. | FINDING F-5 | +| `check_stale()` / `hash_content()` (nb03) | Record freshness, which is infrastructure. Not a layer element, not a conformance tier. | Freshness is keyed to the whole source text, not to `model_ref` (see O-2). | PASS (observation O-2) | +| `revised_source` (nb03: `toaster.cycleTime <= 180.0` edited to `<= 150.0`, loaded, not saved) | A transient edit of `TimelyToast`'s threshold. It is in no fixture. | It is used only to trigger staleness. The layer of the threshold stays open (DL-035). The edit changes a threshold with no derivation, which is harmless for a staleness demo. | PASS (note) | +| Negative control nb01 cell-04 and nb02 cell-04 (an undeclared attribute in a `require constraint` fails the load) | Language tier (name resolution) | This is a valid language-tier control. It is not a control for the chapter's own check. The nb02 comment ("produces no failure verdicts (empty list or error)") does not match its assertion (`not bad.ok`). | FINDING F-6 | +| Negative control nb03 cell-04 (an empty identifier fails `validate_record`) | Record validation (infrastructure) | A valid control for record validation, not for staleness. | FINDING F-6 | + +**C. Inherited elements Chapter 8 exercises: the recurrence check at the ch08 stage** + +| Element (qualified name) | Classification (ruling) | Recurs at ch08? | Status | +|---|---|---|---| +| *`ToasterDemo::Toaster::cycleTime` (`default = 120.0 [SI::s]`)* | *An emergent result entered as a choice (DL-018, DL-022)* | Yes, unchanged. Chapter 8 is where it is "verified", and two records conclude from it. | FINDING F-3 | +| *`ToasterDemo::slow` (`:>> cycleTime = 200.0 [SI::s]`)* | *A usage of the subject; a failing-branch fixture, not a candidate (DL-032)* | Yes. Chapter 8 calls it a "design candidate" (index Purpose, "do the design candidates formally satisfy"). | FINDING F-3 | +| *`ToasterDemo::nominal`* | *A usage of the subject that adds nothing, so it takes no layer (DL-032)* | Yes. Chapter 8 calls it "the nominal design" (conclusion). | FINDING F-3 | +| *`ToasterDemo::TimelyToast`, `ToasterDemo::timely`* | *Layer open until the MoE/MoP justification is recorded (DL-035)* | Unchanged. No label or justification has been added. | context | +| *`ToasterDemo::evidence::assert satisfy timely by slow`* | *A cross-layer traceability claim (DL-033). It is False (DL-039 (4)).* | Yes. The chapter's lesson is built on it. | FINDING F-4 | +| *`ToasterDemo::heatingEvidence::assert satisfy heating by weak`* | *The same kind of claim; also False* | Yes. The same pattern, with the Ch6 fixture. | FINDING F-4 | +| *`ToasterDemo::evidence`, `ToasterDemo::heatingEvidence` (part usages holding only claims)* | *A container defect (DL-033)* | Yes, both. `heatingEvidence` is the Ch6 instance of the same defect. | FINDING F-4 (recurrence noted) | +| *`ToasterDemo::Heater::power` (800 W), `ToasterDemo::weak` (400 W)* | *A physical part value (DL-021, "800 W is a physical sizing choice")* | The settable-result pattern **does not** recur here: a rated power is a choice. `Heater` still specializes no logical def (ch01 F-2, DL-021). | context; OPEN-QUESTION OQ-4 | +| *`ToasterDemo::TimelyToastTest` (ch03 `verification def`)* | *Analysis, not a layer element (DL-023)* | **Absent** from ch08. It was dropped from ch04 onward (backlog §8; `check_construction.py` reports the ch03-to-ch04 containment failure). The checking chapter runs on a model with no verification case. | FINDING F-2, F-7 | +| *`allocate ApplyHeat to HeatingSystem` (`@19`); `BreadLoader::bread`, `BreadEjector::bread`* | *Language non-conformant per the spec (DL-039 (1))* | Yes, all three. Both project checks are `blocked` on ch08. | context (ch05 F-1, F-3) | + +## Per-layer checklist results + +**Functional.** Chapter 8 adds no functional element. The only intent it touches, `TimelyToast`, has its layer open (DL-035). No MoE is added. The inherited `ApplyHeat` mix (DL-030) is not exercised in Chapter 8. + +**Logical.** +- No mechanism, interface or derived MoP threshold is added. +- *Are there no results entered as choices?* In the model Chapter 8 checks, no: `cycleTime` is one (F-3). +- *Do the interfaces match?* The check is `blocked` on ch08 (DL-038, DL-039), not passed. + +**Physical.** +- No concrete part def or value is added. +- *Is the TPM assessed and not asserted?* For `Heater::power` the value is a prescribed rating, so comparing it with a threshold is legitimate in form. No TPM (an assessed value) exists anywhere in the model, because nothing is derived. +- For `cycleTime` the "assessed" value is the entered one (F-3). + +**Across layers.** +- *Stopping rule* (§1.8): not met. It is unchanged from ch07. +- *Emergent result set as a default and then "verified"*: **yes**. This is Chapter 8's main operation (F-3). +- *Judgment recorded with counterevidence and residual uncertainties, and no "accepted" disposition*: the fields are present and the dispositions are `pending`, so it passes in form. The evidence cited is circular (F-5). +- *Figures*: Chapter 8 shows none. No view of the assembled model appears in the chapter. That is recorded here and not raised as a separate finding, because the chapter adds no model element to show. + +## Findings + +**F-1. Chapter 8 adds no model element, and the fixture's provenance comment says it does.** +Element: `models/ch08-cumulative.sysml` line 3. +Check: AGENTS.md §1.7 (implicit parts' "provenance is never hidden") and §1.4 ("Every chapter is one turn of that loop"). +What is wrong: +- The file says its source is "notebook cell-02 TOASTER_INCREMENT in chapter 8's construct-introducing notebooks". No Chapter 8 notebook defines `TOASTER_INCREMENT`. Every cell-02 only reads this file. +- `scripts/check_construction.py` has no chapter 8 entry (and no chapter 6 entry), so nothing generates or checks the ch08 fixture from notebooks. It is a copy of ch07 with the comment edited. +- The notebook file names promise constructs that do not exist: `01-invariant-def.ipynb` (titled "Satisfaction evaluation"; no invariant is defined) and `03-revision-flow.ipynb` (titled "Stale record detection"). + +What I did not do: I did not change the comment, the file names or the construction map. Whether a chapter may add nothing to the model is OQ-2. + +**F-2. Chapter 8 has no formal model-checking construct. The distinction between model checking and simulation is drawn neither in the model nor in the prose, and the prose mislabels what is done.** +Check: AGENTS.md §1.1 item 5 ("Model checking and simulation are complementary: formal properties on one side; scenarios, trajectories and analysis of results on the other"), §1.9 ("probe before you assert"), P1, P5. +What is wrong: +- **In the model.** There is no `verification def`, no `verify`, no `constraint def`, no invariant, and no property stated over a state or parameter space. The ch03 verification def `TimelyToastTest` is absent from ch08 (context row above). Chapter 8 adds nothing (F-1). +- **In the analysis.** + - `verify_satisfaction()` is answered only by the `run` engine, whose authority the tool reports as "observed". The verdicts say "observed by run" and "witnessed by run". + - The engines with formal authority (`check`: bounded; `smt`, `explore`, `solve`: proved) are installed and ready, but they decline these questions as "evaluate questions — not covered". Every claim is about a usage whose values are fixed, so there is nothing to quantify over. + - DL-006 (2025-09-25, before the Pass 1 alignment) replaced `verify_constraint(engine="check")` with `verify_satisfaction()` because the former returned "not covered". It read SA-6 ("bounded model checking: opensysml `check` engine only") as meaning in spirit "no external model checkers". That substitution is where model checking left the chapter. + - My probe shows a further point: `verify_constraint` on `TimelyToast` is a wrong-kind call, because it is a requirement def. +- **In the prose.** The prose claims a formality the analysis does not have: + - index Purpose: "do the design candidates formally satisfy the stated requirements"; + - notebooks 01 to 03 cell 3: "Chapter 8's bounded checks" (the engine used is not the bounded one); + - conclusion: "The violation witness is formal engineering evidence"; + - nb02 cell-06: "a simulation-backed engineering judgment" (no simulation is run or cited). +- **Between chapters.** Chapter 7 nb03 cell 1 says "The matplotlib figure is the simulation evidence referenced by the judgment record in Chapter 8". No Chapter 8 record references it: `AS-C08`'s `evidence_refs` is the verdict string, and `AS-C08-REV` has none. The one link between simulation and checking that the prose promises is missing. +- So the §1.1 item 5 learning outcome is not delivered in Chapter 8, and Chapter 8 does not contrast its checking with Chapter 7's simulation. + +What I did not do: I did not propose a formal property or an engine call, and I did not find the API form that poses a "holds" question to the formal engines (see *Not checked*). + +**F-3. The settable-result pattern recurs, and Chapter 8 is the chapter that "verifies" it.** +Elements: `Toaster::cycleTime`, `nominal`, `slow`, and the two `timely` verdicts. +Check: AGENTS.md §1.5, prescribed versus emergent (the exact case it names); DL-018, DL-022, DL-032; F1; heuristic 5. +What is wrong: +- Chapter 8 compares an entered 120 s and an entered 200 s with 180 s, and presents the result as findings about designs: + - conclusion: "the requirement boundary is real and correctly encoded: the nominal design (cycleTime=120) satisfies TimelyToast; the slow design (cycleTime=200) does not"; + - index: "design candidates". +- DL-018: such a check "can never fail for a reason about the design and verifies nothing". DL-032: neither usage is a candidate, and `slow` is a fixture. +- nb01 cell-07 goes further: its exercise invites the learner to add `fast : Toaster` with `cycleTime = 90.0` and "confirm" that it satisfies the requirement. That teaches the pattern as practice. It also disagrees with the exercise that `index.md` and `conclusion.md` describe (a `weak` witness and HeatingReq staleness). + +What I did not do: I did not edit the text or the exercise prompt. + +**F-4. The false-satisfy pattern recurs, twice, and the chapter's lesson depends on it.** +Elements: `evidence::assert satisfy timely by slow` and `heatingEvidence::assert satisfy heating by weak`. The `heatingEvidence` container is a second instance of the DL-033 container defect. +Check: DL-039 (4) ("a False claim is `failed` ... a deliberately failing branch is expressed as `assert not satisfy` or as a computed check, not as a false positive assertion"; "Re-derived models carry none"); DL-033 (a claim is not evidence). +What is wrong: +- The model asserts two satisfactions that are False. Chapter 8 does not report them as failed claims. It calls one a "violation witness" and turns it into a record with `engineering_conclusion="refuted"`. So the false assertion is treated as the intended content, when DL-039 says it is the fault the loop should catch. +- The registered project check for this property (`satisfaction-claims-evaluated`) is unscheduled (`applies_from=None`) and `blocked` on ch08 by the language-tier gap findings. Run directly, it flags exactly these two claims. +- So the chapter evaluates satisfaction claims by a route outside the conformance model, while the check that would report them as `failed` is not applied anywhere. Placement is OQ-3. + +What I did not do: I did not rewrite either assertion, and I did not schedule the check. + +**F-5. The two judgment records cite the model's own entered value as their evidence.** +Elements: `AS-C08` (nb02) and `AS-C08-REV` (nb03). +Check: DL-033 ("a record that cites the assertion as evidence cites nothing"), DL-034 (an entered result is not cured by a record; a check against it is not the candidate's assessed performance), F4, P1, AGENTS.md §1.6 (no passing check described as proof). +What is wrong: +- `AS-C08`'s evidence is the `run` verdict of `slow.cycleTime=200.0 <= 180.0`. Its rationale calls this "direct computational evidence". +- `AS-C08-REV` concludes "supported" for `nominal` and says in its rationale that the value is set on the definition. +- In both, the evidence is the model's declaration read back. +- The conclusion then calls the witness "formal engineering evidence ... not just a test result". +- What passes: `counterevidence` and `residual_uncertainties` are substantive (`AS-C08` even says `slow` "is a synthetic stress case, not a production design", which agrees with DL-032), and both dispositions are `pending`. + +What I did not do: I did not edit the records. I did not check `AS-C03`, which `AS-C08` cites in `assumption_refs`. + +**F-6. No negative control shows the chapter's own loop catching a fault about the design.** +Check: AGENTS.md §1.4 ("a negative control shows that the loop can detect a mismatch"); DL-032 (`slow` cannot validly play that role, because it fails only because a number was typed in). +What is wrong: +- nb01 and nb02 use the same language-tier control, an unresolved name in a constraint. +- nb03's control is record validation. +- None shows satisfaction evaluation or staleness detection catching a fault. The only failing case is `slow` (DL-032), plus `weak` (OQ-4). +- The nb02 cell-04 comment describes a different outcome ("no failure verdicts (empty list or error)") from the one it asserts (`not bad.ok`). + +What I did not do: I did not add controls. + +**F-7. The ch08 fixture, the repository's most-referenced "full" reference model, is language non-conformant per the spec, lacks the tutorial's only verification case, and three tests encode readings that the rulings reject.** +This is not a layer finding. The contract asked that anything about this fixture be treated as significant. +Check: DL-039 (1) (`model.ok` is a proxy, not the definition of language conformance), DL-038 (3) (an empty `port_type_mismatches` on a model with no port ends is vacuous and is not reported as a pass), DL-023. +What is wrong: +- **Who references it.** `models/ch08-cumulative.sysml` is referenced by two test files (`tests/test_query.py`, `tests/test_conformance.py`), by the default set in `scripts/check_conformance.py`, and by `scripts/probes/query_helpers_draft.py`. Each of ch01 to ch05 is referenced by one test file; ch06 and ch07 by none. +- **It is not "full".** It lacks `TimelyToastTest` and its `verify timely` (dropped since ch04), so no fixture from ch04 onward has a verification case. +- **Three tests.** + - `tests/test_conformance.py::test_language_ok_on_valid_model(ch08)` names ch08 a "valid model". The same file's `test_language_gap_findings_on_real_fixture(ch08)` finds gap violations in it. Under DL-039 (1) it is non-conformant. The assertion itself (`["ok"] is True`) tests only the proxy. + - `tests/test_query.py::test_port_type_check_is_clean_on_ch08` asserts that `port_type_mismatches(ch08) == []` and calls that "clean". ch08 has no `PortUsage`, so the result is vacuous (DL-038 (3), ch05 F-5 (e)). + - `tests/test_conformance.py::test_satisfaction_claims_evaluated_skips_verify_without_subject(ch08)` is meant to exercise the `verify`-without-subject branch. ch08 has no `verify` relationship, so the branch is never reached and the assertion passes vacuously. + +What I did not do: I did not edit tests or fixtures. + +**F-8. The skills and the chapter documentation disagree with the tool and with each other.** +- `.claude/skills/opensysml-api/SKILL.md` lines 95 and 96 show `verify_constraint("ToasterDemo::TimelyToast", ..., engine="check")` and say `# engine values: "check", "ir"`. The tool has no `ir` engine; it lists check, explore, run, smt, solve, sweep, auto and all. `TimelyToast` is a requirement def, so `verify_constraint` on it raises `WrongKindError` under `run` and `auto` (`verify_requirement` is the matching call). The skill does not record the engines' authorities (observed, bounded, proved), and those are exactly what separates evaluation from model checking. +- `.claude/skills/sysml-v2-toaster-model/SKILL.md` lines 36 and 38 place satisfaction evaluation (A2) and stale-dependency detection (A4) in Chapter 9. Chapter 8 introduces both (DL-006: "the introduction point moves to Ch8"; DL-006 also says "SA-6 skill entry to be updated"). +- The glossary has no term for *model checking*, although AGENTS.md §1.1 item 5 names it as a learning outcome. + +Reported only. Skills and the glossary are outside my blast zone. + +## Open questions (for the orchestrator to route) + +**OQ-1. Does DL-006 still stand now that AGENTS.md §1.1 item 5 makes "model checking and simulation are complementary" a learning outcome?** +- **Reading A: DL-006 stands.** Chapter 8 is "constraint checking" by evaluation, and the formal side is taught elsewhere or not at all. + - For: DL-006 is a recorded ruling; `verify_constraint(engine="check")` does return "not covered" on these claims; the model as built has nothing for a formal engine to quantify over. + - Against: AGENTS.md §1.1 postdates DL-006. The chapter title and prose promise formality. I did not audit Chapters 9 and 10, so I cannot say the outcome is delivered elsewhere. +- **Reading B: DL-006 is superseded on this point.** The re-derived Chapter 8 states at least one formal property and checks it with an engine of bounded or proved authority, and contrasts that with Chapter 7's observed runs. + - For: the engines are installed and ready (z3 found). The state machine `Cycle` already explores (`explore_state`: one linearization, complete). AGENTS.md §1.6 says stability "straddles both: an analytic form that can be model checked, plus simulated trajectories". Once cycle time is derived rather than entered (DL-018), a property over a parameter domain becomes checkable. + - Against: I have not shown that a formal engine will answer a "holds" question in any form (see *Not checked*). +- **Recommended default:** Reading B. Escalate to Z, because it affects a learning outcome (P6) and the ruling it would supersede predates the alignment. Which property and which engine to use are not the auditor's call. + +**OQ-2. May a chapter add nothing to the model?** +- **Reading A: yes.** §1.4 says each chapter is "one turn of that loop", and an analysis-only turn on the previous chapter's construction is still a turn. `sysml-v2-toaster-model` line 56 records Chapter 8 as "analysis operations, not new constructs". +- **Reading B: no.** The chapter's formal property is itself a construct (an invariant as a `constraint def`, or a `verification def` with an objective). The file name `01-invariant-def` suggests one was intended. Under §1.4 the construct half is what the analysis half checks. +- **Recommended default:** decide it together with OQ-1. If OQ-1 goes to Reading B, Reading B here follows. + +**OQ-3. From which chapter does the "satisfaction claims evaluated" check apply?** +- **Reading A: from Chapter 8**, the chapter that teaches evaluation of satisfaction claims. +- **Reading B: from Chapter 3**, the chapter that first declares `assert satisfy`. This is by analogy with DL-023's trigger ("applied from the chapter that declares the connection complete"), and it would catch the false `slow` claim when it is written. +- Evidence: DL-038 parked the placement of staged checks; DL-039 (4) defines the check and names `slow` as its natural negative control; the registry has `applies_from=None`. On ch08 the check is `blocked` by the language gaps whichever reading is chosen. +- **Recommended default:** Reading B, routed to the parked placement decision. Chapter 8 would then teach the evaluation of claims that the check has already been screening since Chapter 3. + +**OQ-4. Is `weak` a valid failing-branch fixture under DL-032, given that its failure comes from a chosen part value rather than an entered result?** +- **Reading A: valid in kind.** A rated power is a prescribed physical value (DL-021), so "400 W is below 600 W" is a fact about a design choice. That is the kind of failure DL-032 asks for. The defects around it are separate: it is expressed as a false positive `assert satisfy` (DL-039 (4)), and `Heater` realizes no logical def (ch01 F-2). +- **Reading B: not valid yet.** + - The 600 W threshold is a free-standing number, not shown to be derived from a MoE, so failing it is failing a typed number. + - `weak` is a usage of `Heater`, which realizes no logical slot, so it is not a candidate (DL-032's "candidate" test). + - Note: `HeatingReq` and `weak` are Chapter 6 elements, and I did not audit Chapter 6. +- **Recommended default:** Reading A for the form of the failure. Keep F-4 for the assertion, and route the question of threshold derivation to a Chapter 6 audit. + +## Other observations (not layer findings) + +**O-1. The DL numbers in the backlog and in z-principles are shifted by one from the log headers for DL-032 to DL-037.** +- `decisions/log.md` headers: DL-032 is nominal/slow, DL-033 records and claims, DL-034 assumptions, DL-035 TimelyToast label, DL-036 Start/Finish, DL-037 BreadEjector naming, DL-038 the interface check. +- `decisions/pass4-backlog.md` cites DL-033 for `slow`, DL-034 for records, DL-035 for assumptions, DL-036 for the label, DL-037 for flow types and DL-038 for naming. +- `.claude/skills/ace-protocol/z-principles.md` "Confirmed extensions" cites DL-033 for usages of the subject, DL-034 for judgment records, DL-035 for assumptions and DL-038 for naming. +- This report cites the log headers. The Z-confirmed extension list points at the wrong entries, which matters for anyone following a citation. Route it to whoever owns those files. + +**O-2. Staleness is keyed to the whole source text, not to the referenced element.** `hash_content` differs between ch07 and ch08, although the files differ only in a comment. A record written against ch07 therefore reads as stale on ch08 with no semantic change, and a change elsewhere in the file stales a record whose `model_ref` it does not touch. This is a design property of `toaster.evidence`, not a layer matter. I did not test it beyond the hash comparison. + +## Contract premises that did not hold + +1. **"Audit the elements Chapter 8 adds."** There are none (F-1). The table therefore classifies the analysis-side constructs Chapter 8 introduces and the inherited elements it exercises. +2. **"This is where model checking / bounded verification appears per the chapter title."** The chapter title is "Constraint Checking". No model-checking construct appears: there is no verification def, no `engine="check"` call, and no formal property. The verdicts come from the `run` engine, whose authority is "observed". The phrase "bounded checks" in cell 3 does not match the engine used (F-2). +3. **"A verification def running with engine="check" or similar, per the opensysml-api skill."** The skill's example is a wrong-kind call, and it names a non-existent `ir` engine (F-8). The only verification def the tutorial ever had is absent from ch08. +4. **"Whether Chapter 8 draws a real functional/model-checking distinction from Chapter 7's simulation."** It does not, in the model or in the prose. The one promised link, Chapter 7's figure cited by a Chapter 8 record, does not exist (F-2). +5. **"Whether the false-satisfy and settable-result patterns recur."** + - False-satisfy: yes, for both `slow` and `weak` (F-4). + - Settable result: yes, for `cycleTime` (F-3). No, for `Heater::power`, which is a part value (DL-021; OQ-4). +6. **"The model used throughout the existing test suite as the reference 'full' fixture."** Partly. It is the most-referenced fixture (two test files, plus the CLI default and a probe draft), but it is not full: it lacks the ch03 verification case, and it is language non-conformant per the spec (F-7). +7. **"Lint hits: none for ch08."** Holds. + +## Constructs that could not be classified cleanly + +- Nothing in the model: no element was added. +- The analysis-side constructs are not layer elements (DL-023, DL-033). I classified each by what it bears on and by its tier, which is the ruled method. `check_stale` and `hash_content` are record infrastructure, with neither a layer nor a conformance tier. +- `revised_source` is a transient model that is never saved. I classified it by the element it edits (`TimelyToast`'s threshold, whose layer is open under DL-035). + +## Not checked, and why + +- **Whether any API form makes a formal engine (`check`, `smt`, `explore`, `solve`) answer a "holds" question on this model or a small variant.** Every form I tried was classified as an "evaluate question" and declined: `verify_satisfaction`, `verify_constraint` with and without a subject, and three subject-less `constraint def`s. This leaves OQ-1 Reading B unproven, and I have not described any formal check as working (§1.9). +- **Chapters 9 and 10**, which might introduce model checking: outside this contract. +- **Chapter 6 elements** (`HeatingReq` and its 600 W threshold, `efficient`, `weak`, `heatingEvidence`, `HeatingAssembly`): not audited. They appear only as context and in OQ-4. I also did not check whether Chapter 6 notebooks define a `TOASTER_INCREMENT` that the construction map omits. +- **The Chapter 8 exercise** (`exercises/ch08/exercise.ipynb`): not read. F-3's exercise point rests on nb01 cell-07 and on `index.md` and `conclusion.md`. +- **Record `AS-C03`**, cited by `AS-C08`: not located or read. +- **Spec text on verification cases** (SysML v2 §7.24) and on the semantics of `assert satisfy`: not re-read, because the local PDFs are absent (`glossary check` warnings). +- **Rendered pages**: not built. diff --git a/decisions/cold-start.md b/decisions/cold-start.md new file mode 100644 index 0000000..b1cc24e --- /dev/null +++ b/decisions/cold-start.md @@ -0,0 +1,33 @@ +# Cold-start test, Pass 1 (2026-09-26) + +Purpose: a fresh session, given only `CLAUDE.md`, must reach working alignment from AGENTS.md Part 1, the skills and the glossary CLI, on every model tier the eventual roles may use. Each agent had a private git worktree of this branch (HEAD `ede5dae`, so the glossary and new skills were present, the gitignored PDFs absent), a pinned model, and read-only instructions. Tiers stand in for the role range (roles do not exist yet): **Haiku 4.5** (the novice), **Sonnet 5** (a routine builder), **Opus 5.5** (a judgment-heavy reviewer). The ACE itself is tested separately on Fable 5.1 (`decisions/ace-dry-run.md`). + +A first attempt used the harness's built-in worktree isolation. Those worktrees were created from an older commit (`ef4744d`) with no glossary, so the Sonnet run there could not reach the glossary and the other two were stopped. Lesson recorded: create the worktree yourself with `git worktree add HEAD` and pass its path; do not rely on default isolation to start from the current branch. + +## Task and key + +A. `glossary check` passes in a worktree without the PDFs. B. Classify seven statements. C. Does OpenSysML diagnose a power-to-fuel port connection (no, G4). D. Which source defines logical as "how" (none: Douglas says "who", SEBoK's logical includes the functional view; the tutorial's bridge edge is the "how" reading, a Z-approved differsFrom). E. Can a MoP be "a requirement" (no: it characterizes a requirement; a threshold and a means of checking complete it). F. Steps before a workaround (gap-tracking rule). G. May you hand-edit glossed text; who changes a confirmed definition (no, generated; only Z). H. What was confusing. + +Key for B: 1 functional (MoE); 2 logical (mechanism plus interface); 3 physical; 4 physical TPM and emergent result (either label accepted, with the reason); 5 functional (solution-independent balance); 6 invalid (a prescription tested against a threshold); 7 logical (MoP threshold). + +## Results + +| Tier | A | B (7 items) | C | D | E | F | G | +|---|---|---|---|---|---|---|---| +| Haiku 4.5 | ok, exit 0 | 7 of 7 | correct | correct | correct | correct | correct | +| Sonnet 5 | ok, exit 0 | 7 of 7 | correct | correct | correct | correct | correct | +| Opus 5.5 | ok, exit 0 | 7 of 7 | correct | correct | correct | correct | correct | + +`glossary check` reported `ok (0 errors, 7 warnings)` in every worktree: the seven warnings are "source file absent" for the gitignored PDFs, as designed. All three tiers cited AGENTS.md sections, skills and glossary term ids for their reasons. No tier failed, so the Foundations do not depend on more capability or context than a cold Haiku session has, for this task. + +## What the agents found confusing, and what was done + +- **Item 4 fits two labels** (physical TPM and emergent result). The `architecture-layers` skill now says a TPM is both: physical when asked for a layer, an emergent result when asked whether it was prescribed. Fixed. +- **Cannot find "measure of performance" by name.** `lookup` and `tutorial` matched only the full label with its parenthetical. Terms now resolve by the label without the parenthetical or by the abbreviation (`MoP`). Fixed, with a test. +- **How to reach the ACE** was unclear to a contributor outside the legacy roster. AGENTS.md 1.11 now says: through the orchestrator, or, with none, state the question and a recommended default in your report. Fixed. +- **Part 2's authority matrix versus Part 1** (who may edit AGENTS.md or glossary files; A8's skill authority): known, Part 1 governs, roster rebuild is Pass 2. Recorded as an input in `decisions/next-passes.md` (step 12). +- **Tutorial edge locators** still read "Foundations (AGENTS.md Part 1, to be written at gate M2)". To fix now that Part 1 exists (next commit). +- **The tongs-and-flamethrower Douglas timestamp** is unverified. Re-verify in Chrome before a skill cites a time. +- **Skills not yet updated** (`toaster-recipe` still names Tall; and others) are listed in CLAUDE.md as stale where Part 1 governs. Pass 2 and 4 inputs. +- **Gap status is spread over several files.** `DEFERRED.md` entries and the issue drafts (step 11a) become the single register. +- One agent reported an "older CLAUDE.md" in its session context that differed from its worktree copy. That is the harness loading the original checkout's project instructions, not a repo defect; the worktree copy was the one it followed. diff --git a/decisions/declarative-construction-plan.md b/decisions/declarative-construction-plan.md new file mode 100644 index 0000000..7cb9222 --- /dev/null +++ b/decisions/declarative-construction-plan.md @@ -0,0 +1,564 @@ +# Declarative Construction Architecture — Implementation Plan + +**Status:** ACTIVE — DL-011 logged +**Date:** 2026-09-25 +**Triggered by:** Z's direction: "the notebooks themselves are the code performing the build" +**ACE review:** 2026-09-25 — 4 blocking + 5 minor findings corrected (see DL-011 for findings list) +**Background context:** DL-008 moved model source to `models/chXX-cumulative.sysml` files; +Z identified that the cumulative files represent database *state* but not the *construction* process. +SysML v2 is declarative (like a database language); the tutorial should teach engineering by +having learners construct the model, not pull an already-built one. + +--- + +## What changes + +**Before:** Cell-02 in every notebook loads the full cumulative file silently. The cumulative files +are hand-authored source of truth. + +**After:** Cell-02 in construct-introducing notebooks declares the new increment (via Editor API or +SysML string) and prints the reflection. The cumulative files become generated checkpoints. +Running the notebooks in sequence constructs the model. + +--- + +## What stays the same + +- The 7-cell template structure is preserved. Cell-02 gains construction code; no new cells added. +- The cumulative `.sysml` files remain in `models/` — they are now generated fixtures (checkpoints). +- Ch9–Ch10 analysis notebooks do not need construction cells (they query a finished model). +- The 5 gap construct notebooks already have notes pointing to toaster#9–#13. Their + construction pattern is "print the SysML string declaration" — the string IS the declaration. +- Editor single-use rule: after `editor.apply()`, reload with `conn.load_from_content()` before + constructing anything else. Cell-02 follows this: construct → `editor.apply()` → print → + reload full cumulative. + +--- + +## Scope + +**Notebooks with construction cells (cell-02 updated): 13** + +| Chapter | Notebooks | Count | +|---|---|---| +| Ch1 | nb01 (B), nb02 (A), nb03 (A), nb04 (A) | 4 | +| Ch2 | nb01 (B), nb02 (B) | 2 | +| Ch3 | nb01 (B), nb02 (A — pilot) | 2 | +| Ch4 | nb01 (A), nb02 (A) | 2 | +| Ch5 | nb02 (B), nb03 (A or B — probe first) | 2 | +| Ch7 | nb02 (A or B — probe first) | 1 | +| **Total** | | **13** | + +**Explicitly excluded — no construction cells:** + +| Notebooks | Reason | +|---|---| +| Ch2/nb03, Ch3/nb03, Ch4/nb03, Ch6/nb01–nb03 | Judgment or depth — no new SysML constructs | +| Ch5/nb01 | Navigation-only (model.find); no new SysML construct | +| Ch7/nb01, Ch7/nb03 | Sympy binding / param sweep — no new SysML construct | +| Ch8/nb01–nb03 | Analysis operations (verify_constraint, verify_satisfaction, stale detection) | +| Ch9/nb01–nb03, Ch10/nb01–nb03 | Query the finished model; no construction | + +**5 gap construct notebooks (gap notes already added in prior session):** +Ch1/nb01 (abstract part def, toaster#9), Ch2/nb01 (require constraint, toaster#11), +Ch2/nb02 (attribute :>>, toaster#10), Ch3/nb01 (assert satisfy, toaster#12), +Ch5/nb02 (allocate, toaster#13) + +--- + +## Construction cell convention (all 13 notebooks — all Pattern B) + +**Phase 2 pilot finding (2026-09-25):** The Editor API produces bare declarations only +(no bodies, no `default =` form, calc/action/item defs without inputs or expressions). +All 13 construction notebooks use Pattern B (SysML string fragments) until the Editor API +reaches full spec coverage. See DEFERRED.md D-004 through D-010 for the gap registry. + +### Structural rule: code factored as if we had the API calls + +**The construction cell structure must mirror exactly what Pattern A would look like if the +Editor API were fully implemented.** One named fragment variable per element = one future +`editor.add_*()` call. When the API matures, replace each string with the corresponding +call; everything else stays the same. + +### TOASTER_INCREMENT convention + +`TOASTER_INCREMENT` = the **new declarations introduced by this notebook only** (not the full +cumulative model). It is assembled from the individual fragment variables at the end of the +construction zone and printed as the reflection. + +### Construction zone structure + +For a notebook introducing multiple elements (e.g., part def + two attributes): + +``` +[code cell] ONE fragment variable declared + printed ← mirrors one editor.add_*() call +[markdown cell] narration for that element +[code cell] NEXT fragment variable + printed ← mirrors next editor.add_*() call +[markdown cell] narration +... +[code cell] TOASTER_INCREMENT assembled from fragments + print(TOASTER_INCREMENT) ← reflection: the full increment + load ch0X-cumulative.sysml; assert model.ok ← load for subsequent cells +``` + +### Fragment naming convention + +Fragment variable names mirror the element being declared: +- `PART_DEF`, `HEATER_DEF`, `TOASTING_SYSTEM_DEF` — part definitions +- `POWER_ATTR`, `CYCLE_ATTR` — attribute declarations +- `HEATER_USAGE`, `CONTROL_USAGE` — part usages (composition) +- `TIMELY_REQ`, `DELIVERED_ENERGY_CALC` — requirement/calc usages + +### Example: single-element notebook (Ch1/nb01 — abstract part def) + +```python +from pathlib import Path +import opensysml +from toaster.report import format_diagnostics + +conn = opensysml.connect(version="v0.9.0") + +# abstract modifier not yet supported in Editor API — toaster#9 / OpenSysML#595 +# spec: SysML v2 formal/2026-03-02 §7.3.3 (PartDefinition — AbstractClassifier) +TOASTING_SYSTEM_DEF = """\ +abstract part def ToastingSystem { + doc /* Any system that converts electrical energy into thermal energy + for food preparation. */ +} +""" +print(TOASTING_SYSTEM_DEF) + +TOASTER_INCREMENT = TOASTING_SYSTEM_DEF +source = Path("../../models/ch01-cumulative.sysml").read_text() +model = conn.load_from_content(source, strict=False) +assert model.ok, f"Model failed: {format_diagnostics(model.diagnostics)}" +``` + +### Example: multi-element notebook (Ch1/nb02 — part def + attributes) + +```python +from pathlib import Path +import opensysml +from toaster.report import format_diagnostics + +conn = opensysml.connect(version="v0.9.0") + +# Part def shell — editor.add_part_def(owner='ToasterDemo', name='Heater') when API ships +HEATER_DEF = "part def Heater {" +print(HEATER_DEF) + +# Power attribute — editor.add_attribute(owner=..., name='power', type=..., default=...) when API ships +# default = modifier not yet supported — toaster#N / OpenSysML#N +# spec: KerML formal/2026-03-02 §9.4.2 (FeatureValue — default keyword) +POWER_ATTR = " attribute power : ISQ::PowerValue default = 800.0 [SI::W];" +print(POWER_ATTR) + +# Cycle time attribute +CYCLE_ATTR = " attribute cycleTime : ISQ::DurationValue default = 120.0 [SI::s];" +print(CYCLE_ATTR) + +# Assembly — mirrors editor.apply() new-member output +TOASTER_INCREMENT = f"""\ +{HEATER_DEF} +{POWER_ATTR} +{CYCLE_ATTR} +}} +""" +print(TOASTER_INCREMENT) + +source = Path("../../models/ch01-cumulative.sysml").read_text() +model = conn.load_from_content(source, strict=False) +assert model.ok, f"Model failed: {format_diagnostics(model.diagnostics)}" +``` + +### Fragment size rule + +Each fragment variable: ≤5 lines of SysML, ideally 1–3 lines. If a fragment is longer, +it should be split into multiple fragment variables (one per logical sub-element). The +`TOASTER_INCREMENT` assembly may be longer but must be derivable from its named parts. + +--- + +## Ordering: DL-011 logged BEFORE Phase 0 + +The architectural decision log entry is the authorization for the whole intervention. It must +precede all skill updates and implementation. It is logged immediately (see `decisions/log.md` +DL-011 entry), not at Phase 1e as the original draft had it. + +--- + +## Phase 0 — Skill updates (FIRST — no implementation until complete) + +Skills are the team's shared operating model. Agents must have correct guidance before +implementing; an incorrect skill propagates to every future session. + +### 0a. `opensysml-api` — add Editor API section + +Add after the "Connection" section: + +``` +## Editor API (programmatic construction) + +model.edit() → Editor + +# Structural +editor.add_part_def(owner, name, specializes=[], doc=None) +editor.add_part(owner, name, type=None, specializes=[]) +editor.add_attribute(owner, name, type=None, default=None, multiplicity=None) +editor.add_member(owner, kind, name, ...) # for kinds not covered by typed helpers + +# Calculation +editor.add_calc_def(owner, name, inputs=[], return_type=None, expression=None) + +# Item / state (confirm via probe before using) +editor.add_item_def(owner, name, ...) +editor.add_member(owner, kind="state def", name=...) + +editor.apply() → EditResult +str(result) # FULL MODEL including all prior + new declarations (not just the new member) + +Editor single-use rule: editor is bound to one model hash. +After editor.apply(), must call conn.load_from_content(str(result)) before editing further. + +Gap constructs — do NOT attempt these kinds; use Pattern B (SysML string) instead: + abstract part def → toaster#9 / OpenSysML#595 + attribute :>> → toaster#10 / OpenSysML#596 + require constraint → toaster#11 / OpenSysML#597 + assert satisfy → toaster#12 / OpenSysML#598 + allocate → toaster#13 / OpenSysML#599 +``` + +### 0b. `sysml-v2-toaster-model` — add construction cell patterns + +Add a "Construction cell patterns" section with Pattern A and Pattern B verbatim (from this plan). +Add TOASTER_INCREMENT convention including the Pattern A/B distinction. Note which constructs +use Pattern A vs Pattern B. + +| Construct | Pattern | Chapter/Notebook | +|---|---|---| +| abstract part def | B (gap — toaster#9) | Ch1/nb01 | +| part def + attribute | A | Ch1/nb02 | +| :> specialization | A | Ch1/nb03 | +| part usage (composition) | A | Ch1/nb04 | +| requirement def + require constraint | B (gap — toaster#11) | Ch2/nb01 | +| attribute :>> override | B (gap — toaster#10) | Ch2/nb02 | +| requirement usage + assert satisfy | B (gap — toaster#12) | Ch3/nb01 | +| calc def | A | Ch3/nb02 | +| action def | A | Ch4/nb01 | +| item def | A | Ch4/nb02 | +| allocate | B (gap — toaster#13) | Ch5/nb02 | +| flow | A (if probe confirms) or B | Ch5/nb03 | +| state | A (if probe confirms) or B | Ch7/nb02 | + +### 0c. `toaster-recipe` — update cell-02 description + +Current: "Model increment — full cumulative SysML string (SA-2); conn.load_from_content(…); assert model.ok" + +Replace with: +``` +| 2 | Code | **Model increment** — two-phase: (1) declare the increment (Pattern A: Editor API +returning full model; Pattern B: SysML string for gap constructs); assign to TOASTER_INCREMENT; +print as reflection. (2) load full chapter cumulative from models/chXX-cumulative.sysml; +assert model.ok. Not all cell-02s have TOASTER_INCREMENT — see scope table. | +``` + +A6 checklist update: "TOASTER_INCREMENT is assigned and printed in cell-02 of construct-introducing +notebooks (13 total — see scope table). Judgment, depth, navigation, analysis, and param-sweep +notebooks do not assign TOASTER_INCREMENT." + +### 0d. `tutorial-style-guide` — add construction cell rules + +Append to code style section: +``` +Construction cells (cell-02, construct-introducing notebooks only): +- TOASTER_INCREMENT is the required variable name. + Pattern A: full model after editor.apply(). Pattern B: new SysML fragment only. +- Print TOASTER_INCREMENT immediately after assignment — this IS the reflection. +- For Pattern A: base = model state immediately before this notebook's declarations. +- For Pattern B: state the gap issue number in a comment above the string. +- conn.close() at the end of the last code cell (cell-04), never inside cell-02. +``` + +--- + +## Phase 0e — Probe `flow` and `state` Editor API support — COMPLETE (2026-09-25) + +**Results:** + +| Construct | Probe result | Pattern | DEFERRED entry | +|---|---|---|---| +| `flow X.port to Y.port` | `IllegalMemberKindError kind "flow"` | **B** | D-009 / toaster#14 | +| `state def Cycle` (bare) | Succeeds — adds `state def Cycle;` | A (bare only) | — | +| `state usage` (sub-state) | `IllegalMemberKindError kind "state usage"` | **B** | D-010 / toaster#15 | +| `transition` usage | `IllegalMemberKindError kind "transition"` | **B** | D-010 / toaster#15 | + +**Conclusion:** +- Ch5/nb03 (`flow`): Pattern B — gap D-009 confirmed. +- Ch7/nb02 (full state machine): Pattern B — bare `state def` via Pattern A is insufficient; the tutorial + construct needs sub-states + transitions, which are both gaps (D-010). + +Editor method list (from `dir(editor)`): `add_assoc, add_attribute, add_attribute_def, add_behavior, +add_calc, add_calc_def, add_class, add_classifier, add_datatype, add_feature, add_function, +add_interaction, add_item, add_item_def, add_member, add_metaclass, add_package, add_part, +add_part_def, add_port, add_port_def, add_predicate, add_struct, applied, apply, delete, move, +operations, rename, set_value` + +No `add_state`, `add_flow`, `add_transition` exist. All 7 gap constructs now confirmed. + +**Updated: all 13 construct cells now have a confirmed Pattern assignment. Phase 1 may proceed.** + +--- + +## Phase 1 — Test infrastructure (SECOND — defines acceptance criteria) + +All checkpoint infrastructure must exist before any notebook is modified. This is TDD. + +### 1a. `pyproject.toml` — add checkpoint marker + +```toml +[tool.pytest.ini_options] +markers = [ + "checkpoint: verify notebook construction cells are consistent with committed fixtures", +] +addopts = "-m 'not checkpoint'" +``` + +`pytest` (default): skips checkpoint tests — learners who fork and edit won't see confusing +fixture-mismatch failures. +`pytest -m checkpoint` (CI): runs checkpoint tests explicitly. + +### 1b. `tests/test_model_checkpoints.py` — fixture consistency tests + +```python +import subprocess +import pytest + +@pytest.mark.checkpoint +def test_fixture_consistency(): + """Run construction cells and verify fixtures are consistent.""" + result = subprocess.run( + ["python", "scripts/check_construction.py", "--check"], + capture_output=True, text=True + ) + assert result.returncode == 0, f"Construction inconsistency:\n{result.stdout}\n{result.stderr}" + +@pytest.mark.checkpoint +@pytest.mark.parametrize("chapter", range(1, 9)) +def test_chapter_fixture(chapter): + result = subprocess.run( + ["python", "scripts/check_construction.py", "--check", f"--chapter={chapter}"], + capture_output=True, text=True + ) + assert result.returncode == 0, result.stdout + result.stderr +``` + +### 1c. `scripts/check_construction.py` — verification script + +**Revised scope (B-ACE-3 fix):** The script VERIFIES consistency between notebook construction +cells and committed fixtures. It does NOT reconstruct cumulative files from scratch by +concatenating TOASTER_INCREMENT values (which is impossible given Pattern A/B incompatibility). + +**Design:** + +For each Chapter 1–8, for each construct-introducing notebook (in order): +1. Parse the notebook JSON and extract cell-02 source. +2. Identify whether it is Pattern A (`editor.apply()` path) or Pattern B (string fragment path) + by checking for `editor.` in the source. +3. Execute cell-02 using `exec()` in a prepared namespace (conn already open, correct CWD, + base model loaded). Capture `TOASTER_INCREMENT`. +4. Validation: + - **Pattern A**: load `TOASTER_INCREMENT` via `conn.load_from_content()`; assert `model.ok`. + For the LAST Pattern A notebook in the chapter: compare against the committed + `models/chXX-cumulative.sysml` (normalize whitespace before comparing). + - **Pattern B**: wrap `TOASTER_INCREMENT` in a minimal package with standard imports; + load via `conn.load_from_content()`; assert `model.ok` (validates the fragment parses). +5. Exit 1 and report any failures. + +`--check` mode is the only mode — the script never writes to `models/`. The `// GENERATED FIXTURE` +header on cumulative files is the signal that they SHOULD be kept in sync by running this script, +but the script itself is read-only. + +**Note on `regenerate` mode (deferred):** A future enhancement could add `--regenerate` to +actually write updated cumulative files using the last Pattern A TOASTER_INCREMENT per chapter. +Deferred until after Phase 5 confirms the construction cells are stable. + +### 1d. Update cumulative file headers + +Add to the top of each `models/chXX-cumulative.sysml`: +``` +// GENERATED FIXTURE — do not edit directly. +// Run: python scripts/check_construction.py --check (to verify) +// Source: notebook cell-02 TOASTER_INCREMENT in each chapter's construct-introducing notebooks. +``` + +### 1e. Commit infrastructure (before pilot) + +Commit: `test: checkpoint test infrastructure + fixture headers` + +Files: `pyproject.toml`, `tests/test_model_checkpoints.py`, `scripts/check_construction.py`, +all 8 `models/chXX-cumulative.sysml` headers. + +--- + +## Phase 2 — Pilot (one notebook end-to-end before rollout) + +**Pilot target:** Ch3/nb02 (`calc def DeliveredEnergy`) — Editor API supports this construct; +it has a clear reflection (full model including DeliveredEnergy); existing tests exercise it; +ch02-cumulative.sysml is the natural base. + +### 2a. Implement construction cell in Ch3/nb02 + +Apply Pattern A to cell-02. Load ch02-cumulative.sysml as base. Add `editor.add_calc_def(...)`. +Assign `TOASTER_INCREMENT = str(editor.apply())`. Print. Keep existing cells 3–7 intact. + +### 2b. Verify checkpoint test passes + +``` +pytest -m checkpoint tests/test_model_checkpoints.py::test_chapter_fixture[3] +``` + +Must be GREEN before proceeding. + +### 2c. ACE or Z review of pilot + +The pilot's printed `TOASTER_INCREMENT` is the first example of the reflection pattern. +Confirm the output is pedagogically clear (shows the full canonical model — is that too much? +Should it print only the new section? Resolve before rolling out to all 13 notebooks). + +If the full model is too verbose: switch to printing only the new member text (extracted as +`str(editor.apply())` minus the base). This is a judgment call that must be made here. + +--- + +## Phase 3 — Code (scripts finalization + any src/toaster/ changes) + +Phase 3 exists primarily as a verification gate after the pilot: + +- Confirm `scripts/check_construction.py` works correctly for both Pattern A and B based on + the pilot result. +- Review whether any `src/toaster/` module changes are needed (none expected — the Editor API + is called directly from notebook cells). +- If Phase 2c reveals the full-model reflection is too verbose, implement the extraction approach + here before rolling out to 13 notebooks. + +If Phase 2 resolves cleanly with no code changes needed, Phase 3 is a sign-off checkpoint only. + +--- + +## Phase 4 — Notebook rollout (13 notebooks, chapter by chapter) + +Apply construction cells in this order. Within each chapter, do all notebooks before moving on. +After each chapter batch, run `pytest -m checkpoint --chapter=N` before starting Ch(N+1). + +All 13 notebooks use Pattern B (SysML string fragments). See DL-012. + +| Batch | Notebook | Construct | Gap issue | Gate | +|---|---|---|---|---| +| Ch1 | nb01 | abstract part def | toaster#9 / OpenSysML#595 | — | +| Ch1 | nb02 | part def + attribute `default =` | toaster#16 / OpenSysML#603 | — | +| Ch1 | nb03 | `:>` specialization | (none — string for consistency) | — | +| Ch1 | nb04 | `part` usage (composition) | (none — string for consistency) | — | +| checkpoint | | | | `pytest -m checkpoint --chapter=1` GREEN | +| Ch2 | nb01 | requirement def + require constraint | toaster#11 / OpenSysML#597 | — | +| Ch2 | nb02 | `attribute :>>` override | toaster#10 / OpenSysML#596 | — | +| checkpoint | | | | `pytest -m checkpoint --chapter=2` GREEN | +| Ch3 | nb01 | requirement usage + assert satisfy | toaster#12 / OpenSysML#598 | — | +| Ch3 | nb02 | calc def body (inputs + return) | toaster#17 / OpenSysML#604 | — | +| checkpoint | | | | `pytest -m checkpoint --chapter=3` GREEN | +| Ch4 | nb01 | action def body | toaster#18 / OpenSysML#605 | — | +| Ch4 | nb02 | item def | (none — string for consistency) | — | +| checkpoint | | | | `pytest -m checkpoint --chapter=4` GREEN | +| Ch5 | nb02 | allocate | toaster#13 / OpenSysML#599 | — | +| Ch5 | nb03 | flow | toaster#14 / OpenSysML#601 | — | +| checkpoint | | | | `pytest -m checkpoint --chapter=5` GREEN | +| Ch7 | nb02 | state machine | toaster#15 / OpenSysML#602 | — | +| checkpoint | | | | `pytest -m checkpoint --chapter=7` GREEN | + +**Ch6, Ch8–Ch10:** No construction cells. Do not modify. + +--- + +## Phase 5 — Simulated user testing + ACE synthesis + remediations (LAST) + +Run the full A9 simulated learner battery after ALL 13 construction cells are in place. + +### 5a. Test battery + +Three persona agents per batch. Cover all 10 chapters. +- L-Novice: Ch1–Ch3 (construction cells are most visible here) +- L-SE Practitioner: Ch4–Ch6 + Ch9 +- L-Returning Learner: Ch7–Ch8 + Ch10 + +**Focus question for this batch:** "Does cell-02's construction code make the declarative +nature of SysML v2 visible? Is the reflection output (printed TOASTER_INCREMENT) meaningful? +Does the two-phase structure (construct then load) cause confusion or add clarity?" + +### 5b. ACE synthesis + +ACE handles corroborated blocking issues inline. Non-blocking findings logged. +Any finding that affects the construction cell pattern across multiple chapters escalates to Z +before ACE attempts a fix. + +### 5c. Remediations + +Apply fixes. If remediations touch more than 3 notebooks, re-run the user test battery on the +affected chapters before closing. + +### 5d. DL entry + +Log as DL-012 (or whichever number follows after any interim DL entries). + +--- + +## Acceptance criteria (plan complete when ALL are met) + +- [ ] DL-011 logged (before Phase 0) +- [ ] All 4 skills updated (Phase 0a–d) +- [ ] Phase 0e probe complete; flow and state constructs confirmed as Pattern A or B; skill updated +- [ ] `pyproject.toml` has `checkpoint` marker + `addopts` (Phase 1a) +- [ ] `tests/test_model_checkpoints.py` exists with `@pytest.mark.checkpoint` tests (Phase 1b) +- [ ] `scripts/check_construction.py` exists; `--check` mode exits 0 on clean repo (Phase 1c) +- [ ] All `models/chXX-cumulative.sysml` have `// GENERATED FIXTURE` header (Phase 1d) +- [ ] Pilot (Ch3/nb02) checkpoint test GREEN (Phase 2) +- [ ] Phase 2c review complete; reflection verbosity resolved (Phase 2c) +- [ ] All 13 construct-introducing notebooks have Pattern A or B in cell-02 (Phase 4) +- [ ] Per-chapter checkpoint gates all GREEN (Phase 4) +- [ ] Simulated user test battery complete; remediations applied (Phase 5) +- [ ] DL-012 logged (Phase 5) + +--- + +## What Z asked for and what was added + +Z specified: skill updates → tests → code → notebooks → user testing + ACE + +Added steps not in Z's original list: +1. **Phase 0a–0d are separate skill sub-steps** — each skill has a specific section to update +2. **Phase 0e — flow/state Editor API probes** — explicit GATE before Ch5/nb03 and Ch7/nb02; + do not assume Pattern A or B for these two constructs without probing +3. **TOASTER_INCREMENT Pattern A/B distinction** — Pattern A = full model; Pattern B = fragment; + this distinction affects the regenerate script design and must be in skills before rollout +4. **Pilot step (Phase 2)** — test the pattern end-to-end on one notebook before rolling out +5. **Phase 2c reflection verbosity review** — `editor.apply()` returns the full model which may + be long; decide before rollout whether to print the full model or extract only the new member +6. **Phase 3 as sign-off gate** — not a heavy code phase; exists to catch Phase 2c fallout +7. **Ch1 base model convention (B-ACE-4)** — Ch1 has no ch00-cumulative; Pattern A cells chain + on the previous notebook's TOASTER_INCREMENT within the chapter +8. **`scripts/check_construction.py` scope narrowed (B-ACE-3)** — verifies consistency only; + does not reconstruct from scratch (impossible given Pattern A/B TOASTER_INCREMENT mismatch) +9. **Per-chapter checkpoint gates explicitly in Phase 4 table** — not just a note; listed as + explicit BLOCKED/checkpoint rows that agents cannot skip + +## ACE review findings log (all handled by ACE, 2026-09-25) + +| ID | Severity | Finding | Fix | +|---|---|---|---| +| B-ACE-1 | Blocking | Phase 0b table listed `attribute :>>` as Ch2/nb01; it is Ch2/nb02 | Fixed in Phase 0b table | +| B-ACE-2 | Blocking | Scope said 18 notebooks; Phase 4 table implied 13 | Fixed scope section | +| B-ACE-3 | Blocking | `editor.apply()` returns full model, not new member (probed); regenerate script design invalid | Revised TOASTER_INCREMENT convention + script scope | +| B-ACE-4 | Blocking | Ch1 base model undefined; no ch00-cumulative | Added convention in Pattern A section | +| N-ACE-1 | Minor | Ch5/nb01 mislabeled "analysis"; it is navigation | Fixed in scope table | +| N-ACE-2 | Minor | Phase 3 was hollow; no clear purpose | Clarified as sign-off gate | +| N-ACE-3 | Minor | Ch7/nb01 in scope header but excluded in Phase 4 | Fixed scope section | +| N-ACE-4 | Minor | flow/state probes had no explicit gate | Added Phase 0e as explicit GATE | +| N-ACE-5 | Minor | DL-011 positioned mid-Phase-1; should precede all phases | Moved to ordering note before Phase 0 | diff --git a/decisions/diagram-study-real-fixtures.md b/decisions/diagram-study-real-fixtures.md new file mode 100644 index 0000000..f73fa08 --- /dev/null +++ b/decisions/diagram-study-real-fixtures.md @@ -0,0 +1,61 @@ +# Toaster diagramming — Phase 0 real-fixture rerun + +## Decision brief + +**The original toy-fixture trade study's tool-capability conclusions hold for three render pipelines (OpenSysML's two render forms and sysml-toolkit) — but two of the four tools, the OMG pilot (a common-model tool) and SysMLD (an intent-file tool), cannot be exercised on real content at all, for two different reasons; neither could be retested against real fixture content.** OpenSysML (both PlantUML and DOT/Graphviz render forms) and sysml-toolkit render all six real-fixture views attempted here without incident. The OMG pilot fails on every one of the six real render attempts, on a real capability gap the toy fixture never exposed (qualified-name allocation targets). SysMLD/sysml2d fails to reach any render on real content at all, due to a confirmed bug in its pinned indexer — a different failure mode than the toy fixture's silent staleness, and one that leaves the original staleness finding neither confirmed nor refuted. OpenSysML's known port-collapse limitation reproduces exactly on real content; sysml-toolkit does not share it. + +This is a measured rerun of the original study's method against four real toaster chapter models (Ch5–Ch8), not a final tool selection, not Phase 1 (the per-chapter diagram survey), and not new implementation work. All three remain out of scope here (see "Scope and non-coverage" below and the plan's own Non-goals). + +## Method and evidence + +Four real fixtures were used: `models/ch05-cumulative.sysml` through `models/ch08-cumulative.sysml`, copied verbatim into `decisions/diagram-study-real-fixtures/fixtures/ch05.sysml`–`ch08.sysml`, each with a one-element "mutated" sibling (`ch0N-mutated.sysml`). Tool versions were verified against the original study's pinned versions before any render ran (`decisions/diagram-study-real-fixtures/evidence/provisioning-report.json`): OpenSysML `v0.9.0`, sysml-toolkit `af839f0d22723772676e509213c65756d1e08ef2`, pilot `jupyter-sysml-kernel-0.62.0`, sysml2d `1af88250d355f4e218f6653ef934e93ac8319cd6` — all four report zero mismatches against the pins. + +Four common-model tool pipelines (OpenSysML→PlantUML, OpenSysML→DOT/Graphviz, sysml-toolkit, OMG pilot) rendered `tree` (rooted at `ToasterDemo::Toaster`, all four fixtures), `interconnection` (Ch5), and `state` (Ch7) views, with exit code, output path, and SHA-256 captured for every attempt (`decisions/diagram-study-real-fixtures/evidence/real-fixture-results.json`). That is 6 fixture/view combinations × 4 tools = 24 render attempts; 18 of 24 succeeded (all combinations for OpenSysML-puml, OpenSysML-dot, and toolkit) and 6 of 24 failed (the pilot, on every one of the six combinations it was attempted against) — verified directly against `real-fixture-results.json`, which records `"exit_code": 1, "svg_path": null, "sha256": null` for the `pilot` entry in every one of the six view blocks (`ch05-tree`, `ch05-interconnection`, `ch06-tree`, `ch07-tree`, `ch07-state`, `ch08-tree`) and `"exit_code": 0` with a populated SVG path and hash for the other three tools in each. + +SysMLD was rendered and validated separately, against two hand-authored diagram-intent files (`decisions/diagram-study-real-fixtures/sysml2d-intent/ch05-interconnection.json`, `ch07-state.json`) whose element aliases were copied verbatim from the real fixture source, per the plan's own model-to-picture-integrity requirement. Both failed (`decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-sysmld-render.log`, `ch05-interconnection-sysmld-validate.log`, `ch07-state-sysmld-render.log`, `ch07-state-sysmld-validate.log`), and the ACE's ruling on why is recorded at `decisions/log.md`'s `DL-055` entry and its addendum on `pass1/harness-alignment` (read via `git show pass1/harness-alignment:decisions/log.md`, since this branch's own `log.md` does not yet carry DL-054/055 — this branch forked before they were written). See "Model-to-picture integrity" below for the finding itself. + +Mutation-control reruns (re-render after the fixture's one-element mutation, comparing SVG bytes) were scoped to `ch05-interconnection` and `ch07-state` only, matching Task 7's own scope, not every successful baseline: `ch06-tree` and `ch08-tree` baselines were never rerun against their mutated siblings (`ls decisions/diagram-study-real-fixtures/evidence | grep -c "ch0[68]-mutated"` returns 0). Every combination within that scope that had a successful baseline was rerun, per the plan's vacuous-pass guard (`decisions/diagram-study-real-fixtures/evidence/mutation-control-results.json`, an 8-entry list, cross-checked against the per-tool before/after hashes in `real-fixture-results.json` and `mutation-rerenders.json`). + +An allocation/requirement view-type probe (`decisions/diagram-study-real-fixtures/evidence/view-type-probe.json`) checked whether any of the four common-model tools exposes a dedicated allocation or requirement view kind, by reading OpenSysML's and the pilot's full `-help`/`--help` output (`opensysml-help.log`, `pilot-help.log`) and a hand-recorded summary of sysml-toolkit's `--view --help` output (`toolkit-view-help.log`, a 3-line note rather than a raw capture) rather than scanning for a single flag. + +## Alternatives exercised + +| Candidate | Real-fixture result | Observed limitation on real content | Assessment | +|---|---|---|---| +| **OpenSysML 0.9.0 → PlantUML → SVG** | All 6 attempted view/fixture combinations render, exit 0 (`real-fixture-results.json`). Mutation-control: Ch7 state reflects the mutation; Ch5 interconnection does not (see limitation). | Reproduces the toy-fixture port-collapse finding on real content: zero port-name tokens appear anywhere in the rendered PlantUML source for Ch5's interconnection view, baseline or mutated — only the interface-level connector label `durationInterface` is drawn (`ch05-interconnection-opensysml-puml.puml`, `ch05-mutated-interconnection-opensysml-puml.puml`). The `tree` view rooted at `Toaster` is byte-identical across Ch5–Ch8 (see "Scope and non-coverage"), which is correct, not a bug. | Strong integration candidate for behavior and high-level structure; the interconnection view's port-name gap is a real, load-bearing limitation for a lesson whose subject includes ports, not a toy-fixture artifact. | +| **OpenSysML 0.9.0 → DOT → Graphviz SVG** | Same 6/6 success pattern and same mutation-control pattern as the PlantUML form (`real-fixture-results.json`, `mutation-control-results.json`). | Shares the identical port-collapse omission: zero port-name tokens in `ch05-interconnection-opensysml-dot.dot` or its mutated sibling, only the `durationInterface` edge label. | Same assessment as the PlantUML form; a lower-dependency variant of the same semantic projection. | +| **sysml-toolkit (pinned `af839f0d2`)** | All 6 attempted combinations render, exit 0 (`real-fixture-results.json`). Mutation-control: both Ch5 interconnection and Ch7 state correctly reflect the mutation (`mutation-control-results.json`, `mutation-rerenders.json`). | Does not share OpenSysML's port-collapse limitation: `ch05-interconnection-toolkit.puml` draws the real port names (`durationIn`, `durationOut`) inside their owning parts, not just the interface-level connector. | Strongest challenger on model-derived connectivity confirmed on real content, not just the toy fixture. | +| **OMG pilot `jupyter-sysml-kernel-0.62.0`** | Fails all 6 of 6 attempted real render combinations, exit 1 every time (`real-fixture-results.json`). Mutation-control could not be posed (no baseline to diff, any fixture) (`mutation-control-results.json`). | The parser's name-resolution check rejects the qualified-name allocate target in `models/ch05-cumulative.sysml` line 111 (`allocation heatAllocation allocate ToastBread::applyHeat to Toaster::heating;`) with `ERROR:Must be an accessible feature (use dot notation for nesting)` (`ch05-tree-pilot-emit.log`, `ch05-interconnection-pilot-emit.log`; the same error recurs for Ch6–Ch8, which share the same allocation statement pattern). This is a real capability gap against real content: the original toy fixture had no allocation statements and never exposed it. | Cannot currently render any of the four real fixtures at all; the toy-fixture "familiar notation reference" strength is untested on real content pending an upstream fix or a workaround to the allocate-target syntax. | +| **SysMLD / sysml2d (pinned `1af88250d`)** | Fails to reach any render or strict validation on either real view attempted (Ch5 interconnection, Ch7 state); mutation-control could not be posed. | A confirmed bug in the pinned indexer (`decisions/diagram-study-real-fixtures/evidence/sysmld-indexer-probe.json`): `build_model_index` pops a scope frame on any line beginning with `}`, regardless of whether a tracked keyword opened it, so an untracked brace pair (a doc-comment block, an `assert constraint` body) inside real fixture content pops frames early and erases the package prefix before later elements are indexed. Ch7 fails 8/8 errors, a clean single-cause confirmation of the indexer bug alone (`ch07-state-sysmld-render.log`, `ch07-state-sysmld-validate.log`). Ch5 fails 6/6 errors with two independent causes: 4 from the same indexer bug, and 2 from a separate, pre-existing gap in this plan's own intent-file JSON (missing aliases for two auto-generated connector-endpoint ids) — see "Model-to-picture integrity". All 12 aliases across both intent files were independently verified correct against the real fixture text; this is not an authoring error. | Cannot currently render any real content at all under the pinned commit; not patched, per ACE ruling `DL-055`. | + +Visual observations refer to the pinned versions above; a simplified view may be useful when its omissions are intentional and disclosed, and unsuitable for a lesson whose subject is the omitted information — the same standard the original study applied. + +## Model-to-picture integrity + +**Headline finding on the mutation-control test (independent reviewer's framing, adopted here):** SysMLD's original toy-fixture staleness finding is neither confirmed nor refuted on real content — it fails loudly and early on real content (strict-invalid, exit 1) rather than silently going stale, which is a different and arguably better-behaved failure mode, but the structural risk behind the original finding (a hand-authored intent file drifting from the model without any check catching it) is still present by construction and untested here. + +Supporting detail, from `decisions/diagram-study-real-fixtures/evidence/mutation-control-results.json` (an 8-entry list, not the dict shape the plan originally predicted): + +- **Ch7 (state):** all three runnable tools (opensysml-puml, opensysml-dot, toolkit) correctly reflect the real model mutation — the `Finish` transition's target retargeted from `ready` to `cancelled` (the trigger itself, `Finish`, is unchanged; only the transition's target state changed, per `diff fixtures/ch07.sysml fixtures/ch07-mutated.sysml`) — in their re-rendered output. This is a clean confirmation that these three tools track a real content change when they can render the view at all. +- **Ch5 (interconnection):** opensysml-puml and opensysml-dot show an unchanged render after the mutation (same SHA-256 before and after: `eac2960d...` and `e1dc4e18...` respectively, in `real-fixture-results.json` and `mutation-rerenders.json`). This is annotated in the evidence, and here, as the port-collapse coverage gap above — the mutated port (`durationOut` → `durationOutRenamed`) was never drawn in the first place, so there was nothing in the picture to go stale. This is **not** the same class of risk as SysMLD's original toy-fixture "silent staleness" finding (a picture that should reflect a change but doesn't due to intent-vs-model drift); it is a coverage gap, and toolkit's Ch5 result (SHA-256 changes from `6b335a62...` to `80d295f4...`) confirms the model's connectivity information was available to be drawn, just not drawn by OpenSysML's exporter. +- **Pilot and SysMLD:** the mutation-control question could not be *posed* at all for either tool, on either fixture — no successful baseline render exists to diff against (`mutation-control-results.json` entries 7–8). This is different from "tested and passed" or "tested and failed." + +**SysMLD indexer bug, and the Ch5/Ch7 distinction:** per `decisions/log.md`'s `DL-055` entry and its addendum (read via `git show pass1/harness-alignment:decisions/log.md`), Ch7's failure (8 errors, 0 warnings, `ch07-state-sysmld-validate.log`) is a clean, single-cause confirmation that the pinned indexer's scope-tracking bug alone is sufficient to block real-content indexing — every one of its 8 unresolved-reference errors traces to the bug. Ch5's failure (6 errors, 0 warnings, `ch05-interconnection-sysmld-validate.log`) has two independent causes: 4 of the 6 errors (`toaster`, `heating`, `control`, `conn-control-heating`) are the same indexer bug; the other 2 (`control--heating--src`, `control--heating--tgt`) are a separate, pre-existing gap in this plan's own Task 6 intent-file JSON, which never aliased two `compose`-auto-generated connector-endpoint ids that the original toy-fixture study's own worked example did alias for its analogous case. Both causes are confirmed independently in `sysmld-indexer-probe.json`, including a reviewer's scratch-copy check that adding the two missing aliases still leaves Ch5 failing 6/6 against the unpatched indexer. Whether Ch5 would render cleanly if the indexer bug alone were fixed is **not verified either way**. + +For the three tools that do render real content, a changed picture (or an unchanged one, when explained) is a necessary but limited check; this study also inspected the generated source text directly (the port-name-token check above) rather than relying on hash comparison alone. + +## Scope and non-coverage + +Phase 0 reran a narrower slice of the original study's 4-view-type × 5-tool comparison, against four real fixtures instead of one toy fixture. What was rerun and what was not: + +- **Rerun on real fixtures:** `interconnection` (Ch5) and `state` (Ch7) views, on the four common-model tools (OpenSysML-puml, OpenSysML-dot, toolkit, pilot) plus SysMLD's hand-authored intent files; `tree` (rooted at `ToasterDemo::Toaster`) on all four fixtures (Ch5–Ch8), on the same four common-model tools. +- **Not rerun in Phase 0 (see original study for the toy-fixture result):** the **action-flow** view, on any tool — it was already exercised from Ch4 onward and is not one of the untested gaps this rerun targets, per the plan's own Review Focus item 5. +- **Not rendered anywhere in this phase:** **DEMA SysML2Tools** — the approved spec's Phase 0 method names exactly four tools to rerun (OpenSysML+Graphviz/DOT, sysml-toolkit, the OMG pilot, and SysMLD); DEMA is not one of them, per the plan's Global Constraints. +- **Probed, not exhaustively tested:** allocation and requirement view *kinds*. `decisions/diagram-study-real-fixtures/evidence/view-type-probe.json` confirms, by reading OpenSysML's and the pilot's full `-help`/`--help` output and a recorded summary of sysml-toolkit's, that none of the three common-model tools (opensysml, sysml-toolkit, pilot) documents a dedicated allocation or requirement view kind — only SysMLD's own source (`allocation_view.py`, `requirement_view.py`) has classes for either. Untried routes that might still expose allocation or requirement content, none of them attempted against real fixtures in this phase: declaring a `View` element for OpenSysML (its `-render ` form targets a declared `View`, not a fixed kind); sysml-toolkit's own `mixed` view kind (listed in `toolkit-view-help.log`'s recorded `--view` values: `tree, interconnection, state, action, sequence, case, mixed`); and the pilot's `MIXED` view (`pilot-help.log`: "MIXED Show multiple views"). Nothing committed in this phase demonstrates OpenSysML's `#table`/`#mixed` shorthand forms exist or behave any particular way against real content — `opensysml-help.log` contains no mention of either token — so those two are not listed as confirmed untried routes, only as unconfirmed speculation if raised elsewhere. So "no common tool has a dedicated allocation/requirement view kind" should not be overclaimed as "no common tool can show allocations or requirement satisfaction at all" — several routes above remain untested. +- **Not authored:** SysMLD's `AllocationView`/`RequirementView` content for real fixtures — out of scope per the plan's own Task 6 text: no worked example existed to author against, and `RequirementView`'s schema does not fit Ch8's actual `satisfy`-by-part-usage content anyway. +- **A "wrong root chosen" scope boundary, not a recursion-depth bug and not a rendering bug:** the `tree` view rooted at `ToasterDemo::Toaster` produces byte-identical renders across all four fixtures, for all three tools that render it, because `Toaster`'s own declaration block is genuinely unchanged across Ch5–Ch8 — confirmed by the identical SHA-256 hashes in `real-fixture-results.json` (e.g. `2190e889...` for opensysml-puml in all of `ch05-tree`, `ch06-tree`, `ch07-tree`, `ch08-tree`). Rendering `Toaster`'s tree shows only that one part def's own direct features — one level of *its own* structure, not a recursive descent that merely stopped short. `heatGen` and `rated` are not absent because they sit deeper in `Toaster`'s containment tree; they are not in `Toaster`'s containment tree at all: `Toaster::heating` (`fixtures/ch06.sysml` line 68/70) is typed by the abstract `HeatingSystem`, and nothing in the fixture ever retypes `heating` to the concrete `HeatingAssembly :> HeatingSystem` that owns `heatGen` (`HeatingAssembly` is declared at package level, `fixtures/ch06.sysml` lines 146–150, and appears nowhere else in the fixture). Likewise `rated` (`fixtures/ch08.sysml` line 199) is a package-level `part rated : ResistanceCoil`, not nested under `Toaster` either. Reaching `heatGen`/`rated` would require rendering a *different* root element entirely — e.g. `ToasterDemo::HeatingAssembly` or the `rated` part itself — not rendering deeper from the same `Toaster` root. Consequently, the Ch6/Ch8 mutations are **inferred to be** not visible at this element choice — this is a sound inference from the identical source blocks (`Toaster`'s own declaration is untouched by either mutation) and the type mismatch above, but it was never directly observed by rendering: no tool was ever run against the mutated `ch06`/`ch08` fixtures for the `tree` view (see "Method and evidence" — mutation-control reruns were scoped to `ch05-interconnection`/`ch07-state` only). Phase 0's baseline renders answer "does the tool show `Toaster`'s own direct features when rooted there" (answer: yes, correctly and identically, for all three tools) rather than "can the tool ever show `heatGen`/`rated` at all" — that question is untested here, at a different root element choice. +- **A minor caveat on the harness itself:** Task 4's harness invokes *OpenSysML specifically* via the `#kind:element` render shorthand (e.g. `-render #tree:ToasterDemo::Toaster`, confirmed in `ch05-tree-opensysml-puml-emit.log`) — sysml-toolkit uses a different, documented CLI form instead (`--view --element `, confirmed in `ch05-tree-toolkit-emit.log`'s own argv), so the two should not be conflated. The OpenSysML shorthand was used in 12 baseline renders (opensysml-puml + opensysml-dot, across the 6 fixture/view rows) plus 4 more in Task 7's mutated reruns (ch05-interconnection and ch07-state, each form), 16 uses in total, every one exit 0. `opensysml-help.log`'s "Rendering views" section documents `-render ` only against a declared `View` element (e.g. `Views::vehicleView`), with no mention of the `#kind:element` shorthand anywhere in the help text — the shorthand is undocumented in `-help`, but all 16 of 16 uses on valid input succeeded. This report cannot independently cite a committed evidence file demonstrating that the shorthand rejects a bogus kind or element with a clear error — if such a check was ever performed, it left no committed evidence; this is flagged for the orchestrator, not resolved here. + +## Reproduction + +Fixtures, harness code, and evidence live under `decisions/diagram-study-real-fixtures/` and `scripts/diagram_study/`. Provisioning is re-checked against the pinned versions in `scripts/diagram_study/provision_check.py` before any render step. No renderer source was modified for this rerun; the SysMLD pinned checkout was probed and not patched, per `DL-055`. The environment is a measured research setup for Phase 0, not a production dependency lock, and this document is not a final tool selection. diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-dot-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-dot-emit.log new file mode 100644 index 0000000..dc83bf0 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-dot-emit.log @@ -0,0 +1,9 @@ +$ /private/tmp/functional-toaster-design/sysml decisions/diagram-study-real-fixtures/fixtures/ch05.sysml -render #interconnection:ToasterDemo::Toaster -render-form dot -o decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-dot.dot +exit_code=0 elapsed_seconds=0.068 + +--- stdout --- + +--- stderr --- +✓ package ToasterDemo +wrote decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-dot.dot (dot, 1006 bytes) + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-dot-render.log b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-dot-render.log new file mode 100644 index 0000000..9639706 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-dot-render.log @@ -0,0 +1,7 @@ +$ dot -Tsvg decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-dot.dot -o decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-dot.svg +exit_code=0 elapsed_seconds=0.067 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-dot.dot b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-dot.dot new file mode 100644 index 0000000..595dd8d --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-dot.dot @@ -0,0 +1,18 @@ +// kind: interconnection +// stated: no view declared; rendering ToasterDemo::Toaster directly +// layout: dot +digraph { + graph [fontname="Helvetica"]; + node [shape=box, style=filled, fillcolor=white, color="#181818", fontname="Helvetica", fontsize=14, penwidth=0.5]; + edge [color="#181818", fontname="Helvetica", fontsize=13, penwidth=1]; + subgraph "cluster_n0" { + label=<Toaster
«part def»>; + color=black; + penwidth=0.5; + "n0" [shape=point, style=invis, width=0, height=0, label=""]; + "n1" [style="rounded,filled", label=<cycleTime : DurationValue
«attribute»>]; + "n2" [style="rounded,filled", label=<heating : HeatingSystem
«part»>]; + "n3" [style="rounded,filled", label=<control : ControlSystem
«part»>]; + } + "n3" -> "n2" [label="durationInterface", arrowhead=none, penwidth=3]; +} diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-dot.svg b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-dot.svg new file mode 100644 index 0000000..47a0807 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-dot.svg @@ -0,0 +1,46 @@ + + + + + + + + +cluster_n0 + +Toaster +«part def» + + + + +n1 + +cycleTime : DurationValue +«attribute» + + + +n2 + +heating : HeatingSystem +«part» + + + +n3 + +control : ControlSystem +«part» + + + +n3->n2 + +durationInterface + + + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-puml-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-puml-emit.log new file mode 100644 index 0000000..db280bb --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-puml-emit.log @@ -0,0 +1,9 @@ +$ /private/tmp/functional-toaster-design/sysml decisions/diagram-study-real-fixtures/fixtures/ch05.sysml -render #interconnection:ToasterDemo::Toaster -render-form plantuml -o decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-puml.puml +exit_code=0 elapsed_seconds=0.056 + +--- stdout --- + +--- stderr --- +✓ package ToasterDemo +wrote decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-puml.puml (plantuml, 1080 bytes) + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-puml-render.log b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-puml-render.log new file mode 100644 index 0000000..c81b770 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-puml-render.log @@ -0,0 +1,7 @@ +$ /opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java -Djava.awt.headless=true -jar /opt/homebrew/opt/plantuml/libexec/plantuml.jar -tsvg decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-puml.puml +exit_code=0 elapsed_seconds=0.89 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-puml.puml b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-puml.puml new file mode 100644 index 0000000..2e6988c --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-puml.puml @@ -0,0 +1,49 @@ +@startuml +' interconnection rendering (no view declared; rendering ToasterDemo::Toaster directly) + +skinparam wrapWidth 300 +hide stereotype +rectangle "**Toaster**\n//«part def»//" as n0 <> { + rectangle "**cycleTime : DurationValue**\n//«attribute»//" as n1 <> <> + rectangle "**heating : HeatingSystem**\n//«part»//" as n2 <> <> + rectangle "**control : ControlSystem**\n//«part»//" as n3 <> <> +} +n3 -[thickness=3]- n2 : durationInterface +@enduml diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-puml.svg b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-puml.svg new file mode 100644 index 0000000..6d85b65 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-opensysml-puml.svg @@ -0,0 +1 @@ +Toaster«part def»cycleTime : DurationValue«attribute»heating : HeatingSystem«part»control : ControlSystem«part»durationInterface \ No newline at end of file diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-pilot-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-pilot-emit.log new file mode 100644 index 0000000..558402f --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-pilot-emit.log @@ -0,0 +1,116 @@ +$ /opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java -Djava.awt.headless=true -cp /private/tmp/toaster-diagram-study/pilot/sysml/jupyter-sysml-kernel-0.62.0-all.jar /private/tmp/toaster-diagram-study/PilotRender.java /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library decisions/diagram-study-real-fixtures/fixtures/ch05.sysml ToasterDemo::Toaster interconnection decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-pilot.svg +exit_code=1 elapsed_seconds=2.833 + +--- stdout --- +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Performances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Transfers.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Base.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Observation.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Triggers.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Clocks.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/SpatialFrames.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/ControlPerformances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/KerML.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Objects.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/FeatureReferencingPerformances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Metaobjects.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/StatePerformances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/TransitionPerformances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Occurrences.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Links.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/RationalFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/ControlFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/BooleanFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/VectorFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/RealFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/CollectionFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/ComplexFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/NumericalFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/TrigFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/IntegerFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/NaturalFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/StringFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/DataFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/ScalarFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/BaseFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/SequenceFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/OccurrenceFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Data Type Library/ScalarValues.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Data Type Library/VectorValues.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Data Type Library/Collections.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Parts.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/SysML.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/VerificationCases.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/AnalysisCases.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/States.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Flows.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Views.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Requirements.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Interfaces.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Actions.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/UseCases.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Items.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Metadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Allocations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Calculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Constraints.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Attributes.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Ports.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/StandardViewDefinitions.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Connections.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Cases.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/SI.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQBase.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/Quantities.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQLight.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQElectromagnetism.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQCondensedMatter.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/MeasurementRefCalculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/USCustomaryUnits.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQMechanics.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/VectorCalculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/SIPrefixes.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQSpaceTime.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQAcoustics.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQ.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/MeasurementReferences.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/TensorCalculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQCharacteristicNumbers.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQInformation.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/Time.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQChemistryMolecular.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/QuantityCalculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQAtomicNuclear.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQThermodynamics.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Analysis/AnalysisTooling.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Analysis/TradeStudies.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Analysis/SampledFunctions.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Analysis/StateSpaceRepresentation.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Cause and Effect/CauseAndEffect.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Cause and Effect/CausationConnections.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Geometry/SpatialItems.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Geometry/ShapeItems.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Metadata/RiskMetadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Metadata/ParametersOfInterestMetadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Metadata/ModelingMetadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Metadata/ImageMetadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Requirement Derivation/RequirementDerivation.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Requirement Derivation/DerivationConnections.sysml... +ERROR:Must be an accessible feature (use dot notation for nesting) (1.sysml line : 111 column : 40) +ERROR:Must be an accessible feature (use dot notation for nesting) (1.sysml line : 111 column : 65) + + +--- stderr --- +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::staticFieldBase has been called by com.google.inject.internal.aop.HiddenClassDefiner (file:/private/tmp/toaster-diagram-study/pilot/sysml/jupyter-sysml-kernel-0.62.0-all.jar) +WARNING: Please consider reporting this to the maintainers of class com.google.inject.internal.aop.HiddenClassDefiner +WARNING: sun.misc.Unsafe::staticFieldBase will be removed in a future release +log4j:WARN No appenders could be found for logger (org.eclipse.xtext.parser.antlr.AbstractInternalAntlrParser). +log4j:WARN Please initialize the log4j system properly. +log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. +WARNING: Final field index in class org.omg.kerml.xtext.library.LibraryIndex has been mutated reflectively by class com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1 in unnamed module @2364305a (file:/private/tmp/toaster-diagram-study/pilot/sysml/jupyter-sysml-kernel-0.62.0-all.jar) +WARNING: Use --enable-final-field-mutation=ALL-UNNAMED to avoid a warning +WARNING: Mutating final fields will be blocked in a future release unless final field mutation is enabled +Exception in thread "main" java.lang.IllegalStateException: Model diagnostics + at PilotRender.main(PilotRender.java:12) + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-sysmld-render.log b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-sysmld-render.log new file mode 100644 index 0000000..51d8485 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-sysmld-render.log @@ -0,0 +1,16 @@ +$ (cwd=decisions/diagram-study-real-fixtures/sysml2d-intent) python3 -c from sysmld.cli import main; raise SystemExit(main()) render ch05-interconnection.sysmld +exit_code=1 elapsed_seconds=0.057 + +--- stdout --- + +--- stderr --- +Traceback (most recent call last): + File "", line 1, in + from sysmld.cli import main; raise SystemExit(main()) + ~~~~^^ + File "/private/tmp/toaster-diagram-study/sysml2d/src/sysmld/cli.py", line 172, in main + target = render_svg(Path(args.path), output_path=output, strict=strict) + File "/private/tmp/toaster-diagram-study/sysml2d/src/sysmld/render_svg.py", line 46, in render_svg + raise ValueError(f"Cannot render strict-invalid diagram: {messages}") +ValueError: Cannot render strict-invalid diagram: SYSMLD-REF-002: unresolved model reference: heating -> ToasterDemo::Toaster::heating; SYSMLD-REF-002: unresolved model reference: control -> ToasterDemo::Toaster::control; SYSMLD-REF-002: unresolved model reference: control--heating--tgt -> control--heating--tgt; SYSMLD-REF-002: unresolved model reference: control--heating--src -> control--heating--src; SYSMLD-REF-002: unresolved model reference: toaster -> ToasterDemo::Toaster; SYSMLD-REF-002: unresolved model reference: conn-control-heating -> ToasterDemo::Toaster::durationInterface + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-sysmld-validate.log b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-sysmld-validate.log new file mode 100644 index 0000000..43a1af2 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-sysmld-validate.log @@ -0,0 +1,14 @@ +$ (cwd=decisions/diagram-study-real-fixtures/sysml2d-intent) python3 -c from sysmld.cli import main; raise SystemExit(main()) validate ch05-interconnection.sysmld --strict +exit_code=1 elapsed_seconds=0.054 + +--- stdout --- +ERROR SYSMLD-REF-002: unresolved model reference: conn-control-heating -> ToasterDemo::Toaster::durationInterface +ERROR SYSMLD-REF-002: unresolved model reference: toaster -> ToasterDemo::Toaster +ERROR SYSMLD-REF-002: unresolved model reference: control--heating--tgt -> control--heating--tgt +ERROR SYSMLD-REF-002: unresolved model reference: control -> ToasterDemo::Toaster::control +ERROR SYSMLD-REF-002: unresolved model reference: heating -> ToasterDemo::Toaster::heating +ERROR SYSMLD-REF-002: unresolved model reference: control--heating--src -> control--heating--src +6 errors, 0 warnings + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-toolkit-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-toolkit-emit.log new file mode 100644 index 0000000..dd038a9 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-toolkit-emit.log @@ -0,0 +1,7 @@ +$ /Users/z/Documents/GitHub/sysml-toolkit/target/release/sysmlv2 viz decisions/diagram-study-real-fixtures/fixtures/ch05.sysml --view interconnection --element ToasterDemo::Toaster -o decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-toolkit.puml +exit_code=0 elapsed_seconds=0.006 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-toolkit-render.log b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-toolkit-render.log new file mode 100644 index 0000000..5ce4941 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-toolkit-render.log @@ -0,0 +1,7 @@ +$ /opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java -Djava.awt.headless=true -jar /opt/homebrew/opt/plantuml/libexec/plantuml.jar -tsvg decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-toolkit.puml +exit_code=0 elapsed_seconds=0.894 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-toolkit.puml b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-toolkit.puml new file mode 100644 index 0000000..368f91d --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-toolkit.puml @@ -0,0 +1,11 @@ +@startuml +rectangle "Toaster" as n1 <> { + rectangle "heating : HeatingSystem" as n2 <> { + port "durationIn : ~DurationPort" as n3 + } + rectangle "control : ControlSystem" as n4 <> { + port "durationOut : DurationPort" as n5 + } +} +n5 -- n3 : «interface» durationInterface +@enduml diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-toolkit.svg b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-toolkit.svg new file mode 100644 index 0000000..a1055ec --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-toolkit.svg @@ -0,0 +1 @@ +«part def»Toaster«part»heating : HeatingSystem«part»control : ControlSystemdurationIn : ~DurationPortdurationOut : DurationPort«interface» durationInterface \ No newline at end of file diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-dot-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-dot-emit.log new file mode 100644 index 0000000..f979eb5 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-dot-emit.log @@ -0,0 +1,9 @@ +$ /private/tmp/functional-toaster-design/sysml decisions/diagram-study-real-fixtures/fixtures/ch05-mutated.sysml -render #interconnection:ToasterDemo::Toaster -render-form dot -o decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-dot.dot +exit_code=0 elapsed_seconds=0.067 + +--- stdout --- + +--- stderr --- +✓ package ToasterDemo +wrote decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-dot.dot (dot, 1006 bytes) + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-dot-render.log b/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-dot-render.log new file mode 100644 index 0000000..ef299a0 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-dot-render.log @@ -0,0 +1,7 @@ +$ dot -Tsvg decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-dot.dot -o decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-dot.svg +exit_code=0 elapsed_seconds=0.074 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-dot.dot b/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-dot.dot new file mode 100644 index 0000000..595dd8d --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-dot.dot @@ -0,0 +1,18 @@ +// kind: interconnection +// stated: no view declared; rendering ToasterDemo::Toaster directly +// layout: dot +digraph { + graph [fontname="Helvetica"]; + node [shape=box, style=filled, fillcolor=white, color="#181818", fontname="Helvetica", fontsize=14, penwidth=0.5]; + edge [color="#181818", fontname="Helvetica", fontsize=13, penwidth=1]; + subgraph "cluster_n0" { + label=<Toaster
«part def»>; + color=black; + penwidth=0.5; + "n0" [shape=point, style=invis, width=0, height=0, label=""]; + "n1" [style="rounded,filled", label=<cycleTime : DurationValue
«attribute»>]; + "n2" [style="rounded,filled", label=<heating : HeatingSystem
«part»>]; + "n3" [style="rounded,filled", label=<control : ControlSystem
«part»>]; + } + "n3" -> "n2" [label="durationInterface", arrowhead=none, penwidth=3]; +} diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-dot.svg b/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-dot.svg new file mode 100644 index 0000000..47a0807 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-dot.svg @@ -0,0 +1,46 @@ + + + + + + + + +cluster_n0 + +Toaster +«part def» + + + + +n1 + +cycleTime : DurationValue +«attribute» + + + +n2 + +heating : HeatingSystem +«part» + + + +n3 + +control : ControlSystem +«part» + + + +n3->n2 + +durationInterface + + + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-puml-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-puml-emit.log new file mode 100644 index 0000000..73461eb --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-puml-emit.log @@ -0,0 +1,9 @@ +$ /private/tmp/functional-toaster-design/sysml decisions/diagram-study-real-fixtures/fixtures/ch05-mutated.sysml -render #interconnection:ToasterDemo::Toaster -render-form plantuml -o decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-puml.puml +exit_code=0 elapsed_seconds=0.117 + +--- stdout --- + +--- stderr --- +✓ package ToasterDemo +wrote decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-puml.puml (plantuml, 1080 bytes) + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-puml-render.log b/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-puml-render.log new file mode 100644 index 0000000..e0942e8 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-puml-render.log @@ -0,0 +1,7 @@ +$ /opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java -Djava.awt.headless=true -jar /opt/homebrew/opt/plantuml/libexec/plantuml.jar -tsvg decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-puml.puml +exit_code=0 elapsed_seconds=1.108 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-puml.puml b/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-puml.puml new file mode 100644 index 0000000..2e6988c --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-puml.puml @@ -0,0 +1,49 @@ +@startuml +' interconnection rendering (no view declared; rendering ToasterDemo::Toaster directly) + +skinparam wrapWidth 300 +hide stereotype +rectangle "**Toaster**\n//«part def»//" as n0 <> { + rectangle "**cycleTime : DurationValue**\n//«attribute»//" as n1 <> <> + rectangle "**heating : HeatingSystem**\n//«part»//" as n2 <> <> + rectangle "**control : ControlSystem**\n//«part»//" as n3 <> <> +} +n3 -[thickness=3]- n2 : durationInterface +@enduml diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-puml.svg b/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-puml.svg new file mode 100644 index 0000000..6d85b65 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-puml.svg @@ -0,0 +1 @@ +Toaster«part def»cycleTime : DurationValue«attribute»heating : HeatingSystem«part»control : ControlSystem«part»durationInterface \ No newline at end of file diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-toolkit-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-toolkit-emit.log new file mode 100644 index 0000000..4ac1af9 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-toolkit-emit.log @@ -0,0 +1,7 @@ +$ /Users/z/Documents/GitHub/sysml-toolkit/target/release/sysmlv2 viz decisions/diagram-study-real-fixtures/fixtures/ch05-mutated.sysml --view interconnection --element ToasterDemo::Toaster -o decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-toolkit.puml +exit_code=0 elapsed_seconds=0.017 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-toolkit-render.log b/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-toolkit-render.log new file mode 100644 index 0000000..044b332 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-toolkit-render.log @@ -0,0 +1,7 @@ +$ /opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java -Djava.awt.headless=true -jar /opt/homebrew/opt/plantuml/libexec/plantuml.jar -tsvg decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-toolkit.puml +exit_code=0 elapsed_seconds=0.897 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-toolkit.puml b/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-toolkit.puml new file mode 100644 index 0000000..907b531 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-toolkit.puml @@ -0,0 +1,11 @@ +@startuml +rectangle "Toaster" as n1 <> { + rectangle "heating : HeatingSystem" as n2 <> { + port "durationIn : ~DurationPort" as n3 + } + rectangle "control : ControlSystem" as n4 <> { + port "durationOutRenamed : DurationPort" as n5 + } +} +n5 -- n3 : «interface» durationInterface +@enduml diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-toolkit.svg b/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-toolkit.svg new file mode 100644 index 0000000..d41e8d4 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-toolkit.svg @@ -0,0 +1 @@ +«part def»Toaster«part»heating : HeatingSystem«part»control : ControlSystemdurationIn : ~DurationPortdurationOutRenamed : DurationPort«interface» durationInterface \ No newline at end of file diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-dot-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-dot-emit.log new file mode 100644 index 0000000..5aba196 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-dot-emit.log @@ -0,0 +1,9 @@ +$ /private/tmp/functional-toaster-design/sysml decisions/diagram-study-real-fixtures/fixtures/ch05.sysml -render #tree:ToasterDemo::Toaster -render-form dot -o decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-dot.dot +exit_code=0 elapsed_seconds=0.066 + +--- stdout --- + +--- stderr --- +✓ package ToasterDemo +wrote decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-dot.dot (dot, 1044 bytes) + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-dot-render.log b/decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-dot-render.log new file mode 100644 index 0000000..f6ed368 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-dot-render.log @@ -0,0 +1,7 @@ +$ dot -Tsvg decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-dot.dot -o decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-dot.svg +exit_code=0 elapsed_seconds=0.073 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-dot.dot b/decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-dot.dot new file mode 100644 index 0000000..8e39a7a --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-dot.dot @@ -0,0 +1,17 @@ +// kind: tree +// stated: no view declared; rendering ToasterDemo::Toaster directly +// layout: dot +digraph { + graph [fontname="Helvetica"]; + node [shape=box, style=filled, fillcolor=white, color="#181818", fontname="Helvetica", fontsize=14, penwidth=0.5]; + edge [color="#181818", fontname="Helvetica", fontsize=13, penwidth=1]; + "n0" [label=<Toaster
«part def»>]; + "n1" [style="rounded,filled", label=<cycleTime : DurationValue
«attribute»>]; + "n0" -> "n1" [arrowhead=none]; + "n2" [style="rounded,filled", label=<heating : HeatingSystem
«part»>]; + "n0" -> "n2" [arrowhead=none]; + "n3" [style="rounded,filled", label=<control : ControlSystem
«part»>]; + "n0" -> "n3" [arrowhead=none]; + "n4" [style="rounded,filled", label=<durationInterface
«interface»>]; + "n0" -> "n4" [arrowhead=none]; +} diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-dot.svg b/decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-dot.svg new file mode 100644 index 0000000..04c342c --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-dot.svg @@ -0,0 +1,67 @@ + + + + + + + + + +n0 + +Toaster +«part def» + + + +n1 + +cycleTime : DurationValue +«attribute» + + + +n0->n1 + + + + +n2 + +heating : HeatingSystem +«part» + + + +n0->n2 + + + + +n3 + +control : ControlSystem +«part» + + + +n0->n3 + + + + +n4 + +durationInterface +«interface» + + + +n0->n4 + + + + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-puml-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-puml-emit.log new file mode 100644 index 0000000..61f20d9 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-puml-emit.log @@ -0,0 +1,9 @@ +$ /private/tmp/functional-toaster-design/sysml decisions/diagram-study-real-fixtures/fixtures/ch05.sysml -render #tree:ToasterDemo::Toaster -render-form plantuml -o decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-puml.puml +exit_code=0 elapsed_seconds=0.112 + +--- stdout --- + +--- stderr --- +✓ package ToasterDemo +wrote decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-puml.puml (plantuml, 1163 bytes) + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-puml-render.log b/decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-puml-render.log new file mode 100644 index 0000000..b0168a5 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-puml-render.log @@ -0,0 +1,7 @@ +$ /opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java -Djava.awt.headless=true -jar /opt/homebrew/opt/plantuml/libexec/plantuml.jar -tsvg decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-puml.puml +exit_code=0 elapsed_seconds=1.32 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-puml.puml b/decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-puml.puml new file mode 100644 index 0000000..c1d6590 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-puml.puml @@ -0,0 +1,54 @@ +@startuml +' tree rendering (no view declared; rendering ToasterDemo::Toaster directly) + +skinparam wrapWidth 300 +hide stereotype +hide circle +hide empty members +class "**Toaster**\n//«part def»//" as n0 <> +class "**cycleTime : DurationValue**\n//«attribute»//" as n1 <> <> +n0 -- n1 +class "**heating : HeatingSystem**\n//«part»//" as n2 <> <> +n0 -- n2 +class "**control : ControlSystem**\n//«part»//" as n3 <> <> +n0 -- n3 +class "**durationInterface**\n//«interface»//" as n4 <> <> +n0 -- n4 +@enduml diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-puml.svg b/decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-puml.svg new file mode 100644 index 0000000..8d58d34 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-tree-opensysml-puml.svg @@ -0,0 +1 @@ +Toaster«part def»cycleTime : DurationValue«attribute»heating : HeatingSystem«part»control : ControlSystem«part»durationInterface«interface» \ No newline at end of file diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-tree-pilot-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch05-tree-pilot-emit.log new file mode 100644 index 0000000..5cc90da --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-tree-pilot-emit.log @@ -0,0 +1,116 @@ +$ /opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java -Djava.awt.headless=true -cp /private/tmp/toaster-diagram-study/pilot/sysml/jupyter-sysml-kernel-0.62.0-all.jar /private/tmp/toaster-diagram-study/PilotRender.java /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library decisions/diagram-study-real-fixtures/fixtures/ch05.sysml ToasterDemo::Toaster tree decisions/diagram-study-real-fixtures/evidence/ch05-tree-pilot.svg +exit_code=1 elapsed_seconds=2.957 + +--- stdout --- +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Performances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Transfers.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Base.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Observation.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Triggers.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Clocks.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/SpatialFrames.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/ControlPerformances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/KerML.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Objects.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/FeatureReferencingPerformances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Metaobjects.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/StatePerformances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/TransitionPerformances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Occurrences.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Links.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/RationalFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/ControlFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/BooleanFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/VectorFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/RealFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/CollectionFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/ComplexFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/NumericalFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/TrigFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/IntegerFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/NaturalFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/StringFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/DataFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/ScalarFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/BaseFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/SequenceFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/OccurrenceFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Data Type Library/ScalarValues.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Data Type Library/VectorValues.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Data Type Library/Collections.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Parts.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/SysML.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/VerificationCases.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/AnalysisCases.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/States.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Flows.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Views.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Requirements.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Interfaces.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Actions.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/UseCases.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Items.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Metadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Allocations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Calculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Constraints.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Attributes.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Ports.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/StandardViewDefinitions.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Connections.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Cases.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/SI.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQBase.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/Quantities.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQLight.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQElectromagnetism.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQCondensedMatter.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/MeasurementRefCalculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/USCustomaryUnits.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQMechanics.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/VectorCalculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/SIPrefixes.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQSpaceTime.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQAcoustics.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQ.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/MeasurementReferences.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/TensorCalculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQCharacteristicNumbers.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQInformation.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/Time.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQChemistryMolecular.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/QuantityCalculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQAtomicNuclear.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQThermodynamics.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Analysis/AnalysisTooling.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Analysis/TradeStudies.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Analysis/SampledFunctions.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Analysis/StateSpaceRepresentation.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Cause and Effect/CauseAndEffect.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Cause and Effect/CausationConnections.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Geometry/SpatialItems.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Geometry/ShapeItems.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Metadata/RiskMetadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Metadata/ParametersOfInterestMetadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Metadata/ModelingMetadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Metadata/ImageMetadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Requirement Derivation/RequirementDerivation.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Requirement Derivation/DerivationConnections.sysml... +ERROR:Must be an accessible feature (use dot notation for nesting) (1.sysml line : 111 column : 40) +ERROR:Must be an accessible feature (use dot notation for nesting) (1.sysml line : 111 column : 65) + + +--- stderr --- +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::staticFieldBase has been called by com.google.inject.internal.aop.HiddenClassDefiner (file:/private/tmp/toaster-diagram-study/pilot/sysml/jupyter-sysml-kernel-0.62.0-all.jar) +WARNING: Please consider reporting this to the maintainers of class com.google.inject.internal.aop.HiddenClassDefiner +WARNING: sun.misc.Unsafe::staticFieldBase will be removed in a future release +log4j:WARN No appenders could be found for logger (org.eclipse.xtext.parser.antlr.AbstractInternalAntlrParser). +log4j:WARN Please initialize the log4j system properly. +log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. +WARNING: Final field index in class org.omg.kerml.xtext.library.LibraryIndex has been mutated reflectively by class com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1 in unnamed module @2364305a (file:/private/tmp/toaster-diagram-study/pilot/sysml/jupyter-sysml-kernel-0.62.0-all.jar) +WARNING: Use --enable-final-field-mutation=ALL-UNNAMED to avoid a warning +WARNING: Mutating final fields will be blocked in a future release unless final field mutation is enabled +Exception in thread "main" java.lang.IllegalStateException: Model diagnostics + at PilotRender.main(PilotRender.java:12) + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-tree-toolkit-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch05-tree-toolkit-emit.log new file mode 100644 index 0000000..8652350 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-tree-toolkit-emit.log @@ -0,0 +1,7 @@ +$ /Users/z/Documents/GitHub/sysml-toolkit/target/release/sysmlv2 viz decisions/diagram-study-real-fixtures/fixtures/ch05.sysml --view tree --element ToasterDemo::Toaster -o decisions/diagram-study-real-fixtures/evidence/ch05-tree-toolkit.puml +exit_code=0 elapsed_seconds=0.007 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-tree-toolkit-render.log b/decisions/diagram-study-real-fixtures/evidence/ch05-tree-toolkit-render.log new file mode 100644 index 0000000..7ffb566 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-tree-toolkit-render.log @@ -0,0 +1,7 @@ +$ /opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java -Djava.awt.headless=true -jar /opt/homebrew/opt/plantuml/libexec/plantuml.jar -tsvg decisions/diagram-study-real-fixtures/evidence/ch05-tree-toolkit.puml +exit_code=0 elapsed_seconds=1.022 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-tree-toolkit.puml b/decisions/diagram-study-real-fixtures/evidence/ch05-tree-toolkit.puml new file mode 100644 index 0000000..fad7606 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-tree-toolkit.puml @@ -0,0 +1,16 @@ +@startuml +hide empty members +class "Toaster" as n1 <> { + cycleTime +} +class "heating : HeatingSystem" as n2 <> +class "control : ControlSystem" as n3 <> +class "durationInterface" as n4 <> +class "(port)" as n5 <> +class "(port)" as n6 <> +n1 *-- n2 +n1 *-- n3 +n4 o-- n5 +n4 o-- n6 +n1 *-- n4 +@enduml diff --git a/decisions/diagram-study-real-fixtures/evidence/ch05-tree-toolkit.svg b/decisions/diagram-study-real-fixtures/evidence/ch05-tree-toolkit.svg new file mode 100644 index 0000000..adde09c --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch05-tree-toolkit.svg @@ -0,0 +1 @@ +«part def»ToastercycleTime«part»heating : HeatingSystem«part»control : ControlSystem«interface»durationInterface«port»(port)«port»(port) \ No newline at end of file diff --git a/decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-dot-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-dot-emit.log new file mode 100644 index 0000000..ae0d732 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-dot-emit.log @@ -0,0 +1,9 @@ +$ /private/tmp/functional-toaster-design/sysml decisions/diagram-study-real-fixtures/fixtures/ch06.sysml -render #tree:ToasterDemo::Toaster -render-form dot -o decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-dot.dot +exit_code=0 elapsed_seconds=0.076 + +--- stdout --- + +--- stderr --- +✓ package ToasterDemo +wrote decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-dot.dot (dot, 1044 bytes) + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-dot-render.log b/decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-dot-render.log new file mode 100644 index 0000000..3b06741 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-dot-render.log @@ -0,0 +1,7 @@ +$ dot -Tsvg decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-dot.dot -o decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-dot.svg +exit_code=0 elapsed_seconds=0.081 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-dot.dot b/decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-dot.dot new file mode 100644 index 0000000..8e39a7a --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-dot.dot @@ -0,0 +1,17 @@ +// kind: tree +// stated: no view declared; rendering ToasterDemo::Toaster directly +// layout: dot +digraph { + graph [fontname="Helvetica"]; + node [shape=box, style=filled, fillcolor=white, color="#181818", fontname="Helvetica", fontsize=14, penwidth=0.5]; + edge [color="#181818", fontname="Helvetica", fontsize=13, penwidth=1]; + "n0" [label=<Toaster
«part def»>]; + "n1" [style="rounded,filled", label=<cycleTime : DurationValue
«attribute»>]; + "n0" -> "n1" [arrowhead=none]; + "n2" [style="rounded,filled", label=<heating : HeatingSystem
«part»>]; + "n0" -> "n2" [arrowhead=none]; + "n3" [style="rounded,filled", label=<control : ControlSystem
«part»>]; + "n0" -> "n3" [arrowhead=none]; + "n4" [style="rounded,filled", label=<durationInterface
«interface»>]; + "n0" -> "n4" [arrowhead=none]; +} diff --git a/decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-dot.svg b/decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-dot.svg new file mode 100644 index 0000000..04c342c --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-dot.svg @@ -0,0 +1,67 @@ + + + + + + + + + +n0 + +Toaster +«part def» + + + +n1 + +cycleTime : DurationValue +«attribute» + + + +n0->n1 + + + + +n2 + +heating : HeatingSystem +«part» + + + +n0->n2 + + + + +n3 + +control : ControlSystem +«part» + + + +n0->n3 + + + + +n4 + +durationInterface +«interface» + + + +n0->n4 + + + + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-puml-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-puml-emit.log new file mode 100644 index 0000000..9ab48b1 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-puml-emit.log @@ -0,0 +1,9 @@ +$ /private/tmp/functional-toaster-design/sysml decisions/diagram-study-real-fixtures/fixtures/ch06.sysml -render #tree:ToasterDemo::Toaster -render-form plantuml -o decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-puml.puml +exit_code=0 elapsed_seconds=0.055 + +--- stdout --- + +--- stderr --- +✓ package ToasterDemo +wrote decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-puml.puml (plantuml, 1163 bytes) + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-puml-render.log b/decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-puml-render.log new file mode 100644 index 0000000..530806c --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-puml-render.log @@ -0,0 +1,7 @@ +$ /opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java -Djava.awt.headless=true -jar /opt/homebrew/opt/plantuml/libexec/plantuml.jar -tsvg decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-puml.puml +exit_code=0 elapsed_seconds=0.932 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-puml.puml b/decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-puml.puml new file mode 100644 index 0000000..c1d6590 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-puml.puml @@ -0,0 +1,54 @@ +@startuml +' tree rendering (no view declared; rendering ToasterDemo::Toaster directly) + +skinparam wrapWidth 300 +hide stereotype +hide circle +hide empty members +class "**Toaster**\n//«part def»//" as n0 <> +class "**cycleTime : DurationValue**\n//«attribute»//" as n1 <> <> +n0 -- n1 +class "**heating : HeatingSystem**\n//«part»//" as n2 <> <> +n0 -- n2 +class "**control : ControlSystem**\n//«part»//" as n3 <> <> +n0 -- n3 +class "**durationInterface**\n//«interface»//" as n4 <> <> +n0 -- n4 +@enduml diff --git a/decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-puml.svg b/decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-puml.svg new file mode 100644 index 0000000..8d58d34 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch06-tree-opensysml-puml.svg @@ -0,0 +1 @@ +Toaster«part def»cycleTime : DurationValue«attribute»heating : HeatingSystem«part»control : ControlSystem«part»durationInterface«interface» \ No newline at end of file diff --git a/decisions/diagram-study-real-fixtures/evidence/ch06-tree-pilot-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch06-tree-pilot-emit.log new file mode 100644 index 0000000..e87c372 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch06-tree-pilot-emit.log @@ -0,0 +1,118 @@ +$ /opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java -Djava.awt.headless=true -cp /private/tmp/toaster-diagram-study/pilot/sysml/jupyter-sysml-kernel-0.62.0-all.jar /private/tmp/toaster-diagram-study/PilotRender.java /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library decisions/diagram-study-real-fixtures/fixtures/ch06.sysml ToasterDemo::Toaster tree decisions/diagram-study-real-fixtures/evidence/ch06-tree-pilot.svg +exit_code=1 elapsed_seconds=2.93 + +--- stdout --- +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Performances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Transfers.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Base.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Observation.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Triggers.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Clocks.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/SpatialFrames.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/ControlPerformances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/KerML.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Objects.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/FeatureReferencingPerformances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Metaobjects.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/StatePerformances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/TransitionPerformances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Occurrences.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Links.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/RationalFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/ControlFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/BooleanFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/VectorFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/RealFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/CollectionFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/ComplexFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/NumericalFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/TrigFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/IntegerFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/NaturalFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/StringFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/DataFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/ScalarFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/BaseFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/SequenceFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/OccurrenceFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Data Type Library/ScalarValues.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Data Type Library/VectorValues.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Data Type Library/Collections.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Parts.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/SysML.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/VerificationCases.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/AnalysisCases.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/States.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Flows.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Views.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Requirements.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Interfaces.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Actions.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/UseCases.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Items.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Metadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Allocations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Calculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Constraints.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Attributes.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Ports.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/StandardViewDefinitions.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Connections.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Cases.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/SI.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQBase.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/Quantities.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQLight.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQElectromagnetism.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQCondensedMatter.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/MeasurementRefCalculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/USCustomaryUnits.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQMechanics.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/VectorCalculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/SIPrefixes.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQSpaceTime.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQAcoustics.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQ.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/MeasurementReferences.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/TensorCalculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQCharacteristicNumbers.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQInformation.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/Time.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQChemistryMolecular.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/QuantityCalculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQAtomicNuclear.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQThermodynamics.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Analysis/AnalysisTooling.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Analysis/TradeStudies.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Analysis/SampledFunctions.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Analysis/StateSpaceRepresentation.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Cause and Effect/CauseAndEffect.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Cause and Effect/CausationConnections.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Geometry/SpatialItems.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Geometry/ShapeItems.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Metadata/RiskMetadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Metadata/ParametersOfInterestMetadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Metadata/ModelingMetadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Metadata/ImageMetadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Requirement Derivation/RequirementDerivation.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Requirement Derivation/DerivationConnections.sysml... +ERROR:Must be an accessible feature (use dot notation for nesting) (1.sysml line : 117 column : 40) +ERROR:Must be an accessible feature (use dot notation for nesting) (1.sysml line : 117 column : 65) +ERROR:Must be an accessible feature (use dot notation for nesting) (1.sysml line : 150 column : 43) +ERROR:Must be an accessible feature (use dot notation for nesting) (1.sysml line : 150 column : 70) + + +--- stderr --- +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::staticFieldBase has been called by com.google.inject.internal.aop.HiddenClassDefiner (file:/private/tmp/toaster-diagram-study/pilot/sysml/jupyter-sysml-kernel-0.62.0-all.jar) +WARNING: Please consider reporting this to the maintainers of class com.google.inject.internal.aop.HiddenClassDefiner +WARNING: sun.misc.Unsafe::staticFieldBase will be removed in a future release +log4j:WARN No appenders could be found for logger (org.eclipse.xtext.parser.antlr.AbstractInternalAntlrParser). +log4j:WARN Please initialize the log4j system properly. +log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. +WARNING: Final field index in class org.omg.kerml.xtext.library.LibraryIndex has been mutated reflectively by class com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1 in unnamed module @2364305a (file:/private/tmp/toaster-diagram-study/pilot/sysml/jupyter-sysml-kernel-0.62.0-all.jar) +WARNING: Use --enable-final-field-mutation=ALL-UNNAMED to avoid a warning +WARNING: Mutating final fields will be blocked in a future release unless final field mutation is enabled +Exception in thread "main" java.lang.IllegalStateException: Model diagnostics + at PilotRender.main(PilotRender.java:12) + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch06-tree-toolkit-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch06-tree-toolkit-emit.log new file mode 100644 index 0000000..d56e487 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch06-tree-toolkit-emit.log @@ -0,0 +1,7 @@ +$ /Users/z/Documents/GitHub/sysml-toolkit/target/release/sysmlv2 viz decisions/diagram-study-real-fixtures/fixtures/ch06.sysml --view tree --element ToasterDemo::Toaster -o decisions/diagram-study-real-fixtures/evidence/ch06-tree-toolkit.puml +exit_code=0 elapsed_seconds=0.007 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch06-tree-toolkit-render.log b/decisions/diagram-study-real-fixtures/evidence/ch06-tree-toolkit-render.log new file mode 100644 index 0000000..4c3daad --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch06-tree-toolkit-render.log @@ -0,0 +1,7 @@ +$ /opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java -Djava.awt.headless=true -jar /opt/homebrew/opt/plantuml/libexec/plantuml.jar -tsvg decisions/diagram-study-real-fixtures/evidence/ch06-tree-toolkit.puml +exit_code=0 elapsed_seconds=1.054 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch06-tree-toolkit.puml b/decisions/diagram-study-real-fixtures/evidence/ch06-tree-toolkit.puml new file mode 100644 index 0000000..fad7606 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch06-tree-toolkit.puml @@ -0,0 +1,16 @@ +@startuml +hide empty members +class "Toaster" as n1 <> { + cycleTime +} +class "heating : HeatingSystem" as n2 <> +class "control : ControlSystem" as n3 <> +class "durationInterface" as n4 <> +class "(port)" as n5 <> +class "(port)" as n6 <> +n1 *-- n2 +n1 *-- n3 +n4 o-- n5 +n4 o-- n6 +n1 *-- n4 +@enduml diff --git a/decisions/diagram-study-real-fixtures/evidence/ch06-tree-toolkit.svg b/decisions/diagram-study-real-fixtures/evidence/ch06-tree-toolkit.svg new file mode 100644 index 0000000..adde09c --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch06-tree-toolkit.svg @@ -0,0 +1 @@ +«part def»ToastercycleTime«part»heating : HeatingSystem«part»control : ControlSystem«interface»durationInterface«port»(port)«port»(port) \ No newline at end of file diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-dot-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-dot-emit.log new file mode 100644 index 0000000..dca1120 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-dot-emit.log @@ -0,0 +1,9 @@ +$ /private/tmp/functional-toaster-design/sysml decisions/diagram-study-real-fixtures/fixtures/ch07-mutated.sysml -render #state:ToasterDemo::Cycle -render-form dot -o decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-dot.dot +exit_code=0 elapsed_seconds=0.074 + +--- stdout --- + +--- stderr --- +✓ package ToasterDemo +wrote decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-dot.dot (dot, 1212 bytes) + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-dot-render.log b/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-dot-render.log new file mode 100644 index 0000000..306b5b8 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-dot-render.log @@ -0,0 +1,7 @@ +$ dot -Tsvg decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-dot.dot -o decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-dot.svg +exit_code=0 elapsed_seconds=0.072 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-dot.dot b/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-dot.dot new file mode 100644 index 0000000..f24679d --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-dot.dot @@ -0,0 +1,25 @@ +// kind: state +// stated: no view declared; rendering ToasterDemo::Cycle directly +// layout: dot +digraph { + graph [fontname="Helvetica"]; + node [shape=box, style=filled, fillcolor=white, color="#181818", fontname="Helvetica", fontsize=14, penwidth=0.5]; + edge [color="#181818", fontname="Helvetica", fontsize=13, penwidth=1]; + subgraph "cluster_n0" { + label=<Cycle
«state def»>; + color=black; + penwidth=0.5; + "n0" [shape=point, style=invis, width=0, height=0, label=""]; + "n5" [shape=point, fillcolor=black, label=""]; + "n1" [style="rounded,filled", label=<idle
«state»
initial>]; + "n2" [style="rounded,filled", label=<heating
«state»
do>]; + "n3" [style="rounded,filled", label=<ready
«state»>]; + "n4" [style="rounded,filled", label=<cancelled
«state»>]; + } + "n5" -> "n1"; + "n1" -> "n2" [label="accept Start"]; + "n2" -> "n4" [label="accept Finish"]; + "n2" -> "n4" [label="accept Cancel"]; + "n3" -> "n1"; + "n4" -> "n1"; +} diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-dot.svg b/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-dot.svg new file mode 100644 index 0000000..3363b48 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-dot.svg @@ -0,0 +1,93 @@ + + + + + + + + +cluster_n0 + +Cycle +«state def» + + + + +n5 + + + + +n1 + +idle +«state» +initial + + + +n5->n1 + + + + + +n2 + +heating +«state» +do + + + +n1->n2 + + +accept Start + + + +n4 + +cancelled +«state» + + + +n2->n4 + + +accept Finish + + + +n2->n4 + + +accept Cancel + + + +n3 + +ready +«state» + + + +n3->n1 + + + + + +n4->n1 + + + + + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-puml-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-puml-emit.log new file mode 100644 index 0000000..fc4e925 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-puml-emit.log @@ -0,0 +1,9 @@ +$ /private/tmp/functional-toaster-design/sysml decisions/diagram-study-real-fixtures/fixtures/ch07-mutated.sysml -render #state:ToasterDemo::Cycle -render-form plantuml -o decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-puml.puml +exit_code=0 elapsed_seconds=0.075 + +--- stdout --- + +--- stderr --- +✓ package ToasterDemo +wrote decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-puml.puml (plantuml, 1178 bytes) + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-puml-render.log b/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-puml-render.log new file mode 100644 index 0000000..5a92352 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-puml-render.log @@ -0,0 +1,7 @@ +$ /opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java -Djava.awt.headless=true -jar /opt/homebrew/opt/plantuml/libexec/plantuml.jar -tsvg decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-puml.puml +exit_code=0 elapsed_seconds=0.924 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-puml.puml b/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-puml.puml new file mode 100644 index 0000000..ec33059 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-puml.puml @@ -0,0 +1,56 @@ +@startuml +' state rendering (no view declared; rendering ToasterDemo::Cycle directly) + +skinparam wrapWidth 300 +hide stereotype +hide empty description +state "**Cycle**\n//«state def»//" as n0 <> { + state "**idle**\n//«state»//\ninitial" as n1 <> <> + state "**heating**\n//«state»//\ndo" as n2 <> <> + state "**ready**\n//«state»//" as n3 <> <> + state "**cancelled**\n//«state»//" as n4 <> <> + [*] --> n1 +} +n1 --> n2 : accept Start +n2 --> n4 : accept Finish +n2 --> n4 : accept Cancel +n3 --> n1 +n4 --> n1 +@enduml diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-puml.svg b/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-puml.svg new file mode 100644 index 0000000..2978e35 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-puml.svg @@ -0,0 +1 @@ +Cycle«state def»idle«state»initialheating«state»doready«state»cancelled«state»accept Startaccept Finishaccept Cancel \ No newline at end of file diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-toolkit-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-toolkit-emit.log new file mode 100644 index 0000000..278d2dc --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-toolkit-emit.log @@ -0,0 +1,7 @@ +$ /Users/z/Documents/GitHub/sysml-toolkit/target/release/sysmlv2 viz decisions/diagram-study-real-fixtures/fixtures/ch07-mutated.sysml --view state --element ToasterDemo::Cycle -o decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-toolkit.puml +exit_code=0 elapsed_seconds=0.007 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-toolkit-render.log b/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-toolkit-render.log new file mode 100644 index 0000000..b9550e2 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-toolkit-render.log @@ -0,0 +1,7 @@ +$ /opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java -Djava.awt.headless=true -jar /opt/homebrew/opt/plantuml/libexec/plantuml.jar -tsvg decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-toolkit.puml +exit_code=0 elapsed_seconds=0.907 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-toolkit.puml b/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-toolkit.puml new file mode 100644 index 0000000..7e80e21 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-toolkit.puml @@ -0,0 +1,15 @@ +@startuml +state "Cycle" as n1 <> { + state "idle" as n2 <> + state "heating" as n3 <> + n3 : do / generateHeat : GenerateHeat + state "ready" as n4 <> + state "cancelled" as n5 <> + [*] --> n2 + n2 --> n3 : Start + n3 --> n5 : Finish + n3 --> n5 : Cancel + n4 --> n2 + n5 --> n2 +} +@enduml diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-toolkit.svg b/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-toolkit.svg new file mode 100644 index 0000000..fe15f47 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-toolkit.svg @@ -0,0 +1 @@ +Cycleidleheatingdo / generateHeat : GenerateHeatreadycancelledStartFinishCancel \ No newline at end of file diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-dot-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-dot-emit.log new file mode 100644 index 0000000..49982bf --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-dot-emit.log @@ -0,0 +1,9 @@ +$ /private/tmp/functional-toaster-design/sysml decisions/diagram-study-real-fixtures/fixtures/ch07.sysml -render #state:ToasterDemo::Cycle -render-form dot -o decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-dot.dot +exit_code=0 elapsed_seconds=0.074 + +--- stdout --- + +--- stderr --- +✓ package ToasterDemo +wrote decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-dot.dot (dot, 1212 bytes) + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-dot-render.log b/decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-dot-render.log new file mode 100644 index 0000000..80e275e --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-dot-render.log @@ -0,0 +1,7 @@ +$ dot -Tsvg decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-dot.dot -o decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-dot.svg +exit_code=0 elapsed_seconds=0.071 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-dot.dot b/decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-dot.dot new file mode 100644 index 0000000..f102524 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-dot.dot @@ -0,0 +1,25 @@ +// kind: state +// stated: no view declared; rendering ToasterDemo::Cycle directly +// layout: dot +digraph { + graph [fontname="Helvetica"]; + node [shape=box, style=filled, fillcolor=white, color="#181818", fontname="Helvetica", fontsize=14, penwidth=0.5]; + edge [color="#181818", fontname="Helvetica", fontsize=13, penwidth=1]; + subgraph "cluster_n0" { + label=<Cycle
«state def»>; + color=black; + penwidth=0.5; + "n0" [shape=point, style=invis, width=0, height=0, label=""]; + "n5" [shape=point, fillcolor=black, label=""]; + "n1" [style="rounded,filled", label=<idle
«state»
initial>]; + "n2" [style="rounded,filled", label=<heating
«state»
do>]; + "n3" [style="rounded,filled", label=<ready
«state»>]; + "n4" [style="rounded,filled", label=<cancelled
«state»>]; + } + "n5" -> "n1"; + "n1" -> "n2" [label="accept Start"]; + "n2" -> "n3" [label="accept Finish"]; + "n2" -> "n4" [label="accept Cancel"]; + "n3" -> "n1"; + "n4" -> "n1"; +} diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-dot.svg b/decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-dot.svg new file mode 100644 index 0000000..e34cde8 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-dot.svg @@ -0,0 +1,93 @@ + + + + + + + + +cluster_n0 + +Cycle +«state def» + + + + +n5 + + + + +n1 + +idle +«state» +initial + + + +n5->n1 + + + + + +n2 + +heating +«state» +do + + + +n1->n2 + + +accept Start + + + +n3 + +ready +«state» + + + +n2->n3 + + +accept Finish + + + +n4 + +cancelled +«state» + + + +n2->n4 + + +accept Cancel + + + +n3->n1 + + + + + +n4->n1 + + + + + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-puml-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-puml-emit.log new file mode 100644 index 0000000..723288f --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-puml-emit.log @@ -0,0 +1,9 @@ +$ /private/tmp/functional-toaster-design/sysml decisions/diagram-study-real-fixtures/fixtures/ch07.sysml -render #state:ToasterDemo::Cycle -render-form plantuml -o decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-puml.puml +exit_code=0 elapsed_seconds=0.054 + +--- stdout --- + +--- stderr --- +✓ package ToasterDemo +wrote decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-puml.puml (plantuml, 1178 bytes) + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-puml-render.log b/decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-puml-render.log new file mode 100644 index 0000000..d35cffa --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-puml-render.log @@ -0,0 +1,7 @@ +$ /opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java -Djava.awt.headless=true -jar /opt/homebrew/opt/plantuml/libexec/plantuml.jar -tsvg decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-puml.puml +exit_code=0 elapsed_seconds=0.965 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-puml.puml b/decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-puml.puml new file mode 100644 index 0000000..0614886 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-puml.puml @@ -0,0 +1,56 @@ +@startuml +' state rendering (no view declared; rendering ToasterDemo::Cycle directly) + +skinparam wrapWidth 300 +hide stereotype +hide empty description +state "**Cycle**\n//«state def»//" as n0 <> { + state "**idle**\n//«state»//\ninitial" as n1 <> <> + state "**heating**\n//«state»//\ndo" as n2 <> <> + state "**ready**\n//«state»//" as n3 <> <> + state "**cancelled**\n//«state»//" as n4 <> <> + [*] --> n1 +} +n1 --> n2 : accept Start +n2 --> n3 : accept Finish +n2 --> n4 : accept Cancel +n3 --> n1 +n4 --> n1 +@enduml diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-puml.svg b/decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-puml.svg new file mode 100644 index 0000000..e476839 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-state-opensysml-puml.svg @@ -0,0 +1 @@ +Cycle«state def»idle«state»initialheating«state»doready«state»cancelled«state»accept Startaccept Finishaccept Cancel \ No newline at end of file diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-state-pilot-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch07-state-pilot-emit.log new file mode 100644 index 0000000..3499e6d --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-state-pilot-emit.log @@ -0,0 +1,119 @@ +$ /opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java -Djava.awt.headless=true -cp /private/tmp/toaster-diagram-study/pilot/sysml/jupyter-sysml-kernel-0.62.0-all.jar /private/tmp/toaster-diagram-study/PilotRender.java /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library decisions/diagram-study-real-fixtures/fixtures/ch07.sysml ToasterDemo::Cycle state decisions/diagram-study-real-fixtures/evidence/ch07-state-pilot.svg +exit_code=1 elapsed_seconds=3.023 + +--- stdout --- +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Performances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Transfers.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Base.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Observation.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Triggers.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Clocks.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/SpatialFrames.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/ControlPerformances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/KerML.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Objects.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/FeatureReferencingPerformances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Metaobjects.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/StatePerformances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/TransitionPerformances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Occurrences.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Links.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/RationalFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/ControlFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/BooleanFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/VectorFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/RealFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/CollectionFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/ComplexFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/NumericalFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/TrigFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/IntegerFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/NaturalFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/StringFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/DataFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/ScalarFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/BaseFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/SequenceFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/OccurrenceFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Data Type Library/ScalarValues.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Data Type Library/VectorValues.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Data Type Library/Collections.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Parts.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/SysML.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/VerificationCases.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/AnalysisCases.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/States.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Flows.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Views.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Requirements.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Interfaces.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Actions.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/UseCases.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Items.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Metadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Allocations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Calculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Constraints.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Attributes.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Ports.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/StandardViewDefinitions.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Connections.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Cases.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/SI.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQBase.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/Quantities.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQLight.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQElectromagnetism.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQCondensedMatter.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/MeasurementRefCalculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/USCustomaryUnits.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQMechanics.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/VectorCalculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/SIPrefixes.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQSpaceTime.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQAcoustics.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQ.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/MeasurementReferences.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/TensorCalculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQCharacteristicNumbers.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQInformation.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/Time.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQChemistryMolecular.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/QuantityCalculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQAtomicNuclear.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQThermodynamics.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Analysis/AnalysisTooling.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Analysis/TradeStudies.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Analysis/SampledFunctions.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Analysis/StateSpaceRepresentation.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Cause and Effect/CauseAndEffect.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Cause and Effect/CausationConnections.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Geometry/SpatialItems.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Geometry/ShapeItems.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Metadata/RiskMetadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Metadata/ParametersOfInterestMetadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Metadata/ModelingMetadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Metadata/ImageMetadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Requirement Derivation/RequirementDerivation.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Requirement Derivation/DerivationConnections.sysml... +ERROR:Must be an accessible feature (use dot notation for nesting) (1.sysml line : 119 column : 40) +ERROR:Must be an accessible feature (use dot notation for nesting) (1.sysml line : 119 column : 65) +ERROR:Must be an accessible feature (use dot notation for nesting) (1.sysml line : 173 column : 43) +ERROR:Must be an accessible feature (use dot notation for nesting) (1.sysml line : 173 column : 70) +WARNING:Bound features should have conforming types (1.sysml line : 200 column : 9) + + +--- stderr --- +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::staticFieldBase has been called by com.google.inject.internal.aop.HiddenClassDefiner (file:/private/tmp/toaster-diagram-study/pilot/sysml/jupyter-sysml-kernel-0.62.0-all.jar) +WARNING: Please consider reporting this to the maintainers of class com.google.inject.internal.aop.HiddenClassDefiner +WARNING: sun.misc.Unsafe::staticFieldBase will be removed in a future release +log4j:WARN No appenders could be found for logger (org.eclipse.xtext.parser.antlr.AbstractInternalAntlrParser). +log4j:WARN Please initialize the log4j system properly. +log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. +WARNING: Final field index in class org.omg.kerml.xtext.library.LibraryIndex has been mutated reflectively by class com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1 in unnamed module @2364305a (file:/private/tmp/toaster-diagram-study/pilot/sysml/jupyter-sysml-kernel-0.62.0-all.jar) +WARNING: Use --enable-final-field-mutation=ALL-UNNAMED to avoid a warning +WARNING: Mutating final fields will be blocked in a future release unless final field mutation is enabled +Exception in thread "main" java.lang.IllegalStateException: Model diagnostics + at PilotRender.main(PilotRender.java:12) + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-state-sysmld-render.log b/decisions/diagram-study-real-fixtures/evidence/ch07-state-sysmld-render.log new file mode 100644 index 0000000..e1110af --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-state-sysmld-render.log @@ -0,0 +1,16 @@ +$ (cwd=decisions/diagram-study-real-fixtures/sysml2d-intent) python3 -c from sysmld.cli import main; raise SystemExit(main()) render ch07-state.sysmld +exit_code=1 elapsed_seconds=0.05 + +--- stdout --- + +--- stderr --- +Traceback (most recent call last): + File "", line 1, in + from sysmld.cli import main; raise SystemExit(main()) + ~~~~^^ + File "/private/tmp/toaster-diagram-study/sysml2d/src/sysmld/cli.py", line 172, in main + target = render_svg(Path(args.path), output_path=output, strict=strict) + File "/private/tmp/toaster-diagram-study/sysml2d/src/sysmld/render_svg.py", line 46, in render_svg + raise ValueError(f"Cannot render strict-invalid diagram: {messages}") +ValueError: Cannot render strict-invalid diagram: SYSMLD-REF-002: unresolved model reference: cancelled -> ToasterDemo::Cycle::cancelled; SYSMLD-REF-002: unresolved model reference: cancel -> ToasterDemo::Cancel; SYSMLD-REF-002: unresolved model reference: ready -> ToasterDemo::Cycle::ready; SYSMLD-REF-002: unresolved model reference: cycle -> ToasterDemo::Cycle; SYSMLD-REF-002: unresolved model reference: finish -> ToasterDemo::Finish; SYSMLD-REF-002: unresolved model reference: idle -> ToasterDemo::Cycle::idle; SYSMLD-REF-002: unresolved model reference: start -> ToasterDemo::Start; SYSMLD-REF-002: unresolved model reference: heating -> ToasterDemo::Cycle::heating + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-state-sysmld-validate.log b/decisions/diagram-study-real-fixtures/evidence/ch07-state-sysmld-validate.log new file mode 100644 index 0000000..a076582 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-state-sysmld-validate.log @@ -0,0 +1,16 @@ +$ (cwd=decisions/diagram-study-real-fixtures/sysml2d-intent) python3 -c from sysmld.cli import main; raise SystemExit(main()) validate ch07-state.sysmld --strict +exit_code=1 elapsed_seconds=0.046 + +--- stdout --- +ERROR SYSMLD-REF-002: unresolved model reference: heating -> ToasterDemo::Cycle::heating +ERROR SYSMLD-REF-002: unresolved model reference: start -> ToasterDemo::Start +ERROR SYSMLD-REF-002: unresolved model reference: cycle -> ToasterDemo::Cycle +ERROR SYSMLD-REF-002: unresolved model reference: finish -> ToasterDemo::Finish +ERROR SYSMLD-REF-002: unresolved model reference: cancelled -> ToasterDemo::Cycle::cancelled +ERROR SYSMLD-REF-002: unresolved model reference: ready -> ToasterDemo::Cycle::ready +ERROR SYSMLD-REF-002: unresolved model reference: cancel -> ToasterDemo::Cancel +ERROR SYSMLD-REF-002: unresolved model reference: idle -> ToasterDemo::Cycle::idle +8 errors, 0 warnings + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-state-toolkit-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch07-state-toolkit-emit.log new file mode 100644 index 0000000..9f2ffe8 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-state-toolkit-emit.log @@ -0,0 +1,7 @@ +$ /Users/z/Documents/GitHub/sysml-toolkit/target/release/sysmlv2 viz decisions/diagram-study-real-fixtures/fixtures/ch07.sysml --view state --element ToasterDemo::Cycle -o decisions/diagram-study-real-fixtures/evidence/ch07-state-toolkit.puml +exit_code=0 elapsed_seconds=0.007 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-state-toolkit-render.log b/decisions/diagram-study-real-fixtures/evidence/ch07-state-toolkit-render.log new file mode 100644 index 0000000..cbbb0fa --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-state-toolkit-render.log @@ -0,0 +1,7 @@ +$ /opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java -Djava.awt.headless=true -jar /opt/homebrew/opt/plantuml/libexec/plantuml.jar -tsvg decisions/diagram-study-real-fixtures/evidence/ch07-state-toolkit.puml +exit_code=0 elapsed_seconds=0.974 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-state-toolkit.puml b/decisions/diagram-study-real-fixtures/evidence/ch07-state-toolkit.puml new file mode 100644 index 0000000..e38084a --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-state-toolkit.puml @@ -0,0 +1,15 @@ +@startuml +state "Cycle" as n1 <> { + state "idle" as n2 <> + state "heating" as n3 <> + n3 : do / generateHeat : GenerateHeat + state "ready" as n4 <> + state "cancelled" as n5 <> + [*] --> n2 + n2 --> n3 : Start + n3 --> n4 : Finish + n3 --> n5 : Cancel + n4 --> n2 + n5 --> n2 +} +@enduml diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-state-toolkit.svg b/decisions/diagram-study-real-fixtures/evidence/ch07-state-toolkit.svg new file mode 100644 index 0000000..3502fb5 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-state-toolkit.svg @@ -0,0 +1 @@ +Cycleidleheatingdo / generateHeat : GenerateHeatreadycancelledStartFinishCancel \ No newline at end of file diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-dot-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-dot-emit.log new file mode 100644 index 0000000..3fac0eb --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-dot-emit.log @@ -0,0 +1,9 @@ +$ /private/tmp/functional-toaster-design/sysml decisions/diagram-study-real-fixtures/fixtures/ch07.sysml -render #tree:ToasterDemo::Toaster -render-form dot -o decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-dot.dot +exit_code=0 elapsed_seconds=0.072 + +--- stdout --- + +--- stderr --- +✓ package ToasterDemo +wrote decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-dot.dot (dot, 1044 bytes) + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-dot-render.log b/decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-dot-render.log new file mode 100644 index 0000000..2e15bb4 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-dot-render.log @@ -0,0 +1,7 @@ +$ dot -Tsvg decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-dot.dot -o decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-dot.svg +exit_code=0 elapsed_seconds=0.067 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-dot.dot b/decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-dot.dot new file mode 100644 index 0000000..8e39a7a --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-dot.dot @@ -0,0 +1,17 @@ +// kind: tree +// stated: no view declared; rendering ToasterDemo::Toaster directly +// layout: dot +digraph { + graph [fontname="Helvetica"]; + node [shape=box, style=filled, fillcolor=white, color="#181818", fontname="Helvetica", fontsize=14, penwidth=0.5]; + edge [color="#181818", fontname="Helvetica", fontsize=13, penwidth=1]; + "n0" [label=<Toaster
«part def»>]; + "n1" [style="rounded,filled", label=<cycleTime : DurationValue
«attribute»>]; + "n0" -> "n1" [arrowhead=none]; + "n2" [style="rounded,filled", label=<heating : HeatingSystem
«part»>]; + "n0" -> "n2" [arrowhead=none]; + "n3" [style="rounded,filled", label=<control : ControlSystem
«part»>]; + "n0" -> "n3" [arrowhead=none]; + "n4" [style="rounded,filled", label=<durationInterface
«interface»>]; + "n0" -> "n4" [arrowhead=none]; +} diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-dot.svg b/decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-dot.svg new file mode 100644 index 0000000..04c342c --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-dot.svg @@ -0,0 +1,67 @@ + + + + + + + + + +n0 + +Toaster +«part def» + + + +n1 + +cycleTime : DurationValue +«attribute» + + + +n0->n1 + + + + +n2 + +heating : HeatingSystem +«part» + + + +n0->n2 + + + + +n3 + +control : ControlSystem +«part» + + + +n0->n3 + + + + +n4 + +durationInterface +«interface» + + + +n0->n4 + + + + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-puml-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-puml-emit.log new file mode 100644 index 0000000..90cd084 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-puml-emit.log @@ -0,0 +1,9 @@ +$ /private/tmp/functional-toaster-design/sysml decisions/diagram-study-real-fixtures/fixtures/ch07.sysml -render #tree:ToasterDemo::Toaster -render-form plantuml -o decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-puml.puml +exit_code=0 elapsed_seconds=0.055 + +--- stdout --- + +--- stderr --- +✓ package ToasterDemo +wrote decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-puml.puml (plantuml, 1163 bytes) + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-puml-render.log b/decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-puml-render.log new file mode 100644 index 0000000..9402437 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-puml-render.log @@ -0,0 +1,7 @@ +$ /opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java -Djava.awt.headless=true -jar /opt/homebrew/opt/plantuml/libexec/plantuml.jar -tsvg decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-puml.puml +exit_code=0 elapsed_seconds=0.884 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-puml.puml b/decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-puml.puml new file mode 100644 index 0000000..c1d6590 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-puml.puml @@ -0,0 +1,54 @@ +@startuml +' tree rendering (no view declared; rendering ToasterDemo::Toaster directly) + +skinparam wrapWidth 300 +hide stereotype +hide circle +hide empty members +class "**Toaster**\n//«part def»//" as n0 <> +class "**cycleTime : DurationValue**\n//«attribute»//" as n1 <> <> +n0 -- n1 +class "**heating : HeatingSystem**\n//«part»//" as n2 <> <> +n0 -- n2 +class "**control : ControlSystem**\n//«part»//" as n3 <> <> +n0 -- n3 +class "**durationInterface**\n//«interface»//" as n4 <> <> +n0 -- n4 +@enduml diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-puml.svg b/decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-puml.svg new file mode 100644 index 0000000..8d58d34 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-tree-opensysml-puml.svg @@ -0,0 +1 @@ +Toaster«part def»cycleTime : DurationValue«attribute»heating : HeatingSystem«part»control : ControlSystem«part»durationInterface«interface» \ No newline at end of file diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-tree-pilot-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch07-tree-pilot-emit.log new file mode 100644 index 0000000..e91f47c --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-tree-pilot-emit.log @@ -0,0 +1,119 @@ +$ /opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java -Djava.awt.headless=true -cp /private/tmp/toaster-diagram-study/pilot/sysml/jupyter-sysml-kernel-0.62.0-all.jar /private/tmp/toaster-diagram-study/PilotRender.java /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library decisions/diagram-study-real-fixtures/fixtures/ch07.sysml ToasterDemo::Toaster tree decisions/diagram-study-real-fixtures/evidence/ch07-tree-pilot.svg +exit_code=1 elapsed_seconds=3.09 + +--- stdout --- +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Performances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Transfers.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Base.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Observation.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Triggers.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Clocks.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/SpatialFrames.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/ControlPerformances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/KerML.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Objects.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/FeatureReferencingPerformances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Metaobjects.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/StatePerformances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/TransitionPerformances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Occurrences.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Links.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/RationalFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/ControlFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/BooleanFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/VectorFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/RealFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/CollectionFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/ComplexFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/NumericalFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/TrigFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/IntegerFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/NaturalFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/StringFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/DataFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/ScalarFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/BaseFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/SequenceFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/OccurrenceFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Data Type Library/ScalarValues.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Data Type Library/VectorValues.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Data Type Library/Collections.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Parts.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/SysML.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/VerificationCases.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/AnalysisCases.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/States.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Flows.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Views.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Requirements.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Interfaces.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Actions.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/UseCases.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Items.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Metadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Allocations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Calculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Constraints.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Attributes.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Ports.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/StandardViewDefinitions.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Connections.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Cases.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/SI.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQBase.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/Quantities.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQLight.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQElectromagnetism.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQCondensedMatter.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/MeasurementRefCalculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/USCustomaryUnits.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQMechanics.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/VectorCalculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/SIPrefixes.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQSpaceTime.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQAcoustics.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQ.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/MeasurementReferences.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/TensorCalculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQCharacteristicNumbers.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQInformation.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/Time.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQChemistryMolecular.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/QuantityCalculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQAtomicNuclear.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQThermodynamics.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Analysis/AnalysisTooling.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Analysis/TradeStudies.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Analysis/SampledFunctions.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Analysis/StateSpaceRepresentation.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Cause and Effect/CauseAndEffect.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Cause and Effect/CausationConnections.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Geometry/SpatialItems.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Geometry/ShapeItems.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Metadata/RiskMetadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Metadata/ParametersOfInterestMetadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Metadata/ModelingMetadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Metadata/ImageMetadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Requirement Derivation/RequirementDerivation.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Requirement Derivation/DerivationConnections.sysml... +ERROR:Must be an accessible feature (use dot notation for nesting) (1.sysml line : 119 column : 40) +ERROR:Must be an accessible feature (use dot notation for nesting) (1.sysml line : 119 column : 65) +ERROR:Must be an accessible feature (use dot notation for nesting) (1.sysml line : 173 column : 43) +ERROR:Must be an accessible feature (use dot notation for nesting) (1.sysml line : 173 column : 70) +WARNING:Bound features should have conforming types (1.sysml line : 200 column : 9) + + +--- stderr --- +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::staticFieldBase has been called by com.google.inject.internal.aop.HiddenClassDefiner (file:/private/tmp/toaster-diagram-study/pilot/sysml/jupyter-sysml-kernel-0.62.0-all.jar) +WARNING: Please consider reporting this to the maintainers of class com.google.inject.internal.aop.HiddenClassDefiner +WARNING: sun.misc.Unsafe::staticFieldBase will be removed in a future release +log4j:WARN No appenders could be found for logger (org.eclipse.xtext.parser.antlr.AbstractInternalAntlrParser). +log4j:WARN Please initialize the log4j system properly. +log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. +WARNING: Final field index in class org.omg.kerml.xtext.library.LibraryIndex has been mutated reflectively by class com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1 in unnamed module @2364305a (file:/private/tmp/toaster-diagram-study/pilot/sysml/jupyter-sysml-kernel-0.62.0-all.jar) +WARNING: Use --enable-final-field-mutation=ALL-UNNAMED to avoid a warning +WARNING: Mutating final fields will be blocked in a future release unless final field mutation is enabled +Exception in thread "main" java.lang.IllegalStateException: Model diagnostics + at PilotRender.main(PilotRender.java:12) + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-tree-toolkit-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch07-tree-toolkit-emit.log new file mode 100644 index 0000000..4431043 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-tree-toolkit-emit.log @@ -0,0 +1,7 @@ +$ /Users/z/Documents/GitHub/sysml-toolkit/target/release/sysmlv2 viz decisions/diagram-study-real-fixtures/fixtures/ch07.sysml --view tree --element ToasterDemo::Toaster -o decisions/diagram-study-real-fixtures/evidence/ch07-tree-toolkit.puml +exit_code=0 elapsed_seconds=0.006 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-tree-toolkit-render.log b/decisions/diagram-study-real-fixtures/evidence/ch07-tree-toolkit-render.log new file mode 100644 index 0000000..165eee9 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-tree-toolkit-render.log @@ -0,0 +1,7 @@ +$ /opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java -Djava.awt.headless=true -jar /opt/homebrew/opt/plantuml/libexec/plantuml.jar -tsvg decisions/diagram-study-real-fixtures/evidence/ch07-tree-toolkit.puml +exit_code=0 elapsed_seconds=0.976 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-tree-toolkit.puml b/decisions/diagram-study-real-fixtures/evidence/ch07-tree-toolkit.puml new file mode 100644 index 0000000..fad7606 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-tree-toolkit.puml @@ -0,0 +1,16 @@ +@startuml +hide empty members +class "Toaster" as n1 <> { + cycleTime +} +class "heating : HeatingSystem" as n2 <> +class "control : ControlSystem" as n3 <> +class "durationInterface" as n4 <> +class "(port)" as n5 <> +class "(port)" as n6 <> +n1 *-- n2 +n1 *-- n3 +n4 o-- n5 +n4 o-- n6 +n1 *-- n4 +@enduml diff --git a/decisions/diagram-study-real-fixtures/evidence/ch07-tree-toolkit.svg b/decisions/diagram-study-real-fixtures/evidence/ch07-tree-toolkit.svg new file mode 100644 index 0000000..adde09c --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch07-tree-toolkit.svg @@ -0,0 +1 @@ +«part def»ToastercycleTime«part»heating : HeatingSystem«part»control : ControlSystem«interface»durationInterface«port»(port)«port»(port) \ No newline at end of file diff --git a/decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-dot-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-dot-emit.log new file mode 100644 index 0000000..8f5ad19 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-dot-emit.log @@ -0,0 +1,9 @@ +$ /private/tmp/functional-toaster-design/sysml decisions/diagram-study-real-fixtures/fixtures/ch08.sysml -render #tree:ToasterDemo::Toaster -render-form dot -o decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-dot.dot +exit_code=0 elapsed_seconds=0.059 + +--- stdout --- + +--- stderr --- +✓ package ToasterDemo +wrote decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-dot.dot (dot, 1044 bytes) + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-dot-render.log b/decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-dot-render.log new file mode 100644 index 0000000..2056edc --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-dot-render.log @@ -0,0 +1,7 @@ +$ dot -Tsvg decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-dot.dot -o decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-dot.svg +exit_code=0 elapsed_seconds=0.077 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-dot.dot b/decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-dot.dot new file mode 100644 index 0000000..8e39a7a --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-dot.dot @@ -0,0 +1,17 @@ +// kind: tree +// stated: no view declared; rendering ToasterDemo::Toaster directly +// layout: dot +digraph { + graph [fontname="Helvetica"]; + node [shape=box, style=filled, fillcolor=white, color="#181818", fontname="Helvetica", fontsize=14, penwidth=0.5]; + edge [color="#181818", fontname="Helvetica", fontsize=13, penwidth=1]; + "n0" [label=<Toaster
«part def»>]; + "n1" [style="rounded,filled", label=<cycleTime : DurationValue
«attribute»>]; + "n0" -> "n1" [arrowhead=none]; + "n2" [style="rounded,filled", label=<heating : HeatingSystem
«part»>]; + "n0" -> "n2" [arrowhead=none]; + "n3" [style="rounded,filled", label=<control : ControlSystem
«part»>]; + "n0" -> "n3" [arrowhead=none]; + "n4" [style="rounded,filled", label=<durationInterface
«interface»>]; + "n0" -> "n4" [arrowhead=none]; +} diff --git a/decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-dot.svg b/decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-dot.svg new file mode 100644 index 0000000..04c342c --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-dot.svg @@ -0,0 +1,67 @@ + + + + + + + + + +n0 + +Toaster +«part def» + + + +n1 + +cycleTime : DurationValue +«attribute» + + + +n0->n1 + + + + +n2 + +heating : HeatingSystem +«part» + + + +n0->n2 + + + + +n3 + +control : ControlSystem +«part» + + + +n0->n3 + + + + +n4 + +durationInterface +«interface» + + + +n0->n4 + + + + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-puml-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-puml-emit.log new file mode 100644 index 0000000..573a99b --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-puml-emit.log @@ -0,0 +1,9 @@ +$ /private/tmp/functional-toaster-design/sysml decisions/diagram-study-real-fixtures/fixtures/ch08.sysml -render #tree:ToasterDemo::Toaster -render-form plantuml -o decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-puml.puml +exit_code=0 elapsed_seconds=0.06 + +--- stdout --- + +--- stderr --- +✓ package ToasterDemo +wrote decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-puml.puml (plantuml, 1163 bytes) + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-puml-render.log b/decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-puml-render.log new file mode 100644 index 0000000..ec3417a --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-puml-render.log @@ -0,0 +1,7 @@ +$ /opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java -Djava.awt.headless=true -jar /opt/homebrew/opt/plantuml/libexec/plantuml.jar -tsvg decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-puml.puml +exit_code=0 elapsed_seconds=0.906 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-puml.puml b/decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-puml.puml new file mode 100644 index 0000000..c1d6590 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-puml.puml @@ -0,0 +1,54 @@ +@startuml +' tree rendering (no view declared; rendering ToasterDemo::Toaster directly) + +skinparam wrapWidth 300 +hide stereotype +hide circle +hide empty members +class "**Toaster**\n//«part def»//" as n0 <> +class "**cycleTime : DurationValue**\n//«attribute»//" as n1 <> <> +n0 -- n1 +class "**heating : HeatingSystem**\n//«part»//" as n2 <> <> +n0 -- n2 +class "**control : ControlSystem**\n//«part»//" as n3 <> <> +n0 -- n3 +class "**durationInterface**\n//«interface»//" as n4 <> <> +n0 -- n4 +@enduml diff --git a/decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-puml.svg b/decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-puml.svg new file mode 100644 index 0000000..8d58d34 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch08-tree-opensysml-puml.svg @@ -0,0 +1 @@ +Toaster«part def»cycleTime : DurationValue«attribute»heating : HeatingSystem«part»control : ControlSystem«part»durationInterface«interface» \ No newline at end of file diff --git a/decisions/diagram-study-real-fixtures/evidence/ch08-tree-pilot-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch08-tree-pilot-emit.log new file mode 100644 index 0000000..e9c08db --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch08-tree-pilot-emit.log @@ -0,0 +1,119 @@ +$ /opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java -Djava.awt.headless=true -cp /private/tmp/toaster-diagram-study/pilot/sysml/jupyter-sysml-kernel-0.62.0-all.jar /private/tmp/toaster-diagram-study/PilotRender.java /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library decisions/diagram-study-real-fixtures/fixtures/ch08.sysml ToasterDemo::Toaster tree decisions/diagram-study-real-fixtures/evidence/ch08-tree-pilot.svg +exit_code=1 elapsed_seconds=3.109 + +--- stdout --- +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Performances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Transfers.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Base.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Observation.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Triggers.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Clocks.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/SpatialFrames.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/ControlPerformances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/KerML.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Objects.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/FeatureReferencingPerformances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Metaobjects.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/StatePerformances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/TransitionPerformances.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Occurrences.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Semantic Library/Links.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/RationalFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/ControlFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/BooleanFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/VectorFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/RealFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/CollectionFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/ComplexFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/NumericalFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/TrigFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/IntegerFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/NaturalFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/StringFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/DataFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/ScalarFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/BaseFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/SequenceFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Function Library/OccurrenceFunctions.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Data Type Library/ScalarValues.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Data Type Library/VectorValues.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Kernel Libraries/Kernel Data Type Library/Collections.kerml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Parts.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/SysML.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/VerificationCases.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/AnalysisCases.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/States.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Flows.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Views.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Requirements.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Interfaces.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Actions.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/UseCases.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Items.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Metadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Allocations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Calculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Constraints.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Attributes.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Ports.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/StandardViewDefinitions.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Connections.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Systems Library/Cases.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/SI.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQBase.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/Quantities.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQLight.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQElectromagnetism.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQCondensedMatter.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/MeasurementRefCalculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/USCustomaryUnits.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQMechanics.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/VectorCalculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/SIPrefixes.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQSpaceTime.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQAcoustics.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQ.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/MeasurementReferences.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/TensorCalculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQCharacteristicNumbers.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQInformation.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/Time.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQChemistryMolecular.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/QuantityCalculations.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQAtomicNuclear.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Quantities and Units/ISQThermodynamics.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Analysis/AnalysisTooling.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Analysis/TradeStudies.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Analysis/SampledFunctions.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Analysis/StateSpaceRepresentation.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Cause and Effect/CauseAndEffect.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Cause and Effect/CausationConnections.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Geometry/SpatialItems.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Geometry/ShapeItems.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Metadata/RiskMetadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Metadata/ParametersOfInterestMetadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Metadata/ModelingMetadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Metadata/ImageMetadata.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Requirement Derivation/RequirementDerivation.sysml... +Reading /private/tmp/toaster-diagram-study/pilot/sysml/sysml.library/Domain Libraries/Requirement Derivation/DerivationConnections.sysml... +ERROR:Must be an accessible feature (use dot notation for nesting) (1.sysml line : 119 column : 40) +ERROR:Must be an accessible feature (use dot notation for nesting) (1.sysml line : 119 column : 65) +ERROR:Must be an accessible feature (use dot notation for nesting) (1.sysml line : 173 column : 43) +ERROR:Must be an accessible feature (use dot notation for nesting) (1.sysml line : 173 column : 70) +WARNING:Bound features should have conforming types (1.sysml line : 200 column : 9) + + +--- stderr --- +WARNING: A terminally deprecated method in sun.misc.Unsafe has been called +WARNING: sun.misc.Unsafe::staticFieldBase has been called by com.google.inject.internal.aop.HiddenClassDefiner (file:/private/tmp/toaster-diagram-study/pilot/sysml/jupyter-sysml-kernel-0.62.0-all.jar) +WARNING: Please consider reporting this to the maintainers of class com.google.inject.internal.aop.HiddenClassDefiner +WARNING: sun.misc.Unsafe::staticFieldBase will be removed in a future release +log4j:WARN No appenders could be found for logger (org.eclipse.xtext.parser.antlr.AbstractInternalAntlrParser). +log4j:WARN Please initialize the log4j system properly. +log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. +WARNING: Final field index in class org.omg.kerml.xtext.library.LibraryIndex has been mutated reflectively by class com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1 in unnamed module @2364305a (file:/private/tmp/toaster-diagram-study/pilot/sysml/jupyter-sysml-kernel-0.62.0-all.jar) +WARNING: Use --enable-final-field-mutation=ALL-UNNAMED to avoid a warning +WARNING: Mutating final fields will be blocked in a future release unless final field mutation is enabled +Exception in thread "main" java.lang.IllegalStateException: Model diagnostics + at PilotRender.main(PilotRender.java:12) + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch08-tree-toolkit-emit.log b/decisions/diagram-study-real-fixtures/evidence/ch08-tree-toolkit-emit.log new file mode 100644 index 0000000..1d6f9e0 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch08-tree-toolkit-emit.log @@ -0,0 +1,7 @@ +$ /Users/z/Documents/GitHub/sysml-toolkit/target/release/sysmlv2 viz decisions/diagram-study-real-fixtures/fixtures/ch08.sysml --view tree --element ToasterDemo::Toaster -o decisions/diagram-study-real-fixtures/evidence/ch08-tree-toolkit.puml +exit_code=0 elapsed_seconds=0.007 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch08-tree-toolkit-render.log b/decisions/diagram-study-real-fixtures/evidence/ch08-tree-toolkit-render.log new file mode 100644 index 0000000..8dca33b --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch08-tree-toolkit-render.log @@ -0,0 +1,7 @@ +$ /opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home/bin/java -Djava.awt.headless=true -jar /opt/homebrew/opt/plantuml/libexec/plantuml.jar -tsvg decisions/diagram-study-real-fixtures/evidence/ch08-tree-toolkit.puml +exit_code=0 elapsed_seconds=1.004 + +--- stdout --- + +--- stderr --- + diff --git a/decisions/diagram-study-real-fixtures/evidence/ch08-tree-toolkit.puml b/decisions/diagram-study-real-fixtures/evidence/ch08-tree-toolkit.puml new file mode 100644 index 0000000..fad7606 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch08-tree-toolkit.puml @@ -0,0 +1,16 @@ +@startuml +hide empty members +class "Toaster" as n1 <> { + cycleTime +} +class "heating : HeatingSystem" as n2 <> +class "control : ControlSystem" as n3 <> +class "durationInterface" as n4 <> +class "(port)" as n5 <> +class "(port)" as n6 <> +n1 *-- n2 +n1 *-- n3 +n4 o-- n5 +n4 o-- n6 +n1 *-- n4 +@enduml diff --git a/decisions/diagram-study-real-fixtures/evidence/ch08-tree-toolkit.svg b/decisions/diagram-study-real-fixtures/evidence/ch08-tree-toolkit.svg new file mode 100644 index 0000000..adde09c --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/ch08-tree-toolkit.svg @@ -0,0 +1 @@ +«part def»ToastercycleTime«part»heating : HeatingSystem«part»control : ControlSystem«interface»durationInterface«port»(port)«port»(port) \ No newline at end of file diff --git a/decisions/diagram-study-real-fixtures/evidence/mutation-control-results.json b/decisions/diagram-study-real-fixtures/evidence/mutation-control-results.json new file mode 100644 index 0000000..012d55f --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/mutation-control-results.json @@ -0,0 +1,54 @@ +[ + { + "tool": "opensysml-puml", + "baseline_rendered": true, + "svg_changed": false, + "verdict": "STALE: picture unchanged after a real model edit", + "fixture": "ch05-interconnection", + "annotation": "unchanged because this view never draws port names (confirmed in Task 4's element-presence check), not the same class of finding as SysMLD's silent staleness -- this is a known coverage limitation, not a picture-goes-stale-while-claiming-correspondence risk." + }, + { + "tool": "opensysml-dot", + "baseline_rendered": true, + "svg_changed": false, + "verdict": "STALE: picture unchanged after a real model edit", + "fixture": "ch05-interconnection", + "annotation": "unchanged because this view never draws port names (confirmed in Task 4's element-presence check), not the same class of finding as SysMLD's silent staleness -- this is a known coverage limitation, not a picture-goes-stale-while-claiming-correspondence risk." + }, + { + "tool": "toolkit", + "baseline_rendered": true, + "svg_changed": true, + "verdict": "reflects the mutation", + "fixture": "ch05-interconnection" + }, + { + "tool": "opensysml-puml", + "baseline_rendered": true, + "svg_changed": true, + "verdict": "reflects the mutation", + "fixture": "ch07-state" + }, + { + "tool": "opensysml-dot", + "baseline_rendered": true, + "svg_changed": true, + "verdict": "reflects the mutation", + "fixture": "ch07-state" + }, + { + "tool": "toolkit", + "baseline_rendered": true, + "svg_changed": true, + "verdict": "reflects the mutation", + "fixture": "ch07-state" + }, + { + "tool": "pilot", + "verdict": "not runnable: baseline render failed (exit_code=1 on all real fixtures, per Task 4 -- allocation syntax parse error, see ch05-tree-pilot-emit.log)" + }, + { + "tool": "sysmld", + "verdict": "not runnable: baseline render failed (see decisions/diagram-study-real-fixtures/evidence/ch05-interconnection-sysmld-render.log / ch07-state-sysmld-render.log and sysmld-indexer-probe.json)" + } +] \ No newline at end of file diff --git a/decisions/diagram-study-real-fixtures/evidence/mutation-rerenders.json b/decisions/diagram-study-real-fixtures/evidence/mutation-rerenders.json new file mode 100644 index 0000000..7a63c70 --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/mutation-rerenders.json @@ -0,0 +1,36 @@ +{ + "ch05": { + "opensysml-puml": { + "exit_code": 0, + "svg_path": "decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-puml.svg", + "sha256": "eac2960d7573676c08260fbc131969cf4500143038c7c68a8b9317a41d764010" + }, + "opensysml-dot": { + "exit_code": 0, + "svg_path": "decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-opensysml-dot.svg", + "sha256": "e1dc4e188f5160cef8b17d80b151866ec7b4d0bf5443a9439b5a98a1ac2437be" + }, + "toolkit": { + "exit_code": 0, + "svg_path": "decisions/diagram-study-real-fixtures/evidence/ch05-mutated-interconnection-toolkit.svg", + "sha256": "80d295f443e3f96a151dbf298abeb8b94fe731fd0aeafc0db553d6855e8c78fc" + } + }, + "ch07": { + "opensysml-puml": { + "exit_code": 0, + "svg_path": "decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-puml.svg", + "sha256": "1e98ffd8045d499dfaded499ff3c2960adf5620b6b379056d7b1180a55d2dc4b" + }, + "opensysml-dot": { + "exit_code": 0, + "svg_path": "decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-opensysml-dot.svg", + "sha256": "f5a68981ed33051c98adfcd521e064eed213d3bea333af6c347d2e846b055d6f" + }, + "toolkit": { + "exit_code": 0, + "svg_path": "decisions/diagram-study-real-fixtures/evidence/ch07-mutated-state-toolkit.svg", + "sha256": "5fe09784e64bb1ac9d0fd8835bd8a3d1e376ad81d9d0769dd9425a41b608ed5b" + } + } +} \ No newline at end of file diff --git a/decisions/diagram-study-real-fixtures/evidence/opensysml-help.log b/decisions/diagram-study-real-fixtures/evidence/opensysml-help.log new file mode 100644 index 0000000..8d6d3db --- /dev/null +++ b/decisions/diagram-study-real-fixtures/evidence/opensysml-help.log @@ -0,0 +1,717 @@ +Usage: sysml [options] [file...] + +sysml loads the models it is given — a file, a directory to walk or a glob +— as a single model, so a declaration in one file resolves against the +others whichever order they were named in. With no expression or check to +carry out it opens an interactive prompt; otherwise it does what was asked and +exits on the verdict, which is what lets a run gate a build. + +Examples: + sysml # Start interactive REPL + sysml -e "5 + 3" # Evaluate and exit + sysml -e "expr" file.sysml # Load file, evaluate, and exit + sysml file.sysml # Load file and start REPL + sysml -debug file.sysml # Load file, reporting every diagnostic + sysml -trace file.sysml # Load file, reporting each execution step + +General: + -h, -help Show this help and exit + -v, -version Show the version and exit + -man Write this command's manual page, in roff, to + stdout and exit + +Evaluating: + -e, -eval Evaluate this expression, against the model when + one is loaded, and exit (repeatable) + -query Evaluate this OSLC Query text against the model + and exit + +Checking a model: + -validate[=] Report the model's diagnostics and exit, nonzero + on an error; -validate= checks instead + every assertion about that object (repeatable) + -strict Judge the model as conforming SysML v2: notation + no pinned production admits is an error, not a + warning + -constraint Evaluate this constraint and exit (repeatable) + -requirement Evaluate this requirement, and every + verification case verifying it, and exit + (repeatable) + -satisfy[=] Evaluate every satisfaction assertion, or with + -satisfy= those the named element states, + and exit (repeatable) + -calc Invoke this calculation and report its result, + as -calc "Fall(3, 4)" (repeatable) + -analysis Run this analysis or verification case and + report its outputs and verdict, as -analysis + "Pkg::Case(3.0) Pkg::part" (repeatable) + -record-run Run this analysis case as -analysis does and + record the run into the model as AnalysisRecords + elements, one record per -sweep value or -runs + run (repeatable) + -record-into Record -record-run runs into this package + instead of a Records package beside the case's + -run-query Execute this document query and report its rows, + as -run-query "Heavy root=telescope" + (repeatable) + -instantiate Create an object of this definition or usage + before the checks, so a verdict is about it + (repeatable) + -json Report checks as one JSON document rather than + as lines + +Running behaviors: + -action Run this action to completion, as -action "Drive + rover1" to run it on an object (repeatable) + -state Run this state machine, as -state "Mission + rover1" to run it on an object (repeatable) + -advance