Skip to content

Latest commit

 

History

History
401 lines (201 loc) · 93.7 KB

File metadata and controls

401 lines (201 loc) · 93.7 KB

07 — Decision Log

Architecture Decision Record (ADR) style log for the Intergalactic Vegan Soup Factory RFLP model. Each entry records the context, the decision, and its consequences.


ADR-001: Three separate physical .slx models per variant, not System Composer variant components

Context. The physical layer needs to represent three competing architecture concepts (HyperCook, LeanBroth, EverSimmer) for a trade study. System Composer supports representing design alternatives either as separate models or as Variant components/choices within a single model.

Decision. Model each physical variant as its own standalone model (PhysicalHyperCook.slx, PhysicalLeanBroth.slx, PhysicalEverSimmer.slx) rather than as Variant components inside one shared physical model.

Consequences. Each variant can be rolled up (mass/power/cost/volume/throughput totals) and have requirements allocated independently, without needing to activate/deactivate variant choices to analyze one concept at a time. Comparison across variants is done externally (roll-up + MCDA trade study) rather than via System Composer's built-in variant-choice mechanism. The cost is some duplication: each model repeats the same 12-component topology inherited from the logical layer, and any topology-level fix must be applied three times (or scripted). This tradeoff was accepted because the variants differ enough in internal structure (e.g., EverSimmer triplicates prep/cook/QC/packaging per cell, while HyperCook and LeanBroth do not) that a single shared-topology variant model would not cleanly express the differences without extensive internal variant nesting.


ADR-002: Functional ↔ logical realization is 1:1 at this abstraction level

Context. The logical architecture must realize each of the 12 functional-layer functions as one or more logical components. Options ranged from 1:1 realization to splitting/merging functions across logical components.

Decision. Realize each function as exactly one logical component (see 03_logical_architecture.md §2), with matching names and topology across the functional and logical models.

Consequences. Full requirement traceability is preserved end-to-end (SR → function → logical component) with no ambiguity. The logical layer's value at this level is entirely in typing the interfaces (see 03_logical_architecture.md §3) and naming solution roles, not in restructuring the topology. This is appropriate because the functions were already defined at system-component granularity; if a future refinement finds that one function should be split (e.g., decomposing TransportMaterialsInternally into per-zone transport components) or that several functions should be merged behind a shared logical unit, the 1:1 mapping would need to be revisited at that point — it is not treated as a fixed rule for all future work, only the appropriate choice for this system level today.


ADR-003: Status fan-in modeled as separate named ports on ProductionControlSystem

Context. CoordinateProduction/ProductionControlSystem needs to receive an OperationalStatus/UnitStatus report from each of the other 11 functions/components. System Composer does not provide merge-block semantics for combining multiple signal sources onto a single port the way some other modeling tools do.

Decision. Model the status fan-in as 11 separate, individually named input ports on ProductionControlSystem (one per reporting function/component), rather than attempting to merge them onto a single status port.

Consequences. Every status source is individually traceable and addressable in analysis and (later) simulation — no ambiguity about which component's status is being read. The port list on ProductionControlSystem is correspondingly large (11 status inputs plus 11 command outputs), which is visually busier than a single merged port would be, but this is a direct, faithful representation of the star topology rather than a workaround, and avoids hiding fan-in behavior behind a modeling construct System Composer does not natively support.


ADR-004: Internal transport modeled as a coordination service function, not inline on the material chain

Context. TransportMaterialsInternally (SR-GS-023, SR-GS-024) represents the automated movement of materials between receiving, storage, prep, cooking, packaging, and dispatch. It could have been modeled either as literal point-to-point transport blocks interposed on every link of the material chain, or as a separate coordination-style function.

Decision. Draw the primary material chain as direct point-to-point flows between the processing functions (ReceiveInboundDeliveriesStoreIngredientsPrepareIngredientsCookSoupAssureQualityPackageSoupDispatchShipments), and model TransportMaterialsInternally separately as a function that receives a ControlDirective and reports OperationalStatus, representing the transport capability and its automation/rate requirements rather than inserting it as a literal block on every material link.

Consequences. The material chain diagram stays readable as a clear pipeline instead of being interrupted by a transport block between every pair of stages. SR-GS-023 (transfer rate) and SR-GS-024 (automation percentage) are traced to a single, clearly identifiable function/component rather than being split across multiple inline transport instances. The physical variants are free to realize this capability differently — HyperCook as a high-speed conveyor network, LeanBroth as shared AGV carts, EverSimmer as autonomous robotic transport (see 04_physical_variants.md) — without those choices affecting the functional or logical topology.


ADR-005: Gravity compensation as an explicit function, CompensateGravity

Context. SR-GS-015 (0.1 g-12 g operating range) and SR-GS-016 (12 g structural tolerance) derive from SN-GS-008 (gravity range) and could have been treated as a non-functional/structural constraint applied uniformly across all functions rather than as a function in its own right.

Decision. Model gravity compensation as its own function, CompensateGravity, taking the root AmbientGravity input and reporting EnvironmentState/GravityState into the production-coordination status fan-in, realized at the logical layer as GravityCompensationSystem.

Consequences. SR-GS-015 and SR-GS-016 get first-class traceability to a specific function/component instead of being scattered as an implicit assumption across all 12 functions. Production coordination can explicitly react to gravity/environment state alongside throughput and fault status. The tradeoff is that every physical variant must still separately account for gravity-driven structural margin in its own equipment (mounts, structure) — CompensateGravity/GravityCompensationSystem captures the sensing/compensation behavior, not a substitute for structural design margin in every other component.


ADR-006: Power generation modeled only at the physical layer

Context. All three physical variants include a power source (fusion plant, fission reactor, redundant reactors), driven by the power budget SR-GS-012. This could have been represented as a functional-layer function (e.g., GeneratePower) or introduced only at the physical layer.

Decision. Do not create a functional-layer function or logical-layer component for power generation. Power sources appear only in the physical variant models as physical-only components with no functional/logical counterpart.

Consequences. SR-GS-012 (power budget) is treated purely as a resource constraint to be verified by roll-up analysis at the physical layer, consistent with how the other three budget requirements (mass, cost, volume — SR-GS-011, SR-GS-013, SR-GS-014) are handled (none of the four budget SRs are allocated to a function; see 02_functional_architecture.md §2). This keeps the functional/logical layers focused on what the system does rather than what powers it, and keeps power source selection (fusion vs. fission vs. redundant fission) purely a physical/implementation decision that differentiates the variants without requiring functional or logical model changes.


ADR-007: A single PhysicalProperties stereotype for all quantitative roll-up properties

Context. Each physical component across all three variants needs quantitative properties (mass, power, cost, volume, throughput, automation level, operators required, MTBF, gravity rating, and whether its throughput parallelizes) to support a uniform roll-up analysis and the downstream MCDA trade study.

Decision. Define one stereotype, PhysicalProperties (profile GalacticSoupProfile), carrying all ten properties (Mass_kg, Power_kW, Cost_kCredits, Volume_m3, Throughput_bph, AutomationLevel, OperatorsRequired, MTBF_hr, GravityRating_g, UseParallelThroughput), and apply it uniformly to every component in every physical variant model, rather than defining separate stereotypes per component category (e.g., one for production equipment, one for infrastructure, one for power).

Consequences. Roll-up analysis code and the MCDA trade study can iterate over all physical components uniformly, reading the same property set regardless of variant or component type, without per-category special-casing. Some properties will be not-applicable or zero for some components (e.g., Throughput_bph/UseParallelThroughput for a power plant, GravityRating_g for a purely electronic controller); this is accepted as a minor modeling simplification in exchange for uniform, mechanical roll-up logic across all three variants.


ADR-008: Seven-criterion min-max weighted-sum MCDA with Dirichlet Monte Carlo sensitivity

Context. The trade study needs to rank three compliant physical variants (HyperCook, LeanBroth, EverSimmer) that each excel on a different axis (throughput/logistics, resource budgets, resilience/autonomy respectively; see 04_physical_variants.md). A scoring method was needed that (a) is transparent enough for stakeholders to audit the ranking rationale, (b) is anchored directly to the system requirements rather than ad hoc judgment, and (c) does not silently bake in whichever single weighting the analyst happens to pick.

Decision. Score the variants with a seven-criterion, min-max normalized, weighted-sum MCDA (ThroughputMargin, ResourceMargin, CostMargin, Automation, CrewMargin, Availability, N1Retention — each traceable to a specific SR or SR group), evaluated under four named stakeholder weighting scenarios (Balanced, ThroughputFirst, CostLean, MissionAssurance), and cross-checked with a 5,000-sample Dirichlet random-weight Monte Carlo sweep (rng(42)) reporting each variant's win share across the full space of plausible weightings. Full method detail is in 05_trade_study_methodology.md.

Consequences. Every criterion and every scenario weight is traceable back to a requirement or an explicit stakeholder priority statement, so the scoring is auditable rather than a black box — anyone can recompute a variant's score from its raw metrics and the published weights. The Monte Carlo sweep guards against weight-picking bias: a variant that only wins because of how the four named scenarios happen to be weighted would show a correspondingly modest Monte Carlo win share, whereas a robust winner wins across most of the weighting space (as EverSimmer does, at 84%; see 06_trade_study_results.md §5). The cost of this approach is the threats to validity documented in 05_trade_study_methodology.md §4 — min-max normalization is relative to this specific 3-variant set, and the method still depends on point-estimate stereotype properties and hand-authored stage tables rather than stochastic simulation.


ADR-009: Select EverSimmer as the baseline physical architecture

Context. The trade study (ADR-008, 06_trade_study_results.md) is complete and all three variants are SR-compliant (all 8 gates pass for HyperCook, LeanBroth, and EverSimmer). A single variant must be selected as the baseline to carry forward into detailed design.

