Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

DETERMINISTIC CONTROL · EMBEDDABLE RUST

Open Control Engine

Parse CXF, freeze a control schedule, and tick CDL control sequences deterministically inside your application—without bringing a daemon, runtime framework, or database.

Choose your path

EMBED

Run the guarded Quickstart

Start with the mechanically reused, compiled example, then read the host safety boundary before connecting equipment.

Embed the engine →

CONTRIBUTE

Change the engine safely

Understand the development and release gates, what a green check proves, and the testing standard expected of every change.

Contribute to the engine →

Evidence, not adjectives

The documentation separates deterministic self-output traces, independent signal oracles, structural checks, and work that is still deferred.

Read the verification accounting →

Embed the engine

This is the repository Quickstart, extracted mechanically from the first Rust block in README.md. That block is byte-compared with the compiled crates/oce-api/examples/quickstart.rs example.

Before connecting real equipment, read Host responsibilities. The host owns sample quality, missing-data, timing, fault, and safe-state policy.

use oce_api::{Engine, Value};

const ECONOMIZER: &str = "http://example.org#g36.ahu_economizer";

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut engine = Engine::in_memory(); // Default in-memory store, no database.
    let cxf_bytes = std::fs::read("crates/oce-cxf/tests/fixtures/g36/ahu_economizer.jsonld")?;
    engine.load_cxf(&cxf_bytes)?;

    // The host owns cadence and supplies every required observation on each frame.
    for index in 0..=4 {
        let time = f64::from(index);
        let observations = [
            (format!("{ECONOMIZER}.return_air_temp"), Value::Real(24.0)),
            (
                format!("{ECONOMIZER}.outdoor_air_temp"),
                Value::Real(18.0 + time),
            ),
            (format!("{ECONOMIZER}.operating_mode"), Value::Integer(1)),
        ];
        let entries: Vec<_> = observations
            .iter()
            .map(|(p, v)| (p.as_str(), v.clone()))
            .collect();
        let prepared = engine.prepare_frame(time, &entries)?;
        let completed = engine.execute_frame(prepared)?;
        println!(
            "frame {} at {}: {:?}",
            completed.sequence(),
            completed.time(),
            completed.outputs()
        );
    }
    Ok(())
}

Continue with Architecture and CDL coverage.

Documentation

Reference documentation for Open Control Engine. The project README is the front door; these pages are the detail behind it.

Start here

PageRead it when you want to know
Product contractVersioned executable-CXF/HostTick requirements, domain-owner delegations, limitations, evidence and explicitly future outcomes
Authority claims and supersessionThe generated cross-domain summary, checked versus review-only boundaries, source-owner update procedure, and non-exhaustive supersession map, from the index
ArchitectureHow the engine is layered, where the CDL §7.17 seam sits, what each of the 17 crates owns, and the platform and MSRV policy
Execution profileWhy each host tick is one state transition, and where CDL.Logical.Pre differs from Modelica same-time event iteration
Canonical replay recordBounded per-accepted-frame exact bytes, typed refusals and host-owned ordered replay with separate snapshot sidecars
Release-candidate compatibilityRetained directed matrix, no-N-1 refusal policy, cold-start/rollback checklist and future RC note template
Compatibility manifestPer-release CXF import contract, schema revisions, content ids and state/replay formats, generated by crates/oce-api/tests/compatibility_manifest.rs
Verification and evidenceWhat has actually been proven about this engine, what has not, and which checks are deliberately not running
CDL coverageWhether your sequence runs — which classes and G36 sequences are supported, and what “supported” is defined to mean
CXF round tripWhat export guarantees, and the conditions under which it silently drops part of your model
CXF composite subsetThe normative contract, if you are writing a tool that emits CXF for this engine
Host responsibilitiesWhat safety behavior you must implement yourself, before wiring the engine to equipment
CI and the gateWhat runs when, and what a green check does and does not prove
BenchmarksCurrent complete-frame allocation/latency harness and historical throughput, qualified by the commit and host that produced it
Stability baselineThe dated OCE/downstream ref and pin evidence snapshot, its authority limits, and deterministic verifier
Public surface contractWhich oce-api and oce-store items are stable candidates, conditional, deferred, deprecated, or scheduled for removal, with the machine-checked ledger
Facade migrationFrame-only execution migration, removed pre-release names, Warning-only diagnostics and preserved host boundaries
Package, feature, and publication policyWhich of all 17 workspace packages are supported or private, which oce-api feature selections are supported, and which 12 packages are eligible for a future release, with the machine-checked ledger

Elsewhere in the repository

DocumentPurpose
README.mdProject front door: what this is, who it is for, and how to try it
TESTING.mdThe testing standard every change is held to. Read before writing a test
CONTRIBUTING.mdHow to work on the repository
SECURITY.mdReporting, threat model, and the known hardening limit
CHANGELOG.mdNotable changes

Two pages worth reading even if you are only evaluating

Verification and evidence is the honest accounting. Six evidence layers in this repository are called “tests” and they prove different things — one of them proves nothing about correctness at all. That page says which is which, names the two global report tiers that are not wired, and bounds the separate four-case OpenModelica evidence.

Host responsibilities is the one to read before anything touches a physical output. The engine implements no fail-safe policy of its own, by design, and that page is the checklist of what your host layer therefore has to do.

A note on these documents

Claims here cite file:line wherever they are checkable, so you can verify rather than trust. Where something is unverified, these pages say so rather than rounding up — several of them were written specifically to correct claims that had drifted out of date.

Current source links resolve in a clone. Explicit historical locators in the supersession map are inert text, not clone prerequisites or current authority.

Executable CXF and HostTick product contract

Document revision: 15 Grounding SHA: e81480b02271456719d55cbe1e5090b0dea6d63c

This is the aggregate product boundary and requirement-to-evidence map for the work toward a stable embeddable kernel. It records current observations, host obligations, and future acceptance outcomes separately. It does not declare a published stable product or change runtime behavior. The document revision is not a runtime profile, catalog, snapshot, execution ABI, wire, or release identity. HostTick v1 remains the fixed existing profile, not a new selectable API.

Authority and owners

Domain authorities retain their scope; this aggregate does not supersede them:

Owner roleDelegated authority
Contract maintainersThis document’s revision, traceability and acceptance boundaries; affected domain owners decide semantics.
Facade maintainersPublic surface contract, exact facade baseline and storage baseline.
Release maintainersPackage and publication policy, including its separate feature matrix.
CXF maintainersComposite subset and round-trip contract.
Execution maintainersExecution profile, complete-frame contract, load/execute/state implementation and focused tests below.
Block semantics maintainersLocal block behavior and bounded conformance boundary; upstream provenance is a separate evidence question.
Host integratorHost responsibilities; qualification of the actual consuming application, adapter and equipment.

These are accountable roles, not claims of named-person assignments. Conflicts go to the affected domain owner with source/test evidence before a revision is accepted. Existing baselines, ledgers, and the authority index retain their separate checks. This is not another signature ledger or generated authority projection.

Reading the requirements

The single table below is the normative grammar. Each physical row has the eight displayed cells, one immutable PC- number, one uppercase obligation keyword in the Requirement cell, a named actor and owner, a nonempty limitation, grounding links, and either named test links or one future-outcome assignment. IDs are allocated in ascending order without reuse; withdrawn obligations remain visible pending an explicit revision rather than disappearing silently.

  • CURRENT: observed behavior or present claim boundary, not a published stability promise. PC-031 is accepted normative contract/evidence delivery, not a runtime frame implementation.
  • HOST-OBLIGATION: required host policy now. Engine boundary tests show why responsibility is outside the engine; they do not prove that any host complies.
  • FUTURE: acceptance outcome only, explicitly not implemented by this contract. Existing partial mechanisms are not evidence that the complete future contract is available.

Grounding links identify source or delegated policy. test links name an existing test declaration; the line fragment includes that declaration. future links name a later work item and point to its clone-visible outcome here. Multiple test links use semicolons. Limitations are part of each row, not optional caveats. All product obligations are indexed in this table; linked domain contracts define their detail without changing the current/future acceptance boundary.

Requirements

IDStatusActorOwnerRequirementLimitationGroundingEvidence
PC-001CURRENTContract maintainerContract maintainersMUST retain immutable requirement IDs and record a document revision, affected-owner approval, evidence assessment and migration review for normative changes; promotion from FUTURE requires implementation acceptance.Traceability tests check structure only; human approval and semantic relevance are not mechanically proven. Editorial-only changes still receive a change record.Change recordtest test_report_is_an_independent_byte_golden
PC-002CURRENTProduct claimantRelease maintainersMUST preserve oce-api as host facade, oce-store as conditional adapter port and oce-blocks catalog as transitional companion under the delegated package and surface classifications.Nothing is published; stable-candidate is not stable SemVer. Removed placeholder loaders are unavailable; implementation dependencies are not independently supported APIs.Packages; Surface; CXF ingesttest test_ratified_publish_and_private_sets_are_exact; test_owner_approved_categories_cannot_drift_with_same_publish_bit; valid_minimal_loop_loads_clean; retired_facade_symbols_are_absent
PC-003CURRENTProduct claimantCXF maintainersMUST constrain executable CXF claims to the closed, bounded, already-specialized graph profile, including the documented bounded composite lowering.Not general Modelica flattening, source recovery, every CXF construct, or promotion of every accepted syntax to stable support. Semantic and traversal bounds do not bound serialized bytes.Load pipeline; Subset; Removed loaderstest composite_nesting_accepts_the_limit_and_rejects_one_past; boundary_hops_accept_the_limit_and_reject_the_attempted_next_hop; retired_facade_symbols_are_absent
PC-004HOST-OBLIGATIONHostHost integratorMUST cap serialized CXF transport/buffering and apply isolation appropriate to the trust boundary, outside any process actively commanding equipment for untrusted programs.Facade admission defaults to 8 MiB and can only be configured at or below that maximum; a byte cap does not bound memory, CPU, expansion or qualify a host. The low-level parser is not the bounded facade.Admission; Untrusted inputtest inclusive_default_and_stricter_boundaries_preserve_legacy_acceptance
PC-005CURRENTProduct claimantExecution maintainersMUST distinguish ordinary failed-load preservation of the complete in-memory executable/run image from external Store transactionality.Recover, save_model and handle resolution may have non-rollback effects; old external handle validity is not promised. Panic, process death, allocation failure and concurrent host effects are excluded. Flatten and semantics currently have no returned refusal path.Build and commit; Compensationtest pre_store_refusals_preserve_fresh_advanced_and_dirty_run_images; store_refusals_preserve_fresh_advanced_and_dirty_run_images; private_build_tail_refusals_preserve_the_run_image; failed_handle_resolution_leaves_saved_model_and_allocated_store_handles
PC-006CURRENTEngineExecution maintainersMUST use finite nondecreasing model seconds and perform one emit pass followed by one stateful update pass per successful HostTick call, including equal timestamps.Loaded-block time representability checks also apply. Pre emits entry memory then latches current input; no Modelica fixed-point iteration or convergence test.Profile; Preflight; Core; Evaluatortest parameter_seed_is_first_call_output_and_equal_time_calls_advance_memory; nonconvergent_boolean_feedback_is_accepted_and_advances_per_call
PC-007CURRENTEngineExecution maintainersMUST require every executable boundary input exactly once in a complete typed frame instead of sparse input staging.Quality, freshness and plausibility remain host policy; the point inventory is not the executable input schema.Preparation; Migrationtest incomplete_observations_never_stage_prefixes_or_reuse_prior_values
PC-008CURRENTEngineExecution maintainersMUST refuse missing determinants without implicit prior-value, type-seed or Store-sample substitution.Explicit host substitutions are not qualified by the engine. An executable with no inputs admits an empty frame.Determinantstest store_samples_neither_supply_missing_determinants_nor_overwrite_complete_values
PC-009CURRENTEngineExecution maintainersMUST preserve the complete execution image and fresh durable-restore window on preparation refusal, including any valid candidate prefix.Ordinary returned refusal only; panic, allocation failure and host effects are excluded.Preparationtest every_refusal_preserves_fresh_and_advanced_stateful_images_and_store
PC-010CURRENTEngineExecution maintainersMUST prevalidate the whole complete submission before staging or evaluating it.Host trace collection and cadence are separate; there is no built-in simulation restart or input callback.Refusal matrixtest incomplete_observations_never_stage_prefixes_or_reuse_prior_values
PC-011CURRENTEngineExecution maintainersMUST continue current execution state across successive accepted frames without an implicit horizon restart.A fresh load, dirty resume or explicit compatible checkpoint restore has its own lifecycle semantics.Executiontest sampled_delay_matches_hand_recurrence_and_retained_frames_survive_lifecycle_changes
PC-012CURRENTProduct claimantExecution maintainersMUST distinguish per-frame refusal atomicity from a whole host-loop transaction: earlier accepted frames remain committed when a later candidate refuses.There is no whole-horizon rollback or engine-owned trace on error.Outcometest context_readiness_and_time_refusals_preserve_public_images_and_store_calls
PC-013CURRENTEngineExecution maintainersMUST refuse invalid first submissions without clearing the prior clock, reseeding words or staging a prefix.Execution consults no Store sample, so there is no first-frame Store failure after restart.Executiontest every_refusal_preserves_fresh_and_advanced_stateful_images_and_store
PC-014CURRENTEngineExecution maintainersMUST execute frames without Store reads, writes, write helpers or post-write failure behavior.Realtime and Store orchestration are deferred; a completed frame proves computation, not persistence or actuation. Load-time Store validation remains.Execution; Host deliverytest complete_corpus_frames_never_read_or_write_the_store
PC-015CURRENTEngineExecution maintainersMUST interpret halt as parameter-edit permission and dirty resume as block/run re-seeding, not equipment stop or live tuning.Halt alone does not prevent execution. Resume does not recompute schedule, store projection or authored model identity; no transactional resume guarantee. Pending edits refuse state capture/restore.Parameter lifecycle; State preconditionstest param_lifecycle_halt_set_resume_refolds; pending_parameter_edits_take_precedence_over_restore_readiness
PC-016CURRENTEngineFacade maintainersMUST preserve completed-stage diagnostics through contextual load errors: diagnostics exposes terminal diagnostics and all_diagnostics prepends available prior diagnostics.Empty terminal diagnostics does not mean success; severity is not list position. Message text is not a newly frozen schema.Diagnostic accessorstest warning_context_survives_each_later_store_failure
PC-017CURRENTProduct claimantFacade maintainersMUST describe AssertLevel as Warning-only with Default equal to Warning and retained warnings on every completed frame.No escalation, interlock or instance-identity guarantee. Sources remain producer-supplied and emission order is deterministic.Severity and collectortest default_severity_is_warning_not_an_unemitted_failure; boolean_assertions_repeat_warning_records_and_continue_bit_exactly
PC-018CURRENTProduct claimantCXF maintainersMUST distinguish partial successful export from complete exported-document identity and use content_id_complete to refuse warning-bearing exports before treating the tag as complete.Survivor-cone export is not source recovery or whole-model identity. FNV-1a-128 over emitted bytes is noncryptographic, not authentication; authored model identity can stay unchanged as export changes.Round trip; Complete tagtest warning_bearing_content_id_identifies_the_partial_document; content_id_tracks_exported_synthetic_document_while_model_id_stays_authored
PC-019CURRENTEngineExecution maintainersMUST validate compatible process-local checkpoint restore before committing values, words and clock, permitting rewind without evaluating or calling the store.Pending edits and invalid state refuse. Process-local rewind has no durable format or host authority.Checkpoint and prepare/commit; State contracttest checkpoint_refusal_is_bit_atomic; every_manifest_field_refuses_deterministically_without_mutating_engine_or_store
PC-020CURRENTEngineExecution maintainersMUST limit durable restore to a successfully loaded compatible target before a mutation boundary, validate before commit and restore continuation without evaluation or store calls.Startup-only differs from checkpoint rewind. Pending edits refuse first; snapshot bytes omit host epoch, backend history and equipment authority. Equal restored time is another HostTick transition.Durable restore and prepare/commit; State contracttest mutation_boundaries_close_the_durable_restore_window; capture_and_restore_call_no_store_method; snapshot_restores_next_pre_output_at_same_timestamp
PC-021CURRENTProduct claimantFacade maintainersMUST distinguish authored/source model, executable, exported-document, catalog, IO schema, build, execution profile, generation and state-revision identity roles.The manifest checks executable compatibility, not build/generation authentication. Diagnostic model id is not the compatibility key. Facade descriptors version bounded metadata separately; the authenticated host envelope is a precondition, not an OCE build token.Manifest and compatibility; Identity glossary; State authoritytest diagnostic_model_identity_is_not_an_execution_compatibility_key; content_id_tracks_exported_synthetic_document_while_model_id_stays_authored
PC-022CURRENTProduct claimantBlock semantics maintainersMUST NOT claim upstream equivalence for the local two-input TrueHoldWithReset behavior.Its u/clr test proves local names and clear behavior only. Upstream-equivalent existence and provenance remain unresolved, not proven absent; no catalog identity or behavior changes here.Local implementationtest true_hold_with_reset_names_match_its_behaviour
PC-023CURRENTProduct claimantBlock semantics maintainersMUST NOT extrapolate fixture/profile evidence into arbitrary G36 support, Modelica event equivalence, blanket numerical exactness or universal panic freedom.Pre is excluded from expected-green Modelica same-time differential claims; no concurrency, cancellation, deadlock or adapter-panic guarantee is added.Conformance boundary; Evidence contexttest nonconvergent_boolean_feedback_is_accepted_and_advances_per_call
PC-024HOST-OBLIGATIONHostHost integratorMUST qualify input quality, freshness, missing-data reaction and plausibility before or instead of execution.Complete typed values do not establish sensor validity, simultaneous sampling or actual host compliance.Host policytest store_samples_neither_supply_missing_determinants_nor_overwrite_complete_values
PC-025HOST-OBLIGATIONHostHost integratorMUST implement NO_EVAL by not executing the engine and separately own safe-state outputs, equipment interlocks and actuation.NO_EVAL is neither halt nor a fabricated zero frame. Engine tests do not qualify equipment protection or prove command delivery; a halted engine can still execute.Lifecycle boundary; Halttest param_lifecycle_halt_set_resume_refolds
PC-026HOST-OBLIGATIONHostHost integratorMUST own scheduling, model-time cadence, wall-clock mapping and external delivery-failure handling.No scheduler, deadline, cancellation or equipment-stop guarantee; equal-time resubmission advances again. Boundary tests do not prove host policy.Host timetest equal_time_feedback_advances_once_and_refusal_consumes_no_position
PC-027HOST-OBLIGATIONHostHost integratorMUST authenticate an envelope binding exact snapshot bytes to approved build/deployment qualification, freshness and generation before from_bytes, refusing incompatible host combinations, and own persistence, authorization, wall-clock mapping, external point/history state and restored actuator fencing.OCE carries no build token. Integrity and executable compatibility are not authenticity or permission to command. PointStore is not the engine-byte channel; boundary tests do not qualify a host.Host state duties; State authoritytest capture_and_restore_call_no_store_method
PC-028CURRENTFacade deliveryFacade maintainersMUST remove or quarantine deferred and panic-only supported surfaces with coordinated compatibility evidence.Selected facade names are removed; private quarantines and package boundaries are unchanged. Compiler controls and bounded source inspection are not downstream acceptance or universal panic freedom; exact-candidate consumer qualification remains separate.Current ruling; Migration and inventorytest retired_facade_symbols_are_absent; filtered_inventory_refuses_without_store_calls_or_engine_mutation
PC-029CURRENTFacade deliveryFacade maintainersMUST expose versioned facade catalog, diagnostics, IO, values, parameters, assertions and execution-profile contracts with compatibility tests.Additive typed metadata and immutable producer receipts; opaque subjects, Warning-only runtime, no generic value codec, build stamp, admission bounds, rollback or new snapshot/profile selector.Versioned contracts; Adoptiontest canonical_catalog_matches_packaged_bytes_and_repeats_exactly; unification_evidence_survives_structural_refusal_at_its_actual_producer; descriptors_cover_every_domain_with_explicit_shapes_and_semantic_limits
PC-030CURRENTAdmission deliveryCXF maintainersMUST reject serialized inputs above the effective per-engine cap before parsing, Store calls or engine mutation and replace model-bound state/caches only on successful load.Default and maximum are exactly 8 MiB. Configuration above the maximum refuses through a typed error. Receipt refusal stays Import; errors contain counts only. External persistence is not atomic; replacement coverage has the documented stage limitations.Admission; Replacementtest oversized_valid_document_refuses_without_parser_allocation_or_store_calls; oversize_receipts_allocate_only_the_error_box_and_are_deterministic; invalid_configuration_is_typed_and_never_silently_widens; successful_reload_replaces_model_bound_caches_but_not_host_policy
PC-031CURRENTFrame deliveryExecution maintainersMUST retain the normative complete generation-atomic typed input/output frame contract, including completeness, prevalidation, reload fencing, one HostTick transition, refusal preservation and immutable correlated outputs/diagnostics.Contract ratification and bounded runtime evidence do not qualify hosts, persistence or actuators. Historical gap evidence is superseded by frame refusal controls.Frame contract; Refusal matrix; Outcometest incomplete_observations_never_stage_prefixes_or_reuse_prior_values; test_report_is_an_independent_byte_golden
PC-032CURRENTFrame deliveryExecution maintainersMUST resolve and prevalidate complete typed frames, refusing unknown, duplicate, missing, stale-generation and unloaded submissions before mutation.Preparation only; no commit or output frame. Internal incarnation preflight is not host freshness or authorization. No public input aliases exist; injected alias coverage tests logical uniqueness only.Preparation; Contracttest every_refusal_preserves_fresh_and_advanced_stateful_images_and_store; reload_and_cross_engine_identity_refuse_even_identical_model_bytes; dirty_resume_invalidates_but_clean_resume_and_compatible_restore_retain_context; canonical_plan_owns_values_and_repeats_bit_exactly_under_entry_permutations; repeated_large_fixture_preparation_has_a_linear_capacity_and_allocation_census
PC-033CURRENTFrame deliveryExecution maintainersMUST commit one HostTick transition and one immutable output/diagnostic frame per accepted frame, preserving time, state, connector values, output generation and replay identity on ordinary refusal.Native in-place engine transition only; sequence is Engine-lifetime correlation, not serialized replay/deployment identity. No persistence or actuator-delivery atomicity, panic recovery or cancellation guarantee.Execution; Contracttest arithmetic_commits_complete_values_without_store_and_retains_independent_results; sampled_delay_matches_hand_recurrence_and_retained_frames_survive_lifecycle_changes; context_readiness_and_time_refusals_preserve_public_images_and_store_calls; each_preflight_refusal_preserves_every_execution_bit_and_sequence; complete_frames_match_the_independent_hosttick_reference_and_repeat_bit_exactly; allocation_cost_is_only_prepared_targets_boundary_results_and_emitted_warnings
PC-034CURRENTFrame deliveryExecution maintainersMUST retain one shared infallible evaluation core, entered exactly once after complete-frame preflight and staging.Host loops are not a second evaluator or whole-horizon transaction. Parity is limited to equivalent complete-frame schedules.Shared core; Migration; Acceptancetest accepted_frames_enter_the_shared_core_once_and_refusals_never_enter
PC-035CURRENTFrame deliveryExecution maintainersMUST expose only preparation followed by consuming complete-frame execution, removing legacy execution profiles, raw output access and their types without aliases or compatibility bridges.get_output and watch are latest-state non-receipt inspections. No Store write helper, realtime orchestration, durable receipt or downstream qualification is supplied.Frame-only contraction; Compiler controls; Facadetest retired_facade_symbols_are_absent; incomplete_reference_inputs_refuse_in_both_cadences; store_samples_neither_supply_missing_determinants_nor_overwrite_complete_values
PC-036CURRENTIdentity deliveryFacade maintainersMUST expose distinct catalog and complete-export identity types plus a closed, versioned compact descriptor of public catalog, IO/value/parameter revisions, fixed HostTick profile and OCE package version, with optional complete export identity.Public-fact equality is not executable identity, unique-build qualification, generation, state-wire compatibility or authentication. No private execution/state identity is exposed.Typed identities; Descriptor; Contracttest canonical_public_facts_match_the_hand_assembled_golden_and_repeat; partial_exports_refuse_instead_of_becoming_absent_or_complete_content; every_field_changes_canonical_bytes_and_has_an_exact_symmetric_refusal; descriptor_capture_preserves_state_and_survives_report_and_engine_lifetimes
PC-037CURRENTEvidence deliveryBlock semantics maintainersMUST retain and enforce exact comparison for the pinned 21-signal corpus on Linux x86_64/aarch64 in debug/release, with two native runs per cell and zero mismatches.Pinned rustc 1.97.1/libm 0.2.16 only; macOS and other targets remain unqualified at the existing 1e-12 aligned band. The 35-file selected source guard is not a compiled dependency closure. No mathematical correctness, arbitrary-input, whole-executable or Sim qualification follows.Accepted native receipt; Testing standardtest retained_native_linux_evidence_is_complete_exact_and_source_bound; every_corpus_sample_uses_exact_facade_comparison_and_rejects_mutations; qualified_linux_signals_are_exact_and_other_targets_keep_the_aligned_band
PC-038CURRENTState deliveryExecution maintainersMUST support same-loaded-executable durable continuation and explicit portability domains, refusing incompatible execution ABI, referenced catalog, executable, IO, state and target facts before mutation with typed state errors.Host-envelope build/deployment approval precedes decoding; OCE refusal does not authenticate builds or freshness or authorize actuation. Format/ABI 2 refuses format 1 without migration. Portable policy and the finite Linux corpus do not imply arbitrary-input, full-closure or macOS qualification.State contract; Prepare and commit; Host envelopetest every_manifest_field_refuses_deterministically_without_mutating_engine_or_store; changed_input_acceptance_domain_refuses_before_mutation; parsed_snapshot_continuation_preserves_signed_zero_and_retained_results; every_target_bound_class_round_trips_policy_and_refuses_each_foreign_target_component; every_truncation_boundary_is_a_typed_refusal
PC-039CURRENTReplay deliveryExecution maintainersMUST provide one bounded canonical accepted-frame replay record with exact public facts, placement, time, complete inputs/outputs and ordered Warning diagnostics, independently decodable with typed deterministic refusal and exact comparison.Hosts authenticate order, build/executable/prior-state qualification and optional snapshot sidecars; no sequence container, build token, tolerance, second evaluator or rollback on comparison mismatch. Hosted cross-architecture replay-byte comparison remains pending.Replay contract; Facadetest canonical_receipt_matches_independent_bytes_and_repeats_without_reexecution; host_stream_continues_from_separate_snapshot_with_equal_times_and_exact_end_state; host_eligibility_refusals_preserve_fresh_and_advanced_state_store_and_restore_window; header_version_lengths_integrity_and_every_truncation_refuse_repeatedly; inclusive_record_cap_accepts_a_full_string_and_one_past_allocates_nothing; small_wire_entries_cannot_amplify_decode_workspace_past_the_charged_budget; independently_authored_full_domain_bytes_are_the_capture_encoding_too
PC-040CURRENTRelease deliveryRelease maintainersMUST retain a fail-closed directed artifact matrix supporting only current/current and refusing cross-candidate use under host policy before decoding or execution, with exact historical-source identity and current implementation evidence.No N-1 support, migration, release, publication or downstream qualification follows. Producer absence is not decoder refusal; host cold requalification or prior-qualified-binary/own-state rollback stays external.Release compatibility; Publication authoritytest test_retained_matrix_has_the_independent_closed_direction_table; retained_release_matrix_and_hostile_controls_are_enforced; cross_candidate_envelopes_refuse_before_decode_or_engine_mutation

Fulfilled facade contraction

Revision 2 implements the selected M01-PR02 removals and Warning default, with compiler absence controls, baseline reintroduction controls, typed inventory refusal and CXF assertion goldens. This is implementation evidence for the bounded surface outcome, not a merged-release or downstream acceptance claim. The migration record retains compatibility limits.

Facade schemas

Revision 3 implements M01-PR03 as additive facade catalog DTOs, canonical metadata identity, packaged shape descriptors and immutable producer-stage load/export receipts. PC-029 acceptance is bounded by the linked implementation tests and compatibility controls. Existing legacy ordering, Warning-only runtime behavior and snapshot bytes remain; complete frame contracts and build/generation qualification remain future work. Hosted and downstream evidence remain separately qualified in the delivery record.

Bounded admission and replacement

Revision 4 implements the bounded M01-PR04 outcome with an inclusive 8 MiB default/maximum, per-engine tightening and typed pre-parser refusal shared by both load entry points. The failure matrix compares the complete in-memory run image for fresh, advanced and halted/dirty runs. Instantiation/projection use the existing private build seam; flatten and semantics have no current returned refusal path. Store residual effects are tested separately, not hidden by the image comparison. The host owns compensation and external-handle validity. Successful replacement refreshes model-bound caches, while host epoch/admission policy persist. Bounded parser allocation observation is supporting evidence, not a general hostile-input safety or peak-memory bound.

Complete frame contract

Revision 5 fulfills M02-PR01 as the normative frame/output contract and passing contract-to-current-code gap evidence. The owner approved PC-031 promotion on that contract-only implementation acceptance; PC-032 through PC-035 remain FUTURE. The loaded-executable and IO fence is engine-local, distinct from host deployment fencing. No public frame representation, runtime path, diagnostic severity, catalog, state/snapshot bytes, dependency or package changes here.

Complete frame prevalidation

Revision 6 implements M02-PR02: owned input definitions and opaque, nonserializable prepared frames, with canonical first-cause typed refusals, exact domain checks, Store noninterference and engine-local load/rebuild fencing. PC-032 evidence includes stateful before/after images, fan-out, same-byte reload and clean/dirty-resume controls, exact goldens and a repeated allocation census. The internal compatibility seam is exercised directly for future commit reuse; no otherwise-unused public validator or reusable schema/cache API is added. Current input aliases do not exist, and enum/String detached-probe coverage does not broaden the executable CXF profile. PC-033 through PC-035 remain FUTURE. Legacy sparse, hold-last and last-wins behavior, snapshots, profile, diagnostics, dependencies and downstream pins are unchanged. See the migration guidance.

Atomic transition and frame

Revision 7 implements M02-PR03 as consuming execute_frame and owned immutable CompletedFrame. All ordinary refusal conditions precede mutation; one successful call stages every target, evaluates once, refreshes latest outputs and retains lexical boundary outputs plus Warning diagnostics. The lifetime-local accepted sequence cannot rewind on restore/reload/resume or wrap on exhaustion; legacy paths never consume it. The private retained context fence exposes no durable identity. No RunState shadow/rollback or Store path is reused. PC-033 is CURRENT on this bounded evidence; PC-034/035 remained FUTURE at revision 7. The adoption guide and cost observations retain the compatibility and measurement limits. Snapshot/profile/catalog bytes and downstream pins are unchanged.

Shared convenience core

Revision 8 implemented M02-PR04 as private evaluation-core reuse, not complete-frame preparation reuse or universal mode equality. Its weaker convenience profiles and post-write reconciliation account were historical migration context. Revision 9 removes those routes rather than introducing a generation-tagged write failure or a compatibility bridge. The same private infallible evaluator remains behind consuming frame execution, with instrumented emit/update and refusal controls.

Frame-only facade

Revision 9 implements M02-PR05 as a greenfield contraction. Preparation followed by consuming execution is the only public state-advancing execution path. Legacy execution methods, epoch configuration, raw output access, and associated source/spec/report/trace types are removed. All first-party consumers submit complete frames. Uniform and event-aligned conformance modes retain their cadence and comparison semantics, but missing determinants refuse before comparison.

PC-035 is CURRENT for this bounded outcome. Compiler controls run against all supported feature selections; absence sentinels prevent re-blessing retired surfaces. Store noninterference, refusal preservation, hand-derived frame goldens and existing independent sequence references provide behavioral evidence. get_output and watch are latest-state non-receipt inspections. Realtime and Store orchestration remain deferred; execute_frame performs no write-back. Assertion and execution descriptor revisions advance to 2 to remove stale profile claims; HostTick v1, catalog identity, state codecs, accepted-frame sequence and admission/load compensation semantics remain unchanged. No latency improvement, hosted cross-architecture result, host qualification or publication follows.

Typed identities

Revision 10 implements the owner-bounded M03-PR01 public host contract only. Catalog and complete export tags have distinct facade-owned types; an immutable revision-1 descriptor captures public catalog content/schema, IO/value/parameter revisions, fixed HostTick compatibility, the exact OCE Cargo package version and optional complete export content. Canonical bytes and typed first-mismatch outcomes have hand-assembled goldens, repeated captures, per-field mutation controls, compile-time category refusals and unchanged FNV oracle evidence. Warning-bearing export refuses through the existing completeness check; absence is explicit and never a wildcard.

PC-036 is CURRENT only for those facts. Package version is not a unique source/build/compiler/target identity, and IO revision is not a model’s executable input schema. Executable fingerprints, generation and state-wire identities remain private pending the separately authorized state/replay work. No manifest/codec bytes, source normalization, signing/PKI, host qualification or cross-release compatibility follows. Existing APIs/bytes, package closure and state/restore behavior remain intact. The receipt mapping preserves current consumers; actual downstream pins and qualification are unchanged.

Strict-bit evidence

The accepted native receipt from run 37382761120 supports PC-037’s observed-output claim with a reviewed 35-file selected source boundary, only for the 21 inventoried Linux signal cases: four architecture/codegen cells, two native runs per cell, 161 samples per run and zero mismatches. The checked-in qualified captures reconstruct the accepted matrix digest and pass ordinary retained validation. Their recorded synthetic merge checkout is preserved; exactly the selected paths/digests and current oracle/CXF inventory are checked without requiring a later delivery HEAD to equal that capture SHA. The selected boundary covers checker/admission/comparison/workflow/direct formula/harness and supporting sources, not the full compiled transitive facade closure. Exact-head hosted cells (x86_64 native, aarch64 QEMU-emulated) rerun the actual oce_api::Engine path per PR and catch changes under the pinned corpus’s comparison rules. An unbound transitive source change preserving all pinned outputs does not invalidate the historical raw result. Source digests alone do not prove current whole execution semantics. The original macOS observation is not platform qualification. macOS-arm64 stays conservative until M06-PR02, and all other unqualified targets retain the existing aligned band. Whole-executable exactness inherits the least-qualified contributing path, target and input domain; these finite cases alone do not establish it. Mathematical correctness, arbitrary-input and downstream policy claims remain outside this evidence. Sim adoption remains M05-PR07. The original 17-source Linux receipt from run 35492290613 remains unchanged historical evidence and cannot substitute for the current qualification. The evidence-collection run is not a final hosted-gate result.

Same-loaded-executable state

Revision 13 implements M03-PR03 under the owner’s explicit host-enforced-envelope decision. The state contract defines capture, parse, refusal precedence, startup window, bit-exact continuation and encoded portability separately from build qualification. Read-only public portability inspection adds no build token or public executable fingerprint. Format/ABI revision 2 adds effective frame input domains and connector computation units/quantities; revision-1 bytes refuse rather than silently receiving stronger compatibility claims. Capture validates its own encoded bytes through the bounded decoder. Field mutations, canonical goldens, partial-byte refusal, Store noninterference, all 15 target-bound classes and hand-derived equal-time continuation supply bounded engine evidence, not host-envelope or cross-release qualification. Hosted matrices remain separate exact-candidate evidence; no local run fabricates them.

Canonical replay

Revision 14 implements the owner-bounded M03-PR04 v1 as one canonical accepted-frame record. The facade receipt retains accepted inputs and captured placement; record creation never executes again. Independent hand-authored payload bytes, checksum construction, mutation/refusal controls, full native-value domain coverage, snapshot-sidecar continuation and an allocation-counted streamed host prototype supply bounded PC-039 evidence. Exact bits only; no conformance tolerance is reused. Sequence ordering and same-build/deployment/executable qualification stay in the authenticated host envelope. Snapshots are separate sidecars; the private execution fingerprint is not public authority. Hosted replay artifact comparison is pending: changing the existing workflow would cross the accepted strict-bit source guard. Local vectors are retained without reblessing that separate evidence.

Release compatibility

Revision 15 implements the owner-selected fail-closed policy: current/current is the only supported candidate pairing. The retained matrix distinguishes acceptance within delegated contracts, typed mutation refusal, host-envelope refusal, producer absence and unsupported/unqualified directions. The historical annotated v0.1.0 git pin builds and passes its bounded facade suite under its own toolchain, but is not a published release or supported N-1. Its absent snapshot/replay producers are not decoder refusals. Both candidates still report package 0.1.0, demonstrating why that string is not build authority. Exact baseline implementation bytes avoid self-referential delivery hashes.

PC-040 is CURRENT only for the retained policy/evidence, not general release-to-release support. The host fixture refuses cross-candidate envelopes before decoding, preserves engine and Store state, and contrasts same-current continuation with fresh cold requalification. External rollback dispatch is bounded fixture evidence, not an executed prior qualified binary or field qualification. The migration/refusal guide and RC template retain those limits. All listed outcomes now have bounded current evidence; broader release/platform qualification and any new support pairing still require separate owner authorization and acceptance.

Evidence context

Primary-source observations, accessed 2026-09-05: the official OBC specification calls itself a working document whose design may change. The CXF introduction describes configured logic and elementary, composite and extension blocks translated from Modelica; it does not say all CXF is OCE’s executable subset. Modelica 3.7 pre defines same-time iteration to a fixed point. The selected Buildings Pre source also describes event iteration until u equals pre(u). That Buildings SHA selects source bytes, not a pin for the mutable OBC website. HostTick explicitly differs as delegated above. TrueHoldWithReset provenance remains unresolved; there is no assertion of upstream absence.

Downstream inspection is supporting inventory, not qualification against the grounding SHA. Studio was observed compiling/loading/exporting at an older OCE pin, without ticking or a store; its serialized cap and Real/Integer/Boolean adapter boundary are not OCE promises. Library’s older-pin verifier used IO/tick/export with its own tolerance; sibling-path verification is not qualification of this engine revision. Sim had no active OCE dependency and only a bridge scaffold; cxf-json had no OCE dependency. No downstream pin, compatibility claim or adapter policy changes here.

Library, Studio, Edge and Sim retain their downstream roles. Runtime is an additive future M05-PR03 consumer/host qualification candidate only; BOPTEST is Runtime host evidence, not OCE equivalence. This adds no second evaluator, snapshot or replay stack, and no runtime, database, network, driver, quality/staleness, lease, command or fallback policy to OCE.

