EMBED
Run the guarded Quickstart
Start with the mechanically reused, compiled example, then read the host safety boundary before connecting equipment.
Embed the engine →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
Parse CXF, freeze a control schedule, and tick CDL control sequences deterministically inside your application—without bringing a daemon, runtime framework, or database.
EMBED
Start with the mechanically reused, compiled example, then read the host safety boundary before connecting equipment.
Embed the engine →CONTRIBUTE
Understand the development and release gates, what a green check proves, and the testing standard expected of every change.
Contribute to the engine →The documentation separates deterministic self-output traces, independent signal oracles, structural checks, and work that is still deferred.
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.
Reference documentation for Open Control Engine. The project README is the front door; these pages are the detail behind it.
| Page | Read it when you want to know |
|---|---|
| Product contract | Versioned executable-CXF/HostTick requirements, domain-owner delegations, limitations, evidence and explicitly future outcomes |
| Authority claims and supersession | The generated cross-domain summary, checked versus review-only boundaries, source-owner update procedure, and non-exhaustive supersession map, from the index |
| Architecture | How 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 profile | Why each host tick is one state transition, and where CDL.Logical.Pre differs from Modelica same-time event iteration |
| Canonical replay record | Bounded per-accepted-frame exact bytes, typed refusals and host-owned ordered replay with separate snapshot sidecars |
| Release-candidate compatibility | Retained directed matrix, no-N-1 refusal policy, cold-start/rollback checklist and future RC note template |
| Compatibility manifest | Per-release CXF import contract, schema revisions, content ids and state/replay formats, generated by crates/oce-api/tests/compatibility_manifest.rs |
| Verification and evidence | What has actually been proven about this engine, what has not, and which checks are deliberately not running |
| CDL coverage | Whether your sequence runs — which classes and G36 sequences are supported, and what “supported” is defined to mean |
| CXF round trip | What export guarantees, and the conditions under which it silently drops part of your model |
| CXF composite subset | The normative contract, if you are writing a tool that emits CXF for this engine |
| Host responsibilities | What safety behavior you must implement yourself, before wiring the engine to equipment |
| CI and the gate | What runs when, and what a green check does and does not prove |
| Benchmarks | Current complete-frame allocation/latency harness and historical throughput, qualified by the commit and host that produced it |
| Stability baseline | The dated OCE/downstream ref and pin evidence snapshot, its authority limits, and deterministic verifier |
| Public surface contract | Which oce-api and oce-store items are stable candidates, conditional, deferred, deprecated, or scheduled for removal, with the machine-checked ledger |
| Facade migration | Frame-only execution migration, removed pre-release names, Warning-only diagnostics and preserved host boundaries |
| Package, feature, and publication policy | Which 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 |
| Document | Purpose |
|---|---|
README.md | Project front door: what this is, who it is for, and how to try it |
TESTING.md | The testing standard every change is held to. Read before writing a test |
CONTRIBUTING.md | How to work on the repository |
SECURITY.md | Reporting, threat model, and the known hardening limit |
CHANGELOG.md | Notable changes |
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.
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.
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.
Domain authorities retain their scope; this aggregate does not supersede them:
| Owner role | Delegated authority |
|---|---|
| Contract maintainers | This document’s revision, traceability and acceptance boundaries; affected domain owners decide semantics. |
| Facade maintainers | Public surface contract, exact facade baseline and storage baseline. |
| Release maintainers | Package and publication policy, including its separate feature matrix. |
| CXF maintainers | Composite subset and round-trip contract. |
| Execution maintainers | Execution profile, complete-frame contract, load/execute/state implementation and focused tests below. |
| Block semantics maintainers | Local block behavior and bounded conformance boundary; upstream provenance is a separate evidence question. |
| Host integrator | Host 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.
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.
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.
| ID | Status | Actor | Owner | Requirement | Limitation | Grounding | Evidence |
|---|---|---|---|---|---|---|---|
| PC-001 | CURRENT | Contract maintainer | Contract maintainers | MUST 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 record | test test_report_is_an_independent_byte_golden |
| PC-002 | CURRENT | Product claimant | Release maintainers | MUST 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 ingest | test 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-003 | CURRENT | Product claimant | CXF maintainers | MUST 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 loaders | test 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-004 | HOST-OBLIGATION | Host | Host integrator | MUST 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 input | test inclusive_default_and_stricter_boundaries_preserve_legacy_acceptance |
| PC-005 | CURRENT | Product claimant | Execution maintainers | MUST 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; Compensation | test 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-006 | CURRENT | Engine | Execution maintainers | MUST 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; Evaluator | test parameter_seed_is_first_call_output_and_equal_time_calls_advance_memory; nonconvergent_boolean_feedback_is_accepted_and_advances_per_call |
| PC-007 | CURRENT | Engine | Execution maintainers | MUST 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; Migration | test incomplete_observations_never_stage_prefixes_or_reuse_prior_values |
| PC-008 | CURRENT | Engine | Execution maintainers | MUST 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. | Determinants | test store_samples_neither_supply_missing_determinants_nor_overwrite_complete_values |
| PC-009 | CURRENT | Engine | Execution maintainers | MUST 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. | Preparation | test every_refusal_preserves_fresh_and_advanced_stateful_images_and_store |
| PC-010 | CURRENT | Engine | Execution maintainers | MUST 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 matrix | test incomplete_observations_never_stage_prefixes_or_reuse_prior_values |
| PC-011 | CURRENT | Engine | Execution maintainers | MUST 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. | Execution | test sampled_delay_matches_hand_recurrence_and_retained_frames_survive_lifecycle_changes |
| PC-012 | CURRENT | Product claimant | Execution maintainers | MUST 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. | Outcome | test context_readiness_and_time_refusals_preserve_public_images_and_store_calls |
| PC-013 | CURRENT | Engine | Execution maintainers | MUST 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. | Execution | test every_refusal_preserves_fresh_and_advanced_stateful_images_and_store |
| PC-014 | CURRENT | Engine | Execution maintainers | MUST 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 delivery | test complete_corpus_frames_never_read_or_write_the_store |
| PC-015 | CURRENT | Engine | Execution maintainers | MUST 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 preconditions | test param_lifecycle_halt_set_resume_refolds; pending_parameter_edits_take_precedence_over_restore_readiness |
| PC-016 | CURRENT | Engine | Facade maintainers | MUST 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 accessors | test warning_context_survives_each_later_store_failure |
| PC-017 | CURRENT | Product claimant | Facade maintainers | MUST 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 collector | test default_severity_is_warning_not_an_unemitted_failure; boolean_assertions_repeat_warning_records_and_continue_bit_exactly |
| PC-018 | CURRENT | Product claimant | CXF maintainers | MUST 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 tag | test warning_bearing_content_id_identifies_the_partial_document; content_id_tracks_exported_synthetic_document_while_model_id_stays_authored |
| PC-019 | CURRENT | Engine | Execution maintainers | MUST 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 contract | test checkpoint_refusal_is_bit_atomic; every_manifest_field_refuses_deterministically_without_mutating_engine_or_store |
| PC-020 | CURRENT | Engine | Execution maintainers | MUST 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 contract | test mutation_boundaries_close_the_durable_restore_window; capture_and_restore_call_no_store_method; snapshot_restores_next_pre_output_at_same_timestamp |
| PC-021 | CURRENT | Product claimant | Facade maintainers | MUST 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 authority | test diagnostic_model_identity_is_not_an_execution_compatibility_key; content_id_tracks_exported_synthetic_document_while_model_id_stays_authored |
| PC-022 | CURRENT | Product claimant | Block semantics maintainers | MUST 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 implementation | test true_hold_with_reset_names_match_its_behaviour |
| PC-023 | CURRENT | Product claimant | Block semantics maintainers | MUST 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 context | test nonconvergent_boolean_feedback_is_accepted_and_advances_per_call |
| PC-024 | HOST-OBLIGATION | Host | Host integrator | MUST 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 policy | test store_samples_neither_supply_missing_determinants_nor_overwrite_complete_values |
| PC-025 | HOST-OBLIGATION | Host | Host integrator | MUST 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; Halt | test param_lifecycle_halt_set_resume_refolds |
| PC-026 | HOST-OBLIGATION | Host | Host integrator | MUST 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 time | test equal_time_feedback_advances_once_and_refusal_consumes_no_position |
| PC-027 | HOST-OBLIGATION | Host | Host integrator | MUST 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 authority | test capture_and_restore_call_no_store_method |
| PC-028 | CURRENT | Facade delivery | Facade maintainers | MUST 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 inventory | test retired_facade_symbols_are_absent; filtered_inventory_refuses_without_store_calls_or_engine_mutation |
| PC-029 | CURRENT | Facade delivery | Facade maintainers | MUST 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; Adoption | test 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-030 | CURRENT | Admission delivery | CXF maintainers | MUST 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; Replacement | test 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-031 | CURRENT | Frame delivery | Execution maintainers | MUST 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; Outcome | test incomplete_observations_never_stage_prefixes_or_reuse_prior_values; test_report_is_an_independent_byte_golden |
| PC-032 | CURRENT | Frame delivery | Execution maintainers | MUST 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; Contract | test 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-033 | CURRENT | Frame delivery | Execution maintainers | MUST 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; Contract | test 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-034 | CURRENT | Frame delivery | Execution maintainers | MUST 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; Acceptance | test accepted_frames_enter_the_shared_core_once_and_refusals_never_enter |
| PC-035 | CURRENT | Frame delivery | Execution maintainers | MUST 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; Facade | test retired_facade_symbols_are_absent; incomplete_reference_inputs_refuse_in_both_cadences; store_samples_neither_supply_missing_determinants_nor_overwrite_complete_values |
| PC-036 | CURRENT | Identity delivery | Facade maintainers | MUST 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; Contract | test 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-037 | CURRENT | Evidence delivery | Block semantics maintainers | MUST 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 standard | test 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-038 | CURRENT | State delivery | Execution maintainers | MUST 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 envelope | test 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-039 | CURRENT | Replay delivery | Execution maintainers | MUST 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; Facade | test 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-040 | CURRENT | Release delivery | Release maintainers | MUST 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 authority | test 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 |
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
IoInventory includes internal points, projects enums to
Int, and omits strings. It cannot by itself establish the required executable input set.These are semantic roles, not proposed public field or type names:
| Role | Meaning and limit |
|---|---|
| Loaded-executable context | The 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 compatibility | Canonical 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 correlation | An 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 position | The 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.
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.
| Condition | Required outcome before mutation |
|---|---|
| No successfully loaded executable | Refuse; an empty engine is not an executable with zero inputs. |
| Pending parameter edits | Refuse until resume; halted alone does not prohibit execution. |
| Unknown or non-input identity | Refuse; do not ignore extra values or treat output aliases/internal driven points as boundary inputs. |
| Duplicate boundary input | Refuse even if both values are bit-identical; do not use first-wins or last-wins. Fan-out is not a duplicate. |
| Missing required input | Refuse; no hold-last, zero/false seed or Store fill-in. Apply only explicit executable omission semantics. |
| Wrong type or declared domain | Refuse the complete candidate, including any otherwise valid prefix. |
| NaN or either infinite model time | Refuse. |
| Finite time below the preceding accepted model time | Refuse. |
| Time outside loaded-block representability | Refuse under the existing model-time limits, not by executing first. |
| Stale loaded-executable or IO compatibility context | Refuse frames and references from a superseded successful load; no implicit rebinding. |
| Accepted-frame sequence exhausted | Refuse before staging; never wrap or reset the sequence. |
| All checks pass, including equal finite time | Accept 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.
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.
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.
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.
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.
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:
State(NoLoadedModel), then State(PendingParameterEdits).NonFiniteTime, TimeRegression, then ModelTimeUnrepresentable, in that order.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.FrameDuplicateInput, then FrameMissingInput, each naming the lowest canonical input path.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.
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.
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.
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.
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.
| Operation | Contract |
|---|---|
checkpoint | Owned process-local image; compatible restore can rewind. No persistence format or external authority. |
state_snapshot | Validate 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_bytes | Validate 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_state | Check readiness, target domain, execution ABI, full manifest/fingerprint and payload before one in-memory commit. No evaluation or Store calls. |
restore_checkpoint | Same 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.
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 order | Typed evidence |
|---|---|
| Supplied bytes above 64 MiB, before header inspection | SnapshotTooLarge with actual and maximum counts |
| Short/invalid header; unsupported format; inconsistent total length | MalformedSnapshot / UnsupportedFormat |
| Corrupted body/trailer after valid header and length | IntegrityMismatch with computed and carried checksums |
| Invalid tags/UTF-8/counts, duplicate or unordered sections, inconsistent references, fingerprint, flags or payload shape | MalformedSnapshot with deterministic offset/detail |
| Restore without a successful load; pending edits; advanced durable target | NoLoadedModel, PendingParameterEdits, DurableTargetAdvanced, in that order |
| Foreign target-bound architecture or OS | TargetDomainMismatch before execution compatibility |
| Different execution-state ABI, including a different transition profile | IncompatibleExecution, subject execution-state ABI revision |
| Different manifest | IncompatibleExecution, first named subject in the order below |
| Wrong fingerprint or connector mapping after matching manifest | IncompatibleExecution |
| Invalid initialized/pre-first-tick words, clock relationship or class-specific state | InvalidBlockState |
| Missing stable authored identities or registered state contract during capture/target construction | IneligibleModel |
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.
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.
EngineStateSnapshot::portability() returns a borrowed, cloneable StatePortability:
| Policy | Placement check | Evidence boundary |
|---|---|---|
Portable | No 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.
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/):
Logical.TrueDelay persistence timer of 900 s.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.
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.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.
ReplayRecord::from_bytes. This checks canonical representation,
corruption and bounds, not host trust. Do not patch bytes or relabel placement to make them pass.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.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.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.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.
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/order | Encoding |
|---|---|
| 0..8 | ASCII OCERPLY then one NUL byte |
| 8..12 | u32 format revision, exactly 1 |
| 12..20 | u64 body byte length, excluding this header and the trailer |
| Body first | u8 exactness: 0 = ExactBits; every other tag refuses |
| Placement | u8: 0 = Portable; 1 = TargetBound, followed by str arch, str os; other tags refuse |
| Public facts | str canonical descriptor, grammar below |
| Model time | u64 raw binary64 bits, finite seconds; signed zero is retained |
| Inputs | u32 count; each row is str canonical_path, native value |
| Outputs | u32 count; each row is str canonical_path, native value |
| Diagnostics | u32 count; each row is str block, str message, u64 raw time bits, u8 severity (0 = Warning only) |
| Trailer | u128 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.
| u8 tag | Payload |
|---|---|
| 0 — Real | u64 raw IEEE-754 binary64 bits, including subnormals, infinities, both zeros and every NaN payload/sign |
| 1 — Integer | i64 two’s-complement bits; no f64 conversion or codec-level i32 narrowing |
| 2 — Boolean | u8 exactly 0 or 1 |
| 3 — String | str exact UTF-8, including empty, NUL and LF |
| 4 — Enum | str 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.
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.
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.
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.
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.
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:
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.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 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.
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.
| Artifact | Current to current | Historical to current | Current to historical | Historical to historical | Scope / evidence |
|---|---|---|---|---|---|
| Package/public API | A | U | U | U | Exact current facade/storage baselines and compiler contraction controls. Historical tick/simulate/step_realtime were removed; matching versions do not restore source compatibility. |
| Facade catalog/schema | A | N | U | N | Current canonical facade catalog/schema revision 1; not a claim that the old oce-blocks catalog did not exist. |
| Public descriptor | A | N | H | N | Closed descriptor revision 1, public facts only; absent export is not a wildcard. |
| Diagnostics | A | N | U | N | Versioned 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 profile | A | U | U | U | Fixed HostTick v1, execution descriptor revision 2. No equivalence of historical execution modes is claimed. |
| Executable/frame | A | N | U | N | Complete typed frame preparation then one consuming transition; not durable executable serialization or cross-engine prepared-frame portability. |
| Snapshot | A | N | H | N | Format 2 / execution ABI 2; loaded-executable manifest, startup window and Portable/TargetBound placement remain mandatory. |
| Replay | A | N | H | N | Format 1, ExactBits only, descriptor and Portable/TargetBound placement; host authenticates ordered envelope and optional state sidecars. |
| Strict-bit qualification | A | N | U | N | Only 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.
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.IncompatibleExecution and
foreign placement with TargetDomainMismatch, before mutation or Store calls.CompatibilityMismatch first cause.UnsupportedFormat, UnsupportedExactness,
DescriptorMismatch and TargetMismatch. A verification mismatch after execution is different:
it does not undo the accepted transition.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:
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.
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.
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.
Copy this section as a checklist for a separately authorized RC, not as a release announcement:
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.
oce_blocks::catalog() by test-only owner modules. Python never extracts Rust literals.
A fast-check PASS alone does not mean native comparisons passed.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.
| Package | Classification | Publishable |
|---|---|---|
oce-api | host-facade | true |
oce-bless | test-support | false |
oce-blocks | transitional-companion | true |
oce-conformance | verification-tooling | false |
oce-cxf | implementation-dependency | true |
oce-diag | implementation-dependency | true |
oce-docs | reserved-panic-only | false |
oce-expr | implementation-dependency | true |
oce-extension | experimental-reserved | false |
oce-flatten | implementation-dependency | true |
oce-graph | implementation-dependency | true |
oce-model | implementation-dependency | true |
oce-reference-wal-adapter | private-reference-adapter | false |
oce-semantics | implementation-dependency | true |
oce-store | conditional-adapter-port | true |
oce-store-mem | implementation-dependency | true |
oce-validate | implementation-dependency | true |
| Public baseline | Descriptor rows | SHA-256 (owner descriptor) |
|---|---|---|
| crates/oce-api/tests/public-api.txt | 2560 | db804469b3d16c4d7388a6540204c3304ecacdb5ba55172f07d7016e8a3fb948 |
| crates/oce-store/tests/public-api.txt | 1230 | 78cf5fdbcbd415a4ca3c521501c9c1a9ccca099551839c2fd360edc311fcbafe |
Exact row assignments remain in the public ledger; statuses below are displayed, not re-adjudicated.
| Group | Status |
|---|---|
deprecated-compatibility | deprecated |
domain-key-stable | stable-candidate |
facade-stable | stable-candidate |
schedule-leakage | implementation-leakage-to-remove |
storage-port-conditional | conditional |
CDL source revision: a131864e4c4df22ebcd52bb8da439de0087ac365; catalog fingerprint: 9edead4415592f28.
This is the existing pinned source identity, not a new numeric catalog revision.
| Delegated source | SHA-256 (exact input bytes) |
|---|---|
| catalog-registry | c52b1c5807e78aaf930f09402717ab7b5f1d98d173452d0cd22bbd6cb0b16336 |
| catalog-source | a8109009c6ffebba52522c2d6f96ac898c926b420a6baec369328c55b40d2702 |
| packages | 901e3ad38af0c0d223aeb0624b5bac55398cf4370155a0541301dbb2ebffb74d |
| public-surface | c60488c42c3704b0cb8f15973ff40eea735ba96e9a94410445afd8b646060baf |
| Fact | Expected |
|---|---|
| catalog-entries | 136 |
| catalog-reserved | 3 |
| execution-abi | 2 |
| state-format | 2 |
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.
| Corpus | Provenance records | All members | Tier | depends_on_oce_blocks | Inventory SHA-256 |
|---|---|---|---|---|---|
| tier-a-records | 412 | 1059 | A | false | 09b7f14460296742cc57d0f801465326bf933738c5394a2274205151022fbdae |
| tier-two-records | 46 | 92 | 2 | true | 94a5b189d657aef6441437ad4ee241929a7b73ce40237e729795eb1f64f14149 |
| Family | Existing evidence | Limit |
|---|---|---|
| deferred-capabilities | docs/public-surface-contract.md; docs/cdl-coverage.md | Baseline-row classification is delegated above; broader capability deferral remains human-reviewed. No arbitrary Markdown or Rust semantics are parsed. |
| evidence-quality | docs/verification-evidence.md; TESTING.md | Raw 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-tick | docs/execution-profile.md; crates/oce-api/src/tests/pre_execution_profile_tests.rs | HostTick 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. |
| platforms | docs/architecture.md; .github/workflows/pr-gate.yml | CI runner labels are execution evidence, not a structured support-promise contract. No supported-target matrix is inferred. |
| true-hold-extension | crates/oce-blocks/src/logical_timing.rs; crates/oce-blocks/src/port_names.rs | TrueHoldWithReset 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. |
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 / subject | Historical locator (text only) | Status | Current authority | Superseded by | Reason / responsibility owner |
|---|---|---|---|---|---|
dated-stability / Dated stability snapshot | docs/stability-baseline-2026-08-26.json | historical | docs/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) | superseded | docs/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.md | superseded | docs/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/ | superseded | docs/public-surface-contract.md | — | Tracked public classification and blessed signatures outrank ignored planning inputs. / Public facade maintainers |
true-hold-annotation / TrueHoldWithReset source annotations | crates/oce-blocks/src/logical_timing.rs:440-448; crates/oce-blocks/src/port_names.rs:43-52 | ambiguous | Unresolved | — | The deliberate local two-input extension and historical specification comments need human adjudication; no authority replacement is selected here. / Block semantics maintainers |
expected observation
in the index. Never copy delegated counts, classifications, or matrices into the index.python3 scripts/authority_claims/check.py --write.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.
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.
When public-surface descriptions disagree, use this order:
crates/oce-api/tests/public-api.txt and
crates/oce-store/tests/public-api.txt for exact names and signatures;crates/oce-api/src/guards.rs for only the selected shape invariants it compiles;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.
| Status | Meaning |
|---|---|
stable-candidate | Intended long-lived embeddable contract. Pre-1.0 change control still applies; this is not a SemVer 1.0 guarantee. |
conditional | Supported only inside the stated storage-port or adapter contract and its validity/lifecycle conditions. |
deprecated | Retained compatibility surface whose replacement is documented; new consumers should not adopt it. |
unstable/deferred | Name or shape is reserved, but working behavior or promotion evidence is incomplete. |
implementation-leakage-to-remove | Exposes 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.
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.
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.
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.
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.
| Contradiction | Ruling and executable/source evidence |
|---|---|
PointHandle described as private or unreadable | Public 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 handles | Handles 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-based | Frame 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 facade | The 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 signature | guards.rs now states its selected-subset scope. Store and schedule signatures remain Rust-visible without becoming Python-wrapped promises. |
| Repeatability omitted determinants | Complete-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 PointStore | Engine::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.
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 | Host action |
|---|---|
Engine::load_modelica | Prepare already-specialized, supported CXF externally, then use load_cxf. OCE does not parse Modelica source. |
Engine::load_from_semantic, TemplateRef | Remove 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::SemanticQuery | For 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, OutputTrace | Decode 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_realtime | Collect 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, StepReport | Own wall-clock mapping, persistence and delivery outside execution; realtime/Store orchestration is deferred. No engine write-back helper replaces them. |
Outputs, Engine::outputs | Retain CompletedFrame for committed boundary results and warnings. Use get_output/watch only for explicitly latest-state, non-receipt inspection. |
AssertLevel::Error | Update 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.
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.
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.
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.
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.
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.
The additive closed descriptor does not replace existing receipts or alter their bytes. Migrate only the public-fact comparison portion:
| Current receipt field/use | Mapping |
|---|---|
LoadReport.model_id / load receipt report | Keep 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() revisions | Same revisions through the descriptor’s named schema accessors. Still metadata contracts, not model input-definition identity. |
| Fixed HostTick profile and descriptor | execution_profile() is HostTick-v1; execution_profile_schema_revision() remains the existing revision 2. |
| Host source/build/features stamp | Retain it. oce_api_version() adds only the exact OCE Cargo package version, not a unique build fingerprint. |
ExportReport::content_id_complete() / export receipt report | Pass Some(report) to CompatibilityDescriptor::current; its completeness check remains the sole minting path. Preserve typed refusal on warnings. |
| No export available | Pass None; the canonical receipt records export:none, which never matches present content. |
| Checkpoint/snapshot or frame receipt | No 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.
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.
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.
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:
| Rank | Stage |
|---|---|
| 0 | Import: serialized admission, parse/resolution, including the CXF resolver’s internal passes |
| 1 | Flatten |
| 2 | AttributeUnification |
| 3 | Validation |
| 4 | Instantiation |
| 5 | Schedule |
| 6 | Semantics |
| 7 | Projection |
| 8 | StoreRecovery |
| 9 | StoreSave |
| 10 | StoreInputs |
| 11 | Export |
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.
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.
| Domain | Actual contract and limits |
|---|---|
| Values | Existing 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. |
| IO | Existing point fields and declared static attributes; enums project to Int, strings are omitted. Current inventory classification/defaults are explicit. |
| Parameters | Existing tune-at-rest rows and available static bounds. Absent bounds do not establish freedom from cross-parameter rules. Unit/quantity provenance is currently absent. |
| Assertions | Revision 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 profile | Descriptor 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.
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.
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.
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:
| Label | Covered value |
|---|---|
oce-compatibility | Descriptor revision, exactly 1. |
catalog-schema | Current public catalog schema revision. |
catalog | Verbatim current facade catalog content tag. |
io-schema | Public IO inventory contract revision, not a loaded input-definition digest. |
value-schema | Public value contract revision, not a new value codec. |
parameter-schema | Public parameter metadata contract revision, not instance parameter values. |
execution-profile | Exactly HostTick-v1, the canonical label for fixed HostTick v1. |
execution-profile-schema | Public execution-profile descriptor revision (currently 2). |
oce-api-version | Exact Cargo package version, including any pre-release/build suffix. |
export | Verbatim 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.
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.
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.
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.
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.
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:
| Category | Support meaning | Publication meaning |
|---|---|---|
host-facade | Primary supported embeddable host entry point. | Publishable. |
conditional-adapter-port | Supported only under the documented database-free storage-port and adapter lifecycle contract. | Publishable. |
transitional-companion | Supported companion while current consumers migrate toward facade-owned access. | Publishable. |
implementation-dependency | Registry closure only; no independent host-facing support promise. | Publishable because the facade closure requires it. |
test-support | Repository test infrastructure, not product surface. | Private. |
private-reference-adapter | Verification/reference implementation, not a supported backend. | Private. |
verification-tooling | Conformance and evidence tooling, not runtime product surface. | Private. |
reserved-panic-only | Reserved name with incomplete behavior, including panic-only paths. | Private. |
experimental-reserved | Experimental or reserved boundary without a supported implementation. | Private. |
All 17 Cargo workspace members appear exactly once:
| Package | Category | Manifest publication | Contract |
|---|---|---|---|
oce-api | host-facade | Publishable | Primary host facade. |
oce-bless | test-support | publish = false | Golden-regeneration test support only. |
oce-blocks | transitional-companion | Publishable | catalog() metadata is the supported companion contract. |
oce-conformance | verification-tooling | publish = false | Verification harness, excluded from release selection. |
oce-cxf | implementation-dependency | Publishable | Required by the facade closure; a direct downstream pin is a migration constraint, not promotion. |
oce-diag | implementation-dependency | Publishable | Required by the facade closure. |
oce-docs | reserved-panic-only | publish = false | Reserved document-generation seam; panic-only behavior is not releasable. |
oce-expr | implementation-dependency | Publishable | Required by the facade closure. |
oce-extension | experimental-reserved | publish = false | Reserved extension/FMI boundary with no supported implementation. |
oce-flatten | implementation-dependency | Publishable | Required wired facade seam; no independent support promise. |
oce-graph | implementation-dependency | Publishable | Required by the facade closure. |
oce-model | implementation-dependency | Publishable | Required by the facade closure. |
oce-reference-wal-adapter | private-reference-adapter | publish = false | Durability reference adapter, not a supported backend. |
oce-semantics | implementation-dependency | Publishable | Required wired facade seam; deferred behavior is not independently promoted. |
oce-store | conditional-adapter-port | Publishable | Database-free app-side adapter port and DTO contract. |
oce-store-mem | implementation-dependency | Publishable | Unconditional in-memory implementation in the facade registry closure. |
oce-validate | implementation-dependency | Publishable | Required 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.
oce-api feature matrixoce-api declares only default and mem. The complete supported selection matrix is:
| Selection | Cargo spelling | Enabled oce-api features | Normal OCE dependency result |
|---|---|---|---|
| Default | no feature flags | default, mem | The shared 11-package closure, including oce-store-mem. |
| Explicit legacy memory spelling | --no-default-features --features mem | mem | Exactly the same closure. |
| No default features | --no-default-features | none | Exactly 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.
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;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.
.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.
Current consumer evidence sets a compatibility floor:
oce-api and oce-blocks, and its manifest uses
default-features = false, features = ["mem"]. The legacy spelling stays until that manifest is
migrated.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.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.
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.
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.
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.
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.
The engine is, by design:
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.#![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).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.
These are different values on purpose, and conflating them has already cost this repo one reverted change (PR #209).
| Value | Where | What it means to you | |
|---|---|---|---|
| MSRV | 1.97.0 | Cargo.toml:42 (rust-version) | The floor a consumer needs. Build the engine with any toolchain at or above this. |
| Pin | 1.97.1 | rust-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.
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.
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):
| Crate | Responsibility |
|---|---|
oce-model | Pure value/connector/instance/connection types; the Value enum (Real/Integer/Boolean/String/Enum) and the flattened model graph — the shared executable truth. |
oce-expr | The 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-blocks | The 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-flatten | Reserved 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-validate | Loader conformance: subset rejection, single-assignment, type and attribute unification, parameter rules. |
oce-graph | The deterministic scheduler and executor: direct-feedthrough DAG, algebraic-loop rejection, its own Kahn topological sort, the tick loop. |
oce-cxf | CXF (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-semantics | Active 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-diag | The shared diagnostic vocabulary (Severity / DiagCode / Diagnostic) across the ingest path. Zero dependencies. |
Storage ports (the seam — traits only, no database types):
| Crate | Responsibility |
|---|---|
oce-store | The seam. The ModelStore / PointStore / SemanticStore / Durable traits plus DTOs, unified by the Store supertrait. No database types. |
oce-store-mem | The 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-adapter | Verification-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:
| Crate | Responsibility |
|---|---|
oce-conformance | Verification-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-bless | Test-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-extension | Experimental/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-docs | Reserved 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-api | The 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.
mem compatibility spellingoce-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.
Each of these is a design commitment, not a gap waiting to be closed.
oce-store port, authored app-side.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.
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.
Each successful Engine::execute_frame(prepared) call is one state transition:
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.PreThe 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:
| Boundary | HostTick v1 behavior |
|---|---|
| State allocation | One Boolean memory word is seeded from pre_u_start. |
| Before the first tick | The output connector has its Boolean connector seed, false; allocating state does not execute the block. |
| First successful tick | Pre emits pre_u_start, then latches current u. |
| Later successful ticks | Pre emits the u latched by the preceding call, then latches current u. |
Repeated t_now | Each call advances the memory once, even though model time is unchanged. |
| Feedthrough graph | Pre 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.
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.
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.
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.
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.
Six evidence layers here are called “tests”. They prove different things, and the most visible of them proves nothing about correctness at all.
| Layer | Artifact | Count | Independent of the engine? |
|---|---|---|---|
| Tier-2 determinism goldens | crates/oce-conformance/tests/fixtures/golden/g36_traces/ | 46 traces + 46 .prov.json | No — engine self-output, by construction |
| Tier-A source references | tools/golden-gen/goldens/ | 392 provenance records, 390 signal goldens | Yes — CI-enforced code-dependency firewall |
| Tier-A HostTick profile references | tools/golden-gen/goldens/G36/ | 20 signal goldens | Independent implementation of the engine profile; not a Modelica oracle |
| Structural oracle | third_party/modelica-buildings-cdl/cxf/ | 44 vendored translations; 31 comparable fixtures | Yes — an independent translation of the same upstream source |
| Tier-1 per-block oracle comparisons | crates/oce-conformance/tests/per_block_*.rs | 15 suites; 278 CDL signal goldens: 257 existing exact plus 21 exact on qualified Linux, unchanged 1e-12 aligned band on unqualified targets; bounded receipt | Yes — Tier-A generator is outside the engine workspace |
| Scoped Tier-3 cross-implementation differentials | crates/oce-conformance/tests/fixtures/open_modelica/ | 4 named cases: 2 Boolean, 1 finite Real matrix, and 1 composed G36 leaf; global report skipped | Yes — pinned OpenModelica and Buildings execution |
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.
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:
goldens/CDL/Types/types.prov.json, pinning enum ordinals, and
goldens/CDL/Constants/constants.prov.json).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).
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.
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:
"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.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.
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.
Yes, and it does so in the place where it is hardest to ignore — the end of every gate run.
.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:
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.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.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.
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.
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:
| Class | Status |
|---|---|
CDL.Logical.Nor | indirect G36-sequence evidence only |
CDL.Logical.Pre | HostTick v1 contract tests plus indirect G36 evidence; no Modelica event-iteration oracle |
CDL.Logical.Sources.Constant | indirect G36-sequence evidence only |
CDL.Reals.MovingAverage | indirect G36-sequence evidence only |
CDL.Utilities.Assert | has 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.
If you are evaluating this engine, the defensible summary is:
Pre-dependent fixtures. Those are profile checks, not Modelica event-iteration evidence.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.
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.
Cargo.toml and
Cargo.lock bytes are bound sources; no checker, workflow, math or harness source changed.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.1.97.1 (8bab26f4f 2026-07-14); libm: 0.2.16; baseline repository codegen,
without custom RUSTFLAGS or CARGO_ENCODED_RUSTFLAGS.sha256:0c94225cc2499e577cb597d0c36cbee138fc88bb65791872f1deb5c38e6b6576.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 cell | SHA-256 of both first and repeat capture |
|---|---|
| Linux aarch64 debug | 6ed52dfdeaeb04d8c270943ec8bd594c7383aa5fc37cd8beff9446d675a78751 |
| Linux aarch64 release | f97c6d8c257ab632a1685268a6a350bc46b55ebc5bd4c685b0d8b51ec9588442 |
| Linux x86_64 debug | 84d66c267e71f7f5e7ac02e7050fee99cf33ffca9b18bd81389fd7144e9368b2 |
| Linux x86_64 release | 81087ece58ea985d0d11cf79df78fefe7163b0180f2b8aa05d07902a5d6772d4 |
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.
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:
dbce73fb20ada4a3a91653bb7ad9b48fae7ee87d. All eight captures
honestly record GitHub’s synthetic PR merge checkout
8a63d4a042e1ca91d5dfe7bd3fc33d194f5102bb, not that PR head or a later delivery commit.1.97.1 (8bab26f4f 2026-07-14); libm: 0.2.16; baseline repository codegen,
without custom RUSTFLAGS or CARGO_ENCODED_RUSTFLAGS.sha256:d5b21ca70517c793f46a99d5402d236e2c78494466367fa583ccef27431c31c4.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.
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.
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.
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.
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.
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 engine registers 136 block classes: 133 CDL elementary classes plus 3 reserved internal lowering identities.
| Family | Classes |
|---|---|
CDL.Reals | 52 |
CDL.Logical | 26 |
CDL.Integers | 24 |
CDL.Routing | 15 |
CDL.Discrete | 7 |
CDL.Conversions | 4 |
CDL.Psychrometrics | 3 |
CDL.Utilities | 2 |
| CDL total | 133 |
| Reserved lowering identities | 3 |
| Registry total | 136 |
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).
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().
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:
runtime_sequences, all status
supported-runtime-sequence) over 31 distinct canonical class paths.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.
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:
| Requirement | Field on the catalog row |
|---|---|
Canonical Buildings.Controls.OBC.ASHRAE.G36.* class path | class_path |
| Source provenance | source |
| Supported parameter variants | supported_variant |
| Fixture | fixture |
| Deterministic golden trace | golden_trace, determinism_provenance |
| Independent oracle evidence | oracle_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 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 v1Registry 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.
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:
…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.
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.
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.
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).
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):
| Attribute | Emitted as | Applies to |
|---|---|---|
unit, quantity, displayUnit | bare string | Real connectors |
min, max | bare number, finite only | Real (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.
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):
| Fixture | Blocks in graph | Blocks deferred | Share |
|---|---|---|---|
cooling_only_controller | 213 (crates/oce-api/tests/g36_cooling_only_controller.rs:252) | 83 | 39 % |
multizone_vav_relief_fan_group | 226 (crates/oce-api/tests/g36_relief_fan_group.rs:105) | 63 | 28 % |
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.
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.
Ok export produces bytes that fail re-importBoth are documented, and both are reachable only from a hand-built ModelGraph — never from one the
resolver produced.
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).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 digestExportReport::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:
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.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.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.
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.
Every rejection is a diagnostic with three parts:
malformed-document),@id of the offending node, where one exists),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.
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.
A node is a runtime composite if and only if its
S231:containsBlocklist is non-empty AND its@typedoes not resolve to a registered leaf block class. A node whose@typeresolves to a registered class is a leaf even when it carriesS231: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).
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 withcomposite/root-count(DiagCodemalformed-document). With two or more candidates the message enumerates every candidate in@graphorder 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).
Active composite children lower depth-first in
S231:containsBlockarray order; inactive children are skipped along with their entire subtrees. This flat leaf order assignsBlockIdand blockdecl_order, not authored connector IDs. Authored connectors are numbered by their own@graphpositions. Non-rejecting; no DiagCode.
"S231:containsBlock": [ { "@id": "…#M.sub" }, { "@id": "…#M.post" } ]
lowers …#M.sub’s leaves (depth-first) before …#M.post.
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.
| Surface | Classification | Executable rule and identity consequence |
|---|---|---|
| JSON objects and context spelling | Normalized | Object-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 array | Order-bearing | Authored 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. |
containsBlock | Order-bearing | Active 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 / hasOutput | Normalized | When 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 ports | Order-bearing | When 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 + hasConstant | Normalized for accepted scopes | One 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 dimensions | Order-bearing where Rule 5 specifies | Leaf 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. |
isConnectedTo | Order-bearing, with orientation normalization | Authored 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 lowering | Normalized fanout; order-bearing node positions | external_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 array | Normalized | Array 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 conditionals | Pruned | Inactive 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 diagnostics | Deterministically finalized | Sort 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. |
| Export | Derived from ModelGraph vectors | Emit 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 identity | Derived storage versus stable authored identity | BlockId, 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. |
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.
composite/contains-cycle)The
containsBlockgraph reachable from the root through active children must be acyclic. A cycle rejects withcomposite/contains-cycle(DiagCodemalformed-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).
composite/array-parameter, composite/declaration-cycle, composite/duplicate-declaration)A composite’s active
S231:hasParameterandS231:hasConstantbindings 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 namedb. 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 withcomposite/declaration-cycle(DiagCodemalformed-document): one diagnostic per distinct cycle per chain evaluation. Likecontains-cycle, a composite reachable via multiplecontainsBlockpaths 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 genericgrounding-failed.- One local name declared twice in one composite’s own chain rejects with
composite/duplicate-declaration(DiagCodemalformed-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 withcomposite/array-parameter(DiagCodenon-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 withgrounding-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.
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
@idappears 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.
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 withcomposite/banned-modelica-key(DiagCodenon-subset-construct); the subject is the owning node and the message names the key exactly as authored.
S231:isReplaceable: trueon any active node rejects withcomposite/replaceable(DiagCodeunresolved-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:hasInputorS231:hasOutputlist, 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’sS231:hasInstancelist (acontainsBlockreferent 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: trueor anyS231: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 iscomposite/array-connector(DiagCodenon-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 ascomposite/array-instance(DiagCodenon-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 aS231:hasParameterlisting 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
hasInstanceinterface derivation (an instance declaring neitherhasInputnorhasOutputand carrying aS231:hasInstancelist 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(DiagCodenon-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 throughhasInput/hasOutputis untouched.composite/unsupported-instance-member(DiagCodenon-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(DiagCodenon-subset-construct, subject the colliding IRI, one per collision): a synthesized connector identity that is already an@graphnode or is minted twice for one owner, or a parameter name declared twice for one instance — across its classified members or against its ownhasParameter/hasConstantlist.
{ "@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).
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.
| Rule | Rule id | DiagCode | Message prefix |
|---|---|---|---|
| 2 | root-count | malformed-document | composite/root-count: |
| 4 | contains-cycle | malformed-document | composite/contains-cycle: |
| 7 | replaceable | unresolved-polymorphism | composite/replaceable: |
| 7 | banned-modelica-key | non-subset-construct | composite/banned-modelica-key: |
| 5 | array-parameter | non-subset-construct | composite/array-parameter: |
| 7 | array-connector | non-subset-construct | composite/array-connector: |
| 7 | array-instance | non-subset-construct | composite/array-instance: |
| 5 | declaration-cycle | malformed-document | composite/declaration-cycle: |
| 5 | duplicate-declaration | malformed-document | composite/duplicate-declaration: |
| 7 | vector-port-instance | non-subset-construct | composite/vector-port-instance: |
| 7 | unsupported-instance-member | non-subset-construct | composite/unsupported-instance-member: |
| 7 | colliding-member-identity | non-subset-construct | composite/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.
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.
A document that satisfies rules 1–7 must also meet the general import preconditions before it loads warning-free:
@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.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.@type resolves to a registered block class (else class-not-found).The engine tests itself against a checked-in conformance corpus; point your emitter’s output at the same files and drivers.
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.ModelGraph renders):
crates/oce-cxf/tests/fixtures/golden/composite_contract_*.modelgraph.txt.cargo nextest run -p oce-cxf --test composite_contract_corpusEngine::load_cxf) drivers:
cargo nextest run -p oce-api --test conformance composite_contractcargo nextest run -p oce-cxf --test composite_contract_docresolve_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.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.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/, 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/, 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/, 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).
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.
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.
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.
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.
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:
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.Fault, Stale, Uninitialized and Override mean
for each input, and act on them before or instead of executing. The engine will not.CompletedFrame is your operation.
Its success, rollback, retry policy and actuator acknowledgment are not engine guarantees.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.
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.
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:
EngineStateSnapshot::from_bytes. Inspect portability()
for placement, without treating it as build or numerical qualification.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.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.
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.
@idsEvery 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.
| Bound | Limit | Defined at | Behavior when exceeded |
|---|---|---|---|
| Serialized CXF at both facade load entry points | 8 MiB (8,388,608 bytes) by default; hosts may configure a smaller limit | crates/oce-api/src/admission.rs | OcError::CxfTooLarge { actual_bytes, limit_bytes } before JSON deserialization or Store calls |
| Expression parse and AST nesting | 64 | crates/oce-expr/src/lib.rs | typed NestingTooDeep error |
| Expression size | 4096 nodes | crates/oce-expr/src/lib.rs | typed ExpressionTooLarge error |
Composite nesting (containsBlock lowering) | 64 | crates/oce-cxf/src/resolve/composite.rs | MalformedDocument diagnostic |
| Composite boundary path | 64 non-top isConnectedTo hops | crates/oce-cxf/src/resolve/composite.rs | MalformedDocument diagnostic |
| Composite boundary work | 65,536 target examinations and 8 MiB of aggregate target-IRI bytes per document | crates/oce-cxf/src/resolve/composite.rs | MalformedDocument 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.
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:
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.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.
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.
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.
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.
.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.
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.
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.
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.
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:
| Step | What it covers | Where |
|---|---|---|
| workspace nextest | every unit and integration test in the workspace | release-gate.yml, test-suite job, unit + integration step |
| workspace nextest, release codegen | release panic-freedom, debug_assert paths stripped; inherited ci-release runner policy | release-gate.yml, test-suite job, release step |
cargo test --doc | doctests — nextest cannot run them, so this is a separate step | release-gate.yml, test-suite job, doctest step |
two cargo public-api surface gates | exact public API text for oce-api and oce-store | release-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.
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.
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.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).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)..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.
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.
Measured tick throughput for the Open Control Engine, recorded per run with the commit, host and method that produced it.
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.
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:
| Case | Debug tick | Debug commit | Debug prepare+commit | Release tick | Release commit | Release prepare+commit |
|---|---|---|---|---|---|---|
| Add arithmetic | 211 | 390 | 1,357 | 34 | 76 | 171 |
| Sampled delay | 345 | 518 | 1,473 | 42 | 79 | 158 |
| Zero-input Pre feedback | 282 | 471 | 533 | 35 | 68 | 69 |
| 213-block G36 controller | 18,631 | 17,714 | 26,550 | 2,578 | 2,557 | 3,246 |
| Assert, zero boundary outputs | 214 | 466 | 993 | 28 | 79 | 126 |
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):
| Case | N / T / B / D | Preparation allocations / bytes | Commit allocations / bytes |
|---|---|---|---|
| Add | 2 / 2 / 1 / 0 | 4 / 120 | 2 / 66 |
| Delay | 2 / 2 / 1 / 0 | 4 / 120 | 2 / 59 |
| Pre | 0 / 0 / 2 / 0 | 0 / 0 | 3 / 114 |
| Controller | 14 / 43 / 10 / 0 | 16 / 956 | 11 / 1,136 |
| Assert | 1 / 2 / 0 / 1 | 3 / 64 | 3 / 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.
The now-retired steady-state Engine::tick() on real G36 fixtures, through the then-public facade:
Engine::in_memory() → load_cxf() → tick().
t only ever increases
across warmup and measurement.Stated explicitly, because the gap between these and the numbers below is where a wrong conclusion would come from.
load_ms column.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.b5b19e7 · Apple M5 (10 cores) · rustc 1.95.0 · macOS 26.6 · --releaseWarmup 20,000 ticks · 2.0 s measurement window · dt = 1.0 simulated second per tick.
| fixture | CDL class refs | ns/tick | ticks/sec |
|---|---|---|---|
cooling_only_controller | 222 | 2,508 | 398,801 |
multizone_vav_relief_fan_group | 228 | 2,706 | 369,558 |
multizone_vav_supply_fan | 71 | 755 | 1,325,349 |
ahu_economizer | 12 | 141 | 7,095,155 |
vav_single_zone | 8 | 144 | 6,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.
Reported as first / median / min in one process, because a single first-call figure carries process-start and page-cache cost.
| fixture | KiB | first ms | median ms | min ms | first÷med | MiB/s at median |
|---|---|---|---|---|---|---|
cooling_only_controller | 409 | 7.84 | 2.62 | 2.48 | 3.0× | 154 |
multizone_vav_relief_fan_group | 366 | 2.55 | 2.12 | 2.02 | 1.2× | 169 |
multizone_vav_supply_fan | 112 | 0.89 | 0.68 | 0.66 | 1.3× | 159 |
ahu_economizer | 16 | 0.14 | 0.11 | 0.10 | 1.3× | 144 |
vav_single_zone | 14 | 0.11 | 0.09 | 0.08 | 1.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_groupimports to 226 blocks, more than this fixture’s 213 (pinned atcrates/oce-cxf/tests/resolve_g36_relief_fan_group.rs:145-146), and 377 was the pre-importisConnectedTocount, 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.
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.
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-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.
The Studio entries deliberately keep three identities separate:
open-control-studio product source is the reviewed behavior.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.
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.
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.