Decision. Select EverSimmer (Variant C) as the baseline physical architecture. EverSimmer wins 3 of the 4 named MCDA scenarios (Balanced 0.671, ThroughputFirst 0.585, MissionAssurance 0.812 — all highest of the three variants) and 84% of the 5,000-sample Monte Carlo weight sensitivity sweep, versus 5.0% for HyperCook and 11.0% for LeanBroth — the most robust result across the plausible range of stakeholder priorities. It is the only variant with any single-fault graceful degradation (66.7% N-1 capacity retention vs. 0% for both other variants) and leads on automation (0.956) and availability (0.9789).

Status. Accepted; under renewed team review — the selection is not final, and requirement traces are maintained per candidate architecture in the meantime (ADR-035).

Consequences. Detailed design proceeds against the EverSimmer triplicated-cell topology (3 independent production cells, distributed control triad, redundant reactor pair; see 04_physical_variants.md §3). Three follow-up actions carry forward from the caveats identified in 06_trade_study_results.md §7-8: (1) negotiate a cost reserve or descope items to widen EverSimmer's thin 4.7% cost margin before vendor costs for triplicated equipment are locked in; (2) define a degraded-mode operations procedure for the 160 bph single-cell-loss contingency, documented explicitly as a below-nominal contingency state rather than a compliant alternative to the SR-GS-002 200 bph floor; (3) carry LeanBroth as a documented descope option, since it wins the CostLean scenario (0.615) and takes 11% of Monte Carlo draws, making it the next-best alternative if budget priorities come to dominate over throughput or resilience.


ADR-010: Formalize the eight SR compliance gates as a generated Requirements Table model

Context. The baseline analysis chain evaluates SR compliance as procedural MATLAB comparisons inside runVariantAnalysis.m. Those checks are correct but invisible to the Requirements Toolbox ecosystem: they carry no traceability links, produce no formal artifacts, and their fidelity to the requirement text depends on code review alone. Requirements Toolbox offers the Requirements Table block for expressing formal, machine-checkable requirements inside a Simulink model. Candidate uses for the variant architectures included per-variant behavioral requirements, stimulus/response verification of future behavior models, and formalizing the system-level quantitative gates.

Decision. Use a single Requirements Table block, in a dedicated generated model (GalacticSoupComplianceGate.slx), to formalize the eight quantitative SR gates (mass, power, cost, volume, throughput, automation, operators, gravity). The variants' rolled-up metrics enter as design-output inputs; caps are Parameter symbols resolved from the model workspace, populated at build time by parsing the requirement text (same gsParseBudgetValue mechanism as the roll-up analysis); each formal row carries a Refine link to its source SR. The model is exclusively generated by buildComplianceGate.m and rebuilt whenever requirements change, never hand-edited. Behavioral uses of the block are deferred until architecture components have behavior models. Full detail in 08_formal_compliance_gate.md.

Consequences. The eight most load-bearing SRs in the project are now executable, linked artifacts rather than conventions inside analysis code, and the Requirements Editor shows the formal refinement of each gated SR. The generate-don't-edit rule preserves the single-source-of-truth property for caps and makes the model disposable/reproducible, at the cost that manual edits to the gate model are forbidden (they would be silently destroyed on rebuild) and the generator must handle Requirements Table API subtleties (design-output flags, placeholder-row removal, zombie link cleanup on rebuild; see 08_formal_compliance_gate.md §5).


ADR-011: Formal gate runs as a blocking stage between roll-up and trade study, cross-checked against procedural flags

Context. With both a procedural compliance path (OK_* flags in runVariantAnalysis.m) and a formal path (the Requirements Table gate) available, the chain needed a defined relationship between them: replace one with the other, or run both.

Decision. Keep both and make them check each other. runFullAnalysis.m sequences roll-up, then runComplianceGate.m, then the trade study. The gate harness hard-errors if any formal verdict disagrees with the corresponding procedural flag, and the trade study refuses to run if any variant fails the formal gate.

Status. Accepted.

Consequences. Compliance is established by two independently implemented mechanisms that must agree, so a drift in either (a stale cap constant, a mis-generated postcondition, a changed requirement not propagated) fails loudly instead of silently skewing the trade study. The trade study's precondition (only compliant variants are scored) is now enforced by the pipeline rather than by analyst discipline. The cost is one extra simulation per variant per full run (negligible: a one-step discrete sim of a table block) and the standing obligation to rebuild the gate model when requirement caps change.


ADR-012: Behavioral layer as a shared component library of referenced models, not per-variant monoliths

Context. Raising the physical variants to executable behavior (Simulink dynamics, Stateflow supervisory/batch logic, Simscape thermal physics — see 09_behavioral_models.md) could have been built as three independent monolithic behavior models mirroring the three physical variant models, or as a shared library of reusable component models composed differently per variant.

Decision. Build the behavioral layer as a shared component library: eight model references (BehStorage, BehPrepUnit, BehCookLine, BehCookVat, BehQCStation, BehPackager, BehSupervisor, BehProductionCell) plus two subsystem references (SubTransport, SubFaultGate), using the System Composer architecture components as the componentization guide (one behavioral component per recurring production role, not per physical part).