Broad conformance qualification remains later work (M05), and platform/release qualification remains later work (M06). No general flattening, direct Modelica loader, Python binding, FMI runtime, database, scheduler, driver, universal panic freedom or equipment safety certification is added.

Traceability check boundary

The standard-library checker checks this prescribed table, numbering, closed statuses, actor/owner presence, nonempty limitations, test declarations or future assignments, local regular clone-visible targets and anchors, and required integration pointers. It rejects obligation keywords outside Requirement cells. It is not a universal Markdown parser, Rust compiler, standards semantic parser, proof of test relevance, human approval or host compliance. Named-test lexical existence is only a locator; running the existing tests and auditing relevance remain separate requirements of the contribution process.

Only the explicitly enumerated pending contract/checker and facade-transition evidence paths can be non-ignored untracked targets in a pre-commit checkout; arbitrary untracked targets still refuse. Existing evidence targets remain tracked. A clean clone after commit supplies the publication-facing link check; pre-commit GitHub source links still name the baseline revision and cannot demonstrate remote availability of new files. There is no auto-bless mode or duplicate requirement ledger. The hostile tests retain manually written expected output and deterministic repetitions; the runnable gate remains the gate script.

Change record

  • Revision 1, 2026-09-05: initial bounded executable-CXF/HostTick product contract and traceability map, grounded at f8918501586a1b99ed59a2c66ce79abcc58d0135. No runtime, signatures, catalog identities, state bytes or package selection changed. Future acceptance is not current implementation; normative revision approval and implementation acceptance remain owner/review decisions.
  • Revision 2, 2026-09-05: owner approved the selected deferred-symbol removals and intentional Warning default, then explicitly authorized continuation with the corrected baseline fact that filtered inventory retains OcError::Load. Facade-owned implementation evidence promotes PC-028; PC-002, PC-003 and PC-017 now cite working/absent behavior rather than dead loaders. All IDs, package classifications, execution failure limits, state bytes and catalog identities remain. The migration account distinguishes source breaks from downstream qualification. Pending-path enumeration adds only the new migration and assertion-test evidence needed before commit; no status, assignment, clone-visibility, test-locator or obligation rule is relaxed. Independent delivery review and exact-candidate downstream qualification remain separate acceptance steps.
  • Revision 3, 2026-09-05: owner approved additive catalog/schema contracts and immutable producer-stage receipts, preserving legacy signatures, order, runtime and state compatibility. PC-029 now maps to bounded facade implementation and compatibility tests; PC-021 distinguishes those descriptors from still-future build/generation qualification. Seven domain descriptions include parameter metadata limits and Warning-only collection from all block classes. Real downstream pins, host mapping/truncation policy and future admission/rollback work are unchanged.
  • Revision 4, 2026-09-19: owner fixed the 8 MiB maximum/default, stricter-only per-engine configuration, typed count-only refusal at Import, and the in-memory versus Store compensation boundary. PC-004, PC-005 and PC-030 now map to bounded admission and complete owned-image failure/success tests. The additive facade baseline gains only admission items; the Store baseline, catalog/descriptor identity, state bytes, HostTick and dependencies remain unchanged. Existing raw-load callers intentionally lose above-cap acceptance; widening is unsupported. Pending-path enumeration adds the new admission source/tests only. No parser or transaction oracle is claimed; boundary expectations are hand-derived and existing determinism goldens remain. Exact-candidate review, hosted target checks and host qualification are separate steps.
  • Revision 5, 2026-09-19: owner approved the normative complete-frame contract and PC-031 CURRENT promotion as contract-only implementation acceptance. Execution-maintainer requirements now have clone-visible determinant, refusal, identity, immutable-output and migration detail, plus passing legacy gap evidence. PC-032 through PC-035 remain future implementation/migration; no accepted M01 semantics change. Hand-derived bit goldens and snapshot comparisons expose retained staging, not future atomicity. Pending-path enumeration adds only the new contract and gap-test evidence; checker status/visibility rules stay intact. Runtime is additive future M05-PR03 host evidence, not a qualified consumer or OCE/BOPTEST equivalence claim. API, profile, state, snapshot, catalog, dependencies, publication and downstream pins are unchanged; review and hosted checks remain separate.
  • Revision 6, 2026-09-19: owner approved the opaque prepared-frame plus minimal owned-definition shape, engine-local successful-load/dirty-rebuild invalidation with clean-resume retention, and deterministic first typed cause. Bounded implementation and behavioral evidence promote PC-032; the additive public baseline and its classification ledger are updated together. Preparation changes no run state, Store samples, wire formats or legacy acceptance. Boundary and target bounds are intersected without Integer-to-Real coercion. Input alias and enum/String limitations follow live source rather than a new identity vocabulary or executable profile. Pending-path enumeration adds only preparation source/test evidence; checker rules are unchanged. No commit, output-frame, release, publication, downstream adoption, host qualification or equipment-safety claim follows. PC-033 through PC-035 and hosted architecture evidence remain separate work.
  • Revision 7, 2026-09-19: owner approved single-use prepared submissions, owned lexical executable boundary results, a never-reset Engine-lifetime accepted sequence, private incarnation binding and structural allocation budget without shadow state. Implementation and failure-preservation, hand-golden, independent G36 HostTick, warning, retention and allocation evidence promote PC-033 only. PC-034/035 remain future; their stale source locators are corrected. The additive facade baseline/ledger/guards and migration guidance change together. Pending-path enumeration admits only the new bounded execution evidence; checker rules are not relaxed. Legacy execution, Store baseline, state/checkpoint/wire formats, catalog/profile identities, dependencies, features, MSRV and downstream pins are unchanged. Local timing observations are not a speed guarantee; hosted target checks and downstream qualification remain separate. No stable release, publication, Store transaction or equipment-safety claim is made.
  • Revision 8, 2026-09-19: owner approved evaluation-core reuse only, unchanged legacy failure and lifecycle semantics, caller-selected existing warning sinks, and compatible post-write Err(Store) reconciliation without a generation receipt. Bounded parity, exactly-once controls and retained failure/projection evidence promote PC-034; PC-035 remains future. The pending-path list adds only the private shared-transition suite; checker rules remain unchanged. Public signatures, error variants, native sequence, state/checkpoint/wire formats, catalog/profile identities, dependencies, features, MSRV and downstream pins remain unchanged. Hosted architecture evidence, actual host qualification, stable release, publication, Store atomicity and equipment safety remain unclaimed.
  • Revision 9, 2026-09-19: owner authorized frame-only greenfield delivery and coordinated first-party migration, not aliases or guarantee tags. Implementation and compiler absence, Store-free, refusal, cadence, retained-warning, hand-golden and independent-reference evidence promote PC-035. The sparse/restart/post-write requirements are superseded in place without deleting their IDs. Assertion/execution descriptor revisions are 2; runtime HostTick, frame domains, catalog, state bytes, dependencies and features remain unchanged. Public baseline and classification ledger record the intended contraction. Checker sentinels reject stale active guidance and substituted contraction evidence without claiming semantic proof. Sibling consumers still need migration; downstream qualification, durable identity/replay, publication and hosted gates are not claimed.
  • Revision 10, 2026-09-20: owner limited identity delivery to the closed public host compatibility contract. Bounded descriptor/tag implementation and golden, mutation, completeness, lifecycle and compile-time evidence promote PC-036 only in that narrowed scope. Public baseline/classification and migration documentation change together; package version is explicitly not unique build identity. Private executable/generation/state-wire work stays with later state/replay outcomes. The traceability checker advances its accepted revision and enumerates only the new evidence paths; prior frame-contraction sentinels remain. Catalog/export algorithms, old descriptor bytes, snapshot/restore, frame/admission semantics, dependencies, features and downstream pins are unchanged. Independent delivery review, hosted architecture checks and host qualification remain separate; no stable release, signing authority, replay or state-wire support is claimed.
  • Revision 11, 2026-09-20: owner authorized the bounded 21-signal Linux strict-bit matrix and raw provenance delivery. The checked-in observation was local macOS only; native Linux acceptance was still pending, so PC-037 was not promoted in that revision. Existing aligned bands remained the conservative platform policy; Linux candidates failed closed on disagreement. Runtime formulas, public APIs, state/restore, dependencies, toolchain and downstream policies are unchanged. The checker revision and pending documentation path advance mechanically without weakening earlier sentinels.
  • Revision 12, 2026-09-20: accepted native run 35492290613 supplies the retained four-cell, two-run, 21-signal zero-mismatch Linux corpus result. PC-037 is CURRENT only in that bounded scope. Raw bits and synthetic merge provenance remain unchanged and digest-checked; no final-HEAD equality or source-digest exception is introduced. Existing Linux exact wiring and all Tier-A goldens remain unchanged. macOS/other targets retain the 1e-12 aligned band, with macOS-arm64 unqualified until M06-PR02. No arbitrary-input, mathematical, whole-executable or Sim qualification, runtime/API/state change, dependency change or publication follows. Admission with the reviewed selected source boundary uses run 37382761120, preserving its synthetic merge identity and eight capture files verbatim, with exactly all 35 enumerated paths/digests verified and no bound-source changes. This boundary is not the full compiled dependency closure; PC-037 remains an observed-output claim over the pinned corpus, targets and toolchain. The earlier receipt remains historical; the ordinary current-qualification test validates the newly admitted receipt without a final-HEAD identity requirement or source-map bypass.
  • Revision 13, 2026-09-20: owner explicitly chose host-envelope enforcement of compiled-build and deployment qualification before snapshot decoding; no OCE build token is introduced. Bounded implementation/evidence promotes PC-038 for same-loaded-executable continuation only. Format and execution ABI advance to 2 for missing IO compatibility determinants; HostTick v1 and the 15-class placement policy remain unchanged. Public portability inspection, baseline/ledger and authority projection change together. The traceability checker advances its accepted revision and pending evidence paths without relaxing earlier sentinels. Revision-1 snapshots intentionally refuse; rollback/cold-start policy is host-owned. No replay design, cross-release migration, dependencies, CI policy, downstream qualification, host security service or publication is added.
  • Revision 14, 2026-09-20: owner selected a single canonical exact per-accepted-frame replay record, host-owned ordered envelopes and optional snapshot sidecars. Bounded decoding, independent byte construction, hostile mutations, exact stateful continuation and streamed allocation evidence promote PC-039 only in that scope. New facade items, public baseline/classification and the traceability revision/pending evidence paths advance together; existing contraction sentinels remain. Receipt capture now retains accepted inputs; record-capture refusal does not undo execution. No Engine replay method, second evaluator, sequence container, tolerance, embedded snapshot, build token, dependencies or state/profile revision changes. Cross-architecture replay-byte artifacts and real downstream qualification remain pending; the existing strict-bit evidence is not rewritten.
  • Revision 15, 2026-09-20: owner froze fail-closed, no-N-1 candidate compatibility. Retained directed evidence, source/history identities, hostile checker controls and isolated host fallback fixtures promote PC-040 only to that policy. The historical source build is reproducible but unqualified; absent producers are not labeled decoder refusals. Current runtime/API, package version, formats, dependencies, workflows, strict-bit guard and downstream pins remain unchanged. No publication, migration, cross-build restore, M06 ratification or downstream equipment qualification follows.

Complete generation-atomic input and output frames

Status and authority

This is the normative detail of PC-031 in the product contract. Preparation (PC-032), complete-frame execution (PC-033), shared evaluation core (PC-034), and frame-only facade contraction (PC-035) are implemented. The APIs below supply no profile selector or stable-product guarantee. The separate replay record defines the canonical per-accepted-frame encoding. Execution maintainers own these semantics; host policy remains with the host integrator.

The requirements below describe the sole public execution path. Legacy execution methods have been removed, not reinterpreted or aliased. “Atomic” means an engine-owned transition under the ordinary returned-refusal boundary below, not a distributed, persistent or actuator transaction. The fixed HostTick v1 profile remains unchanged.

Determinants and executable boundary

A transition is determined by the loaded executable (including effective parameters, schedule and value domains), compatible entry execution state, the fixed execution profile, model time, and the complete input values. Completeness closes the external-input determinant set; it does not make a stateful transition independent of its prior state or imply unrestricted cross-build determinism.

  • Every executable boundary input MUST be supplied exactly once unless the executable schema explicitly declares an engine-level default or optionality with a deterministic omission meaning. An absent declaration means required. An optional input, when supplied, is still subject to uniqueness and type validation. An executable with no boundary inputs admits an empty value set only after the other acceptance checks pass.
  • Completeness is over canonical executable input identities, not every row marked input in today’s point inventory, and not the union of all internal connector slots. One boundary identity may fan out to several consumers; the host supplies it once and the engine resolves all its targets. Internal driven connectors and read-only output aliases are not extra host determinants.
  • Values MUST match the executable schema’s types and declared domains, including enum identity and legal enum values where applicable, without lossy coercion through a Store carrier. Point metadata is not that schema: current IoInventory includes internal points, projects enums to Int, and omits strings. It cannot by itself establish the required executable input set.
  • Type seeds, previously staged values, held Store samples, absent quality metadata and host substitutions MUST NOT become implicit defaults. Engine-level optionality is executable schema semantics, not permission for OCE to invent sensor quality, freshness or fallback policy. This contract adds no blanket finite-Real or plausibility rule for signal values; finite time is a separate requirement.
  • Native preparation and transition MUST use only the supplied values and engine execution context, with no Store reads, writes or host callbacks that add hidden input determinants. A host or convenience adapter can obtain observations before submission; it owns their coherence and quality. “Complete” does not prove simultaneous sampling or physical validity.

Compatibility and generation roles

These are semantic roles, not proposed public field or type names:

RoleMeaning and limit
Loaded-executable contextThe engine-local successful load incarnation and the executable it installed. Successful reload invalidates prior prepared frames and resolved references, even when reloading identical bytes or reusing the same point names.
Executable IO compatibilityCanonical boundary identities, direction, types/domains, fan-out and explicit omission semantics used to interpret inputs and outputs. Equality of authored model names or point counts is insufficient.
Accepted-transition correlationAn unambiguous association among an accepted input, its one transition, and its completed output/diagnostics within the compatible run. Equal timestamps identify different transitions; refused submissions consume no accepted-transition position.
Replay compatibility and positionThe canonical per-frame record carries exact public facts and placement; the host authenticates executable/build/prior-state context and ordered sequence position. Ephemeral load tokens and Engine-lifetime sequence are not serialized.

A frame or resolved reference MUST be checked against the current loaded-executable and IO context before mutation; a stale context refuses rather than silently rebinding names. Preparation alone does not grant permission to commit after reload or after the time guard has advanced beyond the submitted time. Ordinary failed load retains the prior engine-local context under the existing in-memory replacement guarantee; external Store effects still have their separate compensation boundary. Compatibility-changing reconfiguration cannot silently reuse old preparation.

This fence is not a deployment generation, lease, authentication token, sensor freshness test, command authorization or restored actuator ownership. A host deployment identifier cannot replace the engine check, and a passing engine check cannot replace host admission. Authored/source model, exported-document and catalog identities retain their distinct roles. Current snapshot executable compatibility is not proof that a reference belongs to the current successful load incarnation.

Preparation supplies the concrete reference representation and typed refusal surface; native execution supplies accepted-result correlation. M03 owns typed identity/compatibility layers and canonical state/replay representation, including continuation/rewind correlation. Future representation choices cannot weaken these reload and correlation semantics. No snapshot fields, catalog identities or state revisions change here, and hosts are not asked to parse private snapshot bytes or build another replay format to fill this gap.

Prevalidation and refusal matrix

All ordinary refusal conditions MUST be validated before any execution/replay mutation. The whole candidate is resolved and checked before any input prefix is staged. There is no evaluation to discover an ordinary input error. Typed error names and deterministic precedence for preparation are specified below; execution retains this prevalidation boundary.

ConditionRequired outcome before mutation
No successfully loaded executableRefuse; an empty engine is not an executable with zero inputs.
Pending parameter editsRefuse until resume; halted alone does not prohibit execution.
Unknown or non-input identityRefuse; do not ignore extra values or treat output aliases/internal driven points as boundary inputs.
Duplicate boundary inputRefuse even if both values are bit-identical; do not use first-wins or last-wins. Fan-out is not a duplicate.
Missing required inputRefuse; no hold-last, zero/false seed or Store fill-in. Apply only explicit executable omission semantics.
Wrong type or declared domainRefuse the complete candidate, including any otherwise valid prefix.
NaN or either infinite model timeRefuse.
Finite time below the preceding accepted model timeRefuse.
Time outside loaded-block representabilityRefuse under the existing model-time limits, not by executing first.
Stale loaded-executable or IO compatibility contextRefuse frames and references from a superseded successful load; no implicit rebinding.
Accepted-frame sequence exhaustedRefuse before staging; never wrap or reset the sequence.
All checks pass, including equal finite timeAccept exactly one HostTick v1 transition.

For every ordinary refused frame, the entire observable engine execution/replay image MUST remain unchanged: model time and monotonic guard; state words; connector image (including staged inputs); visible outputs and output generation; completed-frame diagnostics; replay identity and accepted-transition position. Refusal also preserves existing mutation/readiness boundaries, rather than closing a fresh durable-restore window by staging a prefix. The refusal report describes the attempt; it does not replace the last completed outcome or masquerade as a completed diagnostic frame.

This guarantee excludes panic, process death, allocation failure, cancellation and concurrency outside current guarantees. It is not persistence, Store rollback, delivery acknowledgment or an actuator guarantee. Native Store noninterference does not retroactively undo earlier host/adapter operations. No scheduler, deadline guarantee or universal panic freedom is introduced.

Accepted transition and immutable outcome

One successful complete frame MUST perform exactly one HostTick v1 transition: stage the validated values, emit the frozen schedule once from entry state/current inputs, then update stateful blocks once and expose the completed boundary outputs. Model time is finite nondecreasing seconds. An equal finite timestamp is valid and advances again; it is neither a read nor an idempotent retry. There is no hidden event iteration, convergence test or repeated evaluation to reach a fixed point.

The accepted output frame MUST be immutable and bound to that accepted input/transition and its loaded-executable/IO compatibility context. It contains all completed executable boundary outputs with their identities and types, plus the execution diagnostics belonging to that transition. Later execution or reload cannot change a previously retained outcome. It is not merely an alias to a mutable latest-output view, a subset of convenient point rows, or a Store write receipt.

Output and diagnostic ordering and compatibility/sequence semantics MUST support deterministic host correlation, including two successes at the same time and an intervening refused attempt. Runtime assertion diagnostics follow the existing Warning-only law: warnings do not reject or roll back a transition, escalate to an Error level, or implement an interlock. Collection covers execution diagnostics, not load/export receipt history, host quality assessments or persistence/write errors. Diagnostic source semantics are preserved; no new instance-identity promise is inferred. Wall-clock latency measurements are not deterministic replay identity.

Producing or retaining an output frame means computation completed, not that an adapter persisted it or equipment received it. Execution performs no Store write and has no post-write error path. External delivery failure remains the host’s responsibility and does not relabel a completed frame. Equal-time resubmission is another transition, not a retry of external delivery.

Legacy paths and migration

Revision 9 removes tick/tick_with, set_input, simulate, step_realtime, realtime epoch configuration, Outputs/Engine::outputs, and the simulation/source/trace/report types. There are no aliases, deprecation bridges or profile tags. Sibling consumers migrate to complete frames.

Collect every required observation, call prepare_frame, then consume the plan with execute_frame. Missing and duplicate inputs refuse; there is no sparse, last-wins, hold-last or Store-backed fallback. A host simulation loop supplies each complete frame and owns trace capture. Repeated loops continue the current engine; use a fresh load or explicit compatible checkpoint restore when a restart or rewind is intended. A loop is not a whole-horizon transaction.

get_output and watch are explicitly latest-state, non-receipt inspection. They can read internal points, and load/restore/resume may replace their state without a frame. Only CompletedFrame retains committed boundary results and diagnostics. There is no engine Store-write helper.

The private transition_host_tick remains the sole infallible evaluation/refresh core after frame preflight. The instrumented core test detects bypass, double emit/update and entry on refusal. The conformance driver uses complete frames in both uniform and event-aligned modes; equivalent complete schedules retain bit-exact traces. This does not claim Modelica event iteration, whole-horizon rollback, durability or host qualification.

Host and downstream boundaries

Quality, freshness, plausibility, missing-data reaction, NO_EVAL, safe-state selection, scheduling, wall-clock mapping, persistence, authentication/authorization, deployment fencing, equipment interlocks and actuation remain host responsibilities. NO_EVAL means not executing, not submitting a fabricated zero frame or calling halt as an equipment stop.

Library keeps artifact verification; Studio keeps authoring/translation and simulation integration; Edge keeps deployment, observation admission, NO_EVAL and command boundaries; Sim keeps closed-loop simulation and replay integration. Runtime is only an additive future M05-PR03 consumer/host qualification candidate. BOPTEST results would be Runtime host evidence, not OCE equivalence or reassignment of Sim. No downstream qualification is claimed here.

Tokio, Axum, SQL, HTTP/MCP, drivers, quality/staleness policy, leases, commands and fallback services stay outside OCE. Consumers use the engine-owned evaluator, snapshot and canonical per-frame replay contracts; this work does not create a second evaluator, snapshot or replay stack.

Evidence and remaining acceptance work

The frame refusal controls replace historical gap tests: omission, duplicates and wrong types preserve the prior image without staging a prefix. The Store noninterference suite proves that even supplied Store samples cannot fill missing determinants or overwrite complete values. The Pre profile tests retain the fixed HostTick recurrence and snapshot continuation evidence. Compiler absence controls cover all supported feature selections; they do not qualify downstream consumers. The product checker is still a bounded traceability/claim-sentinel tool, not proof of semantic compliance.

Preparation evidence below fulfills PC-032; the execution evidence below fulfills PC-033 with equal-time correlation, retained immutable results and unchanged images on ordinary refusal. Explicit omission semantics would require a future executable schema change; none is invented here. The existing HostTick conformance limits and later cross-platform/replay qualification still apply.

Current preparation API

Engine::input_definitions() returns an owned Vec<InputDefinition> in lexical UTF-8 canonical path order. Each row has path, exact native value_type, and inclusive min/max as optional native Values. This is the represented executable boundary (external_inputs), not the point inventory or discarded source declarations with no executable consumer. Every row is required; the current schema has no optionality/default declaration. Fan-out yields one row, with the intersection of the boundary declaration’s and all targets’ bounds. Empty intersections accept no value. Real zero-bound ties have deterministic bits; a NaN bound never disappears in intersection. Integer bounds remain exact i64 values; absent Integer bounds use the documented i32 defaults, while explicit bounds are retained. Enum bounds carry the class and legal ordinal endpoints. No Store carrier, handle or public connector index is involved.

Keys are the expanded identities retained by ingest. The current resolver admits no input aliases: compact names are expanded at ingest, not at submission, and elided child names are not alternate setters. Output aliases are read-only. An injected-resolver-alias control checks that two spellings mapped to one logical input cannot defeat duplicate detection. It does not establish a public alias namespace. String/enum schema handling is total, but no current registry block offers those signal ports; private detached probes do not claim broader CXF support.

Engine::prepare_frame(time, &[(&str, Value)]) returns PreparedInputFrame. The plan owns exact values and every resolved target; keys are borrowed only during the call. It is opaque and has no serialization or public constructor. Engine::execute_frame consumes it. Editing a copied definition does not alter validation. Neither preparation success nor preparation refusal stages a value, evaluates, calls Store, replaces diagnostics, changes watches/outputs/time, or closes durable-restore readiness. There is no new replay image.

First-cause refusal precedence is:

  1. State(NoLoadedModel), then State(PendingParameterEdits).
  2. NonFiniteTime, TimeRegression, then ModelTimeUnrepresentable, in that order.
  3. The lexically first invalid submitted key: FrameUnknownInput or FrameNotInput (outputs and internally driven input points). These errors keep at most 64 UTF-8 bytes, cut at a character boundary, and the original key byte count. They do not clone an arbitrary-size submitted key.
  4. FrameDuplicateInput, then FrameMissingInput, each naming the lowest canonical input path.
  5. The first canonical input with a bad value: InputType before InputDomain for that input.

This precedence is independent of entry order. Refusals do not accumulate a report. Unbounded Real values retain NaN payloads/infinities; a declared comparison bound has to hold, so any bound rejects NaN and finite bounds reject the respective infinity. No blanket finite-signal or equipment policy is introduced. Finite equal time is valid.

The internal check_prepared_frame seam checks readiness, incarnation, and current time eligibility before execution can use resolved targets. It returns StalePreparedFrame for a different engine or superseded load/rebuild, without rebinding names. Successful identical-byte reload also invalidates. Failed load preserves the previous incarnation. Dirty resume fences before effective model mutation, even for same-value edits; clean resume and compatible checkpoint/durable restore alone retain the incarnation. Advancing the clock can still make a retained plan’s time ineligible. The fence is a retained process-local allocation identity, not a serialized counter, model name, Store handle, deployment generation or authentication token.

Preparation costs and evidence limits

For N logical inputs and T total fan-out targets, successful preparation allocates N+2 buffers for nonempty N: one temporary N-slot reference array, one N-entry plan, and one target array per input. Values clone without copying String bytes. Retained capacity is N plan entries plus T targets; zero-input preparation allocates no buffers. Input lookup scans submitted key bytes; value checks and target copies are proportional to N+T. The load-time schema sorts canonical keys once. The cooling-only-controller census repeats preparation 128 times, checks exact allocation/byte formulas and capacities, requires no retained allocation after drop, and has a counter positive control. This is a synchronous allocation census, not a latency, throughput or general peak-memory claim. Definition snapshots separately allocate owned metadata.

The public refusal matrix compares fresh and advanced stateful snapshots, output/watch values, restore readiness and Store call counts. The public ordering tests and private plan/lifecycle/domain/census tests pin independently authored expected values and checked-in bit/diagnostic goldens. The nonserialization compile-fail example and exact facade baseline cover opacity. Removing duplicate detection, dropping a fan-out tail, coercing integers via f64, changing signed-zero bits or omitting invalidation is detected by the corresponding assertions. No external Modelica oracle exists for this OCE-specific policy.

Current execution API

Engine::execute_frame(PreparedInputFrame) -> Result<CompletedFrame, OcError> consumes one plan by value, even on refusal. The plan cannot be cloned, serialized or submitted twice. Preflight precedence is unloaded, pending edits, stale incarnation, nonfinite time, regression, block-time representability, then FrameSequenceExhausted. Preparation has already resolved and validated every input and fan-out target. Execution rechecks the mutable conditions, without name rebinding.

After preflight, the implementation stages the entire plan, closes durable restore, calls the existing infallible evaluator once with the Warning collector, updates prev_t and latest-state inspection once, increments the accepted-frame sequence and captures the result. All ordinary returned errors are before this boundary. No Store operation, host callback, shadow RunState, undo log or rollback is involved. Allocation failure remains excluded, including during result capture.

CompletedFrame is owned, Clone + Debug + Send + Sync, with private fields and read-only time(), sequence(), inputs(), outputs() and diagnostics() accessors. Inputs and outputs are (String, Value) pairs; cloning a result gives an independent owned result. Real bits are copied, not normalized by capture (individual block arithmetic retains its existing numerical policy). No public connector indices, Store handles, mutable latest-view, schedule or serialized identity are returned.

The output set is the same disjoint union as Topology.boundary_outputs: represented elided root declarations plus lowered pass-through outputs. It is sorted by canonical lexical UTF-8 identity, not source order. Distinct declarations sharing a driver stay distinct; internal driver paths and the pass-through listing are not appended as duplicate aliases. Undriven source-only declarations are absent under the existing ingest warning contract. Zero boundary outputs is valid, including an Assert-only boundary with internal outputs. Inventory and host-selected trace columns are not the authority for this set.

Sequence starts at one and advances only on a successful native complete-frame commit. It never decreases or resets in one Engine lifetime, including successful reload, dirty/clean resume, checkpoint rewind and durable restore. Refusals consume no position. Thus equal-time commits remain distinct. The private retained Arc<()> incarnation binds each result to its loaded executable/IO and fixed build/profile context without exposing pointer identity. Sequence is correlation only: not replay position, snapshot generation, durability, cross-process identity, deployment authority, lease, authentication or freshness. Neither it nor the result is serialized into existing state/checkpoint bytes. No last-result cache is added to Engine; the caller owns retained outcomes, which remain unchanged by later operations.

The receipt also retains every accepted canonical boundary input, moving the exact prepared value and copying the canonical schema path. replay_record() captures a separate bounded canonical record, not a second transition; oversized record capture can refuse after successful execution. Its v1 contract leaves authenticated order and snapshot sidecars with the host.

Execution evidence and cost boundary

  • Public success goldens: hand-derived Add, sampled delay and equal-time Pre feedback; fan-out, native pass-through bits, lexical/many-to-one boundaries and ordered Warning diagnostics. These OCE-specific scenarios are not a Modelica event-iteration oracle.
  • Public preservation matrix and private preflight controls: fresh/advanced connector/word/time/output/scratch images, snapshot and checkpoint bytes, watches, restore readiness, sequence and Store counts. Private injection exercises nonfinite/unrepresentable time and sequence exhaustion; opaque public plans cannot be forged. The instrumented block boundary detects even idempotent double updates. No postcommit ordinary failure is invented.
  • G36 controller: two complete 1,441-row, ten-boundary-output runs against the existing independent Tier-A HostTick reference, not engine self-output or unrestricted Modelica equivalence.
  • Allocation and latency harness: exact counters and positive control, 128 repetitions each of five fixtures. For B nonempty boundary outputs, output capture allocates B path buffers plus one pair vector; empty B allocates none. Accepted-input retention adds N path buffers plus one N-pair vector for nonempty N, and moves prepared Values. Placement capture scans the loaded class set with the state portability predicate. Warnings add their owned source/message buffers and a geometrically growing event vector. Staging clones only the prepared fan-out values; preparation’s separate N/T formula remains above. Existing evaluator allocation exceptions remain, and latest-state inspection refreshes after each commit. No whole-engine/state copy is charged as result capture. See measured observations for debug/release timing scope; no universal speed or latency-ratio guarantee follows.

Compile-fail rustdoc pins nonserialization and single-use preparation. Exact public baselines and shape guards cover the frame-only API. Hosted architecture qualification and actual downstream host adoption remain separate. PC-035 promotes this contraction only, not stable release, persistence or equipment claims.

State continuation and portability contract

Authority and scope

This is the execution-maintainer contract for same-loaded-executable continuation (PC-038 in the product contract). It is a pre-release supported boundary, not a cross-release migration promise. OCE is synchronous, in-process and database-free. It captures bytes, not durable storage, a replay log or permission to command equipment.

The separate canonical replay record describes one accepted frame. Optional start/end snapshot bytes remain sidecars in the host’s authenticated ordered envelope; they are not embedded in a record. Replay reuses this placement policy without exposing or relabeling the private execution fingerprint as build authority. Neither snapshot format nor execution ABI changes for replay.

The release-candidate matrix and fallback guide support current/current only, with no N-1 support or cross-build state migration. The historical v0.1.0 source pin has no snapshot API; its absence is not the revision-1 wire refusal described below.

“Same-build” is a mandatory host-envelope precondition, not an OCE comparison. Before calling EngineStateSnapshot::from_bytes, the host authenticates a sealed envelope binding the exact snapshot bytes to its approved compiled-build/deployment qualifier and checks freshness and generation. That qualifier can cover source/dependency lock, compiler, features, target and codegen qualification. OCE emits, accepts, stores and enforces no build token. Pass only the OCE snapshot bytes after approval. A compatible manifest from a differently compiled implementation can pass OCE checks; that is not build qualification. See the restart checklist.

CompatibilityDescriptor, package version, catalog metadata tag, diagnostic model ID and complete export tag are not substitutes for either the host envelope or the state manifest. No universal identity is introduced. Differences in unused catalog entries do not affect this executable; changes to implementation code with unchanged descriptors belong to host build qualification.

Capture, parse and restore

OperationContract
checkpointOwned process-local image; compatible restore can rewind. No persistence format or external authority.
state_snapshotValidate loaded state, stable authored identities and registered state contracts; encode and run the bounded canonical decoder before returning. Every success can be parsed by this build. No evaluation or Store calls.
from_bytesValidate wire structure and manifest self-consistency, not compatibility with a loaded target or every class-specific state invariant. Unknown execution ABI can parse but cannot restore.
restore_stateCheck readiness, target domain, execution ABI, full manifest/fingerprint and payload before one in-memory commit. No evaluation or Store calls.
restore_checkpointSame prepare/commit discipline, without the startup-window restriction. Not a durable-byte bypass.

A successful load opens the durable restore window. Accepted frame execution, dirty-parameter resume, or either successful restore closes it. Snapshot/checkpoint capture, read-only inspection, frame preparation, refused operations, halt and clean resume do not close it. A successful reload opens a new window; an ordinary failed reload preserves the prior one. Pending parameter edits refuse capture and restore before any window check. Restore preserves the current parameter-edit mode, model identity, IO metadata, admission policy, private frame incarnation and Engine-lifetime accepted-frame sequence. It replaces connector values, state words, absolute model time and the prior-time guard only. A preprepared frame still rechecks time at execution. Retained completed frames remain immutable and are not restored or reissued.

All returned restore errors precede mutation and preserve both engine state and Store state. Panic, allocation failure, process termination and external host effects are outside this guarantee. Capture/restore do not create a Store transaction. A later delivery failure cannot undo a frame. At restored time, another accepted frame is another HostTick transition, not event iteration.

Compatibility and deterministic refusal

OCE compares complete structures, not just a collision-prone fingerprint. Incompatible structural catalog/profile/executable/IO/state/target facts refuse at parse or restore as follows:

Check / first-cause orderTyped evidence
Supplied bytes above 64 MiB, before header inspectionSnapshotTooLarge with actual and maximum counts
Short/invalid header; unsupported format; inconsistent total lengthMalformedSnapshot / UnsupportedFormat
Corrupted body/trailer after valid header and lengthIntegrityMismatch with computed and carried checksums
Invalid tags/UTF-8/counts, duplicate or unordered sections, inconsistent references, fingerprint, flags or payload shapeMalformedSnapshot with deterministic offset/detail
Restore without a successful load; pending edits; advanced durable targetNoLoadedModel, PendingParameterEdits, DurableTargetAdvanced, in that order
Foreign target-bound architecture or OSTargetDomainMismatch before execution compatibility
Different execution-state ABI, including a different transition profileIncompatibleExecution, subject execution-state ABI revision
Different manifestIncompatibleExecution, first named subject in the order below
Wrong fingerprint or connector mapping after matching manifestIncompatibleExecution
Invalid initialized/pre-first-tick words, clock relationship or class-specific stateInvalidBlockState
Missing stable authored identities or registered state contract during capture/target constructionIneligibleModel

Manifest comparison order is portability; referenced enum paths and ordered members; blocks by canonical key (key, canonical class, algebraic/stateful kind, state revision/length, raw parameter names and bit-exact typed values, ordered input/output bindings); connectors (key, path, declaration order, native type, computation unit and quantity); connections; block schedule; connector schedule; driver map; state-slot offsets/lengths; external input keys; boundary output identities/bindings; then canonical complete-frame input definitions (path, native type and effective min/max).

The input definitions include the intersection of declaration and all fan-out target bounds, with exact Integer/enum values and Real bits. They are the actual acceptance domain, not lossy PointStore bounds. Unit/quantity changes affect host interpretation and refuse. Display units and other non-executing presentation metadata are not compatibility determinants. Model ID is diagnostic only; changing it alone does not refuse. Parameter edits need a compatible newly loaded target.

Typed variants and structured fields are the host action seam; human-readable subject/detail text is deterministic diagnostic evidence, not a second versioned machine protocol. Hostile text in restore incompatibility/state diagnostics is bounded. Do not match error prose to authorize a build.

Wire revision and integrity

Current format revision 2 / execution-state ABI revision 2 retains fixed HostTick v1. Revision 2 adds connector computation unit/quantity and effective input definitions. Revision-1 bytes lacked those compatibility determinants and now return UnsupportedFormat { revision: 1 }. There is no fallback, silent upgrade or migration. Rollback uses the prior qualified build and its own authenticated state, or a host-approved cold start—not a header edit.

The byte cap is 67,108,864 inclusive. Integers and Real/state words are little-endian; Real payloads preserve all bits, including signed zero and NaN payloads. Strings are length-prefixed UTF-8. Compound keys sort by complete encoded key bytes (including little-endian lengths/indices), not Rust numeric or natural string ordering. Named parameter and input-definition paths use UTF-8 lexical order. Schedules and state-slot layout retain execution order/offsets; they are not freely permutable. Connection vector order is canonicalized; duplicate connections are noncanonical.

The fixed header is OCESTAT\0, u32 format, u32 execution ABI, u64 body length. The body contains u128 execution fingerprint, model ID, model time bits, optional prior-time bits, length-prefixed manifest, keyed connector values, state words and zero reserved flags. A 16-byte trailer follows. Revision-2 connector records append optional unit and quantity (0 = absent; 1 plus string = present). After boundary outputs, the manifest appends a u32 input-definition count; each entry is path, native type, optional typed min and optional typed max (0 = absent; 1 plus value = present).

Both fingerprint and trailer use FNV-1a-128: offset 0x6c62272e07bb014262b821756295c58d, XOR each byte and multiply by 0x0000000001000000000000000000013b modulo 2^128. The fingerprint covers little-endian execution ABI followed by canonical manifest bytes. The trailer covers all preceding snapshot bytes. These are accidental-corruption checks, not cryptographic authenticity, freshness or adversarial collision protection. Recomputing both can produce well-formed forged bytes. Host authentication binds exact bytes, not these FNV values.

The decoder bounds counts, lengths and charged allocation workspace; the byte cap is not a process peak-memory or CPU deadline guarantee. Capture also bears encoding/decoding allocation costs; it is not an allocation-free hot-path operation. OCE owns no filesystem. Truncation tests model partial durable bytes, not device power-loss resilience or atomic file publication.

Portability domains and evidence limits

EngineStateSnapshot::portability() returns a borrowed, cloneable StatePortability:

PolicyPlacement checkEvidence boundary
PortableNo architecture/OS restriction in the current class policy; all other checks still apply.Native Linux x86_64/aarch64 debug/release CI compares a populated portable state vector. This is bounded execution evidence, not every input/program/target.
TargetBound { arch, os }Exact capture architecture and OS labels required.CI compares same-target debug/release bytes, requires cross-architecture bytes to differ, and exercises foreign-target public restore refusal. Same target does not mean same build.

Inspection is not admission: an unknown execution ABI may expose a parsed placement tag but still refuses restore. Hosts keep encoded bytes intact and never rewrite the tag to move state.

The conservative target-bound set remains exactly 15 classes: CDL.Reals.Acos, Asin, Atan, Atan2, Cos, Exp, Log, Log10, Sin, Tan (each under CDL.Reals); CDL.Reals.Sources.Sin; CDL.Psychrometrics.DewPoint_TDryBulPhi, SpecificEnthalpy_TDryBulPhi, WetBulb_TDryBulPhi (each under CDL.Psychrometrics); and CDL.Utilities.SunRiseSet. Any participating target-bound class binds the executable.

The accepted strict-bit receipt covers only 21 Linux signal cases on pinned rustc/libm, four native architecture/codegen cells and a selected 35-file source boundary. It is not a full compiled dependency closure, arbitrary-input guarantee, mathematical oracle or macOS qualification. It therefore does not relax this state-placement policy. Local macOS passing tests do not supply the hosted Linux evidence or broaden either variant’s claim.

Continuation of published Library rules

tests/library_rule_state_continuation.rs runs two fault rules from open-control-library through the durable path exactly as a host would: state_snapshot, a host-envelope approval stand-in, EngineStateSnapshot::from_bytes, a freshly loaded engine and restore_state inside the startup window. The CXF graphs and vectors are unedited copies (tests/fixtures/library_rules/):

  • AHU-0016 (simultaneous heating and cooling): a Logical.TrueDelay persistence timer of 900 s.
  • AHU-0004 (excessive operating-state changes): Integers.Change, a 3600 s Reals.MovingAverage window and a 3600 s Logical.TrueDelay.

For all 16 scenarios, a restart after load and after every tick, and a chained restart after every tick, reproduce the uninterrupted run bit for bit: every boundary and internal output, and the full canonical state bytes. Named restarts land mid-dwell (AHU-0016 at 480 s, half way through the delay) and inside partly filled or draining windows (AHU-0004 at 900, 1800, 7800 and 11400 s); a cold start at the same point diverges in every case, so the restored state is load-bearing. The uninterrupted run also meets every Library expectation window. No block in either rule loses state across a restart.

Restore continues model time; it does not decide what a gap in that time means. If the first frame after a restart arrives later than one tick after the snapshot, the restored blocks treat the gap as elapsed time: a satisfied TrueDelay counts the outage toward its delay, and MovingAverage integrates the first post-restart input over the whole gap (with AHU-0004, one state change seen across a one-hour gap reads as twelve). The same frames would do the same in an engine that never restarted. Choosing the resume time, and whether an outage is a gap, a cold start or a host NO_EVAL window, is host frame policy.

Evidence map

  • tests/state_contract.rs: public inspection, changed bounds/units/quantities, historical wire refusal and signed-zero/equal-time continuation against a hand-derived Boolean recurrence.
  • src/tests/state_manifest_refusal_tests.rs: rechecksummed field mutation corpus, repeat typed diagnostics, full snapshot/checkpoint before/after images, retained lifecycle fences and Store noninterference; capture refuses a deliberately injected noncanonical producer image.
  • src/tests/state_format_golden_tests.rs, state_tests.rs, state_io.rs: independent full codec construction, checked byte/fingerprint goldens and hand-assembled domain encoding.
  • src/tests/state_codec_tests.rs, state_resource_tests.rs, state_restore_validation_tests.rs: every truncation boundary, checksum/length/tag hostility, exact cap and one-past, block-state refusals and decode/capture resource checks.
  • src/tests/state_portability_tests.rs: all 15 actual target-bound class captures, independent arch/OS refusals, and artifacts consumed by the unchanged hosted state matrix.
  • tests/library_rule_state_continuation.rs: durable restart of two unedited Library fault rules (persistence timer, moving window) at every tick, chained and mid-dwell, against an uninterrupted run, with cold-start divergence controls and typed refusals on advanced or foreign targets.
  • Existing continuation, family, lifecycle and Pre suites retain complete-frame continuation and current warning/state semantics. These tests qualify OCE boundaries, never host compliance.

Canonical accepted-frame replay record — revision 1

Scope and authority

This is the execution-maintainer detail of PC-039 in the product contract. It defines one accepted-frame record, not a sequence, historian, state snapshot, deployment receipt, network protocol or second evaluator. OCE remains synchronous, in-process, database-free and file-I/O-free. Nothing is published or qualified for cross-release migration by this format. The release-candidate matrix and fallback guide retain current/current evidence only; the historical v0.1.0 source pin has no replay producer. Cross-build envelopes refuse outside OCE before decoding, not through an invented historical replay decoder.

CompletedFrame::inputs() retains the canonical accepted boundary inputs independently of caller buffers and later engine mutation. CompletedFrame::replay_record() encodes those inputs, that transition’s outputs/warnings/time and its captured placement, without executing again. It records the public CompatibilityDescriptor::current(None) facts: export is explicitly absent, not silently claimed complete. The public descriptor is constant for that compiled process; it is not the loaded executable. No export, state capture, private fingerprint or Store operation is involved.

The record contains no sequence number. CompletedFrame::sequence() remains Engine-lifetime correlation only, never durable replay position. Hosts own the authenticated order, gap detection, duplicate policy and optional start/end EngineStateSnapshot sidecars. Even identical record bytes can represent two distinct accepted transitions. Equal timestamps are not idempotence keys.

Host workflow and trust boundary

  1. Bound transport/envelope buffering. Authenticate an envelope binding the exact record bytes, ordered position, compatible executable/parameters, approved same-build/deployment qualification, and any snapshot sidecars. Check freshness/generation and missing/duplicate policy outside OCE. Build qualification can include source/dependency lock, compiler, features, target and codegen. Package version, catalog/export tags and replay content identity are not build authority.
  2. Decode an approved record with ReplayRecord::from_bytes. This checks canonical representation, corruption and bounds, not host trust. Do not patch bytes or relabel placement to make them pass.
  3. Call record.check_compatible(&CompatibilityDescriptor::current(None)?) before preparation. It checks exact public facts in canonical order, then foreign target placement. V1 only decodes exact-bits policy. It never accesses an engine, performs execution or implements a tolerance.
  4. Use an isolated engine loaded with the host-approved compatible executable/parameters. If supplied, decode and restore the separately authenticated compatible start snapshot under the existing startup window and manifest checks. A per-frame record intentionally does not prove prior state or complete executable identity. Without a snapshot the host qualifies the exact cold-start or preceding continuation state.
  5. Borrow record.inputs() into Vec<(&str, Value)>, call existing prepare_frame(record.time(), …), then consuming execute_frame once. Preparation still checks completeness, types, domains, time and current executable; ordinary refusal preserves all state and restore readiness.
  6. Call record.verify(&completed) to compare exact time, accepted inputs, outputs and diagnostics. Comparison also rechecks current public facts (export:none) and receipt placement. A mismatch happens after that transition and does not roll it back. Stop the isolated host replay loop; do not blindly retry or use comparison failure as equipment fail-safe policy.
  7. Drop that record before reading the next unless host retention is intended. Compare optional end-state snapshot bytes exactly. OCE keeps no last record or whole-sequence buffer. Persisting bytes, sidecars and authenticated order, recovery and any equipment command remain host work.

The public-only host prototype executes this workflow through oce_api alone. It is a deterministic integration fixture, not authentication implementation, external downstream adoption or host/equipment qualification. The rustdoc workflow is compiled too.

Wire grammar

All integers are unsigned little-endian unless specified. str is a u32 byte length followed by exact UTF-8 bytes, with no terminator, normalization or padding. Counts are u32. There are no optional extensions, reserved flags, alignment gaps, compression, serde encodings or trailing bytes.

Position/orderEncoding
0..8ASCII OCERPLY then one NUL byte
8..12u32 format revision, exactly 1
12..20u64 body byte length, excluding this header and the trailer
Body firstu8 exactness: 0 = ExactBits; every other tag refuses
Placementu8: 0 = Portable; 1 = TargetBound, followed by str arch, str os; other tags refuse
Public factsstr canonical descriptor, grammar below
Model timeu64 raw binary64 bits, finite seconds; signed zero is retained
Inputsu32 count; each row is str canonical_path, native value
Outputsu32 count; each row is str canonical_path, native value
Diagnosticsu32 count; each row is str block, str message, u64 raw time bits, u8 severity (0 = Warning only)
Traileru128 FNV-1a-128 over all preceding header/body bytes

Input/output paths are nonempty and strictly increasing lexical UTF-8 bytes, separately in each section. Duplicate keys refuse even if values agree. Diagnostic order is emission order; identical repeated diagnostics are allowed. Source/message strings can be empty and include NUL or LF; diagnostic times preserve all bits, without adding a new producer-time restriction. Native frame model time remains finite. Severity is closed Warning-only: never Error, escalation or interlock.

Target labels contain 1..64 ASCII lowercase letters, digits or underscores; capture uses Rust std::env::consts::ARCH and OS. Structurally valid foreign labels can decode for inspection but refuse eligibility. Placement uses the exact same conservative 15-class predicate as state capture; any participating target-bound class binds the record. Portable removes only that target check, not other compatibility or host qualification requirements. The finite Linux strict-bit corpus does not imply universal mathematics or relax the state placement policy.

Native value encoding

u8 tagPayload
0 — Realu64 raw IEEE-754 binary64 bits, including subnormals, infinities, both zeros and every NaN payload/sign
1 — Integeri64 two’s-complement bits; no f64 conversion or codec-level i32 narrowing
2 — Booleanu8 exactly 0 or 1
3 — Stringstr exact UTF-8, including empty, NUL and LF
4 — Enumstr canonical registered enum class path, then u32 1-based ordinal in that class’s domain

Other tags refuse. Enum aliases, unknown classes and illegal ordinals refuse; no process-local EnumClassId is serialized. The currently registered CDL/G36 enum paths/members are the existing value/catalog contract, not a new extension registry. The codec covers every closed Value variant; detached full-domain probes do not add String/enum signal ports to the live CXF profile. Playback preparation retains its executable-specific input-domain checks, including CDL Integer bounds.

Public descriptor grammar

The descriptor is at most 1024 UTF-8 bytes, with the ten existing labeled LF-terminated lines in exact order: oce-compatibility, catalog-schema, catalog, io-schema, value-schema, parameter-schema, execution-profile, execution-profile-schema, oce-api-version, export. No missing/extra lines, CR/BOM, whitespace or escaping is accepted. Revision fields are positive u32 decimal with no leading zeros. Catalog text is catalog:1:fnv1a128: plus 32 lowercase hex digits; export is none or cxf:fnv1a128: plus 32 lowercase hex digits. The profile is HostTick-v plus a positive u32 decimal. Package version uses SemVer syntax: three canonical u64 decimal components, optional dot-separated ASCII alphanumeric/hyphen prerelease (numeric identifiers have no leading zeros), and optional build identifiers. Empty identifiers refuse.

Structurally valid differing facts can decode, then fail check_compatible with the existing CompatibilityMismatch cause. Descriptor revision other than 1 always refuses eligibility. Native capture/verification use export:none; a decoded present export never matches absence. Inspection/comparison of externally carried export facts does not make OCE attest their provenance. No standalone public descriptor parser or arbitrary serde-stability promise is added.

Integrity, identity and exactness

The trailer uses offset 0x6c62272e07bb014262b821756295c58d, XOR each byte then multiply by 0x0000000001000000000000000000013b modulo 2^128. ReplayContentId applies the same FNV-1a-128 algorithm to all canonical bytes including the trailer; Display is replay:1:fnv1a128:<32 lowercase hex digits>. Its private typed representation cannot be confused with catalog/export identity. Both hashes are noncryptographic, collision-prone accidental-content checks. An attacker can recompute them. Only exact host-authenticated bytes are the trust boundary.

Exact means Value::bit_eq and exact diagnostic source/message, severity and time bits, in order. Time uses to_bits, not numeric equality: +0 and -0 differ; matching NaN payloads agree as signal values. No epsilon, funnel, tolerance class or conformance comparison is reused. A record result is computation evidence, never sensor quality, freshness, actuator delivery or security authority.

Bounds and deterministic first cause

MAX_REPLAY_BYTES is 67,108,864 inclusive, counting header, body and trailer. Above-limit input refuses before header inspection and allocation. In addition, canonical decoded-work charge is at most 134,217,728 inclusive: 64 bytes per input/output/diagnostic row, every encoded string’s UTF-8 length (including enum paths), plus 24 bytes per String value for Arc metadata/alignment. These charges are fixed across architectures; compile-time layout guards ensure the Rust row/Arc representation does not outgrow them. Counts also have to fit the remaining raw minimum row bytes. All sums/products and slice boundaries are checked.

The decoder scans/checks every field and charges the entire workspace without allocation, then materializes exact-capacity vectors, owned strings and a copy of canonical bytes. It never trusts a count to allocate before admission. Work is linear in byte/row counts with no recursive structure. Canonical bytes plus charged decoded storage bound the owned record, not total process RSS: caller buffers, allocator overhead, clones, expected-descriptor capture and engine state are separate. Encoding sizes before allocating its bounded byte vector and validates through this decoder; capture temporarily holds that encoding as well as the decoded record. Allocation failure/panic/process death remain outside ordinary returned-error guarantees. This is not a CPU deadline promise.

Decode precedence is size; short/wrong header; format; total length; integrity; then body fields in wire order (exactness, placement, descriptor, finite model time, inputs, outputs, diagnostics, exact end). ReplayError distinguishes those causes, UTF-8, native/placement/severity tags, enum domain, noncanonical keys/Boolean/labels/suffix and workspace limit. Offsets are in the complete record. Eligibility compares descriptor fields in their existing canonical order, then target architecture/OS. Verification checks current eligibility and receipt placement, time, input rows, output rows, then diagnostic rows. The first differing row is reported; on pure count mismatch the index is the common length. Helpers never mutate engine or Store. They do not replace preparation preflight or restore manifest validation. A post-execution mismatch does not undo a prior accepted transition.

Evidence and remaining qualification

  • tests/fixtures/replay_{add,values,warning}.hex are hand-authored expected payload bytes, not blessed engine output. The existing hand-authored descriptor golden and independent tests/replay_oracle/mod.rs compose complete header/body/trailer bytes. Its two-word multiplication implements the specified checksum independently of the product’s u128 hashing. Add’s 3.75 output and warning fields are analytical expectations. No external Modelica oracle exists for this OCE-specific record grammar.
  • tests/replay_codec.rs covers complete-domain decoding, every truncation boundary, rechecksummed hostility, descriptor fields, key ordering/duplicates, target labels, all unknown value tags, other closed tags, native bits, enum bounds, UTF-8, counts/lengths/trailing bytes, integrity, inclusive cap/one-past, workspace amplification and repeated allocation-free refusal.
  • src/replay_capture_tests.rs checks exact independent encoding of every value domain on inputs and outputs, numeric bit classes, diagnostic fields/order/duplicates/counts, enum identity, empty records, oversized capture and unchanged sequence-independent bytes.
  • tests/replay.rs exercises actual accepted receipt ownership, Store noninterference, retained capture, wrong-descriptor/target pre-execution refusal and restore-window preservation, warning goldens, equal-time stateful replay from separately decoded start snapshots, exact end-state bytes, and a 16,384-record public-only host stream. Each iteration has identical bounded allocation peak/total and zero retained allocations; an explicit larger allocation is the positive control. This fixture is not a whole-program performance or all-model memory claim.
  • tests/replay_matrix.rs supplies repeat-exact portable and target-bound local vectors via optional OCE_PORTABLE_REPLAY_OUT / OCE_TARGET_REPLAY_OUT paths and foreign-placement inspection via OCE_FOREIGN_TARGET_REPLAY_IN. The existing debug/release oce-api matrix runs these tests, but cross-architecture replay artifact comparison is pending. Its artifact plumbing was not extended because ci.yml is itself pinned in the accepted strict-bit 35-file source boundary; changing that workflow would require separate requalification rather than silently reblessing evidence. Local artifact equality never fabricates hosted results or macOS qualification.

No state format/ABI, HostTick, catalog facts, conformance tolerance, dependencies, supported feature selections, downstream repository pins or release claims change. The new facade items are additive stable candidates; the exact public baseline and classification ledger record them.

Release-candidate compatibility and refusal

Current policy

Fail closed; no N-1 support. This is PC-040’s bounded current policy in the product contract, not a published release or an M06 release-strategy ratification. Nothing is published to crates.io. Only current/current is a supported candidate pairing, subject to the existing artifact contracts and the host’s actual build/deployment, executable, prior-state, placement and safety qualification. No downstream pin changes follow.

The retained machine-readable matrix has exactly 36 ordered rows: nine artifact classes times four producer/consumer directions. The selected identities are:

  • Current implementation: 3a7bdf023062d5dc3f433c7e28be4cde033224bf, the 0.2.0 release-preparation version bump. Its implementation boundary differs from the previous e81480b baseline only in the package version strings of the root manifest, lockfile and descriptor-carrying sources. Delivery adds tests/evidence/docs only; it does not pretend its future commit can hash itself.
  • Historical source candidate: annotated v0.1.0, tag object 7f3b614dc0e466d54cab4677ce4bb08a5bfaf033, peeled source 909a8ba699e6a2fccf3de6ac0616a9e83a04060f, 178 commits behind the previous e81480b baseline recorded in the historical receipt. This is a historical git pin, never a published release or supported N-1.
  • The current package string is 0.2.0 and the historical one is 0.1.0. Package strings never identified a candidate: before the 0.2.0 bump both read 0.1.0, and equal package strings do not identify the source, lockfile, compiler, features, target, codegen, binary or deployment. Neither descriptor nor FNV identity is build authority. OCE still has no build token.

The historical receipt records an isolated exact-source archive, its pinned Rust 1.95.0, default-feature debug build and 127/127 facade tests in 26 binaries on aarch64-apple-darwin. The initial offline attempt lacked cached stats_alloc; the bounded locked online attempt succeeded. Zero historical doctests were present. Historical public-API extraction was unarmed, not an armed surface check. The task-owned historical package/target was removed after capture. No historical workspace/release/other-target or equipment qualification is claimed. Rebuilding successfully does not turn removed APIs into supported APIs.

Directed artifact matrix

Legend: A = accept within the named current contract, U = unsupported/unqualified, N = unavailable in producer, H = host-envelope refusal before decode. Directions always mean producer to consumer, not upgrade versus downgrade. N is absence, not a decoder refusal. H is host policy, not an engine build check; there is no historical parser to call for these new artifacts. Supposed historical bytes and unknown build qualifiers are rejected by the host before current OCE decoding too, even though no genuine historical artifact exists.

ArtifactCurrent to currentHistorical to currentCurrent to historicalHistorical to historicalScope / evidence
Package/public APIAUUUExact current facade/storage baselines and compiler contraction controls. Historical tick/simulate/step_realtime were removed; matching versions do not restore source compatibility.
Facade catalog/schemaANUNCurrent canonical facade catalog/schema revision 1; not a claim that the old oce-blocks catalog did not exist.
Public descriptorANHNClosed descriptor revision 1, public facts only; absent export is not a wildcard.
DiagnosticsANUNVersioned producer receipts revision 1 and Warning-only assertion descriptor revision 2. Old diagnostic vectors existed, not this versioned contract; text is not a frozen schema.
Execution profileAUUUFixed HostTick v1, execution descriptor revision 2. No equivalence of historical execution modes is claimed.
Executable/frameANUNComplete typed frame preparation then one consuming transition; not durable executable serialization or cross-engine prepared-frame portability.
SnapshotANHNFormat 2 / execution ABI 2; loaded-executable manifest, startup window and Portable/TargetBound placement remain mandatory.
ReplayANHNFormat 1, ExactBits only, descriptor and Portable/TargetBound placement; host authenticates ordered envelope and optional state sidecars.
Strict-bit qualificationANUNOnly the retained finite 21-signal Linux x86_64/aarch64 debug/release corpus; not whole-executable exactness. macOS/other targets remain unqualified.

For current/current, read facade contracts, the complete-frame contract, state compatibility, replay record, public surface and strict-bit evidence. “A” never bypasses their limits. The strict-bit source guard and workflows are unchanged; local macOS tests are not new native Linux evidence. Hosted cross-architecture replay-byte comparison remains pending.

Typed refusal controls are separate from historical absence

The matrix’s typed_controls classify deliberate current-format/contract mutations as typed-refusal. Existing tests exercise exact variants and preservation:

  • state_contract refuses the retained intermediate revision-1 snapshot with EngineStateError::UnsupportedFormat { revision: 1 }. That fixture is not from v0.1.0.
  • State manifest/portability suites refuse ABI/manifest changes with IncompatibleExecution and foreign placement with TargetDomainMismatch, before mutation or Store calls.
  • Descriptor mutation tests assert each exact CompatibilityMismatch first cause.
  • Replay codec/eligibility suites assert UnsupportedFormat, UnsupportedExactness, DescriptorMismatch and TargetMismatch. A verification mismatch after execution is different: it does not undo the accepted transition.

Migration and refusal guide

There is no cross-candidate API, snapshot, replay, descriptor, diagnostic or executable support. Hosts migrate their application source deliberately using the facade migration guide, then qualify the exact new binary and supported CXF. OCE does not translate old CXF semantics or state. Never rewrite a header, ABI, descriptor, checksum or placement label to gain admission. Do not treat a compatible OCE manifest as build approval.

On an incompatible/unsupported state or replay envelope, select one explicitly approved host action:

  1. Cold load and safe requalification: fence commands, load approved CXF/parameters into a fresh isolated current engine, start with fresh parameter-seeded state, validate complete inputs and expected outputs, reconcile external history/time/ownership, then separately authorize delivery. Load may call Store; even failed load has the documented compensation boundary. Cold start is not a state-preserving upgrade and does not silently happen after a restore refusal.
  2. External rollback: select the prior qualified binary with its own authenticated compatible state and host configuration. It is not current OCE restoring prior-build bytes. The historical v0.1.0 pin is not qualified by this document and has no OCE snapshot/replay API; do not fabricate a historical snapshot or claim an actual rollback run. Any prior host’s state is its own contract.
  3. Remain fenced / NO_EVAL if neither action is approved. Halt is not an equipment stop.

The public-only host fixture refuses missing, unauthenticated, unknown and cross-candidate envelopes before its decoder closure, compares fresh and advanced snapshots and Store counters, preserves restore readiness, and contrasts accepted continuation with the independent Pre/Not cold-start recurrence. Valid current bytes carrying a hostile historical label are synthetic controls, not historical producer bytes. Its rollback test only routes opaque prior bytes to a matching external handler, never to current OCE. Authentication, real prior-binary execution and field actuation are deliberately outside this fixture.

Host fallback checklist

  • Preserve exact original bytes and refusal cause; do not migrate or rewrite them.
  • Fence external actuator ownership/commands and select NO_EVAL while deciding.
  • Authenticate exact bytes, full build qualifier, freshness, generation and replay order before decoding. Same Cargo version, descriptor equality and integrity checks are insufficient.
  • Choose explicitly approved cold requalification or separately qualified external rollback; never auto-fallback or cross-build restore.
  • For cold start, qualify CXF, parameters, complete IO domains, cadence and seeded behavior; compensate/reconcile load-time Store effects and external state separately.
  • For rollback, bind the prior binary to its own authenticated state and configuration; reject any mismatch before dispatch. No current decoder participates.
  • Verify field fencing, quality policy, model-time mapping and exclusive command authority before delivery. Successful load, restore, replay or local tests alone authorize no field effect.

Evidence checker and review boundary