Consequences. Each component is unit-testable in isolation (09_behavioral_models.md §6), so a bug is caught and fixed once instead of three times across variant-specific copies — this is exactly the mechanism that surfaced the supervisor vector-size bug (§6) before it could hide inside a monolith. Variants differ only by composition (which components, how many instances) and parameter binding (ADR-016), not by independently-authored logic, which keeps the three plant models thin and auditable. The cost is upfront design discipline: every component's interface has to be generic enough to serve all three variants' uses of that production role, which was not free (see ADR-013 on SubTransport's generic parameter naming).


ADR-013: Model references for stateful roles, subsystem references for small stateless utilities

Context. Ten behavioral building blocks were needed. Simulink offers two component-reuse mechanisms with different tradeoffs: model references (separate compiled unit, independent simulation and testing, model arguments for parameterization, but per-instance file/interface overhead) and subsystem references (lighter weight, no separate top-level interface, shared parameters via masks).

Decision. Use model references for the eight stateful production-role components (BehStorage through BehProductionCell). Use subsystem references for the two small, stateless, cross-cutting utilities: SubTransport (transfer-rate saturation + latency) and SubFaultGate (health/enable gating of a flow). SubTransport is parameterized by generically-named dictionary entries (Transport_Rate_bph, Transport_Latency_s) resolved from whichever variant dictionary the linking plant carries, rather than by a mask — so one transport spec per plant is sufficient with no per-instance mask dialog.

Consequences. The stateful roles get independent unit tests and per-instance model-argument parameterization exactly where that machinery earns its keep (§6, §7 of 09_behavioral_models.md); the two utilities avoid the ceremony of a full model-reference interface for logic that is a few blocks deep and carries no internal state worth isolating. The generic dictionary-entry naming for SubTransport means it makes no assumption about which physical transport concept (conveyor, AGV pool, robotic swarm) it stands in for — it reads whatever rate/latency the current variant's dictionary defines, which keeps it reusable without a mask at the cost of requiring every variant dictionary to define both entries under those exact names.


ADR-014: Continuous-flow abstraction instead of SimEvents entities

Context. Material flow through the behavioral models could be represented as discrete entities (SimEvents, giving per-bowl or per-batch fidelity) or as continuous rate signals (bowls/second, giving a lighter-weight simulation closer to the rate-based metrics the trade study already consumes).

Decision. Model material as continuous rate signals in bowls/second (bowl-equivalents for pre-cook material), with time in seconds. Reject SimEvents.

Consequences. The models integrate cleanly with Simscape (BehCookVat's thermal network) and Stateflow (batch sequencing, supervisory modes) without an entity-to-signal conversion layer, and the reported metrics — throughput in bph, energy per bowl — are natural continuous-signal quantities rather than derived from entity statistics. The batchiness that actually matters for the trade study (cycle time and thermal energy, which distinguish batch from continuous cooking) is captured directly in the BehCookVat sequencer rather than lost to the continuous abstraction. The cost, documented as a threat to validity in 10_behavioral_trade_update.md §7, is that discrete-batch queueing effects at shared downstream resources are not represented, and buffer overflow currently discards excess flow rather than backing pressure up the chain (no SimEvents-style blocking).


ADR-015: Simscape thermal physics confined to BehCookVat; all model references run in Normal simulation mode

Context. Two related decisions arose while adding physical fidelity: which components, if any, warrant Simscape modeling beyond load-scaled power draw; and which simulation mode (Normal vs. Accelerator) the model-reference component library should run in.

Decision. Simscape thermal physics (heater source, lumped thermal mass, convective loss, fresh-charge exchange) is modeled only in BehCookVat — the one place where heat-up time and simmer energy differentiate batch from continuous cooking. Simscape Electrical power-network modeling is rejected as fidelity without a consumer; every other component's power is load-scaled or static draw from the existing stereotype values. Separately, all model references run in Normal simulation mode, never Accelerator.

Consequences. Thermal modeling effort is spent exactly where it changes a metric the trade study reads (energy per bowl, time-to-first-output for batch plants), and is skipped everywhere it would add cost without changing a result. The Normal-mode decision was forced empirically (09_behavioral_models.md §7): Accelerator targets inline "non-tunable expressions," and transfer-function denominators and Constant-block expressions referencing model arguments either error or silently freeze a stale value shared across instances under acceleration — a silent per-instance-parameter corruption risk judged unacceptable for the accuracy the accelerator speedup would buy. Simulation of the full plant models is correspondingly slower than an all-Accelerator configuration would be, accepted as the cost of correct per-instance parameterization.


ADR-016: Parameterization contract — model arguments plus a layered dictionary chain, mode codes as uint8 constants

Context. Ten reusable components need per-variant, per-instance parameter values (rates, capacities, thermal constants, mode/state codes) without hard-coding any variant's numbers into shared component logic.

Decision. Component models declare model arguments (instance parameters) with neutral defaults in their model workspaces. A layered data dictionary chain — BehaviorInterfaces.sldd (shared types) ⊂ BehParamsCommon.sldd (soup/thermal physics constants) ⊂ BehParams<Variant>.sldd (per-variant instance parameters, one dictionary per variant, each referencing Common) — supplies the actual values. Variant plant models link their variant dictionary and bind each model-reference instance's arguments to named dictionary entries; component models themselves link only BehaviorInterfaces.sldd + BehParamsCommon.sldd and carry no variant knowledge. Mode/state codes (plant modes, vat states) are uint8 dictionary constants rather than MATLAB enumeration classes.

Consequences. Components stay reusable and independently testable because they never reference a variant dictionary directly — swapping variants is purely a matter of which dictionary the plant links and which entries it binds, with no component-model edits. The neutral-default-plus-binding pattern means a component can be unit-tested standalone with sensible defaults, with no variant dictionary loaded at all. Choosing uint8 constants over enumeration classes was a dictionary-friendliness tradeoff: enum classes require a .m class definition file on the MATLAB path, which does not travel cleanly through a data dictionary the way a plain constant does; the cost is losing the enum's named-value readability at the workspace level (mode 2 rather than PlantMode.Degraded) in exchange for dictionary portability.


ADR-017: Architecture/behavior integration — plant-level execution, port-preserving adapters, and a BehaviorRealization stereotype

Context. The behavioral component library (09_behavioral_models.md) needed to connect back into the System Composer physical architecture models, and System Composer's native linkToModel mechanism for attaching a Simulink model as a component's behavior has a destructive default (see below) that had to be worked around.

Decision. Three-part integration approach. (a) Executable integration at plant level — the BehPlant* top models are the primary executable artifacts, composed directly from the component library rather than wired through the architecture models. (b) Port-preserving linkToModel via an adapter behavior model whose root ports are bus-element ports matching the target component's existing port names and interfaces exactly; implemented and verified on both LeanBroth kettles, where BehKettleBehavior wraps BehCookVat plus semantic glue converting between the architecture's logistics buses and the component library's flow signals. With the name-matched bus-element ports in place, linkToModel preserved every existing port, interface, and connector, and the full LeanBroth architecture compiled cleanly with live Simscape kettle behavior in the loop. Linking was subsequently reverted for the committed baseline — see Consequences — because of a rollup-breaking side effect discovered only after this verification. (c) A BehaviorRealization stereotype (properties BehaviorModel, IntegrationLevel) applied to 40 components/roots across the three physical models, giving machine-readable traceability from architecture component to behavioral artifact regardless of which of (a)/(b) applies; on the two LeanBroth kettles this trace now carries IntegrationLevel = 'adapter-available' rather than a live link.

Consequences. Rejected alternatives: raw linkToModel without a matching-port adapter, because System Composer's linkToModel replaces a component's architecture ports with the linked Simulink model's root ports whenever names don't already match — destroying the architecture-level interface silently; and full-fleet inline adapters for every component (mechanical repetition with no new information per instance), deferred rather than built now. The accepted debt is numeric parameter duplication inside BehKettleBehavior: because the architecture model's dictionary chain does not include the variant behavioral parameter dictionaries, the adapter's semantic-glue constants are hand-duplicated rather than dictionary-bound, and must be kept in sync manually if the underlying variant parameters change. createSubsystemBehavior's R2026a limitations (no filename argument, inline behavior refuses algorithm blocks) ruled out the simplest-looking alternative before the adapter pattern was adopted. [Superseded by ADR-020 — see correction below: the "inline behavior refuses algorithm blocks" claim was wrong.]

Late finding — linking dropped the kettles out of the roll-up, and R2026a cannot reprogram the fix. After (b) was verified, rolling up PhysicalLeanBroth with the kettles actually linked showed that linkToModel converts the target component into a reference component, and reference components' PhysicalProperties stereotype property values are silently excluded from the roll-up analysis. LeanBroth lost 90 kW of power and 1,400 kg of mass along with both kettles' automation contribution — the automation average fell from 0.800 to 0.700, tripping a second, purely artifactual compliance gate failure on top of the genuine LeanBroth throughput failure (§3 of 10_behavioral_trade_update.md). Re-applying the stereotype to the now-linked components was investigated as a fix and rejected: R2026a exposes no programmatic API to import a GalacticSoupProfile profile into a plain Simulink behavior model (the Profile Editor UI can do this interactively; set_param(..., 'Profile', ...) is the code profiler, unrelated to System Composer profiles), so the dropped values cannot be reapplied to a linked component by script. Decision: the committed baseline keeps both LeanBroth kettles unlinked (restored from git), preserving the stereotype-driven roll-up's integrity. BehKettleBehavior.slx and behavior/build/buildKettleAdapters.m remain in the repository as the verified, reproducible demonstration of the port-preserving pattern — the build script reproduces the linked configuration on demand for sandbox use, and it ran successfully with PhysicalLeanBroth passing update-diagram against live Simscape kettle behavior before the revert. Traceability to that demonstration is carried by the BehaviorRealization stereotype's IntegrationLevel = 'adapter-available' on the two kettles (10 BehaviorRealization traces total on PhysicalLeanBroth). With the revert in place, the analysis chain reproduces the intended baseline exactly: the formal gate is 23/24, with only LeanBroth's Throughput row failing.

Correction (recorded in ADR-020). This entry's claim that createSubsystemBehavior's inline behavior "refuses algorithm blocks" was wrong. The real cause was an unsaved-edit-revert: a bdclose('all') discarded an in-memory createSubsystemBehavior conversion before the population script that added the algorithm blocks had run, so the reverted, pre-population interior is what got inspected — not a real restriction on block kind. This history is left as originally written rather than rewritten; see ADR-020 for the corrected finding and the inline-integration approach it unblocked, and 09_behavioral_models.md §8 for the tool-gotcha writeup.


ADR-018: Behavioral metrics override static roll-up values; gate failures exclude rather than block

Context. With simulated throughput and worst-case single-fault retention now available from runBehavioralAnalysis.m, the roll-up needed a policy for how these interact with the existing static stage-table values and the existing all-or-nothing compliance gate (ADR-011: the trade study refuses to run if any variant fails). The behavioral throughput numbers are lower than the static ones for every variant (yield loss and downtime the static tables never modeled — 10_behavioral_trade_update.md §2), and this pushed LeanBroth below the SR-GS-002 throughput floor for the first time.

Decision. Simulated throughput and N-1 retention replace the corresponding static values in runVariantAnalysis's rolled-up metrics; the static values are retained alongside them (Static_* fields) rather than discarded, for comparison. Gate policy changes from "any failure blocks everything" to "failed variants are excluded from MCDA scoring, and the chain continues" for the remaining compliant variants, requiring at least two compliant variants to proceed.

Consequences. A variant failing the formal gate is now a documented finding — carried into 10_behavioral_trade_update.md §3 as LeanBroth's SR-GS-002 failure and loss of its ADR-009 descope-option status — rather than a reason to withhold trade-study results for the variants that do comply. This is a deliberate departure from ADR-011's rationale: ADR-011's blocking behavior was designed to catch drift between the formal and procedural compliance paths (a modeling or bookkeeping error), and correctly still hard-errors on any formal/procedural disagreement; it was never intended to withhold an otherwise-sound trade study just because behavioral fidelity revealed a genuine, previously-invisible non-compliance in one variant. The minimum-two-compliant-variant requirement prevents the trade study from degenerating to a single-variant non-comparison if a future update fails a second variant.


ADR-019: Rename variant C from IronLadle to EverSimmer

Context. Variant C's codename, "IronLadle," is stakeholder-facing: it appears in requirements links, trade-study reports, and the project blog draft, not just internally. The owner judged that cookware-durability imagery ("iron," "ladle") undersold the variant's actual differentiator, which is not durability but continuity: SR-GS-026 (no single-fault production kill) is the requirement the entire variant is organized around (see 04_physical_variants.md §3), and the owner wanted a name that expresses production that never stops. Alternatives considered and rejected: Trivet (still cookware-durability imagery, and a trivet's three-legged stability doesn't map to the triplicated-cell rationale as directly as continuity does), TripleStock (leans on the cell count rather than the resilience property), and HeartyStew (a menu-flavor name with no connection to the architectural concept at all).

Decision. Rename variant C from IronLadle to EverSimmer everywhere: the physical and behavioral models (PhysicalIronLadlePhysicalEverSimmer, BehPlantIronLadleBehPlantEverSimmer), the logical allocation set (LogicalToIronLadleLogicalToEverSimmer), the behavioral parameter dictionary (BehParamsIronLadle.slddBehParamsEverSimmer.sldd) and its IL_ entry prefix (→ ES_), all analysis scripts, all project docs, and the blog draft. HyperCook, LeanBroth, and all other names are unaffected.

Consequences. This was executed as a mechanical rename of record, not a re-architecture: the topology, parameters, and analysis logic are unchanged, only the label. Because link stores and allocation sets key on the model name rather than a stable internal identifier, renaming the model meant the allocation set (LogicalToEverSimmer, 24 allocations, scenario VariantC) and the 16 Implement requirement links had to be recreated against the renamed model rather than updated in place. The full analysis chain and all 21/21 unit tests reproduce identical results under the new name (formal gate still 23/24, with only LeanBroth's Throughput row failing; EverSimmer still wins 98.4% of the Monte Carlo sensitivity sweep), confirming the rename introduced no behavioral drift. One tooling gotcha worth recording for future renames: slreq.createLink against a renamed model reported "already exists" for links that had migrated along with the model, but duplicate link objects were being created underneath that message anyway; they were not caught until a deduplication pass keyed each link by its (source, type, destination) triple, and any future rename of a linked model should budget for that same dedup step.


ADR-020: Instantiate behavioral components inline in the architecture models

Context. ADR-017 connected the behavioral component library to the System Composer physical architecture at the plant level: the BehPlant* top models were the executable artifacts, and a port-preserving linkToModel adapter was verified but kept as an on-demand demonstration limited to the two LeanBroth kettles, because linking dropped stereotype property values from the roll-up. That arrangement left two model sets in parallel — the architecture models carrying requirements allocation and the stereotype-driven roll-up, and the BehPlant* models carrying the actual simulated dynamics — duplicating a composition topology (which components, how many instances, how they connect) that the architecture connectors already encoded. The user directive for this update was explicit: simulation, behavioral analysis, and MCDA should all run on the System Composer model, not on a separate plant model that merely mirrors it.

Decision. Instantiate the behavioral component library directly inside the three physical architecture models, using createSubsystemBehavior(comp) to convert each behavior-bearing component into an inline subsystem behavior. This mechanism preserves the component's existing architecture ports, connectors, and stereotype property values (isReference stays 0, so roll-up keeps working with no special-casing), unlike linkToModel. Production-path components (storage, prep, cooking, QC, packaging) wrap the matching Beh* component; controllers (CentralControlComputer/OpsConsole/ControlTriad, the three CellControllers) host BehSupervisor and aggregate telemetry; support components (reactors, gravity compensation, refuel, inventory, transport) are status stubs so every consumed bus resolves. The interfaces gained additive elements to carry this: flow_bps on the material buses, power_kW/health on StatusBus, and a new TelemetryBus (totalPower_kW, plantMode) routed to a new root Telemetry port on each variant, so total plant power is now aggregated architecturally by each controller from status buses it already receives. Each architecture model links a new per-variant wrapper dictionary, architecture/Physical<Variant>Data.sldd, chaining PhysicalInterfaces.sldd and the variant's BehParams<Variant>.sldd so instance parameters resolve inside the architecture model. Every faultable component self-gates with a Step block at a model-workspace variable Fault_T_<Comp> (default 1e9), overridden per run via setVariable; routing at fan-outs uses fixed shares (0.5, 1/3) matching the static physical topology, and supervisor reallocation among survivors is deliberately not modeled. The population is performed by a new, idempotent, per-variant build script, behavior/build/buildInlineBehaviors.m.

Status. Accepted.

Consequences. There is now one model set per variant, not two: the architecture model is the executable artifact, and roll-up, requirements allocation, simulation, and MCDA all read from it directly. The three BehPlant* top models, BehKettleBehavior.slx, and behavior/build/buildKettleAdapters.m are retired — the port-preserving-adapter demonstration they represented is superseded by inline conversion applied fleet-wide (~50 components across the three variants) rather than to two kettles on demand. runBehavioralAnalysis.m now simulates PhysicalHyperCook/PhysicalLeanBroth/PhysicalEverSimmer directly. Results are numerically almost unchanged from the retired plant-model baseline (HyperCook 308.4 vs. 308.1 bph, LeanBroth 196.8 vs. 196.6 bph, EverSimmer 231.9 vs. 231.9 bph; energy 1.554/0.814/1.212 kWh/bowl), which validates the migration as behavior-preserving rather than a re-tune; the formal gate is still 23/24 (LeanBroth's Throughput row only), and EverSimmer still wins all 4 named scenarios and 98.4% of the Monte Carlo sweep. HyperCook's worst-case single fault is now InlineQCScanner rather than the conveyor, because the architecture has no explicit conveyor material edge and QC/packaging are HyperCook's actual single strings; LeanBroth's is PrepWorkstation; EverSimmer's is ProductionCell1, gated as a whole cell. Cold-start time-to-first-output improved for HyperCook (149 s → 119 s) as a side effect of the inline topology's surge buffering; LeanBroth and EverSimmer are essentially unchanged (~3,444 s and ~3,439 s). One limitation carries forward as a known gap rather than a regression: each controller's plantMode telemetry stays Nominal under the serial single-string faults that collapse HyperCook and LeanBroth to zero output, because the supervisor watches production-line health rather than output flow — EverSimmer's whole-cell fault is the one case where health and output loss coincide, so its supervisor does correctly report Degraded. The component library, subsystem references, dictionaries, and the 21 unit tests in 09_behavioral_models.md are unchanged and remain the reusable, independently testable layer; only how it connects to the architecture changed.


ADR-021: Organize analysis verification as a tagged MATLAB Test suite

Context. Roll-up totals, formal gate verdicts, MCDA determinism, requirement-link integrity, and simulated system behavior were each verified by procedural asserts and OK_* flags scattered inside runVariantAnalysis.m, runComplianceGate.m, and the trade-study scripts, plus ad hoc command-window inspection. This catches gross breakage — the gate harness already hard-errors on formal/procedural disagreement (ADR-011) — but has no mechanism for catching quiet drift: a golden total, a gate verdict pattern, or an MCDA win share moving without anyone deciding that was correct. The behavioral component library already had this discipline at unit scale (21 matlab.unittest tests, ADR-012/ADR-013); nothing above the component covered the analysis chain or the architecture models' simulated behavior.

Decision. Add a GalacticSoup/tests/ folder of six matlab.unittest classes, tagged by tier via TestTags: 'analysis' (tRollupInvariants, tGateAgreement, tTradeDeterminism), 'traceability' (tTraceability), and 'system' (tSystemNominal, tSystemFault). The pre-existing 21 component tests in behavior/tests/ are left untagged and unchanged, folded into the same suite by folder membership rather than retrofitted with tags. Suite assembly is project metadata rather than a hard-coded list: every test file carries the MATLAB project's Test classification label, and tests/runAllTests.m builds the suite with matlab.unittest.TestSuite.fromProject, optionally filtering to one tag, and attaches a CodeCoveragePlugin over analysis/ and behavior/build/ reporting to work/coverage. Golden values (roll-up totals, gate verdict patterns, MCDA win shares, simulated throughput bands) are baselined as literal expected values in the tests, not derived at test time from the same code under test — drift in a golden value must be a conscious edit to the test, not a silent recomputation that happens to still pass. As part of this work, runComplianceGate.m was fixed to close its gate model on exit, because the model's embedded requirement set otherwise blocks slreq.clear for any downstream code in the same session (previously a documented gotcha in 08_formal_compliance_gate.md §5, now fixed at the source rather than worked around per caller).

Status. Accepted.

Consequences. The golden-totals test caught a real problem before this branch was even finished: tRollupInvariants baselined LeanBroth's mass/power/cost/volume at 7,570 kg / 1,070 kCr / 240 m³, and writing that assertion surfaced that a stale figure — 6,170 kg / 880 kCr / 200 m³, a session dump taken during the ADR-017 linkToModel stereotype-loss incident — had leaked into an explainer card and outlived the fix to the underlying bug. That is exactly the failure mode the golden-value philosophy is meant to close off: the roll-up code was already correct, but nothing forced the documented number back into agreement with it until a test said what the number was supposed to be. One limitation was identified rather than solved: slreq.createLink rejects matlab.unittest.Test elements ("Link creation failed"), and the matlabtest namespace exposes no link-creation API in R2026a, so linking tests to requirements for Requirements-Editor verification-status rollup cannot be scripted — it remains an interactive Requirements Editor / Test Browser workflow. The 'traceability' tier does not substitute for that; it guards a different, programmatically-checkable property (the 36 existing requirement links resolve to live architecture elements, allocation sets stay complete) rather than which test verifies which requirement. The full 37-test suite runs in about 2.5 minutes, dominated by the 'system' tier's six full architecture simulations (three variants × nominal and worst-fault); the other three tiers are fast enough to run repeatedly during iteration via runAllTests(tag).


ADR-022: System tier and requirements verification via Simulink Test

Context. ADR-021 organized the system tier (tSystemNominal, tSystemFault) as two more matlab.unittest classes, on the same footing as the analysis and traceability tiers, and recorded a limitation in passing (11_test_organization.md §5): slreq.createLink rejects a matlab.unittest.Test element outright, and the matlabtest namespace exposes no alternative, so linking a test to the requirement it verifies — and getting that requirement's verification status to roll up in the Requirements Editor from an actually-executed result — could not be scripted for MATLAB Test cases. It remained an interactive-only workflow. sltest.testmanager.TestCase turns out to be a different object that slreq.createLink accepts without complaint, producing a Verify link (domain linktype_rmi_testmgr). That capability, not any deficiency in matlab.unittest's ability to run a simulation, is what motivated moving the system tier.

Decision. Replace tSystemNominal.m/tSystemFault.m with a generated Simulink Test file, tests/system/GalacticSoupSystemTests.mldatx, built by tests/system/buildSystemTestFile.m on the same generate-don't-edit discipline as the compliance gate (ADR-010): two suites (Nominal, WorstFault), six simulation test cases total, two per physical architecture model, with custom-criteria callbacks computing steady-rate throughput bands, the SR-GS-002 floor check on compliant variants, worst-fault retention (0/0/0.672), and end-state plant mode. Fault cases override a per-variant Fault_T_* model-workspace variable to 7,200 s via a parameter set. Three Verify links are created: HyperCook nominal and EverSimmer nominal → SR-GS-002, EverSimmer worst fault → SR-GS-026. Link semantics rule: a Verify link attaches only where a passing case means the requirement is actually met. LeanBroth's nominal case baselines 196.8 bph — below the SR-GS-002 floor — and its fault case, like HyperCook's, baselines a 0% collapse against a requirement (no single-fault kill) it does not satisfy; both stay in the suite as unlinked regression baselines rather than being linked to a requirement they'd misrepresent as verified. That LeanBroth genuinely fails the floor is the formal compliance gate's story (ADR-010), not something a Verify link should assert around. The remaining MATLAB tiers — 21 component tests, 6 analysis-tier tests, 4 traceability-tier tests — are converted from matlab.unittest.TestCase to sltest.TestCase (one-line superclass change), gaining Test Manager compatibility with no behavioral change; they still run via runtests/fromProject. runAllTests.m runs everything as ONE matlab.unittest suite: TestSuite.fromProject collects the MATLAB classes and adapts the .mldatx cases into the same suite, a single runner executes all 37 with coverage, and full runs finish by reloading the requirement set fresh and calling updateVerificationStatus, asserting both SR-GS-002 and SR-GS-026 come back verified-passed. (An interim version ran the .mldatx separately through sltest.testmanager.run, believing only that path registered verification results; empirically the unittest-adapter path registers them too, which is what makes the unified single-suite runner possible.)

Status. Accepted.

Consequences. The headless verification rollup works end-to-end and was proven twice — once interactively (Requirements Editor shows both SRs green/Verified) and once as a pure-API reproduction with no UI involved: build the file, run it, reload the requirement set, updateVerificationStatus, read status. Two state-ordering traps had to be found before that recipe was reliable, and are recorded in 12_simulink_test_organization.md §4 for the next person who hits them: reading verification status from a requirement set whose links were just purged and recreated in the same session reports "unexecuted" even though the test passed — the fix is slreq.clear and a fresh slreq.load immediately before updateVerificationStatus, not a different API; and getVerificationStatus's total field aggregates to 1 regardless of link count, which is a display quirk, not a defect in passed/failed/unexecuted. This closes the limitation ADR-021 left open, but not by fixing matlab.unittest — by moving the one tier that needed the capability to a different test framework built for it. The result is not a replacement of MATLAB Test by Simulink Test: the four MATLAB-based tiers (31 tests) keep the pure-MATLAB analysis/traceability logic, golden-value baselining ergonomics, and coverage tooling that fit them best, while the one Simulink Test file (6 simulation cases) owns model simulation, parameter-override fault injection, and the programmatic requirements verification neither matlab.unittest nor the static compliance gate could provide. runAllTests composes both into one pass/fail stack rather than picking a side. Full-stack status today: 31 MATLAB tests, 6 simulation cases, a coverage report, and SR-GS-002/SR-GS-026 both verified-passed.

ADR-023: Replace code coverage with requirements coverage

Context. runAllTests attached a CodeCoveragePlugin over analysis/ and behavior/build/ (ADR-021). The resulting line-coverage report was circular for this project: the analysis functions are exercised by the very tests asserting their outputs, so high coverage was guaranteed by construction and told the reader nothing about what the system had demonstrated. The coverage question that actually matters here is requirements coverage — which of the 28 system requirements are implemented by the architectures, checked by the formal gate, and verified by an executed test.

Decision. Remove the CodeCoveragePlugin from runAllTests. Full runs now end with a requirements coverage summary computed from the requirement set itself: implementation status (updateImplementationStatus / Implement links from the architecture models), formal gate coverage (Refine links from the Requirements Table rows), and verification status (updateVerificationStatus / Verify links from the Simulink Test simulation cases, colored by the run just executed). One subtlety: unlinked requirements report total = 1 with none = 1 from getVerificationStatus, so the summary counts a requirement as test-covered only when passed + failed + unexecuted > 0. The formal document version — analysis/reporting/makeRequirementsReport.mdocs/deliverables/GalacticSoupRequirementsReport.pdf via slreq.generateReport with implementation and verification status enabled — is generated on demand after a full run, on the same generate-don't-edit discipline as every other deliverable.

Status. Accepted.

Consequences. Today's numbers: 24/28 SRs implemented by architecture components, 8/28 checked by the executable gate, 2/28 verified by executed simulation tests (SR-GS-002, SR-GS-026 — the two the system tier can honestly claim). Those three numbers are a more truthful health dashboard than any line-coverage percentage, and the gaps they expose are actionable: four SRs carry no Implement link, and the verification column has 26 open slots that future test cases can claim one Verify link at a time. work/coverage no longer exists; the work/ folder note in the README was trimmed accordingly.

ADR-024: Root-architecture Implement links for the emergent budget requirements

Context. The requirements coverage summary introduced by ADR-023 reported 24/28 SRs implemented. The four gaps were exactly the budget requirements — SR-GS-011 (mass), SR-GS-012 (power), SR-GS-013 (cost), SR-GS-014 (volume). This is structural, not an oversight: budgets are emergent whole-system constraints. Every component contributes to them and none is responsible for them, so no component-level Implement link is semantically correct, and the traceability they already carried (Derive from stakeholder needs, Refine from the executable gate rows, evaluation by the all-components roll-up) matched their nature. Leaving the dashboard at 24/28 would have been honest but would invite the same investigation from every future reader.

Decision. Link each physical architecture model's root — the model itself, as the "system as a whole" — to the four budget SRs with Implement links (12 links, 3 variants × 4 SRs). The API subtlety: slreq.createLink rejects the systemcomposer.arch.Architecture object, but accepts the model's block-diagram handle (get_param(model,'Handle')), producing a link whose source is the model root (empty source id distinguishes root links from component links, which carry UUIDs — the idempotency check in the linking script keys on that). The semantics read correctly in the Requirements Editor: PhysicalHyperCook, PhysicalLeanBroth, and PhysicalEverSimmer each implement the budgets, the roll-up analysis quantifies whether they do so within the caps, and the gate rows check it formally.

Status. Accepted.

Consequences. Requirements coverage now reads 28/28 implemented, and the answer to "why would a budget be component-implemented?" is encoded in the model rather than in a footnote. The tTraceability regression baselines were consciously updated from 10/10/16 to 14/14/20 links per model — the tests failed with exactly the expected new counts before the baseline edit, which is the baseline philosophy (ADR-021) working as designed. The requirements report regenerates with full implementation coverage.

ADR-025: Parameter-uncertainty Monte Carlo over the behavioral models

Context. The trade study's Monte Carlo (ADR-013) sweeps only stakeholder weights: 5,000 random weightings re-score a fixed criteria matrix, no simulation involved. That answers preference uncertainty and deliberately not performance uncertainty — the QC reject fractions and calibration schedules feeding the behavioral results are engineering estimates (10_behavioral_trade_update.md flags them as the honest caveat), and LeanBroth's compliance verdict (196.8 vs the 200 bph floor, ADR-018) is a point statement three bowls per hour from flipping.

Decision. Add a simulation-based uncertainty study, kept separate from runFullAnalysis and the test suite because of its cost (600 simulations, ~35 min). analysis/sweeps/uncertaintySpec.m defines the verification series as data: three uncertain QC parameters per variant (reject fraction: right-skewed triangular; calibration period: uniform ±25%; calibration duration: triangular ×0.7/1.0/2.5), one seeded 200-draw Latin hypercube shared across variants (common random numbers). analysis/sweeps/runUncertaintySims.m runs a variant's 200 draws through parsim, overriding the parameters in the model workspace — which shadows the attached data-dictionary entries, so no on-disk artifact changes. analysis/sweeps/runUncertaintyStudy.m post-processes: per-variant compliance probability against the requirement-parsed floor, and a double Monte Carlo (200 parameter draws × 25 seeded Dirichlet weight draws = 5,000 scored worlds) in which each draw's compliance rule uses that draw's simulated throughput. tests/analysis/tUncertainty.m (analysis tier) baselines the published numbers and enforces the reproducibility contract: spec regenerates its draws bit-identically, saved batches match the spec (editing the spec without re-running the verification series fails the suite), and the seeded post-processing reproduces the published results from the saved batches exactly.

Status. Accepted.

Consequences. LeanBroth's compliance failure upgrades from a point verdict to a probability: P(comply) = 4%, median 193.1 bph, 95th percentile 199.7 — still short. The claim "LeanBroth fails SR-GS-002" now carries 96% confidence over the stated parameter uncertainty rather than resting on nominal estimates, and the 4% concedes precisely the "a better QC bench puts it back over the floor" scenario the docs had only been able to state qualitatively. The trade verdict is robust to both uncertainty types: EverSimmer takes 97.8% of parameter-and-weight worlds (98.4% weights-only); HyperCook and EverSimmer comply in 100% of draws. Method notes for reuse: common random numbers across variants make per-draw comparisons fair; per-draw compliance gating mirrors the deterministic pipeline's exclude-then-score rule; model-workspace overrides are the correct non-destructive injection point for dictionary-parameterized models.

ADR-026: Gravity-dependent behavior via instance-parameter expressions

Context. SR-GS-015 requires nominal operation across 0.1 g to 12 g, but the behavioral layer had no gravity dependence: the requirement was implemented (gravity-compensation components exist in every variant) yet exercised by nothing. SR-GS-025 (startup readiness) and SR-GS-008 (70-95 C serving temperature) were similarly implemented-but-unverified, despite the behaviors already producing the quantities needed to check them.

Decision. Put the gravity physics in instance-parameter expressions on the existing behavioral model references rather than in the library models: batch vat drain time scales as DrainTime_s/sqrt(Gravity_g) (Torricelli), robotic prep derates 1.5%/g above 4 g, LeanBroth's human-paced prep derates 5%/g above 2 g, and continuous pumped cook lines are untouched by design. Gravity_g (default 1) lives in the shared behavior dictionary; expressions evaluate in the parent architecture's resolution context, so the established model-workspace override mechanism (faults, uncertainty study) sweeps gravity per run with no on-disk changes. All scaling is exactly neutral at 1 g, a contract tGravity/oneGNeutrality pins so every pre-gravity baseline stays authoritative. analysis/sweeps/runGravitySweep.m sweeps 8 points across the range (24 simulations); the Simulink Test file gains a GravityExtremes suite plus startup and serving-temperature criteria on the nominal cases, with Verify links per the ADR-022 semantics rule. The EverSimmer vat's Cell 1 temperature and state are signal-logged by the generator so criteria can compute serving temperature (temp while state == VAT_DRAIN) without interface changes.

Status. Accepted.

Consequences. Requirements verified by executed tests grew from 2 to 5 (SR-GS-002, -008, -015, -025, -026), and the branch produced two design findings. First, EverSimmer fails SR-GS-015 at 0.1 g (189.3 bph vs the 200 floor; drain-phase stretch) and holds only 0.9 bph of margin at 12 g: the trade winner cannot serve a microgravity outpost as designed, and the redesign path is pump-assisted vat drains. Only HyperCook is compliant across the full range (pumped lines, gravity-insensitive; 271 bph at 12 g after robotic derate), so only HyperCook carries the SR-GS-015 Verify links; EverSimmer's 0.1 g case is an unlinked regression baseline documenting the hole. Second, the SR-GS-008 criterion caught a band-edge design: SimmerTemp_C was 95, the top of the serving band, and bang-bang ripple served soup at 95.23 C. Fixed with design margin (target 94 C, serve 92.5-94.2 C, ~3 s cycle impact, all baselines hold), not test tolerance. Suite: 47 tests (21 component, 13 analysis incl. tGravity, 4 traceability, 9 simulation cases), all green; requirements report regenerated.

ADR-027: Contamination detection in the QC stations

Context. SR-GS-007 requires contamination detection before sealing with at least 99% sensitivity. The SoupStream bus has carried a contamination_ppm element since the interfaces were first defined, stubbed to a constant 0 at every producer — the requirement was implemented in the architecture (QC components exist everywhere) and exercised by nothing.

Decision. Model detection in the shared BehQCStation library model as deterministic flow splits, with two new model arguments defaulting neutral: ContamIncidence (0) and DetectSensitivity (0.995, deliberate margin over the 0.99 floor). Contamination applies to the quality-passed stream; detected mass (× sensitivity) moves to the reject stream, escaped mass ships with the passed flow, and two new outports (contamDetected_bps, contamEscaped_bps) expose the split, signal-logged by the generator at one QC instance per variant. Top-level dictionary names QC_ContamIncidence/QC_DetectSensitivity map in via instance-parameter expressions and override per run through the established model-workspace mechanism. analysis/sweeps/runContaminationSweep.m sweeps incidence 0.5-5% across all variants plus a boundary case at the requirement floor; the Simulink Test file gains a Contamination suite (2% incidence, HyperCook and EverSimmer) with Verify links to SR-GS-007; tBehQCStation gains an exact component-level split test and tContamination baselines the sweep.

Status. Accepted.

Consequences. SR-GS-007 verified-passed; test-verified coverage 6 of 28. Measured sensitivity is flat at the 0.995 design value across the sweep (deterministic split; the sweep proves plumbing through three architectures, not statistics), the floor-boundary case measures exactly 0.9900 (zero margin — the argument for the 0.995 design value), and no compliance verdict flips even at 5% incidence (293.1/187.9/220.4 bph). One architectural observation: EverSimmer's per-cell QC confines a contamination excursion to a third of production (34 ppm escaped vs 103 ppm for the single-QC variants at 2% incidence) — the fault-isolation argument reappearing in a quality metric. One naming gotcha now recorded in 15_contamination_detection.md: dictionary-side override names must differ from model-argument names or every simulation warns about symbol shadowing (hence the QC_ prefix). Suite: 52 tests, all green.

ADR-028: Loading docks with fluid queues and measured latency

Context. SR-GS-006 requires packaged soup loaded onto transport within 10 minutes. The loader components were hard-coded rate-capped pass-throughs; Transport_Rate_bph and Transport_Latency_s sat in the dictionaries used by nothing, and no latency was measurable anywhere.

Decision. Each loader gets a dockPath (generator helper): a fluid dock queue — out = min(pickupRate, in + level/30), level saturating at [0, 600] bowls — with the pickup rate now dictionary-driven from Transport_Rate_bph. Loading latency = queue wait + transit, measured as the median mass-threshold lag between the logged packedFlow_bps/loadedFlow_bps cumulative curves; the Transport_Latency_s transit delay rides a measurement-only tap so the root port's mass flow stays undelayed. Two neutrality traps forced that shape, both recorded in 16_transport_loading.md: a BehStorage dock with draw > inflow chatters at empty under the variable-step solver and leaked 5-10% of throughput (HyperCook 308.4 → 277.4 bph), and putting the transit delay in the mass path phase-shifted LeanBroth's bursty flow against the measurement window (196.8 → 194.1 bph, a windowing artifact that would have rippled through every published number). The fluid-queue form passes flow through exactly when empty; the measurement tap keeps every throughput baseline exact. analysis/sweeps/runTransportSweep.m derates pickup capacity through 100/80/60/40%; a TransportLoading suite adds one case per variant with Verify links to SR-GS-006 — including LeanBroth's first link: it fails the throughput floor but genuinely satisfies loading latency, and the link-semantics rule cuts both ways.

Status. Accepted.

Consequences. SR-GS-006 verified-passed for all three variants; test-verified coverage 7 of 28. Nominal latency equals transit exactly (30/120/60 s, empty queues). The sweep's finding is a margin story: everyone holds 600 s at 80% capacity and loses it by 60% (unbounded backlog once pickup < production), but LeanBroth stands closest to the cliff — at 80% its pickup margin is 1.6% and latency has already climbed to 196.6 s. tTransport baselines both the cliff pattern and LeanBroth's thinnest-margin status. Suite: 57 tests, all green.

ADR-029: Runtime recipe rotation with architecture-priced changeover

Context. SR-GS-001 requires at least 8 distinct recipes selectable at runtime; PreparedBatch.recipeId had been a stubbed constant 0 since the interfaces were defined.

Decision. All generator-level (buildInlineBehaviors.m), no library changes: a recipeSchedule helper stamps activeRecipe = 1 + mod(floor(t/Recipe_Block_s), Recipe_Count) into every prep unit's output (logged once per variant), and a flushGate on HyperCook's four continuous cook lines prices changeover — the line drops for Recipe_Flush_s after each switch. Batch vats get no flush by design: recipe changeover between batches rides the clean phase the cycle already pays for. Defaults are neutral (Recipe_Flush_s = 0); Recipe_Count = 8, Recipe_Block_s = 1800 give 8 recipes per 4 h production run. analysis/sweeps/runRecipeSweep.m prices the flush at [0 60 120 300] s; a RecipeRotation suite adds HyperCook (120 s flush) and EverSimmer cases with Verify links to SR-GS-001; tRecipes baselines the sweep and the linearity of the pricing.

Status. Accepted.

Consequences. SR-GS-001 verified-passed; test-verified coverage 8 of 28. Every production run produces all 8 recipes; HyperCook's flush costs a linear ~9.9 bph per flush-minute (289.0 bph at a realistic 120 s, 259.0 at a pessimistic 300 s — never near the floor). The architectural observation is the inverse of the gravity story: recipe flexibility is where batch cooking finally wins one. Continuous-line throughput pays real downtime for every changeover, while the batch architectures absorb switching into cycle structure they already have — LeanBroth and EverSimmer's flush cost is identically zero by construction. A third occurrence of the log-after-wire ordering trap (vat temp, QC contamination, now recipe logging) turned it into a named generator convention: the logLine helper, called only after lineTo. Suite: 61 tests, all green.

ADR-030: The SR-GS-021 storage-endurance finding

Context. SR-GS-021 requires 72 hours of stored ingredients at nominal production rate. Storage capacities (2000/800/1200 bowls) were stereotype-sized for buffering, and nothing had ever simulated a resupply outage.

Decision. Give every variant's resupply source a cutoff gate (Resupply_Cutoff_T, model-workspace default 1e9, the fault-injection mechanism) and measure endurance: stores filled to capacity, resupply cut at t = 3600 s, endurance = last productive instant minus cutoff (analysis/sweeps/runEnduranceStudy.m; a first-dip detector misreads LeanBroth's batch gaps as starvation — 1.04 h vs the true 4.66 h — so last-productive-instant is the required form). Record the result as a FINDING, not a verification: a StorageEndurance suite of three regression-baseline cases with no Verify links, each also asserting endurance < 72 h so a future fix must consciously retire the finding; tests/analysis/tEndurance.m baselines the numbers and the conflict math.

Status. Accepted.

Consequences. Measured endurance: 6.41 / 4.66 / 5.44 h (HyperCook/LeanBroth/EverSimmer) against 72 required — order-of-magnitude non-compliance for every architecture, and the first verification series requirement where the answer was no. Pricing compliance exposes a requirements conflict: 72 h of ingredients at nominal rate masses 12.2 / 7.8 / 9.2 t against the 15 t TOTAL system mass budget (SR-GS-011) that already carries the machinery. SR-GS-021 as written is unsatisfiable alongside SR-GS-011 for any variant; resolution belongs to the requirements owner (relax the hours, raise the budget, or exclude consumables from the system mass — the likely intended reading). Verified-by-test coverage deliberately stays 8 of 28: the dashboard's honesty depends on findings not being dressed up as verifications. Suite: 66 tests, all green.

ADR-031: Rocket turnaround from existing instrumentation

Context. SR-GS-018 caps delivery-rocket loading at 20 minutes. No rocket-level behavior existed, but the transport-loading branch (ADR-028) already logs the loaded-shipment flow at every dock.

Decision. Model turnaround as fill time plus handling overhead, measured from the logged loaded-flow cumulative curve: two new estimate parameters (Rocket_Load_bowls = 60, Rocket_Handling_s = 120) and measurement code only — the cheapest increment of the verification series, riding entirely on ADR-028's instrumentation. analysis/sweeps/runTurnaroundSweep.m prices turnaround for shipments of 40-120 bowls from one nominal simulation per variant; a RocketTurnaround suite adds three cases with Verify links for HyperCook and EverSimmer and an unlinked regression baseline for LeanBroth asserting turnaround > 1200 s (the finding-retirement convention from ADR-030).

Status. Accepted.

Consequences. SR-GS-018 verified for HyperCook (808.1 s) and EverSimmer (1086.0 s, 114 s of margin) at the 60-bowl design shipment; test-verified coverage 9 of 28. LeanBroth misses by 41.5 s (1241.5 s) — fill time is set by production rate, not dock capacity, so the turnaround ranking is the throughput ranking, and the slowest producer pays at the launch pad too. The verdict is estimate-sensitive (LeanBroth passes at 55-bowl shipments), which is why the published product is the compliant-shipment envelope (~92 / ~57 / ~66 bowls for HC/LB/ES), not just the design-point verdict; tTurnaround baselines both. Suite: 71 tests, all green.

ADR-032: SR-GS-021 resolution - consumables excluded from the mass budget

Context. ADR-030 recorded the storage-endurance finding: 6.4/4.7/5.4 h against 72 required, with compliance apparently costing 7.8-12.2 t of stored ingredients against the 15 t SR-GS-011 budget. The requirements owner ruled on 2026-07-08: stored consumables (ingredients and packaged product) are excluded from the system mass budget.

Decision. Implement the ruling end to end. (1) SR-GS-011's description amended (the "15000 kg" figure kept parseable for the cap-extraction code). (2) Ingredient stores resized to 72 h at each variant's nominal rate: 22300/14300/16800 bowls (dictionaries + setup source; initial levels unchanged, so nominal runs never touch the new caps and every throughput baseline holds exactly). (3) The storage components' stereotypes gained rack HARDWARE - which still counts against every budget - at 0.025 kg / 0.0010 m3 / 0.004 kCr per added bowl of capacity (estimates). (4) runEnduranceStudy and the StorageEndurance suite reworked from finding to verification: no starvation across a 12 h unsupplied window plus projected endurance (capacity / measured unsupplied rate) >= 72 h; the finding-era baselines, whose < 72 h assertions existed precisely to force this moment, were retired consciously and all three cases now carry Verify links to SR-GS-021.

Status. Accepted.

Consequences. SR-GS-021 verified for all three variants (72.3/72.6/72.8 h projected); test-verified coverage 10 of 28. The resolution re-opened the compliance gate: HyperCook is no longer compliant. Its 20 kCr cost margin and 3 m3 volume margin could not absorb 20,300 bowls of rack hardware (new totals 14827.5 kg / 2061.2 kCr / 417.3 m3 - mass fits, cost and volume do not); the gate reads 21/24 and the compliant set collapses to {EverSimmer}. runFullAnalysis now handles a single-variant set as a documented FORCED SELECTION (the trade study degenerates to a formality, winShare 1) rather than asserting; the HC-vs-ES trade study and the uncertainty study's HyperCook shares are retained as what-if baselines (tTradeDeterminism/expectedOutcome: 98.42% stands). Roll-up goldens rebaselined (pre-resolution values preserved in the test comment). One instrumentation lesson: the uncertainty study's "reproducible post-processing" read LIVE variantMetrics for its six static criteria, so ADR-032's margin changes moved the double-MC win share (0.0056/0.9780 to 0.0052/0.9784) - reproducibility contracts must either snapshot their inputs or expect rebaselines when upstream metrics change. Net verdict: EverSimmer's win hardened from 98.4% of scored worlds to the only variant left standing.

ADR-033: External Simulink Test harnesses for every simulation case

Context. Best practice: models exercised by Simulink Test should run inside test harnesses, saved externally, so the test boundary is explicit and root-level signals are directly observable. All 22 simulation cases ran the architecture models bare.

Decision. tests/system/buildTestHarnesses.m generates one externally-saved harness per architecture model (HyperCookSystemHarness / LeanBrothSystemHarness / EverSimmerSystemHarness, living beside the models in architecture/), and every case in the test file sets HarnessOwner/HarnessName. The harness makes two things explicit that direct simulation hid: the models' root inports (AmbientGravity, CustomerOrders, InboundSupplies), silently grounded before, are now visible Constant-0 sources; and the virtual Telemetry bus flattens at the model-reference boundary into named scalar outports (Telemetry_totalPower_kW, Telemetry_plantMode), so the criteria now harvest telemetry by yout element name instead of bus fields.

Status. Accepted.

Consequences. All 22 cases run through harnesses; 72/72 green, all 10 verified requirements re-registered. One real constraint surfaced: Test Manager parameter overrides do not reach model-workspace variables through a harness (the six fault/endurance cases errored), while dictionary entries override cleanly. The fault-injection and resupply-cutoff variables (Fault_T_*, Resupply_Cutoff_T) therefore moved from the model workspaces into the behavior dictionaries — Test Manager overrides now reach them, and the direct-simulation path (setVariable(...,'Workspace',mdl), used by the analysis sweeps) still works because a model-workspace assignment shadows the dictionary entry. Mechanics for reuse: external harness files are created in the CURRENT FOLDER (cd to the model's folder first); setProperty requires HarnessOwner before HarnessName in one call; sltest.harness.find returns lowercase field names; Constant-0 harness sources reproduce the grounded-inport baseline exactly (all throughput baselines held to the decimal).

ADR-034: AmbientGravity wired through the harness as the gravity input

Context. The harness rework (ADR-033) exposed root inports the models always had, including AmbientGravity — routed at the architecture level to each variant's gravity-compensator component, whose behavior ignored it and whose envStatus.gravity_g output was stubbed to a constant 1: a sensor that lied at any gravity other than Earth's.

Decision. Make the input real, respecting the constraint that the gravity PHYSICS lives in compile-time instance-parameter expressions a signal cannot feed. Three coordinated changes: (1) the harness AmbientGravity source resolves from the same Gravity_g dictionary entry that drives the physics (bus-typed port, so the Constant takes a struct expression struct('gravity_g', Gravity_g, 'compensation_pct', 100)) — one source of truth, and a Test Manager Gravity_g override moves the signal and the physics coherently; (2) the gravity compensators now CONSUME the input, passing the measured field through to envStatus.gravity_g (truth-telling sensor; exactly neutral at 1 g since it reports 1 where the stub reported 1), with the signal logged as ambientGravity; (3) every GravityExtremes case asserts liveness — the logged signal must equal the overridden gravity to 1e-9.

Status. Accepted.

Consequences. The environment is now an interface signal, observable end to end at the harness boundary, and the gravity test cases prove the source is live rather than decorative. One asymmetry documented: the coherence holds for Test Manager dictionary overrides (which both the harness source and the CUT see) but NOT for direct-sim setVariable(...,'Workspace',CUT) overrides, which shadow the dictionary only inside the CUT — the harness source still reads the dictionary default. Direct simulation of the bare models grounds the port to 0 as before; signal truth requires a driving parent. Bare-model analysis sweeps are unaffected (they read physics, not the signal). Full readability pass also applied in this branch: Simulink.BlockDiagram.arrangeSystem over all 66 diagram levels of the physical models, harnesses, and behavioral library (repeated generator rebuilds had degraded interior layouts). Suite 72/72 including the new liveness assertions.

ADR-035: Report requirement implementation/verification per candidate architecture

Context. All three physical variants carry Implement links to the system requirements, and the Simulink Test file's Verify links attach per ADR-022's rule — wherever a passing case honestly means that variant meets the requirement. Requirements Toolbox rolls implementation and verification status up over ALL loaded link sets, so every coverage artifact (the runAllTests summary, the generated requirements report, the Requirements Editor status columns) aggregated three mutually exclusive architectures. Most sharply: SR-GS-015 (0.1–12 g operating range) showed verified-passed from HyperCook's gravity cases while EverSimmer — the trade-study winner — fails that requirement at 0.1 g (189.3 bph against the 200 floor; ADR-026's first finding). A first cut of this fix scoped everything to EverSimmer as the adopted baseline per ADR-009: the alternates' Implement links were demoted to Relate and their Verify links removed. That was reverted within the same branch — the variant selection is under renewed team review, all three candidates need their evidence intact and presentable side by side, and the Relate demotion discarded real semantics (each candidate genuinely implements the requirements) to work around what is actually a reporting limitation.

Decision. Keep the links symmetric and per-variant honest — all three variants carry Implement links, and Verify links attach per variant per the ADR-022 rule (HyperCook's gravity cases verify SR-GS-015 for HyperCook; nothing verifies it for EverSimmer) — and do the scoping at reporting time: (1) runAllTests ends with a per-variant coverage table, attributing Verify links by the variant each test case simulates (case names lead with the variant) and Implement links by source artifact, asserting each variant's exact verified-SR set (HyperCook 8, LeanBroth 2, EverSimmer 9) and printing each variant's known non-compliances alongside; (2) analysis/reporting/makeVariantTraceMatrix.m generates the design-review evidence — docs/deliverables/variantRequirementsTraces.md and .csv: one full trace per variant (implemented / formal-gate verdict / test evidence per SR) plus a side-by-side 28×3 comparison matrix; (3) the slreq report (makeRequirementsReport) remains as the any-variant aggregate and is documented as such.

Status. Accepted. Supersedes the baseline-scoped mechanism briefly committed earlier on the same branch. ADR-009's selection of EverSimmer is under renewed team review pending this evidence package.

Consequences. Per-variant truth is now visible side by side: EverSimmer verifies 9 SRs by executed simulation but fails SR-GS-015 at 0.1 g; HyperCook verifies 8 including SR-GS-015 but fails the cost and volume gates (post consumables-rack ruling) and SR-GS-026; LeanBroth verifies only transport latency and storage endurance, failing throughput, rocket turnaround, and single-fault retention. A tool limitation is on record for the next person: slreq link sets auto-load with the requirement set from the artifact registry, there is no unload API, and delete on a loaded slreq.LinkSet object is a no-op — so the Requirements Editor status columns and slreq.generateReport cannot be scoped to one variant while all artifacts share one project; per-variant status must be computed by link attribution, which the variant-first case-naming convention makes mechanical. Duplicating the requirement set per variant was rejected outright: three copies of 28 SRs would be three sources of truth.

ADR-036: Bay hierarchy in the physical architectures

Context. The three physical models were built flat: HyperCook carried 19 top-level components and 53 top-level connections, LeanBroth 16, EverSimmer 13 (EverSimmer alone had a production-cell layer, added for its own reasons in ADR-001). The root diagrams bore no resemblance to the variant schematics in docs/figures — a reviewer holding the schematic beside the model could not find the material path, because auto-layout orders blocks by edge length rather than by process. Two structural facts drove the noise: every unit reported status on its own port, so the plant controller carried one input port per unit (19 on HyperCook's CentralControlComputer, 16 on OpsConsole, 13 on ControlTriad), and the controller's single productionDirective fanned out to every unit.

Decision. Introduce one layer of bay hierarchy, grouping each variant's top-level components by their role on the material path — IntakeAndStorage, PrepBay/ProductionLine, CookBay, FinishingLine, LaunchLogistics, PlantServices — and lay the root diagram out as the schematics read: material flow left to right in one row, shared services and the plant controller in a band below it. The controller stays at the root of each model: it is the one component every bay talks to, and burying it in a bay would hide the control topology rather than clarify it. architecture/build/regroupPhysicalBays.m performs the regrouping and architecture/build/layoutPhysicalBays.m the layout, both re-runnable.

The regrouping is metrics-neutral by construction, which was the binding constraint — the committed trade study is a deliverable and this is a readability change, not a design change. Grouping uses Simulink.BlockDiagram.createSubsystem, which MOVES the existing component blocks: SIDs survive (so the 48 requirement links, which resolve by SID, are untouched) and System Composer UUIDs survive (so the 57 LogicalTo* allocations, which resolve by UUID, are untouched). Each new bay receives the PhysicalProperties stereotype so gsRollup's PostOrder pass fills it with the sum of its children, leaving the top-level budget sums unchanged. No component is added or removed, so the leaf set — and therefore AutomationAvg, GravityMin, stage capacities and the availability product — is unchanged. The stage tables in runVariantAnalysis keep naming leaf components; a new gsFindComponent resolves them recursively, because a process stage is a property of the design rather than of where a component sits in the model tree.

Status. Accepted.

Consequences. Top-level components 19/16/13 → 7/5/7; HyperCook's root connections 53 → 35. variantMetrics.csv and behavioralMetrics.csv are byte-identical to their pre-refactor values, the formal gate still reads 21/24 with the compliant set {EverSimmer}, and the suite is 72/72. Verification is by gsFlattenConnections, which reduces a model to its leaf-to-leaf signal set — traversing bay boundaries rather than reporting them — so the set is hierarchy-invariant and diffing it across the refactor proves the transform preserved every signal. It identifies components by UUID rather than name, without which EverSimmer's three identically-named cells collapse into one and a signal rerouted between cells would go unnoticed. That check earned its keep: the first implementation also merged duplicate bay input ports, and the diff caught it silently dropping seven productionDirective connections.

The sharpest trap here was signal logging, and connectivity checking does not catch it. Grouping a component into a bay silently clears the DataLogging flag on signals inside that component — HyperCook kept 2 of its 6 logged signals, and the loss surfaced not as a modelling error but as eight red simulation cases (TransportLoading, RocketTurnaround, Contamination, RecipeRotation) whose criteria fetch signals by name out of logsout and got an empty result. The flags are dropped by the save, not by the edit: they are still set in memory after createSubsystem, so capturing them before the regrouping and restoring them before arch.save() reports success and loses them anyway. The regrouping therefore saves, restores the flags by signal name, saves again, and asserts the set survived the round trip; the layout pass makes the same assertion, since arrangeSystem re-routes lines and the flag rides on the line's source port. General lesson for scripted model surgery in this project: structural verification is necessary but not sufficient — model state that lives on lines and ports (logging, data types, storage class) needs its own before/after assertion, and it needs to be made after the write, not before it.

Two limits are on record. First, the status star is unchanged — the controller still carries one input port per unit, and those lines still dominate the lower half of each root diagram. Collapsing them needs a per-bay status aggregator, which is a real component with mass, power and cost (this is exactly what EverSimmer's CellController is), so it would move AutomationAvg and the availability product and re-open the trade study. That is a design decision, not a readability one, and was deliberately left out of scope. Second, duplicate directive boundary ports remain (CookBay has directive..directive4): where the controller's directive fans out to several members of one bay, createSubsystem sometimes gives each member its own boundary port. Merging them was attempted and reverted — System Composer's connect will not branch an already-connected input port (it returns an empty connector rather than erroring), and destroying the redundant port leaves a dangling Simulink line the architecture API cannot see, so the model ends up inconsistent. A pure-Simulink delete_block/add_line merge is the remaining option and is untried.

ADR-037: Bay status concentrators

Context. ADR-036 grouped each physical model into bays and made the root diagrams read like the variant schematics, but it explicitly left the control star alone: each plant controller still carried one status input per unit (19 on HyperCook's CentralControlComputer, 16 on OpsConsole, 13 on ControlTriad), and those lines still dominated the lower half of every root diagram. It also left behind the duplicate directive boundary ports createSubsystem produced when one controller output fanned to several members of a bay (CookBay had directive..directive4). Collapsing the star was recorded as out of scope precisely because it cannot be done for free: it needs a real component in each bay, and real components carry mass, power, and cost.

Decision. Add one status concentrator per bay — IntakeController, PrepController, CookController, FinishingController, LaunchController, ServicesController and their per-variant equivalents. Each receives the plant directive once and fans it out to its bay's members, and collects its members' StatusBus into a single rolled-up bayStatus. Plant controller inputs drop 19/16/13 → 9/7/9, one bayStatus per bay replaces one status port per unit, and the duplicate directive ports are gone. EverSimmer's three production cells stay at the model root — they already had their own CellControllers, which is the pattern this generalises — so only its intake, launch, and services bays gain one.

Sizing is load-bearing and was chosen deliberately. A concentrator aggregates telemetry and fans out a directive; it does not sequence a production chain the way EverSimmer's CellController (60 kg / 2 kW / 25 kCredits / 1 m³) does, so it is sized well below one: 15 kg / 0.4 kW / 6 kCredits / 0.15 m³, MTBF 40 000 h, automation 0.99. At CellController pricing the three EverSimmer concentrators would have cost 75 kCredits against 32.6 kCredits of remaining cost margin, taking the compliant set to empty and making runFullAnalysis assert on a vacant trade space. The breakeven is about 10.8 kCredits per concentrator; above that EverSimmer loses its cost gate and the project has no compliant variant at all.

A new BayStatusBus interface carries the rolled-up status. It has StatusBus's elements plus a 4-wide lineHealth vector of per-member health, because the supervisor's mode logic counts healthy production units (sum(health(1:NumLines) > 0.5)): a scalar rolled-up health would turn a single failed cook line from Degraded into a full plant Halt. Each concentrator's bayStatus.power_kW reports the sum of its members' power and deliberately excludes its own draw, which is accounted for in the plant controller's UnreportedPower constant instead — folding it in would put a standing offset on the signal the supervisor tests with outFlow > 0.001 and carry the plant out of Startup before any line produced anything. The concentrators are also added to runVariantAnalysis's support lists, since each is a single point of failure for its bay.

Status. Accepted.

Consequences. Simulated throughput (308.37 / 196.85 / 231.90 bph), time-to-first-out, and fault retention are unchanged to five significant figures — the aggregation is behaviour-preserving by construction — and mean power rose by exactly the concentrators' draw (+2.4 / +1.6 / +1.2 kW). Everything else moved as the added hardware implies: mass +90/+60/+45 kg, cost +36/+24/+18 kCredits, volume +0.9/+0.6/+0.45 m³, energy per bowl +0.5–1.0%, and availability down 0.35/0.23/0.18 pp now that the concentrators can fail. Automation average rose (+0.011/+0.038/+0.004), since a concentrator is more automated than the fleet average.

The headline result is that HyperCook lost its power gate. It was running at 498.0 kW of a 500 kW cap; six concentrators at 0.4 kW put it at 500.4. The formal gate goes 21/24 → 20/24, and HyperCook now fails Power as well as Cost and Volume. EverSimmer absorbs its three concentrators with 14.6 kCredits of cost margin left and remains the only gate-compliant variant, so the selection is unchanged — but the margin that survived ADR-032 is now visibly thinner. Goldens rebaselined in tRollupInvariants and tGateAgreement with the pre-change values preserved in comments; suite green.

Two API findings, both of which cost real time. First, System Composer's architecture-level rewiring is unusable on an existing model in R2026a: Connector.destroy leaves the port in a state connect() will not reconnect — not even back to exactly where it came from — and connect() signals refusal by returning an empty connector array rather than erroring, so a first cut of this change produced a model full of silently disconnected ports that only model_check noticed. The working approach is to rewire at the Simulink layer (delete_line / add_line on the ports' SimulinkHandles), which System Composer then picks up as connectors; ComponentPort.destroy additionally errors outright, so controller ports must be removed via the architecture port inside the component. Second, blkOf returns whichever element reader on a bus port it finds first, so once a port has more than one reader the name must be given explicitly — feeding a scalar power reader to the supervisor's 4-wide health input failed only at diagram update, several steps from the mistake. Never call connect() without asserting on its return value.