The standard-library checker derives current version, state ABI/wire, descriptor and domain revisions from source/contracts, checks the existing descriptor golden, and binds exact evidence bytes. Its selected implementation boundary includes all tracked or pending crates/*/src/**, crates/*/contracts/**, crate manifests/build scripts, root manifest, lockfile, toolchain and Cargo config: 326 files at the selected baseline. Canonical path plus SHA-256 records, sorted lexically, produce the retained boundary digest. Missing, added, changed or symlinked source refuses. This is a source drift boundary, not a compiled-binary/dependency-cache or host build attestation; integration tests and their fixtures are separate retained evidence.

The retained JSON is compared byte-for-byte, including object/row order and the final LF, with the independently closed candidate policy and live facts. Missing/extra/duplicate/reordered rows, keys, evidence, malformed encoding and mutated identities, revisions, statuses or directions refuse. --candidate prints review input only; no auto-bless mode exists. Source-boundary updates require a new reviewed baseline/decision, not replacing a failed golden. The hostile tests include independent direction expectations, physical evidence failures, repeat output and a no-op enforcement control.

python3 scripts/release_compatibility/check.py
python3 scripts/release_compatibility/test_check.py
python3 scripts/release_compatibility/check.py --verify-history

The last command additionally verifies annotated/peeled Git objects and baseline source bytes; it fails when those objects are unavailable (for example, a shallow clone), never silently fetches. Ordinary checks use the pinned retained history receipt so existing shallow CI remains usable. An integration sentinel in oce-api runs the checker and all 16 hostile tests in the existing gate; no CI/release workflow or strict-bit guard changes are needed. Run the actual repository gate via the gate script; source/digest equality never substitutes for behavioral runs. Coordinated edits to checker and evidence are a review boundary, not a cryptographic security defense.

Published compatibility manifest

compatibility-manifest.json is the artifact attached to a tagged release. It lists, for the tagged source: the canonical public descriptor and its fields; each contract domain’s schema revision with an FNV-1a-128 tag over the packaged schema bytes; the CXF import contract, document byte limit, vendored CDL source commits and composite rule ids; the cxf:fnv1a128 content-id scheme and the catalog content id; the content id (or, for an export with deferral warnings, the byte tag and warning count) of every document in the swept G36 CXF corpus; the state and replay format revisions; and the selected release-compatibility baseline.

The owner test derives every value from the facade and repository data, cross-checks the matrix descriptor against the live one, and compares the file byte-for-byte. Regenerate it with OCE_BLESS=1 only together with the change that moved a fact. A moved corpus content id means exported CXF bytes changed, which churns every downstream record of an exported content id. The manifest describes one candidate; it grants no compatibility beyond the policy above, and its FNV tags are integrity tags, not signatures.

Future RC release-note template

Copy this section as a checklist for a separately authorized RC, not as a release announcement:

  • Candidate source, implementation boundary, full host build/deployment qualifier and package version: record all separately; attach matrix/checker and exact-candidate test results.
  • Candidate pairs and artifact directions: enumerate accepted, typed-refused, host-refused, producer-unavailable and unsupported/unqualified cases without treating absence as decoder coverage.
  • Public API/catalog/diagnostics/frame changes: cite exact baselines and source migration decisions.
  • State/replay/profile: record format/ABI/descriptor/exactness/placement revisions and refusal causes; explicitly state no N-1, no migration, no cross-build restore under this policy.
  • Fallback: cite host-approved cold requalification or prior-qualified-binary/own-state rollback, freshness/fencing requirements and actual qualification evidence; disclose unexecuted steps.
  • Validation: distinguish local debug/release/doctests, armed surfaces, hosted native architectures, finite strict-bit receipt, pending replay artifact comparison and downstream equipment qualification.
  • Authorization: record the independent release/publication decision. This matrix grants neither; do not infer publication from a tag, eligible manifest, package version or green local gate.

Authority claims and supersession

This is the sole aggregate projection of docs/authority-claims.json, a cross-domain index, not replacement policy. Existing source owners and precedence remain authoritative.

Fast checker: scripts/authority_claims/check.py.

Verification modes and limits

  • Delegated: display structured owner data and bind its exact bytes. The fast checker does not run Cargo/package closure, public row classification, or catalog provenance/registry validators. Those existing owners run separately in CI and the full gate.
  • Native: expected observations below are compared with compiled private constants or oce_blocks::catalog() by test-only owner modules. Python never extracts Rust literals. A fast-check PASS alone does not mean native comparisons passed.
  • Corpus: count raw provenance records and check universal tier/dependency metadata. These are fixture inventory facts, not correctness or independence proofs.
  • Review-only: links and limits are explicit; semantics and support promises are not automated. No arbitrary Markdown, historical archive, or Rust semantics parser is involved.

Checked source bindings

Delegated owner observations

Packages: 17 members; 12 publishable; 5 private. Publication state: deferred-no-crates-published. Feature selections: 3; the shared normal closure is owner-verified, not inferred here.

PackageClassificationPublishable
oce-apihost-facadetrue
oce-blesstest-supportfalse
oce-blockstransitional-companiontrue
oce-conformanceverification-toolingfalse
oce-cxfimplementation-dependencytrue
oce-diagimplementation-dependencytrue
oce-docsreserved-panic-onlyfalse
oce-exprimplementation-dependencytrue
oce-extensionexperimental-reservedfalse
oce-flattenimplementation-dependencytrue
oce-graphimplementation-dependencytrue
oce-modelimplementation-dependencytrue
oce-reference-wal-adapterprivate-reference-adapterfalse
oce-semanticsimplementation-dependencytrue
oce-storeconditional-adapter-porttrue
oce-store-memimplementation-dependencytrue
oce-validateimplementation-dependencytrue
Public baselineDescriptor rowsSHA-256 (owner descriptor)
crates/oce-api/tests/public-api.txt2560db804469b3d16c4d7388a6540204c3304ecacdb5ba55172f07d7016e8a3fb948
crates/oce-store/tests/public-api.txt123078cf5fdbcbd415a4ca3c521501c9c1a9ccca099551839c2fd360edc311fcbafe

Exact row assignments remain in the public ledger; statuses below are displayed, not re-adjudicated.

GroupStatus
deprecated-compatibilitydeprecated
domain-key-stablestable-candidate
facade-stablestable-candidate
schedule-leakageimplementation-leakage-to-remove
storage-port-conditionalconditional

CDL source revision: a131864e4c4df22ebcd52bb8da439de0087ac365; catalog fingerprint: 9edead4415592f28. This is the existing pinned source identity, not a new numeric catalog revision.

Delegated sourceSHA-256 (exact input bytes)
catalog-registryc52b1c5807e78aaf930f09402717ab7b5f1d98d173452d0cd22bbd6cb0b16336
catalog-sourcea8109009c6ffebba52522c2d6f96ac898c926b420a6baec369328c55b40d2702
packages901e3ad38af0c0d223aeb0624b5bac55398cf4370155a0541301dbb2ebffb74d
public-surfacec60488c42c3704b0cb8f15973ff40eea735ba96e9a94410445afd8b646060baf

Native expected observations

FactExpected
catalog-entries136
catalog-reserved3
execution-abi2
state-format2

Raw evidence inventories

Each declared root is enumerated recursively and completely, sorted by repository-relative path. Only tracked regular .csv, *.prov.json, and the fixed Tier-A MANIFEST.txt are admitted; missing, unexpected, untracked, ignored, or symlink members fail. All JSON records must be objects with the stated tier and Boolean depends_on_oce_blocks. Other record fields and CSV contents are opaque, not semantically validated. The inventory SHA-256 hashes sorted lines of <file-sha256> <repository-relative-path>\n, including every member, not just records. Thus added/removed/renamed or byte-changed evidence cannot leave the projection unchanged.

CorpusProvenance recordsAll membersTierdepends_on_oce_blocksInventory SHA-256
tier-a-records4121059Afalse09b7f14460296742cc57d0f801465326bf933738c5394a2274205151022fbdae
tier-two-records46922true94a5b189d657aef6441437ad4ee241929a7b73ce40237e729795eb1f64f14149

Review-only claims

FamilyExisting evidenceLimit
deferred-capabilitiesdocs/public-surface-contract.md; docs/cdl-coverage.mdBaseline-row classification is delegated above; broader capability deferral remains human-reviewed. No arbitrary Markdown or Rust semantics are parsed.
evidence-qualitydocs/verification-evidence.md; TESTING.mdRaw record counts and declared dependency metadata do not prove correctness, semantic independence, solver coverage, or semantic subcounts. Shared kernels and profile-specific interpretations require review.
host-tickdocs/execution-profile.md; crates/oce-api/src/tests/pre_execution_profile_tests.rsHostTick v1 is a documented semantic contract, not a separately encoded runtime or wire profile identity. Behavioral tests remain the evidence; this checker does not interpret their semantics.
platformsdocs/architecture.md; .github/workflows/pr-gate.ymlCI runner labels are execution evidence, not a structured support-promise contract. No supported-target matrix is inferred.
true-hold-extensioncrates/oce-blocks/src/logical_timing.rs; crates/oce-blocks/src/port_names.rsTrueHoldWithReset remains a deliberate local two-input extension with supersession-ambiguous historical annotations. This index records the ambiguity, not a new semantic ruling or a broken compile dependency.

Representative supersession map — non-exhaustive

This is not a global history audit. Historical locators are inert text: never opened, linked, or checked for existence. No archive, _spec/, or _research/ is a prerequisite. Formatting cannot promote a lower-precedence record to authority. A null authority records an unresolved question, not permission to select a new owner.

ID / subjectHistorical locator (text only)StatusCurrent authoritySuperseded byReason / responsibility owner
dated-stability / Dated stability snapshotdocs/stability-baseline-2026-08-26.jsonhistoricaldocs/stability-baseline.md—Dated observations are evidence, not current authority; moving refs do not invalidate a historical capture. / Repository maintainers
local-execution-plan / Local execution specifications_spec/03 and _spec/07 (historical source-comment shorthand)supersededdocs/execution-profile.md—Tracked execution-profile documentation and behavior tests govern HostTick semantics; historical locators are not clone prerequisites. / Execution maintainers
local-package-plan / Local package planning_spec/open-control-engine-2026-08-25/CURRENT-BASELINE.mdsupersededdocs/package-publication-policy.md—The tracked package policy and ledger govern present classification, not the local planning snapshot. / Package policy maintainers
local-public-plan / Local public-surface planning_spec/open-control-engine-2026-08-25/milestones/M00-authority-and-baseline/supersededdocs/public-surface-contract.md—Tracked public classification and blessed signatures outrank ignored planning inputs. / Public facade maintainers
true-hold-annotation / TrueHoldWithReset source annotationscrates/oce-blocks/src/logical_timing.rs:440-448; crates/oce-blocks/src/port_names.rs:43-52ambiguousUnresolved—The deliberate local two-input extension and historical specification comments need human adjudication; no authority replacement is selected here. / Block semantics maintainers

Updating a legitimate claim

  1. Edit the true owner first, following its existing compatibility/review policy.
  2. If a compiled numeric fact legitimately changes, update its native expected observation in the index. Never copy delegated counts, classifications, or matrices into the index.
  3. Explicitly regenerate only this page with python3 scripts/authority_claims/check.py --write.
  4. Run the named owner verifier(s), python3 scripts/authority_claims/check.py --check, and the repository gate described in CI and the gate. Gate execution never regenerates or blesses this artifact.

The tool uses Python 3 and Git, standard library only, no network. Local development admits only declared pending nonignored checker/index/projection files; corpus and existing owner inputs must be tracked. A clean exact-commit checkout verifies final clone availability. Schema, fixed source/verifier bindings, and explicit historical status mappings are closed; expanding them is a reviewed protocol change, not automatic discovery of new authority.

Public surface contract

The generated authority summary indexes this owner without replacing it.

This document and public-surface-ledger.json are the normative classification contract for the public items captured by the oce-api and oce-store blessed baselines. It classifies the surface that exists; it does not change a Rust signature, runtime behavior, or the pre-1.0 compatibility policy. Package support, oce-api feature selections, and future registry eligibility are separately governed by the package, feature, and publication policy.

Authority

When public-surface descriptions disagree, use this order:

  1. this accepted tracked contract and its machine-checked ledger for design classification;
  2. live Rust source together with crates/oce-api/tests/public-api.txt and crates/oce-store/tests/public-api.txt for exact names and signatures;
  3. crates/oce-api/src/guards.rs for only the selected shape invariants it compiles;
  4. other tracked supporting documentation.

Issue prose and local ignored _spec/ material are historical evidence, not clone-visible authority. Prose never substitutes for either blessed baseline’s exact signature inventory.

The ledger binds one-based baseline rows to reviewed classification groups and to each baseline’s SHA-256. public_surface_contract expands every range and rejects an uncovered row, an out-of-range row, an overlap, an unknown status, or baseline-byte drift.

Classification meanings

StatusMeaning
stable-candidateIntended long-lived embeddable contract. Pre-1.0 change control still applies; this is not a SemVer 1.0 guarantee.
conditionalSupported only inside the stated storage-port or adapter contract and its validity/lifecycle conditions.
deprecatedRetained compatibility surface whose replacement is documented; new consumers should not adopt it.
unstable/deferredName or shape is reserved, but working behavior or promotion evidence is incomplete.
implementation-leakage-to-removeExposes an internal mechanism and is targeted for a future coordinated removal, not removal in this change.

The ledger groups mechanically related baseline rows—such as auto-trait and blanket-impl rows—under one rationale while assigning every baseline row exactly once.

Surface ruling

oce-api is the primary host facade. Its working host operations, owned DTOs, typed errors, value types, and state capture/restore shapes are stable candidates except where the ledger says otherwise. oce_api::catalog() supplies independent typed facade metadata and versioned contracts; see facade contracts. oce-blocks::catalog() remains a supported, actively consumed companion surface for block metadata. It remains a separate dependency and is outside these two baselines; that separation is not evidence that the catalog is implementation leakage.

Serialized admission (MAX_CXF_BYTES, Engine::cxf_byte_limit, set_cxf_byte_limit, and the bounded OcError variants) is stable-candidate facade policy. The maximum is 8 MiB; hosts may only select a limit at or below it. Existing load signatures remain, while oversized acceptance intentionally narrows. The migration note and host compensation boundary separate in-memory replacement from non-rollback Store effects and external-handle validity.

The database-free oce-store traits and DTOs are conditional storage-port surface. Hosts may provide adapters, but adapter lifecycle and identity conditions remain part of the contract. Engine::with_store and Engine::store share that classification. The default in-memory facade remains supported through Engine::in_memory.

Complete-frame preparation (Engine::input_definitions, Engine::prepare_frame, owned InputDefinition, opaque PreparedInputFrame, and the explicit OcError causes) is additive stable-candidate facade surface, not a stable release. The artifact has no serialization or exposed resolved/generation internals. Its compatibility preflight stays crate-private and is reused by execution. The frame contract owns ordering, domains, bounded errors and load/dirty-resume invalidation; the adoption guide describes frame-only migration.

Engine::execute_frame, immutable owned CompletedFrame and OcError::FrameSequenceExhausted are likewise additive stable-candidate surface. The plan is consumed; the result exposes only model time, engine-lifetime accepted sequence, lexical boundary (String, Value) pairs and Warning diagnostics through read-only accessors. No mutable Outputs, internal connector indices or public context token escapes. Neither frame type has a serde representation. Reload/resume/restore never reset the sequence, which is correlation, not replay or deployment authority. See the execution contract. Legacy execution surfaces and raw output access are removed, without aliases. Latest-state get_output/watch remain non-receipt inspection.

Engine::schedule is implementation leakage. It stays source- and binary-shape unchanged for now; removal requires a later coordinated change with consumers and tests.

The placeholder loaders, TemplateRef, flat SemanticQuery alias, and InputSource::Csv have been removed, not promoted or placed behind an unstable feature. The conditional oce_api::oce_store::SemanticQuery namespace remains. AssertLevel now contains only Warning, also its intentional default. See the migration account for the source break. The now-empty deferred-surface group is removed from the ledger; its status vocabulary stays closed and unchanged. Compiler controls across all three supported feature selections and baseline reintroduction controls guard the removals independently of ledger hashes.

Engine::point_list(None) remains a supported own-inventory operation with its existing signature. Device filtering (Some) is explicitly outside the supported profile: it always returns the existing typed OcError::Load directly, even with a capable custom store. It is not a delegated semantic query or an experimental supported feature. The refusal and the None path call no store method and preserve the engine; public_storage_adapter exercises this boundary.

ExportReport::content_id is deprecated; use ExportReport::content_id_complete. Exact members are recorded by the ledger rather than by a prose method list.

CompatibilityDescriptor, CatalogContentId, CompleteExportContentId and CompatibilityMismatch are additive stable-candidate facade surface. The closed revision-1 host contract owns canonical text, per-field first-cause comparison, completeness refusal and evidence limits. Only already-public catalog/shape/profile facts, the OCE Cargo package version and optional complete export identity are covered. Private executable/generation/state-wire identities remain private. No parser, build fingerprint, signing authority or restore/replay eligibility is added. The existing seven domain artifacts and all package/feature/publication classifications remain unchanged.

CompletedFrame::inputs and replay_record, ReplayRecord, ReplayExactness, ReplayError, ReplayContentId and MAX_REPLAY_BYTES are additive stable-candidate facade surface. The revision-1 per-frame format owns canonical bytes, bounded independent decoding, exact comparison and target eligibility. It is not an Engine replay method or sequence container. Hosts authenticate exact bytes, ordered position and optional state sidecars, and qualify build, executable and prior state. Noncryptographic record identity is not deployment or build authority.

Future Python bindings wrap a selected subset of the Rust facade. The compile guards constrain that subset and selected owned/thread-safe shapes; they do not assert that every Rust facade signature is Python-facing.

StatePortability and read-only EngineStateSnapshot::portability are additive stable-candidate inspection surface. Their state contract separates encoded placement policy from numerical qualification and restore authority. Format/ABI revision 2 closes missing executable IO compatibility; it deliberately refuses revision-1 bytes without migration. The host authenticates exact snapshot bytes and build/deployment qualification before decoding. No build token, security service, public executable fingerprint or replay format is introduced.

Identity glossary

  • DomainKey is a database-free semantic key DTO. In LoadReport.model_id it is a stable-candidate facade DTO carrying the loaded model’s authored top-composite identity when available, otherwise the documented deterministic projection key. Model id is the prose role; there is no separate Rust ModelId type.

  • ConnectorId is a dense, zero-based connector/index identity within one loaded flattened model. It is not durable and has no cross-reload, cross-model, or host-control identity guarantee.

  • PointHandle is an adapter-defined scalar token containing no backend type. Its public tuple field is intentional: external adapters mint it in PointStore::resolve_points and consume it in PointSnapshot::read_resolved. A handle is valid only for the same adapter mapping from resolution through compatible snapshot reads. It is not durable, global, cross-adapter, cross-reload, or a host/equipment control identity.

  • Catalog content tag identifies all canonical facade metadata bytes within catalog schema revision 1. It is distinct from the existing registry fingerprint and state compatibility key.

  • Contract descriptor revision versions one facade domain’s shapes/semantics. HostTick’s descriptor remains descriptive; it is not a new state-wire or execution-profile selector.

  • Host build identity remains consumer-owned and includes the host’s source/build/features qualifications; catalog metadata alone cannot establish it. The compatibility descriptor’s OCE package version is only a public build fact, not a unique build identity.

  • Complete export content identity is captured as CompleteExportContentId, distinct from CatalogContentId and authored DomainKey. Existing string APIs retain their bytes and algorithms.

  • Compatibility descriptor versions the closed public-fact receipt itself. Equality does not establish executable identity, model-specific IO equality, generation, state compatibility or trust.

Frame preparation and latest-state inspection resolve model-local connector identities in the IO inventory, not Store handles. Load still validates adapter handle cardinality, but retains no runtime Store-input handles. Preparation admits only complete executable boundary inputs with exact types and declared domains. The private incarnation fence is distinct from authored model identity and is neither portable nor serialized into state bytes.

Repeatability and durable state

A frame transition depends on the loaded executable and parameters, compatible prior state, model time, complete typed observations and fixed HostTick profile. Missing inputs refuse rather than inheriting connector seeds, prior values or Store samples. Host simulation loops use that same contract; no implicit horizon restart or whole-horizon transaction is supplied.

Engine::state_snapshot produces engine-owned continuation bytes. The host persists and protects those bytes; they do not travel through the typed PointStore port. That port carries typed point samples keyed by DomainKey. state_tests::capture_and_restore_call_no_store_method verifies that capture and restore do not call any store method.

Existing controls remain the behavioral authority for adjacent cases rather than being duplicated here: frame_purity exercises Store noninterference across the corpus, projection_tests::source_model_iri_becomes_projection_model_id pins model-id projection, and engine_tests::load_validation_rejects_mismatched_handle_count pins adapter cardinality.

Downstream compatibility

Current inspected consumers establish a compatibility floor, not an exhaustive usage proof. Open Control Studio and Logic Studio use oce-api together with oce-blocks::catalog; Verdant Watch uses the facade. Consumers read LoadReport.model_id.as_str(). No inspected consumer directly imports PointHandle or calls Engine::store or Engine::schedule, but absence in that sample does not authorize removal. The dated Studio pin evidence remains available in stability-baseline-2026-08-26.json. The earlier reconciliation changed no signatures; the subsequent facade contraction deliberately removes selected names without editing downstream sources or pins. Inspected usage is not candidate compilation, downstream acceptance or general compatibility certification.

Issue #254 reconciliation

ContradictionRuling and executable/source evidence
PointHandle described as private or unreadablePublic construction/readback is required for external adapters. See PointHandle and public_storage_adapter::external_adapter_uses_only_supported_public_paths. “Opaque” means no backend type is embedded.
A trait-boundary prohibition was asserted for handlesHandles cross the port deliberately: PointStore::resolve_points returns one and PointSnapshot::read_resolved accepts one. The external adapter fixture exercises both sides.
String IO described as handle-basedFrame bindings and IoInventory::resolve_output produce model connector identities; Store handles remain adapter tokens and are not frame determinants.
A prose list claimed to exhaust the public facadeThe two blessed baselines exhaust exact signatures, and the ledger classifies every row. public_surface_contract supplies missing/extra/overlap and baseline-drift negative controls. Guards pin selected shapes only.
Python constraints described every Rust signatureguards.rs now states its selected-subset scope. Store and schedule signatures remain Rust-visible without becoming Python-wrapped promises.
Repeatability omitted determinantsComplete-frame preparation refuses omissions; the frame contract explicitly retains compatible prior state as a determinant. Historical sparse simulation is no longer a public profile.
Durable engine bytes were routed through PointStoreEngine::state_snapshot/restore_state own the byte channel; PointStore owns typed point samples. capture_and_restore_call_no_store_method is the negative behavioral control.

The contract validator also injects each of these historical claims into an in-memory supported-doc corpus and requires the corresponding rule to fail, so a validator that does nothing cannot pass.

Facade migration

This intentional pre-release source break removes legacy execution profiles as well as the earlier speculative facade shapes. Nothing is published to crates.io; no stable SemVer or downstream compatibility certification is implied. The public surface contract and its exact baseline own classification.

Removed names and working alternatives

RemovedHost action
Engine::load_modelicaPrepare already-specialized, supported CXF externally, then use load_cxf. OCE does not parse Modelica source.
Engine::load_from_semantic, TemplateRefRemove speculative loader calls. There is no semantic-template loader replacement; an app can prepare supported CXF itself. The active effective-point metadata resolver is unrelated and remains wired.
Flat oce_api::SemanticQueryFor conditional adapter queries, use oce_api::oce_store::SemanticQuery or direct oce_store::SemanticQuery. Those types/traits are unchanged; this does not supply a template loader.
InputSource and its variants, SimSpec, CollectSpec, SimMetrics, OutputTraceDecode tables and drive a host-owned loop of complete frames. There is no implicit restart, input callback or facade trace collector. Conformance reference-table replay remains host-side.
Engine::tick, tick_with, set_input, simulate, step_realtimeCollect every required input once, call prepare_frame, then move the result into execute_frame. Missing/duplicate/domain-invalid input refuses before mutation. No compatibility aliases exist.
Realtime epoch accessors and realtime-only error variants, StepReportOwn wall-clock mapping, persistence and delivery outside execution; realtime/Store orchestration is deferred. No engine write-back helper replaces them.
Outputs, Engine::outputsRetain CompletedFrame for committed boundary results and warnings. Use get_output/watch only for explicitly latest-state, non-receipt inspection.
AssertLevel::ErrorUpdate exhaustive matches and code that constructed the unimplemented severity. AssertLevel now contains only Warning; AssertEvent.level remains.

Default-value change: AssertLevel::default() is now Warning, previously Error. A signature baseline cannot detect this value change, so a separate explicit default golden pins it. This is not new escalation, abort behavior or safety logic. Actual CDL.Utilities.Assert CXF tests check true is silent, false produces repeated Warning events, exact message/source/time bits, continued execution and repeat-run equality. Expected files are hand-derived from Boolean assertion semantics and the HostTick reporting contract, not an external solver trace. The emitted source is currently the class path CDL.Utilities.Assert, not an instance identity. Every accepted frame retains its warnings; execution does not perform an external write or acquire a post-write failure mode.

Inventory and execution boundaries remain

point_list(None) keeps its existing signature and returns the engine’s own effective inventory. Device filtering (Some, including empty/unknown strings) is outside the supported profile and returns the existing OcError::Load directly. No new Unsupported variant exists and no store query is delegated, even with a capable adapter. This is not an experimental supported feature. The custom-adapter regression checks the None result, specific refusal, no store calls and no engine mutation. Existing error detail text is retained for compatibility, not promoted into an API schema.

Load/store side effects still precede engine-field commit; only the in-memory executable/run image has the replacement guarantee. Frame refusal preserves the current run. Consecutive frames continue state; fresh loads and explicit compatible checkpoint restores are distinct lifecycle operations. State-wire bytes, independent numerical goldens, catalog identity, HostTick arithmetic and engine thread-safety remain unchanged.

Package and panic-inventory boundary

No package/feature/dependency policy changes: 17 members, 12 publishable, five private; all three supported feature selections retain the same eleven normal OCE dependencies and the mem no-op. Private oce-docs retains its point_list_html unimplemented exporter outside the supported closure. Private oce-extension retains its reserved DTO, with no runtime. oce-flatten successfully returns its input model unchanged, and oce-semantics actively derives effective point metadata; both remain wired implementation dependencies. No new feature or replacement placeholder is introduced.

The focused production inventory distinguishes placeholder handlers from invariant checks: CXF resolver composite.rs and composite_orientation/diagnostics.rs contain unreachable! arms after exhausting the two traversal-frame variants. Blocks’ timetable/controller token mappings contain invariant arms over repository-owned constants. Snapshot decoding’s bounds-checked expects, debug assertions and test-helper panics are not deferred feature handlers. None were purged. Typed hostile-input tests bound the exercised paths; there is no universal panic-free, adapter-panic, cancellation or equipment-safety claim.

Compatibility evidence limits

The compiler contract checks retired names plus same-dependency frame controls under default, explicit mem and no-default selections. Baseline reintroduction controls are additional, not a substitute for compilation; unrelated associated Error types remain in the baseline. Only the facade baseline changes (2384 to 2111 rows for this contraction); the storage baseline remains byte-identical.

Prior downstream inspection is dated inventory, not compatibility with the frame-only facade. Actual downstream pins are unchanged. Exact-candidate Studio adapter and Library verifier fixture compilation is separate pre-delivery qualification, not established by that inspection or by the in-repository compiler controls. No downstream acceptance or pin advance is claimed here.

Additive contract adoption

The versioned facade contracts add typed oce_api::catalog() metadata, canonical catalog JSON/content identity and seven packaged shape/semantic descriptors. A new consumer can adapt every rule/default/port field using only oce-api. Existing oce-blocks companion users retain their current surface; coordinated removal and downstream pin migration remain later work. Existing state/ABI/catalogue fingerprints and HostTick behavior are unchanged.

For producer-stage evidence, opt into load_cxf_with_receipt and export_cxf_with_receipt. Success receipts expose the existing report and independently captured immutable diagnostics; into_parts permits legacy report mutation without corrupting evidence. Failures expose terminal stage, complete returned diagnostics and original OcError/standard source context. Compare DiagnosticKey values for the new machine ordering; retain the old methods when legacy ordering is required. Messages are display-only, code strings extensible, subjects opaque or absent, and multiplicity preserved. Diagnostic-free failures remain failures without fabricated codes.

Studio continues to own full build/source/features compatibility stamps, catalog display policy, truncation and authored-target mapping. OCE’s metadata content tag supplies none of those policies. No source/pin is advanced in a real consuming repository. Existing load/export signatures and reports remain. The assertion and execution descriptor revisions advance to 2 to describe the frame-only boundary; this is not a new HostTick profile, catalog identity or snapshot format.

Bounded serialized load adoption

Existing load signatures remain. Both raw load_cxf and load_cxf_with_receipt now refuse byte slices above 8 MiB before deserialization, with the additive OcError::CxfTooLarge containing actual_bytes and limit_bytes only. The receipt stage is Import; diagnostic order and sources below the cap remain unchanged. Inputs formerly accepted above the cap are deliberately no longer supported; widening is not an escape hatch. Hosts retain transport caps and trust isolation.

Read the effective policy with Engine::cxf_byte_limit() and optionally set a smaller value with set_cxf_byte_limit(bytes). The inclusive allowed range is 0..=MAX_CXF_BYTES; values above it return the typed OcError::CxfByteLimitTooLarge, leaving the engine unchanged. Configuration is per-engine, may be changed within that range, persists across reload and is not encoded in snapshots.

Failed ordinary reloads preserve the old in-memory run image, but Store effects are not rolled back and old external handles may no longer be valid. Plan adapter compensation before continuing control. Successful reload requires refreshing model-local IDs, IO views and other ephemeral references as before. No Store trait, catalog/descriptor identity, state bytes, HostTick, package or dependency changes accompany admission. No downstream source or pin migration is performed here.

Compatibility receipt adoption

The additive closed descriptor does not replace existing receipts or alter their bytes. Migrate only the public-fact comparison portion:

Current receipt field/useMapping
LoadReport.model_id / load receipt reportKeep for authored/synthetic diagnostic correlation. Never pass it as export, executable or compatibility identity.
catalog_content_id(catalog())Same text at descriptor.catalog_content_id().as_str(), now category-typed. Caller-edited catalog DTOs are not the current engine catalog descriptor.
IO/value/parameter contract_descriptors() revisionsSame revisions through the descriptor’s named schema accessors. Still metadata contracts, not model input-definition identity.
Fixed HostTick profile and descriptorexecution_profile() is HostTick-v1; execution_profile_schema_revision() remains the existing revision 2.
Host source/build/features stampRetain it. oce_api_version() adds only the exact OCE Cargo package version, not a unique build fingerprint.
ExportReport::content_id_complete() / export receipt reportPass Some(report) to CompatibilityDescriptor::current; its completeness check remains the sole minting path. Preserve typed refusal on warnings.
No export availablePass None; the canonical receipt records export:none, which never matches present content.
Checkpoint/snapshot or frame receiptNo replacement. Existing restore checks and frame lifecycle remain authoritative; descriptor agreement authorizes neither replay nor restore.

Retain the owned descriptor or its canonical to_string() bytes. Compare in-memory descriptors with check_compatible; store canonical bytes and compare exactly when using persisted receipts. No OCE parser, source normalizer, external attestation or signature authority is introduced. Legacy export reports are host-mutable, so capture before edits if producer-returned content is intended. Capture again after re-export to describe parameter edits. Same-byte reload may keep the descriptor equal while invalidating prepared plans: this is deliberately not a generation fence. No actual downstream pin or source is changed or qualified here.

Versioned facade contracts

The additive oce-api contracts describe metadata and evidence independently of a loaded engine. Existing load/export reports, diagnostic aliases, value/IO types and conditional storage aliases retain their signatures and behavior. The public surface contract and row ledger classify the exact API; pre-1.0 change control still applies.

Catalog ownership and identity

oce_api::catalog() returns facade-owned CatalogEntry values with static metadata strings and owned vectors. A caller can clone them without retaining an engine or depending directly on oce-blocks. The sole-facade consumer fixture adapts every rule payload and all Studio-shaped catalog fields under default, explicit mem and no-default feature selections. The existing companion catalog remains supported during migration.

Entries retain registry order; ports, rules, defaults and enum members retain declaration order. The projection includes canonical class paths, ordered port kinds/names, named/positional/width-driven regimes, every rule and default payload, structural-width flags, conservative statefulness and reserved lowering identities. An exhaustive dispatch inside the registry owner requires a complete adapter for each rule variant. The prior owner manifest and its exhaustive serializer remain intact.

Default-parameter ports are a metadata view; resolved instance arity can differ. Conservative statefulness is a class hint: for example, hysteresis-free comparators resolve algebraically. Reserved entries describe engine lowering identities and cannot be authored in CXF. Units, quantities, palette display policy and host ontology are not supplied by the catalog.

catalog_to_json serializes schema revision 1 as compact UTF-8 JSON with lexical object keys and one trailing LF. Arrays preserve their input order. All fields are present, with absent port names and optional rule names encoded as null. Integer payloads are JSON integers. Real bounds and literals are sixteen lowercase hexadecimal digits of the exact binary64 bits; this preserves signed zero, infinity and NaN payloads without introducing a general value codec. The function serializes caller-supplied DTOs without validating them.

CATALOG_JSON contains the packaged artifact. CATALOG_SCHEMA_REVISION and the catalog schema identify its shape. catalog_content_id hashes ASCII oce:catalog:1, one NUL byte, and every canonical JSON byte using FNV-1a-128, returning catalog:1:fnv1a128: plus 32 lowercase hex digits. All fields, rule/default payloads, flags and array order affect the tag. It is a non-security content identifier, with no authentication or engine-build compatibility guarantee. The existing registry fingerprint, state format and execution ABI do not change.

The catalog example writes the live canonical catalog, or writes every packaged descriptor when passed schemas. Schema artifacts are authored contract descriptions; repeated export checks their exact bytes, not derivation from Rust source. The catalog golden is a metadata regression artifact. A separate byte-arithmetic hash oracle and provenance note record its evidence limits.

Immutable producer evidence

Engine::load_cxf_with_receipt and Engine::export_cxf_with_receipt share the legacy operation pipelines. Their success receipts contain the legacy report and independently captured DiagnosticReceipt. into_parts separates them, so caller edits to report warnings cannot change the receipt’s provenance. Legacy entrypoints avoid this additional evidence allocation.

Failures return OperationFailure: terminal stage, completed-stage and terminal diagnostics, and the original OcError/standard error source chain. JSON, build and store errors can carry no diagnostics; terminal stage and error context still explain failure. No engine code is invented. An unloaded export retains its existing export-unsupported diagnostic. A partial export still succeeds with warnings; use the legacy report’s completeness check for emitted-document identity.

Producer stages are explicit boundary labels, not inferred from codes. Revision-1 ranks are:

RankStage
0Import: serialized admission, parse/resolution, including the CXF resolver’s internal passes
1Flatten
2AttributeUnification
3Validation
4Instantiation
5Schedule
6Semantics
7Projection
8StoreRecovery
9StoreSave
10StoreInputs
11Export

The ordering is stage rank, subject category/presence and exact UTF-8 subject text, code string, then severity rank (Error=0, Warning=1, Info=2). Absent sorts before Opaque; an empty present string differs from absence. Subjects can identify authored or synthetic nodes, classes or positional content. Current producers do not supply reliable subject provenance, so the facade preserves opaque text without URI/Unicode normalization or guessing. Hosts own authored-target mapping.

DiagnosticKey equality/order excludes display messages. Equal machine records retain multiplicity and producer-relative tie order, without a prose tie-breaker or uniqueness claim. Code strings are extensible. There is no truncation, deduplication or stable human-message promise. The existing all_diagnostics and legacy warning order remain unchanged. The receipt schema describes this separate revision.

Other contract descriptors

contract_descriptors() returns all seven domain/revision/artifact descriptions. Catalog uses JSON Schema 2020-12. Other artifacts describe actual Rust fields and semantic limits in JSON; they do not promise new JSON wire codecs or schema-driven runtime validation.

DomainActual contract and limits
ValuesExisting Value, ValueType and ConnectorId aliases remain. Real values use bit-preserving binary64; enums retain class and ordinal. Constructor representability does not establish operation-specific validity. Connector IDs are model-local indices.
IOExisting point fields and declared static attributes; enums project to Int, strings are omitted. Current inventory classification/defaults are explicit.
ParametersExisting tune-at-rest rows and available static bounds. Absent bounds do not establish freedom from cross-parameter rules. Unit/quantity provenance is currently absent.
AssertionsRevision 2: CompletedFrame::diagnostics retains all block warnings, including Assert and other classes. Sources are currently class-level, not guaranteed instances. Repeated false Assert inputs warn each evaluation; true is silent.
Execution profileDescriptor revision 2, still fixed HostTick v1: one advance per accepted complete frame, including equal timestamps; no Modelica same-time event iteration. Descriptive, not a runtime selector or snapshot revision.

Every completed frame retains warnings. There is no public no-op-sink execution profile or engine write-back route. Load/Store side effects and commit ordering are unchanged. These descriptors add no rollback, warn-once, escalation, scheduler, equipment policy or safety guarantee. The separately documented serialized admission and replacement policy uses Import for byte refusals without adding/reordering stages or changing descriptor bytes.

Consumer migration boundary

Consumers of removed execution profiles need source migration. New adapters can depend on oce-api alone for the typed catalog and receipts. Studio retains its full source/build/features identity, catalog policy, diagnostic truncation and authored-target mapping. These are separate from the facade catalog content tag. No future OCE commit is embedded in the artifact, and this change advances no downstream source or pin. Coordinated companion removal remains later work.

The migration record distinguishes this additive contract from the earlier facade contraction. Full local tests, exact public baselines and external compiler fixtures establish bounded implementation evidence; hosted cross-architecture checks and actual downstream qualification remain separate evidence.

Closed host compatibility descriptor

CompatibilityDescriptor::current(None) captures public facade facts without an engine. Passing Some(&export_report) additionally captures the existing complete exported-document tag, calling content_id_complete() first. Any warnings return its unchanged ContentIdError::Incomplete; partial export never silently becomes absent or complete content. No export is performed implicitly. This additive artifact does not add an eighth ContractDomain or change the seven existing artifacts.

CatalogContentId and CompleteExportContentId are distinct read-only types. Hosts can borrow their unchanged tag strings with as_str() or print them. Neither accepts an authored DomainKey, an arbitrary string, or the other category. No speculative executable, generation or state-wire type is exposed. The owned descriptor survives report mutation and engine reload/drop; capture again after re-export to describe changed exported content. Capture neither observes nor changes run state.

Canonical bytes and comparison

Display / to_string() is the complete revision-1 artifact, not Debug output. UTF-8 lines occur in exactly this order, with no spaces, BOM or CR, decimal revisions without leading zeros, and one LF after every line, including the last:

LabelCovered value
oce-compatibilityDescriptor revision, exactly 1.
catalog-schemaCurrent public catalog schema revision.
catalogVerbatim current facade catalog content tag.
io-schemaPublic IO inventory contract revision, not a loaded input-definition digest.
value-schemaPublic value contract revision, not a new value codec.
parameter-schemaPublic parameter metadata contract revision, not instance parameter values.
execution-profileExactly HostTick-v1, the canonical label for fixed HostTick v1.
execution-profile-schemaPublic execution-profile descriptor revision (currently 2).
oce-api-versionExact Cargo package version, including any pre-release/build suffix.
exportVerbatim complete CXF content tag, or exactly none if no report was supplied.

The absent-export golden and complete-export golden are hand-assembled from those fields, public revisions/version and the existing independently checked tag goldens. No source normalization is claimed. Catalog FNV-1a-128 covers ASCII oce:catalog:1, NUL, then all catalog_to_json(catalog()) bytes including LF. Export FNV-1a-128 covers exactly ExportReport.bytes, without prefix or length. Both start at 0x6c62272e07bb014262b821756295c58d; each byte is XORed, then multiplied by 0x0000000001000000000000000000013b modulo 2^128. Existing tag prefixes and 32-lowercase-hex formatting stay unchanged. The new artifact adds no hash of its own.

check_compatible requires exact field equality and returns the first CompatibilityMismatch in canonical order: DescriptorRevision, CatalogSchema, CatalogContent, IoSchema, ValueSchema, ParameterSchema, ExecutionProfile (label or descriptor revision), Build (package version), ExportPresence, ExportContent. Either unsupported descriptor revision refuses, even if equal. There is no SemVer range, wildcard, fallback or coercion. Two absent exports agree only on absence; hosts needing content identity separately require presence. Persisted receipts are canonical bytes, not parsed OCE objects: compare exactly against a newly captured descriptor, refusing unknown revisions, changed/missing/extra fields and all byte differences. OCE supplies no receipt parser.

Limits and security

The only build fact is the public oce-api package version. Default, explicit mem and no-default selections intentionally share the same descriptor because their supported package behavior is equivalent. Compiler, source commit, dependency lock, target, codegen and binary identity are not captured. In particular, unpublished same-version builds can disagree while these facts agree. Hosts retain full source/build qualification. Matching descriptors are necessary public-fact checks, not sufficient execution equivalence, cross-platform numerical evidence, restore eligibility or release-to-release compatibility. io() inventory is not input_definitions(); these revisions do not identify either model’s actual IO. LoadReport.model_id remains diagnostic authored/synthetic identity, never a compatibility key.

FNV is non-cryptographic; collision resistance, authenticity and authorization are not supplied. ExportReport is mutable: an empty warning list at capture is not authenticated producer provenance or CXF revalidation. Hosts may hash/sign canonical bytes themselves and own freshness, trust, generation fencing and equipment policy. OCE is not a signing/PKI authority. State/manifest bytes, executable fingerprints, generation tokens and native frame sequence are absent and remain private where currently private. Snapshots, restore, frames and admission limits retain their existing rules.

Complete-frame preparation adoption

Engine::input_definitions and Engine::prepare_frame add the preparation part of the complete-frame contract. Discover inputs from the owned exact-type definitions, not io().iter().filter(In): that point projection includes internal driven points, omits strings and projects enums to Int. Supply every canonical definition once with a real host observation. The definition’s exact inclusive bounds are schema domains, not input values or defaults. Missing values are refused rather than seeded or read from the Store.

Replace old per-value staging with one complete borrowed-key list passed to prepare_frame(time, entries). Preparation returns an owned opaque PreparedInputFrame without staging the loop’s valid prefix. The plan carries all fan-out targets and exact native values, but no Store handles or public connector indices. There is no serializable plan or reusable schema/cache object. Refresh discovery after successful load/reconfiguration; old plans are invalid after reload or dirty resume, including same-byte reload and same-value edits. Clean resume and compatible restore alone preserve the context, while an advanced clock can make the submitted time ineligible.

Do not replace execution with preparation. Pass the owned plan to engine.execute_frame(plan) for one complete transition and an owned CompletedFrame. The old execution methods and supporting types have been removed, without aliases. Hosts own quality, freshness, scheduling, persistence and actuation; neither missing observations nor duplicate names are silently accepted.

The public tests, stateful preservation matrix, and private plan/lifecycle census establish the bounded current preparation evidence. Execution, shared-core and frame-only contraction evidence make PC-033 through PC-035 current. The assertion and execution descriptors are now revision 2; other descriptors, catalog identity, state formats, HostTick v1, stable-release status and downstream pins are unchanged.

Complete-frame execution adoption

Collect host observations, call prepare_frame, then move the plan into execute_frame. The latter rechecks readiness, incarnation, model time and sequence capacity before staging anything. Any ordinary returned error preserves the full engine image and consumes no accepted position, although the Rust plan is moved. On success, retain or clone the returned result rather than relying on latest-state inspection. Read time(), sequence(), outputs() and diagnostics(); there is no serialization API.

The result contains lexical root boundary identities and native typed values, not every internal output connector or durable column. Distinct declared outputs may share a driver; pass-throughs occur once. Warning records retain emission order and producer source semantics. No pointer, Store handle, schedule, deployment token or durable replay identity is exposed. Sequence starts at one, counts successful native frames only and never resets during this Engine’s lifetime, even on reload or checkpoint rewind. Equal-time success is another transition, never an idempotent retry.

Hosts decide whether to execute and what to do with completed values; Store/actuation follow outside this API. Sibling consumers need to migrate; no sibling source or pin is changed or qualified here. See the execution contract and evidence.

The single frame path retains the private HostTick evaluation/refresh implementation. Host loops submit complete frames and own cadence and trace capture. get_output and watch are latest-state, non-receipt views, including internal points. There is no raw output view or Store write helper. Parity evidence is limited to equivalent complete schedules, not arbitrary legacy workflows.

Package, feature, and publication policy

The generated authority summary indexes this owner without replacing it.

This document and package-publication-ledger.json are the normative contract for workspace package support, oce-api feature selections, and release selection. The ledger is the machine-readable inventory; this page defines what its classifications mean. The executable validator is scripts/package_policy/validate.py.

No Open Control Engine crate is published to crates.io yet. A manifest marked publishable is only eligible for a future registry release. Actual publication is deferred to release milestone M06 and requires explicit owner authorization; this policy neither authorizes nor performs a publication.

Authority and terminology

This policy is package-level authority. The separate public surface contract and its blessed oce-api and oce-store baselines remain the authority for Rust item names and signatures. Live Cargo metadata remains the source of observed workspace membership, dependency edges, declared features, and manifest publish flags. The validator requires those facts to agree with this ledger rather than copying dependency edges into prose.

Support status and publication status are different. Publication is a registry-mechanics decision: a package may need to exist on crates.io only because a supported package depends on it. Support is the compatibility promise made to a host. In particular, implementation-dependency packages are publishable without becoming independently supported host-facing APIs.

The closed category vocabulary is:

CategorySupport meaningPublication meaning
host-facadePrimary supported embeddable host entry point.Publishable.
conditional-adapter-portSupported only under the documented database-free storage-port and adapter lifecycle contract.Publishable.
transitional-companionSupported companion while current consumers migrate toward facade-owned access.Publishable.
implementation-dependencyRegistry closure only; no independent host-facing support promise.Publishable because the facade closure requires it.
test-supportRepository test infrastructure, not product surface.Private.
private-reference-adapterVerification/reference implementation, not a supported backend.Private.
verification-toolingConformance and evidence tooling, not runtime product surface.Private.
reserved-panic-onlyReserved name with incomplete behavior, including panic-only paths.Private.
experimental-reservedExperimental or reserved boundary without a supported implementation.Private.

Closed package matrix

All 17 Cargo workspace members appear exactly once:

PackageCategoryManifest publicationContract
oce-apihost-facadePublishablePrimary host facade.
oce-blesstest-supportpublish = falseGolden-regeneration test support only.
oce-blockstransitional-companionPublishablecatalog() metadata is the supported companion contract.
oce-conformanceverification-toolingpublish = falseVerification harness, excluded from release selection.
oce-cxfimplementation-dependencyPublishableRequired by the facade closure; a direct downstream pin is a migration constraint, not promotion.
oce-diagimplementation-dependencyPublishableRequired by the facade closure.
oce-docsreserved-panic-onlypublish = falseReserved document-generation seam; panic-only behavior is not releasable.
oce-exprimplementation-dependencyPublishableRequired by the facade closure.
oce-extensionexperimental-reservedpublish = falseReserved extension/FMI boundary with no supported implementation.
oce-flattenimplementation-dependencyPublishableRequired wired facade seam; no independent support promise.
oce-graphimplementation-dependencyPublishableRequired by the facade closure.
oce-modelimplementation-dependencyPublishableRequired by the facade closure.
oce-reference-wal-adapterprivate-reference-adapterpublish = falseDurability reference adapter, not a supported backend.
oce-semanticsimplementation-dependencyPublishableRequired wired facade seam; deferred behavior is not independently promoted.
oce-storeconditional-adapter-portPublishableDatabase-free app-side adapter port and DTO contract.
oce-store-memimplementation-dependencyPublishableUnconditional in-memory implementation in the facade registry closure.
oce-validateimplementation-dependencyPublishableRequired by the facade closure.

The resulting split is exactly 12 publishable and five private packages. The validator compares every row with Cargo’s live publish value, requires one row per workspace member, and rejects a missing, extra, duplicate, or unknown classification.

Closed oce-api feature matrix

oce-api declares only default and mem. The complete supported selection matrix is:

SelectionCargo spellingEnabled oce-api featuresNormal OCE dependency result
Defaultno feature flagsdefault, memThe shared 11-package closure, including oce-store-mem.
Explicit legacy memory spelling--no-default-features --features memmemExactly the same closure.
No default features--no-default-featuresnoneExactly the same closure.

mem is a legacy no-op compatibility flag. It does not make the in-memory backend optional and is not evidence that disabling defaults removes that backend. oce-store-mem is an unconditional normal dependency in all three selections, while Engine<S: Store = MemStore> supplies the default type. The spelling remains until downstream manifests migrate; making it optional or removing it is a later compatibility change, not part of this policy.

Any other feature name is unsupported and Cargo must reject it. Every supported selection must have the exact same normal OCE closure and must not introduce a private workspace package, a database, tokio, async-std, or unsafe code. The dependency checks live here in the package validator; the workspace unsafe_code = "forbid" and crate-level #![forbid(unsafe_code)] checks remain the compiler-backed unsafe controls.

Registry closure and release selection

The future release workflow deliberately retains Cargo’s workspace publication mechanism:

  • cargo publish --workspace --locked --dry-run verifies the selected packages without uploading;
  • cargo publish --workspace --locked exists only in the manually dispatched, environment-guarded publish job;
  • Cargo skips the five members whose manifests say publish = false, selecting exactly the 12 publishable members.

The intended order is leaf-to-facade, but no prose list owns that mutable order. The validator builds the normal/build workspace graph from live Cargo metadata, rejects cycles and private-package leakage, requires every publishable path dependency to carry a registry version, and derives a deterministic topological order on each run. oce-api’s observed normal closure must equal the ledger’s closure under all three supported feature selections.

The release workflow still does not constitute authorization. A tag verifies only; actual registry publication remains a manual dispatch behind the release environment and remains deferred until M06 plus an explicit owner decision. Before that decision, package and publish commands are dry-run evidence only. A dry-run never uploads.

Immutable workflow approval

.github/workflows/release.yml is the readable execution definition. The literal SHA-256 in scripts/package_policy/release_workflow.py is its approval witness, not a second execution grammar. It approves the raw workflow at commit 2bab88acbc96862f1808b34d305b795f521b3614: tag pushes run verification only; publication is declared only in the manual-dispatch job with needs: verify and environment: release. Only the publish step explicitly maps the registry token. A failed verify job skips its dependent publish job under GitHub’s job dependency rules. The declared environment follows GitHub’s deployment environment semantics, and Cargo’s publish dry-run does not upload.

All bytes are closed. The validator reads bytes and compares their SHA-256 to the fixed literal: no YAML parsing, shell inference, decoding, or semantic normalization. Even comments, whitespace, trailing bytes, CRLF conversion, and malformed encoding are rejected. The ledger retains only the fixed workflow path and the 12/5 selection counts; it does not duplicate command or event grammar. There is no configurable workflow/digest location and no runtime self-blessing or auto-bless mode.

An intentional future workflow change requires a reviewed workflow diff, a manual update to the expected digest with a rationale for the newly approved execution, and updated independent byte golden evidence. Demonstrate that the old approval rejects the new reference and that the new approval rejects the old reference while accepting the new reference, repeatedly. Do not compute the expected digest from whichever candidate the validator is currently checking.

This is a drift guard preserving approved declared direct execution, not a sandbox or a security defense against coordinated edits to the workflow and checker. It does not verify the behavior of invoked actions/scripts, GitHub environment protection configuration, required reviewers, or actual secret placement. The workflow’s environment comments describe intended setup, not evidence that repository settings implement it. The checker and hostile controls run independently in CI and in the local gate; they are not an externally protected checker. See .agents/gate.sh for the authoritative runnable gate, rather than a duplicate command inventory here.

Downstream constraints and migration

Current consumer evidence sets a compatibility floor:

  • Open Control Studio directly selects oce-api and oce-blocks, and its manifest uses default-features = false, features = ["mem"]. The legacy spelling stays until that manifest is migrated.
  • Logic Studio directly pins oce-api, oce-blocks, and oce-cxf. Its active facade/catalog use keeps the catalog transitional companion supported. Its oce-cxf pin constrains migration but does not make oce-cxf an independently supported package.
  • Verdant Watch uses oce-api, reinforcing the facade as the host-facing boundary.

No downstream repository changes under this policy. Direct implementation-package consumers must migrate through coordinated downstream changes before an implementation package can become private or disappear from the registry closure.

Reversal before release freeze

Because nothing has been published, every classification remains reversible before release freeze, but not silently. A reversal requires an owner-approved change to this policy and ledger, matching manifest and workflow changes, a dependency-safe metadata result, all hostile controls and gates, and any required downstream migration. Promoting an implementation package to independent support also requires a stated public contract and compatibility evidence; changing a publishable package to private requires first removing it from every publishable normal/build closure and coordinating any direct consumers.

After first publication, registry history cannot be erased. Reversal then follows the release and pre-1.0 compatibility process rather than pretending the package was never public.

Architecture

This page is for an engineer deciding whether to embed the Open Control Engine in a product. It answers four questions: what the layers are, where the seam between them falls, what each of the 17 crates owns, and what this engine will never do for you.

The product contract owns the aggregate requirement/evidence map and delegates domain semantics without turning future acceptance outcomes into current behavior.

The organising idea: the CDL §7.17 non-computational seam

CDL §7.17 states that point lists, trends, display units, tags, and all Brick / Haystack / ASHRAE 223P semantics do not affect the computation of a control signal. That is the cleanest seam available in this problem domain, and the engine is built along it rather than around it.

Above the seam sits an execution core: a small, deterministic, in-memory dataflow machine that sees only blocks, typed connections, and values. This is the hot path, and it has zero dependency on any database.

Below it sits storage behind a trait. Everything the evaluator must not read — equipment topology, points, instance structure, parameters, trends, semantic triples — plus durable persistence is reached only through the oce-store port traits (crates/oce-store/src/lib.rs:580). The library ships no first-party database. Durable or queryable backends are app-side adapters behind the port, with an in-memory default (oce-store-mem) so that a downstream project can embed the engine for load → flatten → validate → schedule → prepare frame → execute frame with no database at all.

The seam also fixes where responsibility for input quality lives: the host supplies every typed boundary input exactly once. Missing values refuse; Store samples and prior values cannot fill gaps. Typed completeness does not establish sensor quality or freshness, so the engine still implements no fail-safe policy of its own. Staleness limits, fault reactions, and safe-state fallback belong to the host layer above it; see host-responsibilities.md.

The pipeline: build once, tick many

Parsing, elaboration, conformance rejection, algebraic-loop rejection, and topological sorting all run once per load. What survives is a frozen schedule over flat arrays, and TICK walks it: no graph traversal, no hashing, no store access in the graph evaluator.

The current runtime is the fixed HostTick v1 execution profile: every successful frame execution evaluates this schedule once and advances state once, including at a repeated timestamp. It does not implement Modelica same-time event iteration for CDL.Logical.Pre.

Embeddability posture

The engine is, by design:

  • Library-only. No main, no [[bin]], no daemon, no server, no network listener — verified by the absence of any binary target or std::net use in crates/. The host owns process lifecycle, transport, TLS, authN/Z, multi-tenancy, off-host durability, and metrics export.
  • Synchronous and in-process. Every public method is a blocking synchronous call, and no async runtime is pulled at any layer. The check is the dependency set below, not a promise.
  • #![forbid(unsafe_code)] in all 17 crates, belt and braces: each crates/*/src/lib.rs carries the crate attribute, and the workspace sets unsafe_code = "forbid" under [workspace.lints.rust] (Cargo.toml:49).
  • Edition 2024, resolver = "3".

The whole external dependency surface on the embed path is four crates: serde, serde_json, thiserror, and libm. Two more — regex and ryu — exist only inside oce-conformance, which nothing else in the workspace depends on, so a host embedding oce-api never links them.

Two Rust versions, and they are not the same number

These are different values on purpose, and conflating them has already cost this repo one reverted change (PR #209).

ValueWhereWhat it means to you
MSRV1.97.0Cargo.toml:42 (rust-version)The floor a consumer needs. Build the engine with any toolchain at or above this.
Pin1.97.1rust-toolchain.toml (channel)What CI and local development actually build with, so the two agree exactly.

The MSRV cannot be raised to match the pin casually: the release gate’s cargo public-api surface checks shell out to a pinned nightly that identifies as rustc 1.97.0-nightly, and Cargo enforces rust-version against it. Cargo.toml carries the full explanation inline at the declaration.

Determinism, and the two allocation carve-outs

The tick is deterministic: a frozen, topologically-sorted schedule evaluated over flat arrays, with no graph walks, no hashing, and no store access in the graph evaluator. Two carve-outs, both real, both stated here rather than in a footnote.

The evaluator is not allocation-free for every block. The schedule and state arrays are preallocated and the gather scratch is reused, so most blocks tick without allocating. CDL.Reals.Sort uses fixed stack buffers through nin = 64 (SORT_STACK_WIDTH, crates/oce-blocks/src/reals_matrix.rs:340), then falls back to two heap-allocated vectors for wider inputs. CDL.Reals.Log and CDL.Reals.Log10 use static warning messages, so warning emission allocates nothing block-side — but a diagnostic sink may still allocate when it records an event, including the completed-frame warning collector. Size a host loop against the blocks and the diagnostic sink your sequence actually uses, not against a blanket guarantee.

Frame execution is Store-free. Preparation owns the complete typed observations and resolved targets; execution consumes the plan and produces an owned receipt. Their structural allocation budget scales with inputs, fan-out, boundary outputs and emitted warnings. It is not zero allocation or a whole-state clone. Load-time Store recovery, save and handle-cardinality validation remain.

Whether a block tick allocates on the evaluator thread is gated per-PR, registry-wide and with a positive control, by crates/oce-blocks/tests/tick_allocation_census.rs. No current block delegates work to a worker thread; such an implementation would need a companion guard for worker allocation. The facade’s corpus-wide Store guard is crates/oce-api/tests/frame_purity.rs; frame allocation formulas are checked by frame_observations.rs. Throughput figures live in docs/benchmarks.md, recorded per run with the commit and host that produced them, because nothing re-measures them in CI.

The crate map

Seventeen crates. The dependency direction is acyclic and organized around the seam above. Their responsibilities below do not imply equal support or publication status. The normative package, feature, and publication policy classifies all 17 and separates host support from registry dependency closure.

Execution core (Group A — no store, no database):

CrateResponsibility
oce-modelPure value/connector/instance/connection types; the Value enum (Real/Integer/Boolean/String/Enum) and the flattened model graph — the shared executable truth.
oce-exprThe CDL §7.7.2 binding-expression parser and evaluator (closed-world, pure). Bounded on structure: input deeper than MAX_NESTING_DEPTH (64) or wider than MAX_EXPR_NODES (4096) is a typed error, not a stack overflow (crates/oce-expr/src/lib.rs:125, :132).
oce-blocksThe Block trait and the native CDL elementary-block library, publicly enumerable at runtime via catalog() (crates/oce-blocks/src/catalog.rs:155) with ports, parameter rules, and honest parameter defaults per class.
oce-flattenReserved seam; an identity passthrough today. oce-cxf owns lowering because CXF arrives pre-flattened, so flatten() returns the model unchanged (crates/oce-flatten/src/lib.rs:53). Full .mo flattening is deferred. It is a publishable implementation dependency on the oce-api path, not an independently supported package.
oce-validateLoader conformance: subset rejection, single-assignment, type and attribute unification, parameter rules.
oce-graphThe deterministic scheduler and executor: direct-feedthrough DAG, algebraic-loop rejection, its own Kahn topological sort, the tick loop.
oce-cxfCXF (Control eXchange Format) JSON-LD ↔ model graph, both directions. Composite nesting is bounded at 64. Boundary lowering is iterative and separately bounded at 64 non-top isConnectedTo hops per path, 65,536 target examinations, and 8 MiB of aggregate target-IRI bytes per document. The accept/reject contract is written out in cxf-composite-subset.md.
oce-semanticsActive effective-point metadata resolver. Derives metadata from connector attributes, direction, value type and defaults; this is unrelated to the removed semantic-template loader. Raw __cdl / __CDL annotation parsing remains deferred. It stays a wired, publishable oce-api implementation dependency, not an independently supported API.
oce-diagThe shared diagnostic vocabulary (Severity / DiagCode / Diagnostic) across the ingest path. Zero dependencies.

Storage ports (the seam — traits only, no database types):

CrateResponsibility
oce-storeThe seam. The ModelStore / PointStore / SemanticStore / Durable traits plus DTOs, unified by the Store supertrait. No database types.
oce-store-memThe default in-memory backend, so the engine runs with no database. It is an unconditional publishable implementation dependency, not independently supported host surface.
oce-reference-wal-adapterVerification-only, publish = false. A std::fs WAL and atomic-snapshot adapter that exists to prove the frozen seam can carry real durability without a first-party database. Not a supported backend.

Verification, externals, and the host facade:

CrateResponsibility
oce-conformanceVerification-only, publish = false. The funnel-style tolerance-band and golden-trace conformance harness. Standalone: no other crate depends on it. Read TESTING.md for what it does and does not check.
oce-blessTest-support only, publish = false. The single definition of the repo’s environment-variable truthiness policy, so golden-regeneration switches cannot drift apart across crates.
oce-extensionExperimental/reserved, publish = false; nothing consumes it. The intended role is the FMI / extension-block boundary. No crate depends on it, the CXF resolver has no extension-block branch (an unknown class is a hard ClassNotFound), and DiagCode::MissingFmuPath (crates/oce-diag/src/lib.rs:167) is declared but never constructed. Do not plan FMI integration against this crate.
oce-docsReserved panic-only seam, publish = false. The sequence-spec and point-list export surface is declared; point_list_html panics with unimplemented! (crates/oce-docs/src/lib.rs:17). Nothing depends on it.
oce-apiThe primary embeddable host facade: Engine<S: Store = MemStore>, spanning load, complete-frame preparation/execution, parameters, IO inventory, latest-state non-receipt reads (get_output/watch), CXF export with content id, and a read-only topology view. The actively supported oce-blocks::catalog() metadata API is a separate companion surface; see the public surface contract.

Four of those — oce-flatten, oce-semantics, oce-extension, oce-docs — are reserved seams rather than working components. They are named here so that nobody plans a feature against a crate that does nothing yet.

The legacy mem compatibility spelling

oce-api declares default = ["mem"], but mem = [] gates nothing and oce-store-mem is an unconditional dependency, so disabling default features does not remove the in-memory backend. The flag is retained temporarily as a truthful legacy no-op while downstream manifests migrate. What actually makes MemStore the default is the type parameter in Engine<S: Store = MemStore>. To use a different backend, name it: Engine<MyAdapter>. The complete supported matrix and its mechanical closure guard are in the package policy.

What this engine will never do

Each of these is a design commitment, not a gap waiting to be closed.

  • No first-party database. Persistence lives behind the oce-store port, authored app-side.
  • No daemon, no server, no network listener. It is a library; the host owns the process.
  • No async runtime at any layer, pulled by any crate.
  • No fail-safe policy of its own. It does not decide what a stale sample means, what a faulted point should do, or what safe state looks like for your equipment. Those decisions are yours, and they are enumerated in host-responsibilities.md.

For what the engine has and has not been verified against — including the global report tiers that are not wired — see TESTING.md and the verification section of the README.

Execution profile

The generated authority summary distinguishes compiled state revisions from the review-only profile name and semantics; it does not define a new profile identity.

Open Control Engine currently has one execution profile: HostTick v1. It is fixed, not selected through an API option. A future profile with different state-transition semantics would require a separate compatibility and snapshot contract.

The complete-frame contract implements read-only preparation and one native atomic transition with an immutable output/diagnostic result around this same profile, not a second evaluator or profile selector. Preparation does not execute; consuming execute_frame is the only public state-advancing execution surface. Legacy execution profiles were removed.

The frame path enters one private infallible evaluation/refresh core after preflight and staging. The core closes durable-restore readiness, evaluates once, updates the previous time and refreshes latest outputs. It selects no diagnostic policy and performs no Store IO, restart, frame-sequence update or projection. Host loops select cadence and capture traces from completed receipts or explicitly labelled latest-state inspections.

HostTick v1

Each successful Engine::execute_frame(prepared) call is one state transition:

  1. Preparation snapshots every required typed boundary input and finite nondecreasing model time in seconds. Execution rechecks readiness, incarnation, time and sequence capacity before staging.
  2. The engine evaluates the frozen schedule once. Algebraic blocks compute from current inputs; stateful blocks compute from call-entry state and any feedthrough inputs their contracts use.
  3. After all emissions, every stateful block updates once from current-call inputs.
  4. The engine refreshes latest-state inspection and returns immutable CompletedFrame boundary outputs and warnings, correlated by an engine-lifetime accepted-frame sequence.

Repeating a timestamp does not repeat an observation of the same transition. Every successful call advances state again. Time-dependent blocks see zero elapsed time, but call-based state still changes. The engine performs no hidden same-time evaluation, event queue processing, rollback, or fixed-point search.

Ordinary refusal preserves the full execution image, including connector values, clocks, words, restore readiness and sequence. Previously retained completed diagnostics are unchanged. There is no sparse staging prefix, Store determinant or post-write error path.

CDL.Logical.Pre

The upstream Buildings Pre block defines y = pre(u) as a delay of one Modelica event iteration without advancing time. Event iteration continues until u == pre(u). HostTick v1 deliberately uses a different projection:

BoundaryHostTick v1 behavior
State allocationOne Boolean memory word is seeded from pre_u_start.
Before the first tickThe output connector has its Boolean connector seed, false; allocating state does not execute the block.
First successful tickPre emits pre_u_start, then latches current u.
Later successful ticksPre emits the u latched by the preceding call, then latches current u.
Repeated t_nowEach call advances the memory once, even though model time is unchanged.
Feedthrough graphPre reports no direct feedthrough and cuts a scheduling cycle.

Schedule acceptance is based on direct-feedthrough shape, not event convergence. A Boolean loop such as Pre -> Not -> Pre is accepted and alternates once per tick call. That network has no same-time Boolean fixed point, but HostTick v1 neither rejects it nor emits a non-convergence diagnostic.

Host observation

get_output and watch are latest-state, non-receipt inspections. Load, restore and parameter resume can replace their state without a frame. They may read internal connector points that are not boundary outputs. There are no intermediate event-iteration rows or raw output-arena accessor.

CompletedFrame retains accepted inputs and executable root boundary outputs in lexical identity order, plus the Warning diagnostics emitted by that native transition. It is independent of the mutable latest view. Its engine-lifetime sequence increases only on accepted frames, never on refusal, and survives reload/resume/restore without rewinding. The private context fence is not serialized and is not host deployment authority. See the frame contract for preflight precedence.

The separate canonical replay record captures this receipt without reevaluation. Exact replay compares raw values and diagnostic fields, not tolerances. Hosts authenticate order, build/executable context and optional state sidecars, then drive the same prepare/execute surface. No sequence container, embedded snapshot, profile selector or second evaluator is introduced.

Do not drive event iteration by repeatedly executing frames with the same timestamp unless repeated HostTick state transitions are the intended behavior. Those calls also update every other stateful block, not only Pre.

Snapshots

A state snapshot stores both sides of the call boundary: connector values contain the currently visible Pre output, and the block’s Boolean state word contains the value to emit on the next successful call. Capture and restore do not evaluate the model. After restore, a call at the restored timestamp is a new HostTick transition.

HostTick v1 is part of execution-state ABI revision 2 even though the profile name is not a separate wire field. A future same-time event-iteration profile must use a distinct execution-state ABI revision or a newly revised manifest and codec with profile identity. It must not consume HostTick v1 snapshots as semantically equivalent state.

ABI revision 2 preserves HostTick v1 and adds the exact executable IO acceptance domain to the state manifest, alongside format revision 2. See the state compatibility contract. Build/deployment authentication is a mandatory host-envelope precondition before byte decoding; OCE carries no build token. State compatibility does not authenticate a build or authorize actuation.

Conformance boundary

Open Control Engine supports the CDL.Logical.Pre interface and the HostTick v1 behavior above. It does not claim exact Modelica or OpenModelica same-time event-iteration equivalence for Pre, or for a network whose result depends on that iteration. Pre is therefore excluded from expected-green OpenModelica differential claims under this profile.

The boundary is pinned through the public facade in crates/oce-api/src/tests/pre_execution_profile_tests.rs. The tests cover initialization, repeated equal-time calls, all host output views, a non-convergent Boolean feedback loop, and snapshot/restore continuation.

The Tier-A references for Generic.TimeSuppression, CoolingOnly.Controller, and ReliefFanGroup likewise check HostTick v1. Their 20 signal records are independent of oce-blocks, but they do not claim Modelica Pre event-iteration equivalence.

Verification and evidence

The generated authority summary mechanically inventories raw provenance records only. Evidence independence and semantic coverage remain review-only, not inferred counts.

This page is for someone deciding whether to trust this engine near real equipment. It answers one question: what has actually been proven about the Open Control Engine, and what has not.

Three questions are worth asking of any system that claims to be verified. What can tell it that it is wrong? Is that thing independent of the system it is judging? And will it tell you which checks it is not running? This page answers them in that order, and every count on it can be reproduced from a clone with find and grep.

The short version first, because it is the part that matters most: four named cases have been executed through OpenModelica 1.25.1 against pinned Buildings and MSL sources. Nand covers all four two-input Boolean states; Toggle covers one exact stateful event schedule with initially true input, repeated rises, and clear priority; Line covers four limit modes across five finite input regions. Reliefs covers one seven-state exact-bit case for a composed G36 leaf. The global Tier-3 report remains skipped; no full-sequence, arbitrary Real, general tolerance, solver, or cross-architecture raw-byte claim follows from these cases.

CDL.Logical.Pre is outside those executed-reference claims. The engine’s fixed HostTick v1 profile delays Pre by one HostTick transition rather than one same-time Modelica event iteration. The profile is covered by exact engine contract tests, not by an OpenModelica oracle, and no broader stateful conformance claim follows from it.


What can tell this engine it is wrong?

Six evidence layers here are called “tests”. They prove different things, and the most visible of them proves nothing about correctness at all.

LayerArtifactCountIndependent of the engine?
Tier-2 determinism goldenscrates/oce-conformance/tests/fixtures/golden/g36_traces/46 traces + 46 .prov.jsonNo — engine self-output, by construction
Tier-A source referencestools/golden-gen/goldens/392 provenance records, 390 signal goldensYes — CI-enforced code-dependency firewall
Tier-A HostTick profile referencestools/golden-gen/goldens/G36/20 signal goldensIndependent implementation of the engine profile; not a Modelica oracle
Structural oraclethird_party/modelica-buildings-cdl/cxf/44 vendored translations; 31 comparable fixturesYes — an independent translation of the same upstream source
Tier-1 per-block oracle comparisonscrates/oce-conformance/tests/per_block_*.rs15 suites; 278 CDL signal goldens: 257 existing exact plus 21 exact on qualified Linux, unchanged 1e-12 aligned band on unqualified targets; bounded receiptYes — Tier-A generator is outside the engine workspace
Scoped Tier-3 cross-implementation differentialscrates/oce-conformance/tests/fixtures/open_modelica/4 named cases: 2 Boolean, 1 finite Real matrix, and 1 composed G36 leaf; global report skippedYes — pinned OpenModelica and Buildings execution

Tier-2 determinism goldens — they catch drift, not wrongness

Each of the 46 ASHRAE Guideline 36 fixtures carries a committed whole-sequence trace and a sidecar provenance record. All 46 records say the same two things:

{ "tier": "2",
  "source": "engine self-output (determinism snapshot); NOT a correctness oracle",
  "depends_on_oce_blocks": true }

That is not a caveat added by this page; it is a field in every one of the 46 files, and it is the whole meaning of the layer. These goldens were produced by running this engine and committing what it printed. If the engine computes a sequence wrongly and keeps computing it wrongly, all 46 pass forever. They detect one thing well: that a code change moved a number that was not supposed to move. Call that drift detection, and do not call it correctness.

Each record also carries a content_sha256 binding it to the bytes of its CSV, checked per PR by crates/oce-cxf/tests/golden_provenance/mod.rs. That guard is honest about its own limit in its first paragraph: editing a CSV together with its digest passes by design. It detects drift between two checked-in artifacts, not fabrication of both.

Tier-A references — 412 records behind a code-dependency firewall

The reference layer is generated by tools/golden-gen, a crate deliberately held off the workspace. The repository’s workspace is members = ["crates/*"] (Cargo.toml:24), and tools/golden-gen/Cargo.toml declares an empty [workspace] table so the root workspace cannot absorb it. Its entire dependency list is libm, ryu, and serde_json — no oce-* crate.

That is enforced mechanically rather than by convention. .github/scripts/check-golden-gen-anti-tautology.sh runs cargo metadata over the generator and fails if any package named oce-* appears anywhere in its dependency graph. It fails closed: a cargo metadata error, or output that does not contain the golden-gen package itself, is a failure rather than a pass. It runs as its own CI job and again inside .agents/gate.sh.

The layer contains 412 Tier-A provenance records, every one of them recording "tier": "A" and "depends_on_oce_blocks": false — counts verified across the tree, with zero records carrying true. Of these, 392 records check source semantics and 20 check HostTick v1:

  • 280 CDL records — 278 per-signal goldens, plus two fold-time provenance-only records with no CSV beside them (goldens/CDL/Types/types.prov.json, pinning enum ordinals, and goldens/CDL/Constants/constants.prov.json).
  • 132 G36 sequence signal goldens, spanning all 46 fixtures.

410 of those are signal goldens — 389 with existing exact comparisons and 21 inventoried accepted Linux exact cases, still aligned-tolerance on unqualified platforms. The retained native receipt records run 37382761120: Linux x86_64/aarch64 × debug/release, two byte-identical runs per cell, 21 signals and 161 samples per run, zero exact mismatches against Tier-A and across cells. Raw bits, synthetic merge checkout provenance, exactly the 35 selected source paths/digests and oracle/input/CXF integrity are checked permanently, without requiring a later HEAD to equal the captured SHA. PC-037 is CURRENT only for this pinned corpus. macOS-arm64 remains unqualified until M06-PR02; neither libm mathematical correctness nor arbitrary-input or whole-engine exactness follows. No Sim policy changes. The selected-boundary receipt is admitted and the ordinary current-qualification test validates it. The original 17-source receipt from run 35492290613 remains immutable history, not current qualification. The current collection run’s successful numerical comparison is not evidence of final green hosted gates; the linked receipt distinguishes those outcomes. The reviewed 35-file map covers checker/admission/comparison/workflow/direct formula/harness and supporting sources, not the full compiled transitive facade closure. Exact-head hosted cells (x86_64 native, aarch64 QEMU-emulated) rerun oce_api::Engine per PR and catch changes under the pinned corpus’s comparison rules. An unbound transitive source change preserving all pinned outputs does not invalidate the historical raw result. Source digests alone do not prove current whole execution semantics. The 278 CDL signals are compared by the 15 crates/oce-conformance/tests/per_block_*.rs suites through a shared harness that drives each block through the frozen facade, asserts the comparison is unmasked, and asserts compared_points == reference.n_rows so a zero-row comparison cannot pass vacuously. Twelve of the 15 suites run ComparisonMode::Exact with zero tolerances (crates/oce-conformance/tests/block_harness/mod.rs), and four select each of their 21 libm-dependent Real goldens through the inventory: exact on the two qualified Linux targets, ComparisonMode::AlignedTolerance at the unchanged 1e-12 band elsewhere: per_block_reals_transcendental.rs, per_block_reals_sources_transcendental.rs, per_block_psychrometrics.rs, and per_block_utilities.rs — with per_block_reals_sources_transcendental.rs counted in both, because its two CalendarTime cases compare exactly while its single Sin case is inventoried. Boolean outputs in the aligned suites still compare by bits even in that mode (crates/oce-conformance/src/aligned.rs:214), so all 278 CDL goldens are exact on qualified Linux targets, versus 257 exact and 21 aligned elsewhere. The 132 G36 signals are compared by 23 *_funnel.rs and four *_oracle.rs per-fixture suites in the same directory. Their recorded comparison regimes tally exactly: 102 Value::bit_eq f64, 18 exact encoded integer, 12 exact 0.0/1.0.

The semantic claim is narrower than the 410-comparison count. 390 signal goldens check CDL / Buildings source semantics: 369 existing exact and 21 native-matrix-qualified Linux cases (conservative aligned-tolerance on unqualified platforms). The remaining 20 exact G36 signals belong to Generic.TimeSuppression, CoolingOnly.Controller, and ReliefFanGroup. Those references are independent of oce-blocks, but their CDL.Logical.Pre recurrences implement HostTick v1 and are labeled as profile checks rather than Modelica event-iteration oracles.

“Bit-exact” here has a precise definition, in crates/oce-conformance/src/exact.rs: finite Reals compare by IEEE-754 bits, NaN compares equal to any NaN payload, and each signed infinity compares only to itself. Integer and Boolean cells compare their encoded values exactly.

The L1 funnel band is an additional layer applied over the 102 Real G36 outputs, never the primary check. Boolean and Integer outputs are deliberately kept off it, because the funnel is type-blind and a band could otherwise admit a value between two discrete levels (crates/oce-conformance/tests/g36_funnel_band/policy.rs:17-21).

The structural oracle — it bounds fixture fidelity, not block behavior

Every conformance test in the workspace derives from the same 46 catalog fixture documents. The 47th CXF document, member_list_interface.jsonld, is a resolver contract fixture and has no conformance trace. A structurally wrong catalog fixture therefore fails nothing — it makes the entire suite validate the wrong sequence, consistently and permanently. Neither goldens nor oracles can see that, because both are computed from the fixture.

The check that can is crates/oce-cxf/tests/fixture_structural_oracle.rs. It compares each fixture against modelica-json’s independent CXF translation of the same upstream G36 class — 44 .jsonld documents vendored under third_party/modelica-buildings-cdl/cxf/, at Buildings commit a131864e4c4df22ebcd52bb8da439de0087ac365 and modelica-json commit 85721b828a6ff8d9d3c1a48ff9a59808d2fa31fb, pinned and byte-checked by a hash manifest in both directions. The comparison flattens the oracle’s composite hierarchy, resolves every conditional against the fixture’s own parameter values on both sides, canonicalizes array instances and vector ports, and compares instances and undirected edges — counting orientation flips separately, since CXF §8.2 admits either endpoint as the isConnectedTo subject.

The verdict table is itself a committed golden (crates/oce-cxf/tests/fixtures/golden/structural_oracle_verdicts.txt), and its VERDICTS line reads:

VERDICTS: EXACT=30 EXACT-XFOLD=1   EXCLUDED=15

So: 30 EXACT plus 1 EXACT-XFOLD over the 31 comparable fixtures. The XFOLD case is multizone_vav_economizer_controller_single_damper_relief_damper_fixed_21, where one constant-folded subtree (ecoHigLim, 146 reference instances against 0 of ours) is excluded and named in the golden. The 15 excluded fixtures are listed with reasons and are never counted as passes: three are this repository’s own compositions with no upstream class, and twelve are parameter-specialized reductions of AirEconomizerHighLimits that are structurally unverifiable by design.

State the limit next to the result: this compares graph structure only. It contains no numerics and executes no engine code path. It bounds how faithfully the fixture corpus represents upstream G36. It says nothing whatsoever about whether a block computes the right number.

Tier 1 and the global Tier-3 report

This is not a footnote. It is the boundary of everything above.

When conformance report assembly succeeds, it emits five tiers. Tier 1 and Tier 3 are hard-coded as skipped on every successful path:

  • Tier 1 — per-block “same response” against the Buildings library. Skipped with the summary "per-block Buildings-oracle comparison is the per-block corpus, not a full-sequence run" (report.rs:131-136). A per-block corpus does exist — the per_block_*.rs suites described above — but it compares against re-derived references, not against Buildings executed output, and it is not wired into the tier report.
  • Tier 3 — the global row remains skipped because the report cannot represent partial external coverage (report.rs:138-143). The separate Nand, Toggle, Line, and Reliefs tests do not enter the report.

The scoped Nand fixture retains two byte-identical raw OMC runs, one semantic And control, strict raw-to-canonical projection, and the exact facade comparison. Toggle retains two byte-identical raw runs, a one-token Latch control, and the same class of projection and facade checks at explicit event instants. Both are native linux/arm64 cases.

Line retains two repeat-identical native runs on each of linux/arm64 and linux/amd64, with both platform manifests and configs under the shared OCI index. Its canonical bytes match across the two architectures. Four closed views drive the public facade at the emitted timestamp bits and compare OpenModelica and engine outputs against an independent 40-cell expected-bit table. Each architecture retains keep-first canonical output and inspection metadata. The structural sentinel executes the explicit keep-first path again from retained raw input, compares both artifacts byte-for-byte, and derives input-schedule mismatches at rows 2, 4, 6, and 8. This projection control is not a facade-comparator control, and this stateless block can remain green when given internally consistent pre-event rows. The external limit-flag change, swapped output mapping, and four arithmetic reference mutants fail through the facade comparator at pinned rows. This proves only the fixed finite matrix; raw cross-architecture bytes, other Line inputs, non-finite values, signed zero, subnormals, and solver behavior remain outside it.

Reliefs also retains two repeat-identical native runs on each architecture and compares one strict keep-last canonical table across them. The seven retained rows are the initial state followed by the first complete five-input tuple change. The facade drives those tuples at the emitted timestamp bits and reads only the declared yOutDam and yRetDam roots; topology checks bind those roots to the internal Min and Max drivers. All 14 output cells are compared exactly against an independent bit table. A parameter-only uOutDamMax change, swapped root mapping, a nonexistent root, keep-first projection, and inconsistent final limits exercise separate failure or overwrite paths. The final limit control fixes all 21 raw input tuples plus the seven selected tuples, timestamps, and source rows before checking the overwritten outputs. This is one composed G36 leaf at one parameterization, with no claim about other G36 classes or parameters.

Each native architecture record binds every checkout file used through native artifact publication, including workflow, sandbox helpers, OCI metadata, wrappers, and canonicalizer tool inputs. Assembly and final-manifest generation happen after native publication, so their scripts are bound by the final artifact manifest rather than represented as native generator inputs.

Reliefs records one immutable generation contract in crates/oce-cxf/tests/open_modelica_reliefs_reference/generation-revision.json. Its revision is the exact checkout observed while the candidate native artifacts ran, not a reachability requirement. During generation, each record must equal checkout HEAD. The candidate assembler requires both native records to share that observation and the complete generator-input digest map, verifies the current exact input bytes, and emits the candidate contract and manifest together. Retained validation uses those committed bytes and does not inspect Git history. Zero, stale, unrelated, or rehash-substituted observations still fail the fixed contract.

The Line and Reliefs workflows install Rust 1.97.1 and Python 3.13.7 for host-side artifact processing and record compiler, Cargo, Python, and architecture identities in each native log. MSL materialization uses git archive with the explicit Modelica/package.mo -export-subst attribute override. For the pinned source, both the committed and materialized Modelica/package.mo SHA-256 values are c3a060fc29842aaf3b7a565b93dbe80fe29d6a769848e3b077f5101117a65191; separate fields preserve the boundary even though the bytes are equal. Buildings uses no local attribute override, and its committed and materialized file hashes are also recorded separately.

All four regeneration paths disable container networking. The retained Line and Reliefs workflows are manual only after evidence capture; normal CI validates committed evidence and does not run Docker. No Dymola, Spawn, FMI, whole-sequence, or Integer external case exists. These four cases cannot make the engine-wide report pass.

The Reliefs manual workflow produces and verifies one candidate native artifact per architecture, then runs the same two-architecture assembler used for ratification and uploads its candidate manifest and contract. Its green status proves that those workflow outputs enter the assembler; it does not say that the committed fixture is fresh. Admission still requires committing the emitted contract and assembled evidence, followed by retained-graph validation. Repository Python entrypoints disable bytecode writes so ignored __pycache__ files cannot pollute a generation checkout. That Python validator is POSIX-only; the equivalent Rust validator remains the cross-platform retained-evidence check and uses handle metadata on Windows.

Regeneration assumes a trusted host account, checkout, Docker client, Git and shell tools, and executable search path. Rust and Python artifact tools are pinned and recorded; that does not turn the rest of the host into sandbox inputs. The recorded sandbox limits the OpenModelica container; it does not defend against another process running as the invoking user.


Is the thing that judges it independent of it?

The answer differs by layer.

Tier-2: no, and it never claimed to be. The reference is prior engine output. Its provenance records say so in a field a script can read.

Tier-A: yes at the code level, mechanically enforced, with one honest caveat. The firewall guarantees the generator cannot import the implementation under test, so a Tier-A golden can never be a replay of oce-blocks. What the firewall cannot guarantee is derivational independence. tools/golden-gen/src/main.rs:9-11 states the caveat itself: some exact oracles share a pinned math kernel or restate the same documented recurrence the engine uses. Where that is true, a Tier-A pass is evidence about plumbing and transcription of a shared formula — not an independent check that the formula is right. A mechanical shared-kernel detector is filed as follow-up work and does not exist today, so treat the boundary between “independently derived” and “independently transcribed” as un-audited per class.

The structural oracle: yes. modelica-json is an LBL tool, not one of ours, translating upstream .mo sources this project did not author, at a pinned commit whose bytes are gated. It is also the narrowest claim: structure only, over 31 of 46 fixtures.

The scoped OpenModelica cases: yes at execution and source boundaries. A digest-pinned image executes the pinned Buildings Nand, Toggle, Line, and G36 Reliefs classes with inputs supplied by MSL sources. The wrappers contain no expected output. Each comparison remains a discrepancy detector rather than an oracle verdict: analytical evidence comes first in adjudication, and a mismatch cannot change a golden, tolerance, or report status. Their scope is four Boolean pairs for Nand, one event schedule for Toggle, one finite four-mode/five-region matrix for Line, and one seven-state exact-bit case for the composed Reliefs leaf.

One more thing an evaluator should weigh: independence of the oracle does not make the comparison independent of when it runs. See the next section.


Will it tell you which checks it is not running?

Yes, and it does so in the place where it is hardest to ignore — the end of every gate run.

The gate prints what it does not cover

.agents/gate.sh finishes by printing a literal block headed NOT COVERED BY THIS SCRIPT — a green run here does not prove these pass, listing:

  • The cross-architecture determinism matrix (the script’s report still names ubuntu-latest and ubuntu-24.04-arm; CI now runs x86_64 natively and aarch64 under QEMU emulation). One machine cannot reproduce it; CI is the only place it runs.
  • The cargo public-api surface gates for oce-api and oce-store. They need a gate-only nightly toolchain and run in release-gate.yml.
  • cargo deny check advisories. It needs network access and a writable advisory database, so it runs in advisories.yml and in the release gate’s cargo-deny job instead.
  • That these commands still match pr-gate.yml. Nothing verifies that mechanically. An attempt was made and withdrawn; the dormant .github/workflows/ci.yml:293-321 records why — every design either compared argv strings, which RUSTFLAGS=--cap-lints=allow leaves byte-identical while neutering clippy, or reimplemented enough of if: / needs: / matrix semantics to become its own untested gate. CI does execute the script (gate (light)), so every command in it gates a PR; the script says outright that this is coverage, not parity.

A light run adds a fifth line: it did not run the workspace test suite or the doctests, because the per-PR gate does not run them either.

The CI split, read in the dangerous direction

CI is dev-light and release-heavy. The per-PR gate into development runs the state-determinism subset for oce-api, oce-blocks, and oce-expr — the determinism-matrix job and the corresponding steps inside the gate script, on two architectures (x86_64 native, aarch64 under QEMU emulation) in debug and release codegen. The matrix emits populated portable and target-bound engine-state vectors. It requires both to match across codegen profiles, the portable bytes to match across architectures, and the target-bound bytes to differ. The job also parses the aarch64 target-bound snapshot on x86_64 and requires restore_state to return the target-domain refusal.

Separately, the scoped oce-conformance strict-bit subset runs per-PR: strict_bits and the four affected per-block suite binaries, Linux x86_64 (native) / aarch64 (emulated) × debug/release, two independent process captures per cell and fail-closed cross-cell comparison. The retained receipt covers the 21 formerly banded Real cases; Linux exact comparison is not a macOS qualification.

The remainder of oce-conformance still follows release/full-gate coverage. The complete set of 410 reference comparisons — 389 existing exact plus 21 exact on qualified Linux, unchanged 1e-12 aligned elsewhere — runs in the full local gate and release workflow (release PRs, daily cron against development, and manual dispatch). Two oce-cxf input-hygiene audits also run per-PR: fixture port order and the structural oracle, including the vendored-tree and Tier-2 digest guards. A change outside these named subsets can show every check green without running its own tests. A green PR is not evidence that the rest of a changed crate passed.

One more disclosure worth knowing before you read a PR’s checks: CI OK is the single status that summarizes the per-PR gate. Confirm it reported, not merely that nothing is red.

Full detail is in CI and the gate.

Coverage, stated as a fraction rather than a claim

Per-block Tier-A goldens cover 128 of the 133 CDL classes in the registry. The registry pins 136 entries — 133 CDL classes plus 3 reserved internal lowering classes (crates/oce-blocks/src/registry/manifest_tests.rs:21) — and the golden tree contains exactly 128 distinct CDL block class_path values, once the two non-block fold-time records are set aside.

The five without a per-block oracle, and why:

ClassStatus
CDL.Logical.Norindirect G36-sequence evidence only
CDL.Logical.PreHostTick v1 contract tests plus indirect G36 evidence; no Modelica event-iteration oracle
CDL.Logical.Sources.Constantindirect G36-sequence evidence only
CDL.Reals.MovingAverageindirect G36-sequence evidence only
CDL.Utilities.Asserthas no output port; a diagnostics-channel golden is filed, not built

“Indirect G36-sequence evidence” means the class is exercised inside sequences whose outputs are oracle-compared, so a gross error would likely surface — but nothing pins that class’s behavior in isolation against Modelica semantics. Pre is the exception to the edge-case part of that row: its HostTick profile has direct facade tests, but those tests are not an independent correctness oracle. Which classes exist and what “supported” means for sequences is in CDL coverage.


What this adds up to

If you are evaluating this engine, the defensible summary is:

  • Determinism is tested, on two architectures, per PR, against committed goldens.
  • Agreement with CDL / Buildings source semantics is bounded by 390 oracle comparisons: 369 existing exact plus 21 exact on qualified Linux and at the unchanged 1e-12 aligned band on unqualified targets. The retained strict-bit evidence applies only to the pinned corpus/toolchain. References behind the code-dependency firewall cover 128 of 133 classes; the scoped strict-bit subset runs per-PR, while the remainder needs the release/full gate.
  • HostTick v1 agreement is checked separately by 20 exact G36 signals across the three Pre-dependent fixtures. Those are profile checks, not Modelica event-iteration evidence.
  • Fixture fidelity is bounded structurally against an independent LBL translation of upstream sources, per PR, for 31 of 46 fixtures.
  • Agreement with an executed Buildings implementation is bounded for one exhaustive CDL.Logical.Nand Boolean case, one stateful CDL.Logical.Toggle schedule, one finite CDL.Reals.Line matrix, and one composed G36 Reliefs leaf. Global Tier 3 remains skipped.

The gate will tell you which broader checks it skipped. The scoped OMC artifacts do not change those disclosures.

See also: Testing standard for the bar every change is held to, and CI and the gate for what runs when.

Pinned strict-bit signal evidence

Status and claim boundary

Accepted native Linux corpus evidence: receipt data identifies the native matrix from run 37382761120, with an exact, reviewed 35-file selected source boundary covering checker/admission/comparison/workflow/direct formula/harness and supporting sources. The ordinary retained-qualification test requires every enumerated path and digest alongside the eight raw captures and reference/input inventory. This is not a complete compiled dependency closure or proof of current whole execution semantics.

The machine-readable corpus is the authoritative inventory of the 21 existing aligned-tolerance Real signal paths. It contains 161 samples: 101 finite (including 18 signed-zero samples), 52 NaNs and 8 signed infinities. corpus.json retains the original macOS aarch64 debug observation. Separately, the eight qualified native Linux captures are accepted permanent evidence for Linux x86_64/aarch64 × debug/release, two independent process runs per cell. Every run contains all 21 signals and 161 samples, with zero mismatches against the unchanged Tier-A references and across cells under the exact comparator. Every first/repeat pair is byte-identical, including NaN payloads. PC-037 is CURRENT only for this bounded observed-output result. The four suites use exact comparison for these 21 Linux Real signal cases; no Tier-A golden, comparison tolerance or bound source was changed for admission.

The local macOS debug/release tests still compare against the original observation. This is a regression observation, not macOS qualification. macOS-arm64 remains explicitly conservative/unqualified until M06-PR02; macOS and all other unqualified targets keep atoly=rtoly=ltoly=1e-12 in the four suites, with exact time/discrete comparison.

Accepted native receipt

  • GitHub Actions run 37382761120 produced all eight native captures with the selected 35-file map and a successful cross-cell comparison. It was collected for the workspace 0.2.0 version bump (GitHub #333), whose Cargo.toml and Cargo.lock bytes are bound sources; no checker, workflow, math or harness source changed.
  • The run head was f8dcaeb861fa18ef4c720b6fa2963b026f3a8300. All captures preserve GitHub’s actual synthetic PR merge checkout fbae3b96b053836b916f603a0d86c53d30bce628, not the run head or a later delivery commit. Each capture binds 35 source digests, 21 signals and 161 samples.
  • rustc: 1.97.1 (8bab26f4f 2026-07-14); libm: 0.2.16; baseline repository codegen, without custom RUSTFLAGS or CARGO_ENCODED_RUSTFLAGS.
  • Uploaded assembled artifact archive digest: sha256:0c94225cc2499e577cb597d0c36cbee138fc88bb65791872f1deb5c38e6b6576.
  • Downloaded matrix.json SHA-256: 78c5efaf4881977d306fc1f7a09c51f37cf897be338558d14aee9c2313d035d9; its comparison is {"Ok":[]}. The retained test reconstructs this exact aggregate from the eight canonical capture files and verifies the digest, without storing a redundant ninth copy. Each per-cell upload (strict-bits-<runner>-<codegen>) is byte-identical to its two files in the assembled artifact.
Native cellSHA-256 of both first and repeat capture
Linux aarch64 debug6ed52dfdeaeb04d8c270943ec8bd594c7383aa5fc37cd8beff9446d675a78751
Linux aarch64 releasef97c6d8c257ab632a1685268a6a350bc46b55ebc5bd4c685b0d8b51ec9588442
Linux x86_64 debug84d66c267e71f7f5e7ac02e7050fee99cf33ffca9b18bd81389fd7144e9368b2
Linux x86_64 release81087ece58ea985d0d11cf79df78fefe7163b0180f2b8aa05d07902a5d6772d4

The collection run was intentionally not a final green CI run: at collection time the new receipt had not been admitted. Each normal cell suite failed only its required receipt-admission check (source digest changed: Cargo.lock), the cross-cell job failed only its subsequent cell-success check, and gate (light) failed the strict-qualification prerequisite. Capture and cross-cell numerical comparison succeeded; their artifacts are evidence of that bounded result, not evidence that every hosted gate passed. Data-only admission now satisfies the ordinary retained check without changing any bound source. Admission also updates the receipts.json evidence digest in the release-compatibility matrix, which binds receipt bytes but is not itself a strict-bit bound source. Final hosted gate status remains a separate delivery check.

Historical receipts

Run 35494403523 (head d57e946126af76ed58ff10cef1e35ffbb627832f, synthetic merge d16d69a49857ea9abd35a12643e83139ca5c6a6f, matrix.json SHA-256 3f6f9efe8e4f9980a790c1a0d8eba1143111a6ae9138f0d486cca841aa19b7fc) was the previous current receipt over the same 35-file boundary. Its eight captures were replaced in qualified-linux/ by the run above and remain readable in Git history; the receipt schema keeps one historical and one current entry, so it is not separately pinned.

The original Linux captures remain unchanged historical evidence, separately pinned by receipts.json.historical:

  • GitHub Actions run 35492290613 passed all four strict-bit cells and the cross-architecture/codegen job.
  • The provisional PR head was dbce73fb20ada4a3a91653bb7ad9b48fae7ee87d. All eight captures honestly record GitHub’s synthetic PR merge checkout 8a63d4a042e1ca91d5dfe7bd3fc33d194f5102bb, not that PR head or a later delivery commit.
  • rustc: 1.97.1 (8bab26f4f 2026-07-14); libm: 0.2.16; baseline repository codegen, without custom RUSTFLAGS or CARGO_ENCODED_RUSTFLAGS.
  • Uploaded assembled artifact digest: sha256:d5b21ca70517c793f46a99d5402d236e2c78494466367fa583ccef27431c31c4.
  • Downloaded matrix.json SHA-256: 12a2fbdd28c9718c0145c5055240f0b7eab3d7898bc5d1f05ed0ee09db2978ad; its comparison is {"Ok":[]}. The historical test reconstructs this exact aggregate from the eight canonical capture files and verifies that digest, avoiding a ninth duplicate of the raw data. The upload archive digest is a provenance locator, not a locally rebuilt archive.

The historical receipt test reconstructs that aggregate verbatim. Its 17-source map omitted semantic capture/comparison/admission code now required by the selected 35-file admission boundary. The otherwise successful exact-head run 35493024355 used the same smaller source map; neither replaces the current selected-boundary receipt from run 37382761120. No old raw file, Git revision, source map or candidate label is rewritten to imply those sources were captured then.

The active current-qualification test validates the admitted selected-boundary receipt and all four cells, two runs per cell, 21 signals and 161 samples per run, zero mismatches, byte-exact repeats, capture identity and aggregate integrity. It requires exactly the 35 enumerated source paths/digests and current reference, provenance, input/CXF and time inventory. The synthetic checkout SHA is not required to equal a later HEAD or exist in local history. A historical receipt cannot bypass a missing or changed entry in that selected boundary, even when raw outputs agree. This guard does not establish source identity for the rest of the compiled execution path.

libm 0.2.16 does NOT promise cross-architecture correctly rounded transcendentals; strict identity is an empirical pinned observation only. std 1.97.1 likewise does not promise deterministic transcendentals. The versioned libm documentation and Rust f64 precision contract do not justify upgrading a finite-corpus observation into an arbitrary-input guarantee.

The Tier-A generator has an independent dependency direction, enforced by the existing golden-gen firewall, but shares formulas and libm with these engine paths. Strict-bit evidence proves implementation/plumbing/pinned portability over the corpus, not mathematical correctness or arbitrary inputs. The original provenance’s wording about deterministic math is retained verbatim as historical provenance, not adopted as a library guarantee. CDL.Reals.Sqrt and G36 funnel bands are outside this inventory. Public runtime behavior, state/snapshot portability, target-bound restore policy, algorithms, dependencies and non-21 comparison regimes are unchanged.

Artifact contract

Schema 1 is UTF-8 JSON with a single header and one canonically ordered object per signal. It is capped at 128 KiB per capture and 128 samples per signal. The header pins rustc’s full version, libm, Git revision, actual native OS/architecture/codegen cell, explicit qualification limits and SHA-256 of the selected locks, toolchain, fixture definitions, harness, checker and math source files. The Git revision names the observation’s checkout HEAD; hashes separately bind the selected file bytes and generated CXF, without claiming uncommitted bytes were committed at that HEAD or binding unlisted transitive sources.

Each signal names its class, output and unique case path; reference CSV, original provenance and generated CXF digests; original operation/recurrence rule and math-library provenance; proposed Linux (at capture time) and conservative other-platform regimes; every time, oracle value and engine value; and every exact mismatch index. Input/parameter provenance is available through the bound reference CSV and generated CXF. Cases in the four existing suites are a checked projection of this inventory, not a second manually maintained list. No oracle output is generated from engine output.

Words are label:hhhhhhhhhhhhhhhh, a lowercase, exactly 16-digit raw u64 hexadecimal encoding. Labels are finite, +zero, -zero, +inf, -inf, and nan; labels are validated against bits. Raw NaN sign/payload is retained. Cross-cell exact comparison treats all NaNs as one class, as the existing driver does; finite bits (including the sign of zero), signed infinities and time bits are exact. Repeat captures within a cell compare all raw bytes, including NaN payloads. No digest-only result substitutes for raw samples.

Unknown fields, wrong pins, missing/duplicate/unordered signals, changed provenance, incomplete sample arrays, wrong labels and hidden mismatches refuse. Selected-source, lock, toolchain or inventory changes invalidate the retained contract and need deliberate evidence review/refresh. CI cannot admit checked-in observations. Capture always refuses to overwrite an existing artifact; the historical-corpus refresh switch has been removed. Reference inventory validation is intentionally separate from qualification: matching the old reference/input inventory does not admit an old source map. Review every new observation rather than blessing changed engine output as correctness.

Non-circular source binding and admission

The source-digest list enumerates exactly 35 selected files: the original locks/toolchain/build inputs, four math implementations, four suites and three harness modules; plus strict_bits.rs, strict_bits/{evidence,matrix,controls}.rs, the facade driver and its comparison module, exact and aligned comparators, CSV/config/series/masking support and conformance module wiring, model value encoding, the conformance manifest, CI workflow, nextest configuration and gate script. The inventory/control tests require exactly this set and prove that changing any selected file refuses admission even with unchanged raw samples. Specific mutations disable non-finite equality and matrix topology checking; their source changes still refuse before receipt acceptance.

This selected boundary is not the full compiled transitive facade closure. The driver executes through oce_api::Engine, but the map does not hash all oce-api, oce-cxf, registry/lowering or other transitive implementation sources. Exact equality of its 35 digests establishes equality only for those selected bytes, not current whole execution semantics or whole-executable identity. Exact-head hosted cells (x86_64 native, aarch64 QEMU-emulated) rerun the actual facade execution path per PR and catch changes under the pinned corpus’s stated exact-comparison rules. An unbound transitive source change preserving all pinned outputs does not invalidate the historical raw result; neither the receipt nor those reruns prove behavior on arbitrary inputs.

The receipt-binding order is selected source bytes → captures → assembled matrix → receipt data. The checker hashes its own source bytes, which contain no expected source or matrix digest. receipts.json is a closed, terminal data schema containing historical and optional current Git/matrix identities; it contains no executable policy and is not hashed back into captures. Current admission checks those reviewed identity values against actual raw data and current selected-source digests. Final admission changes only receipt/fixture data and documentation, not bound checker code. This avoids both a self-hash fixed point and a matrix-digest cycle; it is not a migration bypass.

The current receipt was admitted through the explicit admission test with OCE_STRICT_MATRIX_DIR naming the download. It validated the selected source entries, reference/input data and the exact aggregate before creating qualified-linux/; all eight retained files equal the downloaded bytes. The test refuses CI and replacement of an existing directory, so a refresh first removes the previous qualified-linux/ (preserved in Git history) and then admits the new download. linux/ and corpus.json remain unchanged history. A bound-source change after the native run needs another native run; receipt-only admission does not. No environment switch or historical-source exception bypasses current qualification.

CI data flow and adjudication

The native receipt above was produced on GitHub Actions, on ubuntu-latest and ubuntu-24.04-arm, by the now-disabled .github/workflows/ci.yml. The per-PR gate is now .github/workflows/pr-gate.yml on GitHub-hosted x86_64 runners, and its scoped strict-bit-matrix job runs on every development PR and every push to development (and on manual dispatch) in four cells: x86_64 natively and aarch64 cross-compiled under QEMU user-mode emulation, each in debug and release. The aarch64 cells are emulated, not native: they keep the cross-cell comparison running per PR but cannot produce admissible native aarch64 evidence. Each cell clears cached evidence, makes two independent nextest process captures at that PR’s checked-out revision (each also repeats the actual facade drive internally), compares their bytes, and runs the four suites plus inventory, strict wiring and hostile controls. Every cell runs even after an earlier one fails. CI does not retain the raw captures: they exist only in the job’s workspace and its log output.

The same job then runs the otherwise-ignored matrix test over all eight files. The checker requires exactly the four cells with first/repeat captures, matching Git and selected-source provenance, the complete signal/sample inventory, and exact oracle and cross-cell agreement. Missing data is failure, not a skipped signal. It emits matrix.json, containing all eight raw captures and signal/sample/expected/actual mismatches, alongside the input files even on a numerical disagreement; like the captures, it is not retained by CI. Success also requires every cell’s wiring and mutation controls to pass; matching output files cannot mask a failed cell test. Synthetic matrix tests exercise each signal in every cell, missing/extra/mislabeled cells, repeat drift, pin/provenance drift, signed zero and NaN class rules; synthetic fixtures are never native evidence.

One-ULP mutations of every finite reference sample, every corpus signed zero, and relevant NaN/Inf class/sign controls run through the facade driver. On Linux they also run through the actual four-suite policy selector and assert the exact signal/sample failure; comparator-only unit tests are not the sole evidence. Unmodified neighboring outputs stay green.

The successful native artifacts above are now checked in, rather than relying on a 90-day upload. The explicit, ignored admission test verifies the downloaded aggregate digest, every canonical capture, the zero-mismatch result and current selected-source equality before creating the retained directory; it refuses replacement and cannot run in CI. It copies native evidence, never local engine output. Ordinary retained validation is read-only and needs no environment opt-in. Future divergence leaves the gate red with exact mismatch bits. Adjudicate the path before any claim or regime change; no automatic downgrade, tolerance widening, bit-pattern acceptance, formula rewrite or Tier-A regeneration is authorized. A changed selected source, pin or corpus needs a new reviewed native receipt, not a rewritten historical observation or environment bypass.

The gate script runs the scoped tests in both codegen profiles locally, but cannot reproduce a cross-architecture result on one machine. The hosted matrix is separate from the existing state-artifact determinism matrix; neither substitutes for the other. The required CI OK status needs the strict-bit job’s success, so the check is not merely an optional status. A future native evidence refresh needs a native arm64 runner; changes to branch protection remain the orchestrator’s responsibility.

Downstream notice

Whole-executable exactness inherits the least-qualified contributing path, target and input domain. These 21 finite-corpus results do not qualify arbitrary compositions, arbitrary inputs or a whole executable; unqualified paths/platforms still prevent such an inherited exactness claim.

This schema is compact, inspectable input for later Open Control Sim consumption, not a Sim policy change or qualification. The supplied downstream inventory says Sim has no active oce-api dependency; its matching libm pin is context only. Sim policy adoption remains M05-PR07. No Sim checkout, source, dependency, tolerance policy or platform claim is changed here.

CDL coverage: what “supported” means

For anyone asking “does this run my sequence?” This page states which CDL classes the engine implements, what the 46 conformance fixtures actually are, and — the part that matters most — what the word “supported” is doing in each of those sentences.

The elementary-block registry

The engine registers 136 block classes: 133 CDL elementary classes plus 3 reserved internal lowering identities.

FamilyClasses
CDL.Reals52
CDL.Logical26
CDL.Integers24
CDL.Routing15
CDL.Discrete7
CDL.Conversions4
CDL.Psychrometrics3
CDL.Utilities2
CDL total133
Reserved lowering identities3
Registry total136

Every number in that table is checkable against the checked-in tools/reference-catalog/oce-blocks.registry-manifest.json, a 136-entry ordered JSON array generated from the registry itself and held byte-identical to it by registry::manifest_tests::checked_in_manifest_matches_regenerated_bytes in oce-blocks. The total is separately pinned at crates/oce-blocks/src/catalog_tests.rs:27.

The three reserved identities are urn:oce:lowering#PassThrough.Real, .Integer, and .Boolean (crates/oce-blocks/src/lowering.rs:66-78). They are what CXF import synthesizes for CDL’s direct boundary input→output connect. They are not authorable CDL — a hand-written CXF document cannot spell them — and each carries reserved: true in the catalog so a palette-building host can filter them out (crates/oce-blocks/src/catalog.rs:98-100).

Introspecting the registry at runtime

oce_blocks::catalog() (crates/oce-blocks/src/catalog.rs:155) returns the registered classes in deterministic registry order. Each CatalogEntry (crates/oce-blocks/src/catalog.rs:74-101) carries resolved input and output ports in declaration order, the port-naming policy, the static parameter rules, the authored parameter defaults, a width_driven flag, a conservative stateful hint, and the reserved flag. This is an actively supported companion API, not an internal implementation detail; its relationship to the primary facade is classified in the public surface contract.

The defaults are honest. DefaultSource (crates/oce-blocks/src/catalog.rs:47-57) has three variants — Literal, Derived { formula }, and Required. A parameter the caller must supply reports Required, not an internal fallback value dressed up as a default. That distinction is the whole reason the type exists.

The caveat that will cost you ten minutes: catalog() lives in oce-blocks, and oce-api does not re-export it. Reading crates/oce-api/src/lib.rs, the string catalog does not appear anywhere in crates/oce-api/src/ — the pub use block at crates/oce-api/src/lib.rs:50-77 re-exports Engine, the error types, ExportReport, IO and sim types, LoadReport, the parameter table, the topology view (Topology, TopologyBlock, TopologyConnection, DeclaredOutput, PassThroughPair), oce_diag::Diagnostic, oce_model::{ConnectorId, Value, ValueType}, and oce_store (including SemanticQuery) — and nothing from oce_blocks. oce-api depends on oce-blocks (crates/oce-api/Cargo.toml:29) and uses it internally, but a consumer depending only on oce-api must add oce-blocks as its own dependency to call catalog().

What “supported” means for a G36 sequence

The repo’s own catalog is explicit, and it under-claims on purpose. The support_policy block in tools/reference-catalog/Buildings.Controls.OBC.ASHRAE.G36.catalog.json records runtime_sequence_status: "selected-explicit-cxf-variants-supported" and states that supported rows “are limited to the listed checked-in explicit-CXF variants and do not imply arbitrary ASHRAE G36 composite support.”

Concretely, what exists today is:

  • 43 pre-specialized runtime variants (runtime_sequences, all status supported-runtime-sequence) over 31 distinct canonical class paths.
  • 3 hand-authored fixture-only fragments (fixture_only_sequences, status supported-fixture-only): ahu_supply_air_temp_reset, ahu_economizer, and vav_single_zone. Each carries canonical_g36_class_path_status: "fragment-of-canonical-source-not-runtime-sequence" — they are pre-flattened CXF graphs built from supported CDL elementary blocks, with source-reviewed-fragment evidence, and they make no canonical runtime-sequence claim.

All of them are pre-flattened CXF at specific parameterizations. A variant is a fixture at a fixed set of parameter values, not a general instantiation of the class.

The support vocabulary itself is defined at tools/reference-catalog/README.md:77-86. Note that one of its three terms, supported-import-fixture, currently labels zero rows — grepping the G36 catalog for that string returns no hits. It is defined vocabulary, not present state.

The promotion bar

A canonical class path is promoted to supported-runtime-sequence only once all six of these exist (tools/reference-catalog/README.md:77-80), and each runtime row carries the corresponding field:

RequirementField on the catalog row
Canonical Buildings.Controls.OBC.ASHRAE.G36.* class pathclass_path
Source provenancesource
Supported parameter variantssupported_variant
Fixturefixture
Deterministic golden tracegolden_trace, determinism_provenance
Independent oracle evidenceoracle_reference, oracle_test

A missing element is a missing promotion. That is the bar, and it is why the supported set is small.

The engine does not read Modelica

The engine executes the block graph that CXF hands it. It does not parse or flatten Modelica .mo sources. oce-flatten is a reserved seam and an identity passthrough today: CXF arrives already flattened and monomorphic, the oce-cxf resolver owns lowering, and full Modelica elaboration — parameter propagation, expression folding, conditional-instance removal, replaceable/redeclare/extends — is explicitly deferred (crates/oce-flatten/src/lib.rs:2-20). If your sequence exists only as .mo, something upstream has to produce CXF first.

CDL.Logical.Pre uses HostTick v1

Registry support for CDL.Logical.Pre means the engine accepts its interface and executes the fixed HostTick v1 profile. It does not mean exact Modelica event-iteration equivalence. Pre emits pre_u_start on its first successful host tick, then emits the input from the preceding successful tick. Repeated calls at one timestamp are separate state transitions.

The scheduler treats Pre as a feedthrough cut and accepts a feedback loop without proving that the corresponding Modelica event iteration converges. This semantic projection applies to any fixture containing Pre; fixture support and deterministic output do not broaden the conformance claim. The Tier-A references for Generic.TimeSuppression, CoolingOnly.Controller, and ReliefFanGroup therefore classify their 20 output signals as HostTick v1 profile checks, not Modelica source-semantics oracles.

47 fixture documents is not 47 sequences

crates/oce-conformance/tests/fixtures/golden/g36_traces/ holds 46 .csv traces and 46 matching .prov.json provenance records — 92 files. EXPECTED_G36_FIXTURES at crates/oce-cxf/tests/export_g36_roundtrip.rs:46 pins 47 CXF documents: those 46 catalog fixtures plus member_list_interface.jsonld, a resolver contract fixture with no conformance trace or G36 catalog claim.

The 46 catalog fixtures are configurations, not 46 distinct G36 sequences. Across the 43 runtime variants, the 31 distinct canonical class paths distribute like this:

  • 29 class paths appear once;
  • …Economizers.Subsequences.Modulations.ReturnFan appears twice;
  • Buildings.Controls.OBC.ASHRAE.G36.Generic.AirEconomizerHighLimits appears 12 times.

Those 12 are the air-economizer high-limit family: 4 ASHRAE 90.1 variants (differential, and fixed dry-bulb at 18 / 21 / 24) and 8 Title 24 variants (4 differential offsets and fixed dry-bulb at 21 / 22 / 23 / 24). Twelve fixtures, one class, twelve parameterizations.

So the honest reading is: 47 checked-in CXF documents comprise 46 catalog configurations covering 31 canonical G36 class paths plus 3 non-canonical fragments, and one resolver contract fixture. Any count of distinct sequences is smaller, and a public-facing number must say which set it means.

What the coverage is evidence of

Breadth of fixtures is not the same as correctness against the standard, and this repo separates the two deliberately. The evidence layers — engine-self-output determinism goldens, the per-PR structural diff against vendored modelica-json translations, and the oracle layer generated behind a code-dependency firewall — are described in ../README.md and ../TESTING.md.

The load-bearing limitation, stated plainly: no complete G36 sequence here has been executed against an external Modelica / Buildings toolchain. Scoped OpenModelica evidence covers one exhaustive CDL.Logical.Nand Boolean case, one stateful CDL.Logical.Toggle event schedule, one finite CDL.Reals.Line matrix with exact binary64 operations, and one seven-state exact-bit case for the composed G36 Reliefs leaf. The Line and Reliefs results do not cover arbitrary Real inputs, general tolerances, solver behavior, or broader G36 behavior. CDL.Logical.Pre is explicitly excluded from an expected-green OpenModelica differential under HostTick v1. The global Tier-3 report remains skipped, and no number on this page stands in for that deferred coverage.

For what happens when you export a loaded sequence back out to CXF, see cxf-round-trip.md.

CXF round-trip: what export guarantees

For an integrator writing or consuming CXF documents against the Open Control Engine. It answers one question: when export returns Ok, what is actually in those bytes — and what is quietly not?

CXF is bidirectional here. oce-cxf imports through the §7.1 resolver (crates/oce-cxf/src/resolve/mod.rs:1, reached via oce_cxf::import_cxf at crates/oce-cxf/src/lib.rs:106) and exports through a separate, deliberately smaller path (oce_cxf::export at crates/oce-cxf/src/lib.rs:200). Import and export do not cover the same ground, and the gap between them is where the surprises live.

The RT-2 contract

Export is specified as a fixpoint, not as source recovery (crates/oce-cxf/src/export.rs:5-9). For a graph G1 that import_cxf produced, re-importing the emitted bytes yields a graph that renders bit-identically to G1 — Reals compared by their IEEE-754 bit patterns, never by an epsilon (crates/oce-cxf/src/lib.rs:124-137; the fixpoint test is crates/oce-cxf/tests/export_roundtrip.rs, which compares through a hand-written renderer using f64::to_bits). Emission order derives from the ModelGraph vectors alone, so repeated exports of the same graph are byte-identical (crates/oce-cxf/src/export.rs:47-52).

The carve-out belongs right here rather than in a footnote: bit-identity holds over the survivor cone, not necessarily over the whole input graph. When nothing is deferred the survivor cone is the whole graph. When deferral fires, it is not, and no re-import can restore what was omitted. The next-but-one section is about exactly that.

That promise is for graphs produced by import_cxf. A hand-built legacy graph may carry external_inputs with an empty boundary_inputs sidecar. Export keeps accepting that shape and emits attribute-free root input declarations; re-import then materializes empty sidecars, so the re-imported graph is not structurally identical to the hand-built input even when no warning was reported (crates/oce-cxf/src/lib.rs:139-143).

What never round-trips at all: cosmetic source content. Labels, layout, and line numbers are not in ModelGraph, so none of them come back. The original root @id is not recorded either — the root composite is emitted under the fixed synthetic IRI urn:open-control:cxf-export:root (crates/oce-cxf/src/export.rs:11-14).

The export subset

Export accepts the flat, ground, single-root, scalar-parameter subset — the shape the resolver produces (crates/oce-cxf/src/lib.rs:114-120). Everything outside it is a typed CxfError::Validation carrying DiagCode::ExportUnsupported error diagnostics whose subject is the offending block, connector owner, or declared boundary node. Never a panic (crates/oce-cxf/src/export.rs:61-64).

Of the §7.4.1 connector attributes, five survive, each emitted as a bare JSON scalar on minted child ports and represented root boundary-input and boundary-output nodes. A boundary input keeps its declaration attrs separate from every child target, including fan-out; export never infers one from the other. Engine::load_cxf joins a declared output and its source in the same §7.10 cluster: conflicting values refuse the load, while a value declared on only one side propagates to the unset peer before export. Boundary-input declaration unification is a separate acceptance change and is not implemented. Low-level callers that compose oce_cxf::import_cxf and export directly must run the graph through oce_validate to apply the current output-side load contract (crates/oce-cxf/src/export.rs:31-45):

AttributeEmitted asApplies to
unit, quantity, displayUnitbare stringReal connectors
min, maxbare number, finite onlyReal (float) and Integer (int) connectors

Attributes are emitted only when Some; an all-default connector emits zero attribute keys, which is byte-identical to an attribute-free port node.

Two attributes are rejected rather than dropped — and the distinction between rejected and dropped is the point. On a surviving block, a connector carrying nominal or unbounded fails the export (crates/oce-cxf/src/export_attrs.rs:42-55), because the importer hardcodes both to None and the value would vanish silently. A non-finite Real min/max bound is rejected for the same reason: serde_json writes it as JSON null, which re-imports as None (crates/oce-cxf/src/export_attrs.rs:56-88).

On a deferred ordinary block, none of that runs. The block is omitted from the document and therefore contributes no error diagnostic of its own — not from its connector attributes, not from its parameters, not from its boundary entries (crates/oce-cxf/src/lib.rs:168-208). A reserved pass-through with hidden state is the exception: the resolver-produced lowering shape is the only valid form in the reserved namespace, so it rejects even when an enum parameter also marks the block deferred. A boundary-input sidecar follows its target owner into that omission; invalid attrs on a declaration whose entire target set is deferred do not abort the partial export. Whole-graph guards behave differently: an empty (zero-block) graph, non-dense ids, and a connection that is not output→input reject either way, because they are attributable to no single block’s presence in the document.

The deferral trap

This is the most important thing on this page.

Ordinary enum-carrying blocks — any ValueType::Enum connector or Value::Enum parameter — are deferred, not rejected. The block and its entire transitive downstream cone are omitted from the emitted document so that the enum-free remainder can still export. Reserved pass-through blocks remain strict: an enum parameter violates the resolver-produced lowering shape, so it rejects despite being selected for deferral. Each omission is reported as a DiagCode::ExportDeferred warning, which is non-aborting (crates/oce-cxf/src/export_defer.rs:1-32). The cone is a least fixpoint: a single enum connector near the front of a chain dooms everything downstream of it.

How large does that get in practice? The G36 corpus pins two cases as tripwires (crates/oce-cxf/tests/export_g36_roundtrip.rs:678-698):

FixtureBlocks in graphBlocks deferredShare
cooling_only_controller213 (crates/oce-api/tests/g36_cooling_only_controller.rs:252)8339 %
multizone_vav_relief_fan_group226 (crates/oce-api/tests/g36_relief_fan_group.rs:105)6328 %

Rejection fires only on total deferral — a graph with no emitted runtime block left after deferred and reserved lowering-only blocks are removed, which would be an unloadable root-only shell (crates/oce-cxf/src/export.rs:112-116). In principle, then, all but one block can vanish from an export that returns Ok.

And export() discards the warnings (crates/oce-cxf/src/lib.rs:210-212 — it destructures them into _warnings). A caller using export() alone cannot distinguish a complete export from one that dropped 39 % of the graph. Both return Ok(Vec<u8>).

Use export_with_report (crates/oce-cxf/src/lib.rs:254). It returns an ExportReport with bytes and warnings (crates/oce-cxf/src/lib.rs:215-240); the bytes are identical to what export() returns for the same graph. An empty warnings list is what certifies that the round trip covered the whole resolver-produced input. The legacy empty-sidecar exception above still applies to hand-built graphs. Treat a non-empty list as “this document is a subset of the model I asked you to write.”

Through the facade, Engine::export_cxf() (crates/oce-api/src/export.rs:98) always goes through export_with_report and keeps the warnings, so the facade route does not expose the trap. It is oce_cxf::export() specifically that drops them.

Pass-through elision is not deferral

CDL allows a boundary input wired straight to a boundary output. Import lowers each such connect to a reserved internal identity block — urn:oce:lowering#PassThrough.Real, .Integer, or .Boolean (crates/oce-blocks/src/lowering.rs:66-78) — and export elides those blocks back to the bare boundary edge (crates/oce-cxf/src/export.rs:743-812, :827-842). Re-import re-synthesizes them, so RT-2 holds by render identity.

The visible consequence: the emitted document lists fewer containsBlock entries than the graph holds blocks, and a canonical imported pass-through produces no warning at all (crates/oce-cxf/src/lib.rs:144-148). Reserved connectors have no emitted child-port node, so a host-built boundary alias or connection involving a surviving reserved block is rejected rather than silently omitted. If cascade deferral omits the reserved owner, well-directed relationships follow the ordinary survivor-cone rule: they are omitted with ExportDeferred warnings. Structural direction errors still reject before survivor filtering. An authored instance identity, parameter, connector attribute, or class/type mismatch on the reserved block rejects because elision has no wire representation for that internal state. Declaration-side attrs remain representable on the emitted boundary input and output. If cascade deferral omits the reserved block, those declarations leave with it rather than appearing without a target. An empty warning list means nothing was deferred; it does not mean the document explicitly lists every internal lowering block. If you are reconciling counts between a ModelGraph and an emitted document, that is the difference to expect.

Two ways an Ok export produces bytes that fail re-import

Both are documented, and both are reachable only from a hand-built ModelGraph — never from one the resolver produced.

  1. Port arity contradicting the class. Export takes no registry dependency, so it does not check a block’s declared port count against the class its class_path names. A hand-built block naming a registered class while declaring fewer ports than that class requires exports Ok; the bytes then fail re-import with MalformedDocument (crates/oce-cxf/src/lib.rs:150-156).
  2. An unregistered class path. Same root cause, different symptom: the bytes export fine and fail re-import loudly with ClassNotFound — never silently (crates/oce-cxf/src/lib.rs:158-160).

Every graph the resolver produces is correct by construction on both axes.

content_id_complete: a checked integrity tag, not a digest

ExportReport::content_id_complete() returns cxf:fnv1a128:<32 hex chars> computed over exactly the emitted bytes when the export is complete. If any content was deferred, it returns the typed ContentIdError::Incomplete { warning_count, .. } instead of minting an identity. Its rustdoc carries a runnable reproduction of the tag computation so a host can verify a returned tag independently. Three properties worth internalizing:

  • It is explicitly non-cryptographic and not a security boundary. A host needing a cryptographic digest must hash the same bytes itself.
  • It is not LoadReport::model_id. model_id preserves the authored top-composite @id; export uses a synthetic root, and resumed parameter edits change exported bytes without recomputing model_id.
  • When warnings is non-empty, the unchecked tag would name only the partial survivor document; content_id_complete() refuses that case and reports the exact warning count. The older content_id() method remains only as deprecated compatibility behavior and should not be used to mint version identities. The checked behavior is pinned by crates/oce-api/tests/export_cxf.rs.

The import side

This page is about export. The normative contract for what nested-composite shapes import accepts and rejects — how S231:containsBlock hierarchies flatten, which shapes reject, and the machine-readable composite/<rule-id>: message tags an emitter can match on — is cxf-composite-subset.md in this directory. Read that one if you are writing a CXF generator.

Composite nesting is bounded at 64. Boundary lowering is iterative and separately rejects paths beyond 64 non-top isConnectedTo hops or documents beyond 65,536 target examinations or 8 MiB of aggregate target-IRI bytes within boundary walks. These are engine acceptance bounds, not CDL semantics. Hosts must still bound input bytes before JSON deserialization when accepting untrusted documents.

For which CDL classes and G36 sequences exist on the other end of that pipe, see cdl-coverage.md.

The CXF composite-subset import contract

This is the canonical statement of which nested-composite CXF shapes the Open Control Engine import accepts and rejects. It is written for the author of an external CXF-emitting tool who has never read the engine source. The behavior described here is what oce_cxf::import_cxf — and therefore Engine::load_cxf in the oce-api facade — enforces; every rule is pinned by tests against the checked-in conformance corpus (see Testing your emitter).

Scope: this contract covers the composite subset of CXF lowering — how S231:containsBlock hierarchies flatten, which hierarchy shapes reject, and the document-wide rejection of active array-valued connector and block-instance nodes. Other leaf-block semantics, connector typing, and post-lowering validation (unit checks, single-assignment) have their own diagnostics and are out of scope here.

Resource bounds are part of this engine’s accepted subset, not CDL semantics. containsBlock nesting is limited to 64. Boundary lowering is iterative and permits at most 64 non-top boundary hops per path, 65,536 target examinations, and 8 MiB of aggregate target-IRI bytes across boundary walks in the document. Exceeding a boundary limit returns MalformedDocument without constructing a partial flat graph. Ordinary direct leaf wiring does not consume the boundary-work budgets.

How rejections are reported

Every rejection is a diagnostic with three parts:

  • a DiagCode (a stable kebab-case code string, e.g. malformed-document),
  • an optional subject (the @id of the offending node, where one exists),
  • a message.

The rejecting rules below — except Rule 6, whose rejections are generic diagnostics with no tag — are contract rules: their messages begin with a stable machine-readable tag of the form composite/<rule-id>: (note the single trailing space after the colon). Match rejections with message.starts_with("composite/<rule-id>: "); the rest of the message is human prose and may change. The tag-to-code mapping is published twice — in the rule catalog table below and as the machine-readable artifact tools/reference-catalog/oce-cxf.composite-rules.json — and a drift-guard test holds this document, the artifact, and the emitting code to the same catalog identities.

Rules 1 and 3 are non-rejecting classification and ordering rules. They carry no DiagCode, no message tag, and no catalog entry — there is nothing to match, because they never fail. They are stated here because an emitter that misunderstands them produces a model that imports cleanly but means something else.

JSON-LD fragments below are illustrative: they elide @context, connector nodes, and unrelated keys. Complete importable documents live in the conformance corpus.

Active nodes

Source profiles may mark components and connectors conditional (S231:isConditionalComponent: true plus an S231:conditionalExpression guard). At load time the guard is evaluated against the owning composite’s own grounded parameters and constants; a false guard makes the node — and, recursively, its inputs, outputs, parameters, constants, and contained blocks — inactive. Everything else is active.

Rules 3, 4, 5, and 7 do not traverse inactive child components, so their whole subtree drops out of the leaf order. During lowering, an active composite’s own scope excludes parameter or constant declarations marked inactive — an inactive array-valued declaration does not reject. Leaf parameter binding is different: an active leaf grounds its ordinary S231:hasParameter and S231:hasConstant declarations, and its node-bearing classified S231:hasInstance parameter members, without filtering activity. Banned Modelica keys or S231:isReplaceable on an inactive node are tolerated. Root classification (rule 2) does not consult activity. Connections are not exempt: an active connection into or out of an inactive node rejects with the generic inactive-conditional-node diagnostic — prune conditional structure so inactive nodes take their connections with them.

Rule 1 — Composite discriminator (non-rejecting)

A node is a runtime composite if and only if its S231:containsBlock list is non-empty AND its @type does not resolve to a registered leaf block class. A node whose @type resolves to a registered class is a leaf even when it carries S231:containsBlock — the carve-out that keeps protected implementation children out of composite classification. Classification never rejects; rule 1 has no DiagCode.

@type resolution operates on the @context-expanded form: the token is first expanded against the document @context (a CURIE with a declared prefix becomes its absolute IRI; anything else stays as written — a typing token is never refused), then take the fragment after the last # (the whole string when there is no #), strip a leading Buildings.Controls.OBC., and look the remainder up in the native block registry (published as tools/reference-catalog/oce-blocks.registry-manifest.json). So http://example.org#Buildings.Controls.OBC.CDL.Reals.Add — or the compact ex:Buildings.Controls.OBC.CDL.Reals.Add under "ex": "http://example.org#" — resolves to the registered class CDL.Reals.Add and is a leaf; S231:Block (expanded, http://data.ashrae.org/S231P#Block) or a vendor class path resolves to nothing and — with children — is a composite. Note the contrast with rule 7: identities and typing tokens expand; property KEYS match by suffix — the banned-key and array-marker matching below stays on the term after the last :, #, or /, whatever the spelling.

{ "@id": "…#M.sub", "@type": "http://…#Vendor.Sequences.ScaleAndForward",
  "S231:containsBlock": { "@id": "…#M.sub.gain" } }

is a runtime composite, while

{ "@id": "…#M.con", "@type": "http://…#Buildings.Controls.OBC.CDL.Reals.Sources.Constant",
  "S231:containsBlock": { "@id": "…#M.con.protected" } }

stays a leaf: it imports as a normal CDL.Reals.Sources.Constant block and the protected child is elided (corpus fixture accepted/registered_leaf_carveout.jsonld).

Rule 2 — Single top root (rejects: composite/root-count)

After classification, exactly one runtime composite must be unreferenced by any other runtime composite’s S231:containsBlock. Zero candidates, or two or more, reject with composite/root-count (DiagCode malformed-document). With two or more candidates the message enumerates every candidate in @graph order and the first candidate is the subject. With zero candidates the diagnostic carries no subject — there is no candidate to name.

Normative consequence: a pure composite containsBlock cycle (every composite referenced, no root at all) classifies as zero roots and is reported as composite/root-count, never as composite/contains-cycle (corpus fixture rejected/pure_cycle.jsonld). The cycle detector of rule 4 only runs below a valid single root.

{ "@id": "…#M",  "@type": "S231:Block", "S231:containsBlock": [ … ] },
{ "@id": "…#M2", "@type": "S231:Block", "S231:containsBlock": [ … ] }

rejects with subject …#M and a message ending found 2 candidate roots: …#M, …#M2 (corpus fixture rejected/multi_root.jsonld).

Rule 3 — Nesting traversal order (non-rejecting)

Active composite children lower depth-first in S231:containsBlock array order; inactive children are skipped along with their entire subtrees. This flat leaf order assigns BlockId and block decl_order, not authored connector IDs. Authored connectors are numbered by their own @graph positions. Non-rejecting; no DiagCode.

"S231:containsBlock": [ { "@id": "…#M.sub" }, { "@id": "…#M.post" } ]

lowers …#M.sub’s leaves (depth-first) before …#M.post.

Ordering and identity contract

This table is the OCE executable profile, not a general JSON-LD ordering guarantee. OBC CXF §8.2 defines containsBlock as a relation between blocks; it does not define the executable array-order semantics below. The OBC code-generation example (§11.3) shows authored arrays and describes modelica-json recursively flattening composites to elementary blocks. Neither that example nor its displayed order establishes a general producer-order guarantee.

Order-bearing means a permutation can change the resolved vectors, port meaning, diagnostics, export bytes or executable compatibility; it need not change numerical outputs. Normalized means the stated permutation does not change the specified result. Derived storage is a dense position inside one resolved executable, not a durable external identity. Authored identity is an expanded IRI/name maintained by the producer and used by the host, not an array offset.

SurfaceClassificationExecutable rule and identity consequence
JSON objects and context spellingNormalizedObject-key/map iteration order carries no executable meaning. Equivalent supported compact/expanded identity and typing tokens, singleton relation spellings, and equivalent context maps resolve alike. Context redefinition order and unsupported JSON-LD features are not covered by this equivalence. Structural property names must still use the supported spelling; this is not arbitrary JSON-LD canonicalization.
@graph node arrayOrder-bearingAuthored instance connector nodes receive ConnectorId and connector decl_order in surviving node order. Boundary definitions follow boundary node order; synthesized connector groups use owner node position. Moving only a leaf’s node does not replace containsBlock as the source of block order. Root-count candidates also follow node order.
containsBlockOrder-bearingActive leaves lower depth-first in child array order, assigning BlockId and block decl_order. Permuting siblings can change schedule tie-breaks, export, state compatibility and replay content. Authored connector node positions do not move merely because containment order changes, but their block owners can receive different IDs.
Named leaf hasInput / hasOutputNormalizedWhen all ports on a side name the class’s declared ports, they bind in class-signature order, regardless of member-array order. Dense connector IDs still follow @graph. Partial name matches refuse rather than guessing.
Positional/unnamed leaf portsOrder-bearingWhen no declared names match, ports bind positionally; classes without declared names also bind positionally. A same-kind swap can change meaning without changing arity or types.
Composite own hasParameter + hasConstantNormalized for accepted scopesOne mutual dependency-ordered scope, including forward references across the two lists. Permutations preserve the accepted graph and guard decisions. Rejected cycles/duplicates retain rule IDs and participant sets, not necessarily subjects or message order; see Rule 5.
Leaf values and dimensionsOrder-bearing where Rule 5 specifiesLeaf declaration chains are not composite mutual scopes. Values use enclosing-first lookup, earlier sibling fallback and no forward sibling grounding; dimensions use nearest-wins lookup. Do not generalize composite declaration-order independence to leaf chains.
isConnectedToOrder-bearing, with orientation normalizationAuthored connection sources follow @graph; targets follow each source’s array. Re-anchored relations follow the orientation rules in Rule 6; node-less derived sources follow authored sources in derived connector order. Direct target permutations can alter export content while leaving executable compatibility unchanged because the state manifest canonicalizes the edge set.
Boundary loweringNormalized fanout; order-bearing node positionsexternal_inputs re-key by (boundary input node position, ConnectorId); fanout target-array permutation does not reorder them. Pass-through pairs re-key by (input boundary node position, output boundary node position). Declared input/output definitions follow boundary @graph order. Boundary hops and composite nodes disappear, while declared boundary IRIs retain their public IO role.
hasInstance member arrayNormalizedArray order is load-bearing for nothing: derived ports use names/class signature, classified parameters append in class-signature order, and synthesized connectors append after authored connectors ordered by (owner @graph position, class-signature position). This does not make owner node order or authored connector node order inert.
Inactive conditionalsPrunedInactive children and entire subtrees contribute no leaf or connector positions. Moving an inactive child among active siblings does not move the active order. Active connections referencing inactive nodes still refuse; pruning is not permission to leave dangling wiring. See Active nodes for declaration/guard exceptions.
Resolver diagnosticsDeterministically finalizedSort by numeric connector dense ID first (including recognized connector#N subjects), then subject, code string and message. Non-connector subjects use the final ID bucket: no subject before named subjects, then lexical subject/code/message order. Early failures with no connector index use that non-connector handling. Severity is not a sort key. Export warnings retain their separate block/cascade order, not this resolver sort.
ExportDerived from ModelGraph vectorsEmit the synthetic root, surviving blocks and their parameters, surviving connectors, then boundary nodes in their vector-derived order. Deferral filters the survivor cone without lexically sorting it. Export is flat, not source recovery; a partial export tag is not whole-model identity. See the round-trip contract.
Dense IDs versus authored identityDerived storage versus stable authored identityBlockId, ConnectorId, decl_order, schedule/vector positions and synthetic positional port names are never stable external identities. Hosts key by authored expanded IRI/name, preserving that identity across producer revisions deliberately. Authored identity does not imply executable compatibility: unchanged names can accompany incompatible storage/order. Export content identity, state compatibility and replay identity remain distinct.

Emitter guidance and qualification limits

Emit deterministic @graph, containsBlock, positional ports, leaf declaration chains and connection arrays; preserve those orders across exports of the same source. Do not run a generic RDF/set sort over them and assume it is executable-neutral. Prefer declared port names over positional binding. Compare both authored-name inventories and the order-sensitive executable contract; identical names alone do not qualify a state restore or replay. Boundary fanout and named-port normalization are specific rules, not permission to reorder every array.

The executable oracle here is OCE-only. Studio producer order is unqualified: no Studio source or producer run is accepted as evidence by these tests. The OCL routine compiler does not emit CXF and is not a qualified producer for this contract. Downstream qualification must run the actual producer against these cases; neither is a prerequisite for freezing the OCE contract. For issue #249 accounting, this establishes ordering/identity evidence only. It introduces no parameter-shadowing policy, option or diagnostic, and does not close that separate policy question.

Rule 4 — Acyclicity (rejects: composite/contains-cycle)

The containsBlock graph reachable from the root through active children must be acyclic. A cycle rejects with composite/contains-cycle (DiagCode malformed-document); the message names all participants in traversal path order, ending at the re-entered id, and the re-entered id is the subject.

Normative consequence: one diagnostic per re-entry. A cycle reachable via k distinct paths yields k truthful path-ordered diagnostics; a consumer must not assume one diagnostic per structural cycle (corpus fixture rejected/diamond_cycle.jsonld: one cycle, two paths, two diagnostics). The degenerate self-loop (A contains A) reports the two-entry list …#A -> …#A (corpus fixture rejected/self_loop.jsonld).

{ "@id": "…#R", "@type": "S231:Block", "S231:containsBlock": { "@id": "…#A" } },
{ "@id": "…#A", "@type": "S231:Block", "S231:containsBlock": { "@id": "…#B" } },
{ "@id": "…#B", "@type": "S231:Block", "S231:containsBlock": { "@id": "…#C" } },
{ "@id": "…#C", "@type": "S231:Block", "S231:containsBlock": { "@id": "…#A" } }

rejects with subject …#A and message tail …#A -> …#B -> …#C -> …#A (corpus fixture rejected/reachable_cycle.jsonld).

Rule 5 — Parameter-scope inheritance (rejects: composite/array-parameter, composite/declaration-cycle, composite/duplicate-declaration)

A composite’s active S231:hasParameter and S231:hasConstant bindings form one mutual scope: every binding’s value may reference any sibling of either kind, declared earlier or later — declaration array order carries no meaning for the composite’s own scope. A document that loads does so with a byte-identical imported model and an identical diagnostic vector under any permutation of the two arrays; a document these rules refuse refuses under every permutation with the same rule ids and the same participant sets — only a diagnostic’s subject may relocate, because subjects follow the chained declaration order that permutation changes. Inside an own binding’s value, an own local name always denotes the own sibling, shadowing a same-named binding of an enclosing composite; only names with no own binding fall through to the enclosing scope chain (innermost composite first). The grounded scope is inherited by every child composite and leaf. Identifier-shaped text inside an expression String literal is data, not a sibling reference: S231:value: "\"b\"" creates no dependency on an own declaration named b. Three shapes reject:

  • A reference cycle among a composite’s own bindings — including the length-1 self-reference x = "x * 2", which is never an enclosing read — rejects with composite/declaration-cycle (DiagCode malformed-document): one diagnostic per distinct cycle per chain evaluation. Like contains-cycle, a composite reachable via multiple containsBlock paths is evaluated once per path (enclosing scopes can differ per path), so the same cycle can surface once per visit — consumers must not assume one diagnostic per structural cycle document-wide. Subject = the participant earliest in the params-then-constants chained declaration order; the message’s arrow list is the participant ring in chained declaration order closing on the first (…#M.a -> …#M.b -> …#M.a) — not the discovered edge path. Bindings outside the cycle still ground (maximal progress); cycle members keep their own binding’s name — masking a same-named enclosing binding — but are absent from the scope, so a reference to one fails with a generic grounding-failed.
  • One local name declared twice in one composite’s own chain rejects with composite/duplicate-declaration (DiagCode malformed-document): one diagnostic per occurrence beyond the first in chained order (three declarations of one name emit two), subject = that later occurrence, message naming it and the first occurrence’s @id. The first occurrence stays a normal binding.
  • An array-valued (S231:isArray: true) active parameter or constant on a composite rejects with composite/array-parameter (DiagCode non-subset-construct); the subject is the parameter node.

Leaf members are a different level and keep their order-sensitive contract: a leaf member’s value reference resolves enclosing-first — when the name is bound both in the enclosing scope chain and by an earlier sibling member, the enclosing binding wins, and within each region the most recently grounded binding shadows earlier ones (issue #239) — so a leaf member’s forward reference to a sibling member still fails grounding. A leaf dimension reference (S231:sizeOfDimensions) still resolves nearest-wins over the undivided scope, so there a sibling binding shadows a same-named enclosing one — when the sibling is grounded earlier; member array order still decides the dimension reading (for an enclosing-bound name, values are order-invariant under member order, dimensions are not). When the two readings of one name disagree on an array’s shape, the element-count divergence refuses with grounding-failed (both counts in the message); a value divergence with a matching count is silent, exactly like the scalar path.

Conditional-guard specialization evaluates guards against the same own-scope semantics through the same mechanism, so guard decisions are equally order-independent. The specialization pass also grounds leaf declaration chains that carry conditional members; on that pass, generic grounding machinery is non-emitting, and the two tagged rules above apply to composite chains only — a leaf chain’s bindings are member modifications (the leaf level described above), so a cycle or duplicate among them produces no tagged finding there: the participants simply fail to ground in the guard scope. A composite-chain defect visible to both passes is reported once, from the lowering view; a composite chain only the specialization pass grounds (for example one pruned by a false guard) still surfaces through the two tagged rules; and a guard that genuinely cannot evaluate refuses through the guard’s own diagnostics — never as a bare grounding-failed. A leaf with a legal array parameter plus a conditional member therefore loads (corpus fixture accepted/leaf_array_parameter_conditional_member.jsonld), and so does the leaf identity-modification idiom — a leaf parameter samplePeriod = "samplePeriod" reading the same-named enclosing composite parameter, beside a conditional member (corpus fixture accepted/leaf_identity_parameter_modification.jsonld, the member value grounding enclosing-first per the leaf rules above); the specialization model itself — what a guard means and how pruning propagates — is unchanged.

References use the local name — the segment after the last . of the binding’s @id — so two same-named bindings at different nesting levels shadow (own-scope-wins for the composite’s own bindings, enclosing-first for leaf member values, nearest-wins for dimensions), while two same-named bindings in one composite’s own chain reject under composite/duplicate-declaration. Give bindings distinct local names unless shadowing is intended; the corpus does. Element names minted by leaf array expansion (k[2] → k_1, k_2) shadow like any sibling binding: a later member’s value reference to k_1 reads a same-named enclosing binding when one exists, not the minted element, while a same-named sibling parameter collides and refuses (ArrayFlattenCollision). Because grounded values feed block construction, own-scope resolution can change what a document means, so a constructed document that imported under the older order-sensitive reading can refuse under this rule (a cycle or a duplicate) or ground differently (a forward or shadowed sibling reference). Measured against the pre-change base (43d8a13, which held 147 checked-in CXF documents: 103 crate fixtures plus 44 vendored modelica-json translations), all 103 crate documents are byte-identical in import outcome under the rule; 12 vendored documents — every one still refusing on unrelated grounds — shed 48 grounding-failed diagnostics in exactly the two ruled classes (forward sibling references now grounding, and specialization-pass generic machinery going non-emitting), with zero new diagnostics anywhere. The tree now holds 205 documents (161 crate plus 44 vendored). The wider reach exists off-corpus.

{ "@id": "…#M", "@type": "S231:Block",
  "S231:hasParameter": [ { "@id": "…#M.kBase" }, { "@id": "…#M.kTop" } ], … },
{ "@id": "…#M.kBase",      "S231:value": { "@value": "0.25", "@type": "…#double" } },
{ "@id": "…#M.kTop",       "S231:value": "kBase + 0.25" },
{ "@id": "…#M.sub.kInner", "S231:value": "kTop" }

grounds the sibling reference kTop to 0.5; the child composite’s constant kInner (declared under …#M.sub via S231:hasConstant) inherits it through the parent scope, and the leaf parameter "S231:value": "kInner" grounds the chain’s end — the kBase → kTop → kInner → gain.k chain of corpus fixture accepted/minimal_nested.jsonld.

Rule 6 — Boundary elision (rejects: generic diagnostics)

Composite boundary connectors are lowered away. A boundary input rewires to the child connectors it drives; the top composite’s boundary inputs surface as the imported model’s external inputs. A boundary output of a non-top composite is followed through to its final targets. A boundary output of the top composite is elided outright: its @id appears on no connector in the flat model, and a leaf output whose only target is a top boundary output ends with no connection at all — the driving leaf connector remains, carrying no source @id. The composite node itself never becomes a runtime block. Boundary elision rejects invalid direction (DirectionMismatch), mismatched value types (TypeMismatch), unresolved endpoints or missing boundary nodes (UnresolvedReference), and boundary datatype declarations that cannot be derived (MalformedDocument).

CXF §8.2 permits either endpoint of a connection to carry S231:isConnectedTo; subject position does not encode signal direction. Before boundary elision, the importer therefore derives each endpoint’s source/sink role from its owning block, its port direction, and the peer’s location inside or outside that owning composite, then re-anchors reverse-spelled edges on their canonical driver. This is an orientation rule over the existing connector and containment data, not a new runtime model. Edges whose roles cannot be derived — a dangling or non-connector peer, a port claimed by two owners, non-tree containment — as well as same-polarity (contradictory) pairs and reverse spellings whose canonical driver has neither a node nor a synthesized connector identity, are left exactly as authored and reject under the existing Rule 6 diagnostics when the relation survives lowering. If boundary elision would erase an active relation that cannot be kept or swapped, the importer defers a direction diagnostic until the bounded boundary walk succeeds. This applies whether the boundary is the authored source or target. An active elided boundary source targeting an inactive node similarly retains the ordinary inactive-node refusal. A node-less output listed by hasInstance, or padded from an omitted declared output, can be a canonical driver; its lowered edges follow authored sources in derived connector order. Re-anchoring never invents or silently removes a relation: an input driven twice still rejects. Authoring the same relation from both endpoints collapses when either spelling required re-anchoring. In particular, both directions between one composite’s input and output denote one pass-through relation, not a boundary cycle.

The boundary walk preserves canonical target order and duplicate multiplicity below its resource limits. It checks an active-path cycle or missing boundary node before the hop limit, so those shapes retain their UnresolvedReference outcome at the boundary. The target-examination budget counts inactive, terminal, dangling, and cycle-revisit targets before classification; it prevents a shallow branching graph from expanding without bound even when every path is short. The aggregate byte budget also charges an authored target that orientation turns into a synthesized canonical driver, before the completed target lists are cloned. Resource-limit diagnostics omit the attempted target subject to avoid an additional untrusted IRI copy at refusal. Deferred diagnostics, including the inactive-target variant, also omit their subject. If bounded expansion repeats one missing endpoint through ordinary or boundary-specific orientation, the importer emits one unresolved-reference diagnostic for that endpoint rather than copying its subject once per edge.

What an emitter must NOT expect to survive import: composite nodes as blocks, boundary connector hops, nesting depth, or the authored bytes. The import-parity boundary is flat by contract: re-importing an exported document reproduces the flat ModelGraph — never the original nested/authored bytes. Round-tripping a nested document through the engine and comparing bytes will always “fail”; compare imported models instead.

{ "@id": "…#M.u",     "@type": "S231:RealInput",
  "S231:isConnectedTo": { "@id": "…#M.sub.u" } },
{ "@id": "…#M.sub.u", "@type": "S231:RealInput",
  "S231:isConnectedTo": { "@id": "…#M.sub.gain.u" } }

imports as one external input feeding …#M.sub.gain.u directly; …#M.sub.u is gone.

Rule 7 — Rejected constructs (rejects: composite/banned-modelica-key, composite/replaceable, composite/array-connector, composite/array-instance, composite/vector-port-instance, composite/unsupported-instance-member, composite/colliding-member-identity)

Six Modelica construct keys are banned on any active node: redeclare, constrainedby, extends, extendsFrom, moSource, modelicaSource. Matching is on the term after the last :, #, or / in the key, so the bare (extends), prefixed (S231:extends), and absolute-IRI (http://data.ashrae.org/S231P#extends) spellings all reject. A banned key rejects with composite/banned-modelica-key (DiagCode non-subset-construct); the subject is the owning node and the message names the key exactly as authored.

S231:isReplaceable: true on any active node rejects with composite/replaceable (DiagCode unresolved-polymorphism). The subject is the replaceable node. Replaceable components must be resolved to concrete classes before export.

An active connector — any node referenced by an active node’s S231:hasInput or S231:hasOutput list, anywhere in the document, whether or not the referencing node is reachable from the top-level root, or any member of an active derivation-shaped node’s S231:hasInstance list (a containsBlock referent that is not a runtime composite, declares neither port list, and carries a member list) — rejects when it carries an array marker. The member source matches the existing sources on reachability and is narrower only in shape: an orphan node’s list and a runtime composite’s list contribute nothing, where the existing sources take any active node’s list at all; the scan stays reference-based and class-independent on both. The markers: S231:isArray: true or any S231:sizeOfDimensions. Marker keys match on the term after the last :, #, or /, like the banned-key matching above, so absolute-IRI spellings reject too. The rejection is composite/array-connector (DiagCode non-subset-construct) with the connector node as subject. Flatten connector arrays to one connector per element.

An active block instance — any node referenced by an active node’s S231:containsBlock — rejects under the same array markers as composite/array-instance (DiagCode non-subset-construct) with the instance node as subject. Flatten block arrays to one instance per element. A node referenced as both connector and instance receives both rejections, and a S231:hasParameter listing does not exempt it. Array-valued parameters on a composite are governed by Rule 5; an array-valued parameter on a leaf block is preserved and expanded, not rejected. Inactive conditional subtrees are invisible to these checks.

Three rules govern the hasInstance interface derivation (an instance declaring neither hasInput nor hasOutput and carrying a S231:hasInstance list derives its interface from the list; a node declaring either port list keeps its own interface). Each refusal skips the instance’s derivation whole — the tagged rejection replaces the generic arity mismatch rather than doubling it:

  • composite/vector-port-instance (DiagCode non-subset-construct, subject the instance node, one per instance): the resolved class publishes no declared port names — its port count is a function of a parameter, so one member stands for N scalar connectors and this subset derives scalar interfaces only. A document declaring the same class’s ports explicitly through hasInput/hasOutput is untouched.
  • composite/unsupported-instance-member (DiagCode non-subset-construct, subject the member IRI, one per offending member): a member outside its owner’s namespace (not <owner>.<oneSegment>), a member that is itself a block instance, or a member whose local name is neither a declared port nor a declared parameter of the class.
  • composite/colliding-member-identity (DiagCode non-subset-construct, subject the colliding IRI, one per collision): a synthesized connector identity that is already an @graph node or is minted twice for one owner, or a parameter name declared twice for one instance — across its classified members or against its own hasParameter/hasConstant list.
{ "@id": "…#M.c2", "@type": "…MultiplyByParameter",
  "S231:isReplaceable": true,
  "redeclare": "…#SomeBase", … }

rejects twice: once under composite/banned-modelica-key naming `redeclare`, once under composite/replaceable (corpus fixtures rejected/banned_key_*.jsonld, rejected/replaceable.jsonld).

Rule catalog

The contract identities, mirroring tools/reference-catalog/oce-cxf.composite-rules.json (catalog order). Rules 1 and 3 do not appear here because they are non-rejecting. Rule 6 has no composite/ rule identity; its rejections are generic diagnostics.

RuleRule idDiagCodeMessage prefix
2root-countmalformed-documentcomposite/root-count:
4contains-cyclemalformed-documentcomposite/contains-cycle:
7replaceableunresolved-polymorphismcomposite/replaceable:
7banned-modelica-keynon-subset-constructcomposite/banned-modelica-key:
5array-parameternon-subset-constructcomposite/array-parameter:
7array-connectornon-subset-constructcomposite/array-connector:
7array-instancenon-subset-constructcomposite/array-instance:
5declaration-cyclemalformed-documentcomposite/declaration-cycle:
5duplicate-declarationmalformed-documentcomposite/duplicate-declaration:
7vector-port-instancenon-subset-constructcomposite/vector-port-instance:
7unsupported-instance-membernon-subset-constructcomposite/unsupported-instance-member:
7colliding-member-identitynon-subset-constructcomposite/colliding-member-identity:

Every message prefix is composite/<rule-id>: — colon, then one trailing space (U+0020), which markdown table cells cannot render unambiguously. Match with starts_with("composite/<rule-id>: "), trailing space included. The drift-guard test (crates/oce-cxf/tests/composite_contract_doc.rs) checks the Rule id and DiagCode columns of this table against the catalog artifact and derives the prefix from the rule id; the prefix column above is display-only.

Generic diagnostics

Three diagnostics that can accompany or replace a contract rejection are shared import machinery, deliberately untagged (no composite/ prefix, no catalog entry):

  • unresolved-reference — a containsBlock child, parameter node, composite @id, or a connection/boundary reference naming a hasInstance member of an instance whose interface was not derived (an unregistered class, or a composite/vector-port-instance refusal), referenced but not resolvable. A classifiable member itself is never reported under this code: a node-less port member becomes a synthesized connector and a node-less parameter member refuses as grounding-failed at derivation.
  • grounding-failed — a parameter value that cannot ground: a missing S231:value (values are required — Ground mode, on the hasParameter/hasConstant route and the hasInstance member route alike), an unknown identifier (including a reference to a cycle-refused sibling, or a leaf member’s forward reference to a later sibling member), or an expression error.
  • conflicting-interface-declaration — an instance declaring hasInput/hasOutput beside a hasInstance list whose class-declared names its own routes do not carry (a warning; compared one-directional, list minus own), or one parameter name valued differently on the two routes (an error — two values for one name state a contradiction).

They are not contract rules because they do not describe a composite shape; they fire anywhere in the import pipeline. Match them by DiagCode, not by message. The conditional-pruning rejection inactive-conditional-node (see Active nodes) is generic machinery in the same sense — untagged, no catalog entry.

Acceptance preconditions

A document that satisfies rules 1–7 must also meet the general import preconditions before it loads warning-free:

  1. Exactly one top composite, and its @type must be unregistered (S231:Block works). A registered leaf standing alone — even one with containsBlock — classifies as zero composites and rejects under rule 2 with zero candidates.
  2. Every parameter and constant carries a S231:value (missing values are grounding-failed). A parameter declared through a S231:hasInstance member is covered too: a valueless or node-less member named after a declared parameter refuses the same way.
  3. Every leaf @type resolves to a registered block class (else class-not-found).
  4. Accepted means warning-free: the corpus drivers assert an empty diagnostic report, not merely a non-error one.

Testing your emitter

The engine tests itself against a checked-in conformance corpus; point your emitter’s output at the same files and drivers.

  • Corpus: crates/oce-cxf/tests/fixtures/composite_contract/{accepted,warned,rejected}/*.jsonld, one fixture per contract behavior, indexed in the corpus README.md. The warned/ category holds documents that load successfully with a pinned advisory vector — untagged import machinery such as undriven-boundary-output, not a composite-shape rule.
  • Accepted-fixture goldens (byte-exact ModelGraph renders): crates/oce-cxf/tests/fixtures/golden/composite_contract_*.modelgraph.txt.
  • Resolver-layer drivers: cargo nextest run -p oce-cxf --test composite_contract_corpus
  • Full-pipeline (Engine::load_cxf) drivers: cargo nextest run -p oce-api --test conformance composite_contract
  • Doc/catalog drift guard: cargo nextest run -p oce-cxf --test composite_contract_doc
  • Ordering oracle: resolve_ordering.rs, with independently authored compact/expanded documents, hand-listed block/connector/edge and export survivor orders, object-key reversal, declaration/member permutations, and exact ordered diagnostic vectors. These are OCE profile expectations, not an external numerical oracle.
  • Public identity: composition_identity.rs checks complete export content tags and public state-restore compatibility. It distinguishes normalized permutations, incompatible containment/connector/positional-port changes, and direct fanout permutations that change content but preserve executable compatibility. It neither exposes private fingerprints nor changes format revisions.
  • Complementary contract evidence: resolve_external_input_order, resolve_declaration_scope, resolve_param_precedence, and resolve_composite_orientation retain boundary, leaf-scope and orientation cases. fixture_structural_oracle independently checks the vendored corpus’s structure, not these OCE array-order semantics; fixture_port_order and vendored_corpus_delta retain their input-hygiene and diagnostic-delta roles.

To check a document your tool produced:

  • Rejected: drop it under rejected/, add a README index row, and add its expected (DiagCode, subject, message) triples to the pin tables in both drivers (expected_rejections() in composite_contract_corpus.rs, COMPOSITE_REJECTIONS in crates/oce-api/tests/conformance.rs).
  • Warned: drop it under warned/, add a README index row, add its exact complete warning vector to expected_warnings() in composite_contract_corpus.rs, and add its ordered warning triples to the composite_warnings() table in crates/oce-api/tests/conformance.rs — the end-to-end warned driver is table-driven and its on-disk listing pin is taken over that table, so a warned fixture cannot land half-wired.
  • Accepted: drop it under accepted/, add a README index row, add the pair to the ACCEPTED table in composite_contract_corpus.rs — the golden filename convention is tests/fixtures/golden/composite_contract_<fixture-stem>.modelgraph.txt — then bless the golden with OCE_BLESS=1 cargo test -p oce-cxf --test composite_contract_corpus accepted_fixtures_match_their_blessed_modelgraph_goldens_byte_exactly and review the blessed bytes before committing. The oce-api driver picks the new file up automatically and requires the warning-free load.

The corpus completeness tests fail on any unindexed or unpinned fixture, so a fixture cannot land half-wired.

The 44 vendored modelica-json translations under third_party/modelica-buildings-cdl/cxf/ are additionally held to a per-document characterization capture (crates/oce-cxf/tests/vendored_corpus_delta.rs): per-DiagCode counts, severities, and duplicate diagnostic triples, re-blessed only deliberately. The hasInstance interface derivation moved that capture in both directions, every increase declared: 904 arity mismatches and 2,579 unresolved references removed, 30 arity mismatches replaced by composite/vector-port-instance, single-assignment arriving at 57 (31 undriven and 8 multiply-driven derived inputs, plus 18 multiply-driven declared boundary outputs assessed for this dialect for the first time), grounding-failed rising 62 → 204 as member values and member connector bounds ground for the first time on class-translation documents, and inactive-conditional-node rising 11 → 44 because pruning now reaches a conditional instance’s listed members as well as its @graph nodes (32 members still carrying active connections, 12 connection targets).

Host responsibilities

For anyone about to wire this engine to real equipment. It answers one question: what safety behavior must you implement, because the engine deliberately does not?

This engine executes a control sequence. It does not supervise the equipment that sequence drives, and it does not judge the quality of the data it is fed. Those are your job, and the engine will not warn you if you skip them.

See the product contract for numbered host obligations and the bounded requirement-to-evidence map; engine boundary tests are not host-compliance evidence.

Native complete frames and the host delivery boundary

The complete-frame contract supplies current typed, read-only preparation and an engine-local load/rebuild fence (PC-032). execute_frame consumes a prepared submission and returns one immutable CompletedFrame after one atomic HostTick transition (PC-033). PC-034 retains the shared private evaluation core. PC-035 removes the weaker legacy profiles; preparation followed by consuming execution is the only public state-advancing execution surface.

Completeness means every executable boundary input exactly once, except omission explicitly defined by the executable schema. It does not establish sensor coherence, quality, freshness or plausibility. Preparation neither fills gaps from Store samples nor infers host defaults. Its reload fence does not authorize deployments or commands. Persistence, authentication, authorization, deployment fencing, scheduling/wall-clock mapping, NO_EVAL, safe states, equipment interlocks and actuation stay host-owned. NO_EVAL means not executing.

Native execution calls no Store method or host callback. Persisting or delivering a retained result is a separate host operation; a later delivery failure does not turn the accepted frame into a refusal. Do not blindly resubmit at the same time: that produces another state transition and sequence. The accepted-frame sequence correlates only within one Engine lifetime; reload, resume and restore do not reset it. It is deliberately absent from snapshot bytes and supplies no durable replay position, lease, authentication or equipment authority. Retained results remain unchanged across those operations. The result owns Warning-only diagnostics; it adds no escalation, interlock or safe-state decision.

Execution has no write-back or post-write error path. Hosts retain the completed receipt and own any subsequent persistence or actuation attempt. Do not repeat execution merely to retry delivery: even equal model time advances state again. get_output and watch inspect latest state, not a retained receipt; load, restore and resume can replace that state without executing a frame.

Library, Studio, Edge and Sim retain their roles; Runtime is only an additive future M05-PR03 consumer/host qualification candidate. BOPTEST is Runtime host evidence, not OCE equivalence. No host services or second evaluator/snapshot/replay stack move into OCE.

Canonical per-frame replay

CompletedFrame::replay_record() captures accepted inputs, exact completed outputs/warnings, model-time bits, public compatibility facts and placement without a second execution. The v1 record contract defines independent decoding and read-only eligibility and comparison. Capture can refuse its 64 MiB bound after successful execution; that does not undo the frame or permit blind resubmission. No bytes are persisted by OCE.

Authenticate exact bytes and qualify the same build/deployment, executable/parameters and prior state outside OCE before decoding. Then check ReplayRecord::check_compatible, prepare its canonical inputs, execute once on an isolated compatible engine and verify the completed result. A post-execution mismatch is not rollback or an equipment interlock. The content tag and public descriptor are non-authoritative; no OCE build token is created or enforced.

The host owns the ordered sequence envelope, missing/duplicate policy and optional start/end EngineStateSnapshot sidecars. The engine-lifetime sequence is not durable position and is absent from records. Drop/process one record at a time for bounded retention. State continuation and placement still obey the existing manifest/restore-window rules; portable does not mean universally qualified mathematics, and conformance tolerances are not replay acceptance policy.

Complete values are not sensor-quality evidence

Every executable boundary input is required exactly once. A missing determinant returns FrameMissingInput before mutation, even if the connector had a prior value or the Store holds a sample. There is no implicit hold-last, type seed, sparse write or Store fill-in.

Frames carry typed values, not PointStatus or wall-clock freshness. A host that deliberately resubmits stale or faulted observations can still obtain a valid computational result. Validate quality and observation coherence before preparation. The engine neither invents missing data nor qualifies a host’s substitution policy.

The engine implements no fail-safe policy of its own

Completeness and type/domain checks do not implement degraded-operation or equipment protection. If your plant needs to fail safe, that policy lives in the host or equipment. At minimum:

  1. Per-point staleness limits. PointSample carries at_unix_nanos in the storage port; frame execution does not read it. Track sample age yourself and define, per point, how old is too old.
  2. A status reaction policy. Decide what Fault, Stale, Uninitialized and Override mean for each input, and act on them before or instead of executing. The engine will not.
  3. A defined safe state, and a path to it. Know what output set is safe for the equipment, and drive it from the host when the input contract is violated — do not expect the sequence to produce it.
  4. Plausibility checks on inputs. Range, rate-of-change and cross-sensor consistency, applied before frame preparation.
  5. Equipment protection below the engine. Any interlock you are relying on to prevent physical damage — freeze protection, high-limit cutouts, minimum off-times enforced in hardware — must exist in the host layer or in the equipment itself. The engine executes the sequence you gave it and nothing else; a sequence that omits an interlock has no interlock.
  6. Delivery-failure handling. Persisting or delivering a CompletedFrame is your operation. Its success, rollback, retry policy and actuator acknowledgment are not engine guarantees.

Time is host-supplied

The engine never reads a wall clock. Model time is host-supplied finite nondecreasing f64 seconds; a decrease returns OcError::TimeRegression. Loaded blocks also enforce representability. The host owns cadence and any mapping between model seconds and external UNIX timestamps. Realtime epoch configuration and realtime/Store orchestration are not facade capabilities.

Supply time from a source you trust to be monotonic. The engine cannot detect a clock that jumped.

One call is one HostTick transition

The engine uses the fixed HostTick v1 execution profile. Every successful execute_frame call advances state once, even when its model time equals the previous timestamp. Equal time means zero elapsed time to timers and integrators; it does not make the call observational. In particular, CDL.Logical.Pre emits its stored Boolean and latches current input once per call.

Do not execute repeatedly at one timestamp to imitate Modelica event iteration. The engine does not search for a fixed point, and every stateful block updates on each call. A Pre-cut Boolean loop that cannot converge under Modelica may continue changing on every tick call without a diagnostic. The completed result retains that transition’s boundary outputs and diagnostics; latest-state inspection can subsequently change. A host simulation loop is a sequence of complete submissions, not an implicit restart. Split schedules continue the existing state. Earlier successful frames remain committed if a later frame refuses, but that refusal stages no prefix. Use a fresh load for a fresh run, or an explicit compatible process-local checkpoint for branching/rewind. There is no whole-horizon transaction.

Persist engine state outside the store port

Engine::state_snapshot returns the engine-owned canonical bytes needed to continue a run. It does not write them anywhere. Engine::checkpoint, state_snapshot, restore_checkpoint, and restore_state call no Store method. The state contract defines the same-loaded-executable guarantee, typed refusals and portability limits. The host owns durable storage and an authenticated sealed envelope binding exact snapshot bytes to its approved compiled-build/deployment qualifier, freshness and generation. Verify that envelope before EngineStateSnapshot::from_bytes, then pass only the OCE bytes. OCE emits, accepts, stores and enforces no build token; equal ABI/manifest, package or catalog facts do not authenticate a build.

Capture only after a model has loaded successfully and while no parameter edits are pending. A durable capture also requires authored stable identities and registered state contracts for every stateful block. The decoder enforces a 64 MiB limit, validates canonical ordering and manifest self-consistency, and checks an integrity trailer. Capture runs this same bounded decoder before returning a successful artifact. Format and execution ABI are now revision 2; revision-1 bytes refuse without migration because their manifest omitted executable input-domain/unit facts. Class-specific block-state invariants are checked during restore, when a target engine is available. The trailer detects accidental corruption; it is not an authenticity or freshness proof. Protect snapshot bytes according to the trust boundary of the host that consumes them.

Durable continuation has a narrow restore window:

  1. Stop command delivery and fence the old process/actuator owner. An OCE halt is not this fence.
  2. Read bounded host-envelope bytes using the host’s durable-publication/recovery policy. Reject partial writes; authenticate the exact payload and build/deployment qualifier, freshness, rollback/replay policy and deployment generation before decoding. Package version or an FNV tag is insufficient. Refuse a mismatched build/deployment here, outside OCE.
  3. Parse only approved OCE bytes with EngineStateSnapshot::from_bytes. Inspect portability() for placement, without treating it as build or numerical qualification.
  4. Load the exact compatible executable/parameters into a fresh target. Load can have Store effects even if it fails; apply the separate compensation policy below.
  5. Call restore_state before any accepted frame, dirty-parameter resume or either earlier restore. On refusal do not patch bytes, retry on an advanced target or bypass admission. Select a qualified target, approved cold start or rollback under host policy.
  6. Reconcile external point values, quality, timestamps, histories and backend transaction state. Re-establish model-time/wall-clock mapping and choose the first complete, quality-approved observation set. An equal-time retry executes again; OCE has no durable delivery acknowledgment or engine-owned replay sequence position.
  7. Acquire the new generation’s exclusive actuator authority/lease and verify fencing at the delivery boundary before resuming writes. Record snapshot/command acknowledgments externally; successful restore alone authorizes nothing.

The target model must have the same executable manifest: block classes and parameters, port bindings, connector types, schedule, state-slot layout, enum descriptors, external inputs, and boundary outputs, computation units/quantities and effective complete-frame acceptance bounds. The diagnostic model id may differ; executable compatibility may not. A refusal is atomic and leaves engine and store state unchanged. Durable restore also refuses after the target crosses a mutation boundary, even if that mutation was otherwise harmless as defined by the state contract.

Snapshots for models that use the unchanged 15-class libm-dependent set are target-bound. They restore only on the same architecture and operating system; restore_state returns EngineStateError::TargetDomainMismatch before commit on another target. Other models carry StatePortability::Portable, meaning no target restriction in that policy, not universal exactness. The finite 21-signal Linux receipt does not widen it to arbitrary inputs, full closure or macOS. Treat a target-domain refusal as a placement failure, not corrupt state; bind placement metadata to the authenticated envelope rather than rewriting the OCE bytes.

Snapshots restore absolute model time and the prior-tick monotonicity guard. They do not carry the real-time UNIX epoch, backend point history, point status or timestamps, backend transaction state, or host safety policy. The current connector image, including staged input values, is part of the snapshot. Restore the other state outside the engine. Use EngineCheckpoint instead when branching or rewinding within one process; it is opaque and has no persistence format.

For CDL.Logical.Pre, a snapshot preserves both the currently visible output connector and the Boolean memory that will be emitted on the next HostTick call. Restore does not evaluate the block. A call at the restored timestamp advances it again.

For candidate changes, use the release compatibility and host fallback checklist. Current/current is the only supported pairing; equal package strings are not build authority. No N-1 state/replay migration is supplied. Cold requalification starts fresh; external rollback uses the prior qualified binary with its own authenticated state, never a current-engine transplant.

Lifecycle names are not equipment controls

Engine::halt() does not stop complete-frame execution or host-owned output writes. It changes only the parameter-edit permission mode: set_param is accepted while halted. The host must stop calling execution methods if it intends execution to stop.

A halt / set_param / resume cycle is also a run restart, not live tuning. When parameters are dirty, resume rebuilds blocks, allocates all state again, refreshes outputs, and clears the prior model time. Every stateful block—including integrators, latches, timers, and filters—is re-seeded, and monotonic-time history is lost. Plan parameter edits as a new run.

Executable ingest uses load_cxf; the never-working semantic/Modelica loaders have been removed. Hosts prepare supported CXF outside the engine. Likewise, hosts decode CSV/table input outside the facade and provide complete prepare_frame observations. AssertLevel contains only Warning, which is now also its default; assertion reports neither escalate nor stop equipment. The collector preserves the block’s diagnostic source (currently the Assert class path, not an instance identity). See facade migration for the intentional pre-release source/default break.

point_list(None) still returns the engine’s own effective inventory. Its existing argument remains, but device filtering is outside support: every Some returns OcError::Load directly, without querying the store or changing the run. Wiring a capable SemanticStore does not enable that filter.

CXF point identities are the authored @ids

Every point path — on the host-visible IoInventory and in the durable PointDto projection sent through the PointStore port — is an authored @id from the source CXF document, expanded against the document’s @context to canonical absolute form at ingest: for a connector driven by a composite boundary input it is the declared boundary input’s @id (one host point fans out to every internal consumer, which is why the G36 corpus’s 3020 connectors surface as 2895 points), and for every other connector it is the connector’s own node’s @id. CXF ingest rejects a connector node without an @id, so a document-loaded point can never receive a positional identity. Because keys are canonical, a document re-serialized between compact and expanded spellings keeps its point paths; a relative @id that no @context can canonicalize is refused at load with a typed relative-iri diagnostic rather than admitted under a spelling-dependent key. The supported @context form is an inline prefix map — a single map, or a list of maps merged in order with later bindings winning; a remote context reference, @base, @import, @vocab, prefix bindings that are not absolute IRIs, and term definitions that use another active prefix are refused at load as non-subset constructs rather than silently ignored. The last case is a nested compact IRI; it includes an absolute-looking value such as urn:oce:names# when the same context also declares urn as a term. Recursive context-term expansion is outside the supported subset. A direct @context on an @graph node, one of its identity/type reference objects, or a modeled value/term object is also refused: context bindings are document-level only, and the engine never applies a scoped context to one semantic value. The canonical-key guarantee therefore holds for every document that loads at all.

The document’s declared boundary-output names (root S231:hasOutput) are a second read-only identity space: each resolves on get_output and watch as an alias for its driving internal connector’s slot, and Topology.boundary_outputs enumerates the (path, driver_path) pairs. Declared names stay out of point_list and IoSummary; committed frames enumerate only the executable boundary. Their unit, quantity, and bounds are one §7.10 contract: conflicts refuse at load and one-sided values propagate to the unset peer. Frame preparation never accepts a declared output name. Because the driver’s connector supplies host point metadata, a declared alias can supply a previously unset driver unit, quantity, or bound. That changes the driver’s IoInventory and point_list(None) row for an unchanged input document; propagated unit and quantity also reach the durable PointDto. IoSummary remains a count-only surface and does not change when metadata propagates. Hosts that retain point metadata outside the store port must refresh it after loading with this rule. An undriven declared output resolves nowhere; its load-time undriven-boundary-output warning is its only representation.

A related contract for emitters and durable stores: array order is load-bearing wherever the resolver reads an array — @graph node position, containsBlock order, each instance’s port and parameter lists, isConnectedTo order. The one carve-out is the boundary-input elision vector (external_inputs) and the pass-through pair list: both are re-keyed on the boundary port’s own @graph node position instead of inheriting the order of that port’s isConnectedTo array (crates/oce-cxf/src/resolve/mod.rs, Step 9). Neither array order nor node position is a stable identity: key by authored name, never by position.

Point histories persisted under the earlier positional conn#<N> keys are disposable, not migratable: an index is not traceable to an authored connector after the document that produced it changes.

Ingest resource bounds

BoundLimitDefined atBehavior when exceeded
Serialized CXF at both facade load entry points8 MiB (8,388,608 bytes) by default; hosts may configure a smaller limitcrates/oce-api/src/admission.rsOcError::CxfTooLarge { actual_bytes, limit_bytes } before JSON deserialization or Store calls
Expression parse and AST nesting64crates/oce-expr/src/lib.rstyped NestingTooDeep error
Expression size4096 nodescrates/oce-expr/src/lib.rstyped ExpressionTooLarge error
Composite nesting (containsBlock lowering)64crates/oce-cxf/src/resolve/composite.rsMalformedDocument diagnostic
Composite boundary path64 non-top isConnectedTo hopscrates/oce-cxf/src/resolve/composite.rsMalformedDocument diagnostic
Composite boundary work65,536 target examinations and 8 MiB of aggregate target-IRI bytes per documentcrates/oce-cxf/src/resolve/composite.rsMalformedDocument diagnostic

Composite nesting and boundary traversal are different walks and have separate limits. Boundary traversal is iterative, so an accepted path does not consume one call-stack frame per hop. Its work budgets also bound shallow fan-out and repeated long IRIs that a depth limit alone would miss. Direct leaf wiring is outside those budgets. Below the limits the walk keeps target order and duplicate paths intact for single-assignment validation.

Treat untrusted CXF as untrusted input

A CXF document is a program. Loading one from a source you do not control is running code you did not write. If you must:

  • Bound transport/buffering before handing bytes to the loader. Both load_cxf and load_cxf_with_receipt enforce Engine::cxf_byte_limit() before deserialization, defaulting to MAX_CXF_BYTES (8 MiB). set_cxf_byte_limit accepts 0..=MAX_CXF_BYTES; a larger request returns CxfByteLimitTooLarge without changing configuration. Zero refuses nonempty input; empty input still fails normal JSON parsing. Policy persists across reload and is not snapshot state.
  • Load in a process or thread whose loss you can absorb when your threat model requires isolation.
  • Never load an untrusted document in the same process that is actively commanding equipment.

The byte cap does not bound peak memory, CPU time, expansion, or total graph complexity. It is not a sandbox, cancellation/deadline guarantee, or evidence that every below-cap document is safe. Receipt admission refusals use the existing Import stage, with byte counts only and no input content, source error or structured diagnostics in the OcError; OperationFailure still exposes that original error through its normal source chain. Legacy malformed-input errors remain unchanged.

Load replacement and the Store compensation boundary

Ordinary returned load failures preserve the prior in-memory executable/run image: model and identity, blocks/schedule, state words and connector/output values, IO/parameters, mode/dirty flags, prior time, semantic warnings, loaded state and durable-restore readiness. Successful reload replaces model-bound caches and state, resets time and parameter lifecycle, and opens the fresh durable-restore window. Refresh all model-local/ephemeral references after success; admission policy persists and accepted-frame sequence does not reset.

This is not a Store transaction. recover, save_model, and resolve_points execute before the in-memory commit and can have effects even when a later operation (or that call itself) fails. The port has no abort/compensation hook. A saved candidate model and allocated point handles can remain after refusal. The host/adapter owns compensation, isolation and re-establishing a usable backend; old external handle validity is not promised. Load-time handles are validated but not retained for execution. Do not blindly resume equipment control after a backend refusal. Panic, process death, allocation failure, and concurrent host effects are outside the ordinary returned-error guarantee.

reload_tests compares the complete owned image for fresh, advanced and halted/dirty runs across real import, unification, validation, schedule and injected Store refusals; the private build tail covers instantiation and projection. Flatten is currently an infallible identity shim and semantics currently returns metadata without a refusal path; no injected failures are claimed for those stages. The separate residual-effects test demonstrates the MemStore compensation boundary.

The structural ingest paths above are bounded and return typed diagnostics rather than panicking. ../TESTING.md requires new ingest code to assert the specific DiagCode or error variant rather than “an error occurred.”

The tests cited on this page live in oce-api and run per PR on x86_64 and arm64 under debug and release codegen. The full workspace and doctests still wait for the release gate. See ci-and-the-gate.md for the exact split.

CI and the gate

For contributors, and for anyone looking at a green check mark on a pull request and wondering what it proves. The short answer is: less than you would assume. The split is deliberate, and it is easy to misread in the dangerous direction.

Where CI runs

CI runs on GitHub Actions, the project’s primary host, from the workflows in .github/workflows/ on GitHub-hosted Linux x86_64 runners: pr-gate.yml (per-PR into development), release-gate.yml (release PRs into main and a daily development-tip run), advisories.yml, docs-pages.yml, the tag-verify / manual-publish release.yml, and the manual native-arm64 OpenModelica evidence workflows. Actions in the gating workflows and docs-pages.yml are pinned to full commit SHAs, which check-workflow-gates.sh enforces for the gating workflows. release.yml and the OpenModelica evidence workflows are byte-bound by their own approval and evidence manifests, so they keep tag references until their next reviewed refresh. The ci.yml in that directory is not the CI: it is the GitHub-era gate that produced the retained strict-bit evidence, stays byte-identical because it is a bound source of that evidence, and is disabled in the repository’s Actions settings. (CI briefly ran on a self-hosted Forgejo instance, now retired.)

Each gating workflow ends in a CI OK job that needs every other job and fails unless each one succeeded; check-workflow-gates.sh asserts that its needs: lists every other job in the file and that no gating job carries a job-level if:. Branch protection requires exactly that one status: the CI OK check from pr-gate.yml on development and from release-gate.yml on main. Both gates also run on pushes to their branch, so the merged commit is re-checked.

GitHub PR runs test a synthetic merge of the PR head into its base. Branch protection should also require a PR branch to be up to date with its base before merging, so the tested merge is what lands.

Docs-site validation (.github/workflows/docs-pages.yml) is path-filtered to docs/**, README.md, scripts/docs/**, scripts/authority_claims/**, site/** and its own workflow file. It reports its own build docs artifact status and is not part of CI OK, so a PR that does not touch those paths shows no docs check at all. A push to main touching those paths also deploys the site to GitHub Pages.

One command, one source of truth

.agents/gate.sh is the only place the gate’s command list is written down. Every other document in this repo — including this page — points at it rather than restating it, because nine divergent prose copies existed before the script was written and two of them were materially weaker than CI (see the script’s header). There are two invocations:

bash .agents/gate.sh        # light — mirrors the per-PR gate
bash .agents/gate.sh full   # full  — adds the workspace suite and doctests

CI does not merely mirror that script, it executes it: the gate (light) job at .github/workflows/pr-gate.yml and gate (full) at .github/workflows/release-gate.yml. So every command in the script gates a pull request whether or not pr-gate.yml also runs it as its own job. Read that as coverage, not as parity, and note that the implication does not run the other way: gate (light) is bash .agents/gate.sh plus any steps of its own. The Quickstart-executes step was exactly that for a while — a required check no local run of the script performed — and an earlier revision of this paragraph used a numeric citation that stopped one line short of it. Nothing verifies mechanically that the two files still list the same commands. That check was attempted and withdrawn, and the gate job’s header in the dormant .github/workflows/ci.yml records why — every design either compared argv strings that RUSTFLAGS=--cap-lints=allow leaves byte-identical while neutering clippy, or reimplemented enough of the workflow if:/needs:/matrix semantics to become its own untested gate.

The script’s steps group into: formatting, file-size and secret hygiene; the repository-invariant gates (the default build links no database or async runtime; the golden generator cannot bless its own output as the oracle; package, feature, and publication selection is closed); behavior fixtures for those gates, because a gate that cannot fail is not a gate; build, clippy and rustdoc under -D warnings; supply-chain checks; the determinism subset; and two fixture input-hygiene audits. A failing step never aborts the run, so one round trip reports every problem instead of the first (see the script’s step function).

The authority index and generated projection add a fast, bounded consistency check and hostile controls. Native numeric observers run inside the existing oce-api/oce-blocks subset; package/public/catalog validators retain ownership. The full gate also executes those owners. This does not check arbitrary Markdown claims or workflow parity, and regeneration is never gated in.

Dev-light, release-heavy

A green PR is not evidence that the change’s own tests pass.

The per-PR gate into development runs the state-determinism subset for oce-api, oce-blocks, and oce-expr. That is the determinism-matrix job in .github/workflows/pr-gate.yml: it runs that three-crate subset natively on x86_64 and, cross-compiled, on aarch64 under QEMU user-mode emulation, each twice — once under debug codegen, once under release codegen. Each architecture emits populated revision-2 portable and target-bound state vectors. The job compares both across codegen profiles, requires the portable files to match and the target-bound files to differ across architectures, then parses and refuses the aarch64 target-bound bytes on x86_64. The emulated leg excludes one test that re-executes its own binary (an OCE_BLESS truthiness probe, not a determinism test), which the native leg still runs. Emulation keeps the cross-architecture comparison on every PR with a single x86_64 runner; it is CI signal, not native aarch64 evidence. The gate script runs the test commands locally and adds two named oce-cxf test binaries, which are input hygiene rather than engine coverage: the port-order audit sweeps 47 CXF documents, of which 46 are Guideline 36 catalog fixtures and one is a resolver contract; the structural oracle compares the catalog fixtures it can pair with vendored modelica-json translations (see the gate script’s fixture input-hygiene section). That oracle compares document structure — instances and undirected edges — not simulated behavior.

The scoped oce-conformance strict-bit subset also runs per-PR: strict_bits plus the four affected per-block suite binaries, in Linux x86_64 (native) / aarch64 (emulated) × debug/release, with two independent captures per cell and a fail-closed cross-cell comparison. The retained evidence covers exact comparison of 21 pinned Real cases on qualified Linux; unqualified targets retain the unchanged 1e-12 aligned band. This is not the whole conformance suite or a libm accuracy claim.

The remainder waits for the release/full gate. A change outside the named test subsets can show a fully green PR having executed none of its own tests. Before claiming tests pass, run bash .agents/gate.sh full first-hand and read the tail.

Every pull request runs the gate

The per-PR gate runs on every PR, drafts included; there is no draft carve-out, because a gating job with a job-level if: would read as a failure in CI OK. A PR with no checks still looks a lot like a PR with no failing checks — confirm CI OK actually reported.

cargo-deny is not skippable, but advisories do not gate a PR

The standalone cargo-deny job in pr-gate.yml runs cargo-deny’s bans, licenses and sources checks on every PR, and the gate script runs them too (see the script’s cargo-deny step).

advisories is a different story, and the carve-out belongs next to the claim. It is deliberately excluded from the script — it needs network access and a writable advisory database, neither of which a sandboxed lane has. It runs daily in advisories.yml and on release PRs (the release-gate.yml cargo-deny job). advisories.yml has no pull_request trigger at all, so a PR into development that introduces a dependency with a known RustSec advisory merges green and is caught by the next scheduled run, not by its own gate.

What the release gate adds

release-gate.yml fires on development → main PRs, on pushes to main, on manual dispatch, and on a daily cron against the development tip (see its trigger block). It is disjoint from pr-gate.yml by base branch, so the two never both fire on one PR. It re-runs the light correctness gates against the release tip and adds four things:

StepWhat it coversWhere
workspace nextestevery unit and integration test in the workspacerelease-gate.yml, test-suite job, unit + integration step
workspace nextest, release codegenrelease panic-freedom, debug_assert paths stripped; inherited ci-release runner policyrelease-gate.yml, test-suite job, release step
cargo test --docdoctests — nextest cannot run them, so this is a separate steprelease-gate.yml, test-suite job, doctest step
two cargo public-api surface gatesexact public API text for oce-api and oce-storerelease-gate.yml, test-suite job, per-crate surface steps

--no-tests=fail is explicit on the nextest steps: a run that discovers zero tests hard-fails rather than passing, which catches tests that silently stop compiling or being found.

Nextest policy and reports

Local setup and CI pin cargo-nextest 0.9.143; .config/nextest.toml also declares that version as both required and recommended, so an older local binary exits before testing. The default profile is fail-fast. Automated debug runs use ci; release-codegen runs use ci-release, which inherits the same retries, timeout, leak, and reporter policy instead of copying it. The two public-API runs inherit that policy through separate child profiles because their nested nightly builds need a longer per-test timeout and separate reports.

Retries are zero and a flaky pass is still a failure. Ordinary tests terminate after 120 seconds; the public-API surface tests allow 10 minutes for their nested nightly rustdoc builds. A run stops after 15 minutes, and a child process retaining inherited output handles for more than two seconds fails as a leak. CI writes Jenkins-compatible JUnit XML to target/nextest/<profile>/junit.xml and requires every expected report to exist and be non-empty; the reports are no longer uploaded as artifacts. The emulated aarch64 legs use scripts/ci/nextest-emulated.toml instead of .config/nextest.toml: the same no-retry, flaky-is-failure policy, with wider time limits because QEMU runs test binaries several times slower.

Partitioning and build archives are deliberately off: the full test execution takes seconds while compilation dominates, and each determinism leg must execute the complete selected set under its own architecture and codegen mode. Experimental record/replay is also off in CI; enabling a feature that nextest still marks unstable would make the gate depend on a non-stable format. Test groups and thread reservations remain available when measurement identifies a shared resource or heavy test; none is known today.

The public-api baselines are the strongest stability evidence in this repo. They are checked-in text files — crates/oce-api/tests/public-api.txt (1357 lines) and crates/oce-store/tests/public-api.txt (1230 lines) — and the tests at crates/oce-api/tests/public_api.rs and crates/oce-store/tests/public_api.rs diff the crate’s real surface against them, so any unintended addition, removal or signature change fails the gate rather than shipping. Two env vars interlock to keep the gate honest: OCE_PUBLIC_API_NIGHTLY arms it and names the pinned nightly to shell out to, and OCE_REQUIRE_SURFACE_CHECK=1 turns a missing nightly into a hard panic instead of a silent skip, so disarming the gate turns it red, never green (see the surface steps’ arming environment). The two crates run as separate steps on purpose: merging the package selectors would let one surviving crate hide the other’s vanished test.

The exact rows are classified without replacing these signature baselines by the public surface contract and its machine-checked ledger.

What CI cannot observe

  • Operating systems other than Linux. Every gating workflow — pr-gate.yml, release-gate.yml, advisories.yml, and docs-pages.yml (per-PR on docs/**, README.md, scripts/docs/**, scripts/authority_claims/**, and site/**) — runs on ubuntu-latest, a Linux x86_64 runner. Cross-architecture is covered for the determinism and strict-bit subsets — x86_64 native and aarch64 emulated, debug and release. macOS and Windows are not built or tested anywhere.
  • Native aarch64 execution. The per-PR gate runs its aarch64 legs under QEMU user-mode emulation on x86_64. The retained native qualification in qualified-linux/ is still verified on every PR, but a new native aarch64 capture needs a native arm64 runner (ubuntu-24.04-arm, today used only by the manual OpenModelica evidence workflows).
  • Anything derived from git history. No workflow sets fetch-depth, so actions/checkout takes its default of a single commit. A check that needs history cannot run in CI. The visible consequence: golden provenance records bind to a content digest of the checked-in bytes rather than to the engine revision that produced them (crates/oce-cxf/tests/golden_provenance/mod.rs:3-5).
  • Line-ending behavior. .gitattributes:1 pins * text=auto eol=lf, but an ubuntu-only CI never performs a CRLF checkout, so that normalization is asserted by git configuration and exercised by no test. Goldens here are compared bit-exactly, which is precisely where a stray \r would show up.

The script says the rest itself, in its closing report: a green local run does not prove the cross-arch determinism matrix passes (one machine cannot reproduce it), does not prove the two cargo public-api surface gates pass (they need the gate-only nightly), does not prove cargo deny check advisories passes, and does not prove that the script and pr-gate.yml still agree. (The script’s own closing report still names ubuntu-24.04-arm and ci.yml; the script is a bound source of the retained strict-bit evidence, so its text changes only with an evidence refresh.) A light run additionally does not prove the workspace suite or doctests pass, because the per-PR gate does not run them.

Publishing

release.yml publishes with the crates.io token held in its release GitHub Environment. It is decoupled from both gates and from each other’s triggers. Pushing a v* tag runs verify only — tag/version match, fmt, clippy, a workspace cargo test, and a full cargo publish --dry-run — with no token and no publish, so a tag can be re-cut safely (see release.yml, verify job). Publishing is a separate manual workflow_dispatch into the release GitHub Environment. Cargo’s workspace selection includes the 12 publishable members and skips the five members with publish = false; the exact split and feature closure are guarded by the package, feature, and publication policy. No crate is on crates.io yet, and actual publication remains deferred pending explicit owner authorization.

Related: host-responsibilities.md for what the engine deliberately leaves to the embedder, and ../TESTING.md for the testing standard a change is expected to meet.

Benchmarks

Measured tick throughput for the Open Control Engine, recorded per run with the commit, host and method that produced it.

Read this first

Historical throughput numbers are not gated. Nothing in CI or in .agents/gate.sh re-measures those runs, so they are a record of what was observed, not a promise about HEAD. A performance figure that no test enforces drifts silently — the same failure mode that got a git SHA deleted from every provenance record in PR #204, and the reason the numbers live here rather than in README.md, where they would be read as a standing claim.

Treat a run below as evidence about that commit on that host. To make a claim about a different commit, measure the current contract with the compiled harness described below. The retired execution profiles are not interchangeable with complete-frame work.

The complete-frame harness below now runs structural allocation assertions and emits non-gating latency observations. Historical throughput runs retain their original method and limits.

Complete-frame observations

2026-09-19, implementation working tree based on 64e7ba83ff78a6d07750502d0f3f037b8b39f519. Apple M5, aarch64-apple-darwin, macOS 27.0 (26A5425a), Rust 1.97.1. Default features, locked dependencies; dev/debug and release (workspace thin LTO, one codegen unit). These are local observations, not hosted x86_64/arm64 qualification, equipment cadence or universal speed claims.

The historical version of frame_observations.rs used five representative fixtures. Load was excluded. Constant schema-valid synthetic inputs were staged once for legacy tick; native frames supply the same complete values each time. Equal model time 0.0 deliberately tests repeat transitions. After 64 warmup calls, five 2,048-call batch means are measured, alternating commit/tick order. Commit timing excludes preparation and outer plan-batch allocation but includes plan and result destruction. End-to-end preparation+commit is also reported. Legacy tick includes its MemStore snapshot where inputs are bound, uses no-op diagnostics and produces no retained frame, so this is a cost comparison between different contracts, not an equivalent-work speedup. Only this test was scheduled during explicit measurements; other machine activity was not controlled.

Median of five batch means, nanoseconds per call:

CaseDebug tickDebug commitDebug prepare+commitRelease tickRelease commitRelease prepare+commit
Add arithmetic2113901,3573476171
Sampled delay3455181,4734279158
Zero-input Pre feedback282471533356869
213-block G36 controller18,63117,71426,5502,5782,5573,246
Assert, zero boundary outputs2144669932879126

Commit batch-mean ranges (debug / release ns): arithmetic 388–399 / 63–78; delay 488–526 / 78–86; feedback 419–473 / 63–69; controller 17,625–17,951 / 2,538–2,637; assertion 453–475 / 72–91. These short samples expose host drift, not tail latency or confidence intervals. No timing value or ratio is an assertion; the functional determinism tests are separate.

The historical debug/release harness asserted the exact allocation formula over 128 preparation+commit repetitions per fixture and zero outstanding allocations after drop, with a 1,024-byte positive control. It counts the synchronous thread only. N is logical inputs, T fan-out targets, B boundary outputs and D emitted warnings (these cases have at most one):

CaseN / T / B / DPreparation allocations / bytesCommit allocations / bytes
Add2 / 2 / 1 / 04 / 1202 / 66
Delay2 / 2 / 1 / 04 / 1202 / 59
Pre0 / 0 / 2 / 00 / 03 / 114
Controller14 / 43 / 10 / 016 / 95611 / 1,136
Assert1 / 2 / 0 / 13 / 643 / 262

Preparation uses N+2 buffers for nonempty N. Historical commit capture used B+1 buffers for nonempty B, with B * size_of::<(String, Value)>() + sum(path_bytes) bytes; zero B allocates none. Each warning copies its source/message and grows a Vec geometrically (one warning uses four event slots). Value cloning preserves bits and shares immutable String payloads. This budget is structural: no shadow RunState, whole-engine copy or rollback. Existing evaluator allocations, notably wide Sort, remain a separate baseline cost; this fixture census does not erase them.

The shared evaluation-core extraction based on 8ea3e8f38d580868179bfa985b443b71ca3b86c1 re-ran this exact census in debug and release on aarch64-apple-darwin/Rust 1.97.1: all five preparation/commit counts and byte budgets above remain unchanged. The latency test also ran, but these observations are not a speed gate or a before/after performance claim. The historical timing table above is not re-blessed. The frame-only contraction removes the legacy comparison; the current harness measures commit and preparation-plus-commit only, retaining the allocation formula checks. All accepted frames now retain warnings. No current latency claim is inferred from the historical tick columns.

Canonical replay delivery additionally retains the accepted inputs in every completed receipt: N canonical path buffers plus one N-pair vector for nonempty N, costing N * size_of::<(String, Value)>() + sum(input_path_bytes). Prepared Values move into this vector; there is no second value-byte copy. Placement capture scans block classes under the state policy. The current harness asserts these added counts/bytes over the same 128 repetitions with zero retained allocations after drop. The historical table above is not a current total or latency prediction. Record encoding/decoding is separate opt-in work; the replay contract documents its caps and streamed-host allocation evidence, not a speed claim.

Run observations explicitly with --success-output immediate --test-threads 1 on the focused frame_observations nextest binary. It is also included in the existing oce-api matrix test set; normal success-output suppression hides passing timing logs, but allocation checks still execute. Use --locked --profile ci --no-tests=fail for debug and --locked --profile ci-release --cargo-profile release --no-tests=fail for release. The full repository gate remains .agents/gate.sh, not this measurement selection.

What the historical throughput runs measured

The now-retired steady-state Engine::tick() on real G36 fixtures, through the then-public facade: Engine::in_memory() → load_cxf() → tick().

  • Load is excluded from the tick figure and reported separately. Loading happens once; ticking happens forever, so blending them would flatter the result and describe neither.
  • Ticks are run for a fixed wall-clock window after a warmup, so first-touch page faults and any lazily initialised state land in the warmup rather than the measurement.
  • The clock is read in batches, so the timing call is not itself the workload.
  • Time advances monotonically. The engine rejects time regression, so t only ever increases across warmup and measurement.

What is not measured

Stated explicitly, because the gap between these and the numbers below is where a wrong conclusion would come from.

  • Load / parse / resolve throughput beyond the single load_ms column.
  • Tail latency. These are means over millions of ticks. For equipment control the tail usually matters more than the mean, and one property that governs it — whether the evaluator thread allocates during a block tick — is gated separately and much more strictly, by crates/oce-blocks/tests/tick_allocation_census.rs (registry-wide, with a positive control), which runs per-PR. Current facade frame allocation and Store-noninterference checks also run per-PR in frame_observations.rs and frame_purity.rs. Historical throughput is not gated.
  • Multi-core or concurrent engines. Single engine, single thread.
  • Any architecture other than the one in the run header. CI runs a determinism matrix across x86_64 and arm64 precisely because one machine does not speak for both.
  • Store-backed input staging under a real durable store. Runs below use the in-memory store.

Runs

2026-07-30 · b5b19e7 · Apple M5 (10 cores) · rustc 1.95.0 · macOS 26.6 · --release

Warmup 20,000 ticks · 2.0 s measurement window · dt = 1.0 simulated second per tick.

fixtureCDL class refsns/tickticks/sec
cooling_only_controller2222,508398,801
multizone_vav_relief_fan_group2282,706369,558
multizone_vav_supply_fan717551,325,349
ahu_economizer121417,095,155
vav_single_zone81446,932,518

Repeated back to back; the two runs agreed within ~2% on the large fixtures and ~6% on the smallest. Measured on an otherwise idle machine — an earlier attempt taken while a parallel build was running produced numbers that were not reproducible, which is why the method above insists on it.

Load, same commit and host — 60 iterations per fixture

Reported as first / median / min in one process, because a single first-call figure carries process-start and page-cache cost.

fixtureKiBfirst msmedian msmin msfirst÷medMiB/s at median
cooling_only_controller4097.842.622.483.0×154
multizone_vav_relief_fan_group3662.552.122.021.2×169
multizone_vav_supply_fan1120.890.680.661.3×159
ahu_economizer160.140.110.101.3×144
vav_single_zone140.110.090.081.3×150

These numbers were measured before #230, which added a working-clone @context expansion pass to ingest; load cost moved roughly +9–12% there, tick cost not at all.

Correction to an earlier revision of this file. It reported 11.0 ms for cooling_only_controller and placed the “runs agreed within ~2%” sentence where it read as covering that column too. Both were wrong. That figure was a single first call in a cold process — it is the first fixture measured, so it absorbed process start and page-cache misses. Measured properly it is 2.62 ms, a 4.2× overstatement. A second process invocation shows the same fixture’s first/median ratio collapse from 3.0× to 1.3× once the page cache is warm, while every other fixture sat at 1.1–1.5× in both runs. The tick figures above were unaffected: they were always taken after a 20,000-tick warmup, which is exactly the discipline the load column lacked.

Observation — load throughput is flat. 144–169 MiB/s across a 29× size range, through the whole load_cxf pipeline: import_cxf (JSON-LD parse and resolve to a flat ground ModelGraph), flatten, §7.10 attribute unification, structural validation, then the build tail (registry, schedule, state, outputs, io, params, store recovery). That is not a JSON parse, so a plain parser MB/s intuition does not apply. The measured revision re-ran pure validation in the build tail. Current load_cxf validates once before entering build_validated_model_in_memory (crates/oce-api/src/engine.rs:231-244), so this historical table includes work the current path no longer performs.

Observation — cost is linear in block count. Across a 28× size range the per-block cost holds at roughly 11 ns (11.3 / 11.9 / 10.6 / 11.8 ns for the four largest). vav_single_zone is the exception at ~18 ns per block, and it is the expected one: at 8 blocks the fixed per-tick overhead (finite/monotonic time checks, output refresh) stops being amortised. No superlinear term is visible, so a sequence twice the size costs about twice as much.

Caveat on “CDL class refs”. That column counts CDL type references in the fixture’s JSON-LD. It is a proxy for scheduled block count, not the count itself, so the per-block figures are indicative rather than exact. The linearity across 28× is the load-bearing part and does not depend on the proxy being tight.

In deployment terms. cooling_only_controller is the largest fixture document in the corpus (409 KiB; 213 blocks and 268 connections after import) and ticks in ~2.5 µs. Building control sequences run at a 1 Hz cadence or slower.

Correction, 2026-07-31. This paragraph previously called it “the largest sequence in the fixture corpus (213 instances, 377 edges)”. Both halves were wrong: relief_fan_group imports to 226 blocks, more than this fixture’s 213 (pinned at crates/oce-cxf/tests/resolve_g36_relief_fan_group.rs:145-146), and 377 was the pre-import isConnectedTo count, not the 268 connections the tick loop actually runs. The measured timing above is unchanged and was not re-run — only the description of what was measured is corrected.

Reproducing a run

Use the repository’s compiled frame_observations nextest binary for current measurements, with the focused settings above. It submits a complete frame on every iteration and separately reports commit-only and preparation-plus-commit cost. There is no sparse staging or implicit hold-last benchmark profile. Historical tables retain the method and limits of their recorded revisions; they are not predictions for the frame-only facade. No older timing table has been regenerated.

Run it on an idle machine, and run it at least twice — a figure that does not reproduce is not a measurement.

To measure load rather than ticks, loop the Engine::in_memory() + load_cxf pair on its own (60 iterations is plenty) and report first / median / min, not a single call. The first call in a cold process absorbs process start and page-cache misses; on the largest fixture that inflated the figure by 3× and produced the erroneous 11.0 ms corrected above. Reporting only a median hides the cold cost from anyone who cares about startup, and reporting only a first call is simply wrong — report both. Time Engine::in_memory() inside the measured region and let the engine drop outside it, since teardown is not part of load.

Adding a run

Append a new ### <date> · <short SHA> · <host> · <toolchain> · <profile> section above the previous ones, newest first. Never edit an older run to match a newer one: the value of this file is the trend, and a rewritten history has no trend in it. If a run regresses, record it and say so — that is the entire point of keeping the record.

Stability baseline and downstream pin ledger

stability-baseline-2026-08-26.json is a dated, machine-readable evidence snapshot. It records exact Open Control Engine refs and topology, package/toolchain facts, tag versus GitHub Release state, issue and pull-request inventories, repository-policy unknowns, Studio product/program identities, and downstream OCE pin states.

This is evidence, not authority. Live repository source and repository policy remain authoritative. Moving branch heads do not invalidate this historical capture, and a later capture must be added as a new dated artifact with an explicit supersession note; this file must never be silently rewritten as though its old observations were current.

Identity boundaries

The Studio entries deliberately keep three identities separate:

  1. open-control-studio product source is the reviewed behavior.
  2. The selected program locator is the immutable program revision named by that product source.
  3. The program repository head is newer planning work and does not define product behavior until a reviewed product locator change selects it.

Likewise, the Library’s declared ENGINE_PIN remains its exact commit even though that commit’s OCE tree equals the captured OCE development tree. Tree equivalence never substitutes for commit identity. Sim and cxf-json use none_at_revision, which is intentionally different from UNKNOWN.

Provenance and supersession

Primary Git source is named in each artifact entry by repository, exact revision, and path. The OCE package claims come from Cargo.toml, rust-toolchain.toml, and crates/oce-api/Cargo.toml at the captured development commit. Studio pins come from Cargo.toml and its selected locator from docs/roadmap/README.md at the captured product commit. Library uses ENGINE_PIN; Sim uses its Cargo manifests and ocs-bridge source; cxf-json uses all Cargo manifests. Dated GitHub observations name the API endpoint used for compare, tag, release, issue, and pull-request evidence.

The local-only 2026-08-25 planning input at _spec/open-control-engine-2026-08-25/CURRENT-BASELINE.md is absent from clean checkouts. Its relevant stale statements are quoted in the JSON rather than treated as authority: Studio product main was c756d320aa495ddd66630f0987202c6d852f27f5, its locator was 5bc001ff9dbf980e5fb7f106e2d5eb9329e842c3, and the program head was 5dd50c97472020142503ec8fdb79823fc0e56c78. The 2026-08-26 capture records the refreshed values without modifying that historical input.

Branch protection and ruleset state is UNKNOWN: the available API evidence was authorization-limited, so no absence of protection is inferred. One concrete closure step remains: a repository administrator must inspect Settings → Rules → Rulesets and Settings → Branches and record every active rule applying to development and main. Issue program owners are also UNKNOWN; empty assignee lists do not permit inferring an owner from an issue author.

Deterministic verification

This verification tool requires Python 3.11 or newer for the standard-library tomllib module. That prerequisite applies only to this documentation/evidence tool; Open Control Engine runtime and library consumers do not require Python.

The standard-library-only tool performs no network access and never compares captured refs with today’s moving branch heads:

python3 tools/stability_baseline/baseline.py --check
python3 tools/stability_baseline/baseline.py --check --source open-control-engine=.

--check rejects duplicate/extra fields, malformed or shortened SHAs, noncanonical serialization, changed exact facts, pin/tree mismatches, conflated Studio identities, missing issue/PR distinctions, and no-pin/unknown substitution. The source option additionally verifies exact local Git objects, trees, divergence, and source files by commit. It does not fetch, so source access is explicit and historical verification cannot accidentally depend on mutable heads.

Downstream exact source can be checked from explicit local clones, individually or together:

python3 tools/stability_baseline/baseline.py --check \
  --source open-control-studio=PATH \
  --source open-control-studio-program=PATH \
  --source open-control-library=PATH \
  --source open-control-sim=PATH \
  --source cxf-json=PATH

Regeneration is deterministic and review-only:

python3 tools/stability_baseline/baseline.py --write
python3 -m unittest discover -s tools/stability_baseline -p 'test_*.py' -v

Regenerate only when reviewing this dated artifact itself. A new observation date belongs in a new artifact rather than rewriting historical evidence.