isoproject

Headless engineering project model for Isochronon

engineering
project
fieldbus
moonbit
moon add mokomoking2501/isoproject@0.1.0
Download zip
Version
0.1.0
License
Apache-2.0
Last updated
last month
Downloads
12
README

#IsoProject

mokomoking2501/isoproject is the headless engineering-project model for Isochronon. It describes a local project as a device tree, applications, task configuration, library/provider registry, and runtime profiles.

E1 intentionally stays offline/static: it does not scan devices, open native interfaces, parse fieldbus packets, or replace MoonBit moon.work / moon.mod. Later packages can turn this model into run plans and commissioning reports.

mokomoking2501/isoproject/ingest contains the narrow fixture ingest layer for this model. It accepts a stable JSON object subset and a YAML-like repeated key/value subset, then returns the same typed IsoProject model and lint report; it is not a general project-file format or live import path.

mokomoking2501/isoproject/plan turns a typed project into an offline run/test plan artifact. It records runtime profiles, Lockwire backend references, trace/observer/report routes, provider slots, a stable digest, and the sim-wasm timeline event stream/viewer artifact description without executing live I/O. Profiles with provider slots also declare a bottom-layer operator acceptance artifact slot so commissioning can later attach the real operator gate report.

mokomoking2501/isoproject/commissioning turns a run/test plan into an offline commissioning report skeleton. It records scan/import/check/plan/run/record/ replay/report evidence slots, keeps observer verdicts missing until an observer actually runs, carries wasm timeline artifact slots, and links live NIC, pcap/tshark, real-device, and certification evidence to locked blockers. A separate operator handoff acceptance path can attach the bottom-layer operator gate to the commissioning report without changing the offline-only baseline.

mokomoking2501/isoproject/bottom_layer_operator_acceptance composes the Lockwire bottom-layer acceptance report with the fieldbus provider operator package report. This keeps Lockwire provider-agnostic while giving the engineering project layer one gate for run/test substrate plus provider CLI handoff readiness. Provider CLI coverage failures are surfaced here, so the commissioning/operator package path cannot pass with only a partial MoonECAT/PROFINET command matrix. Bottom-layer script, runner, live-chain, command-flow, artifact-intake, provider-composition, and pcap/TShark gate failures are surfaced here as distinct counts rather than a single boolean. It still does not execute scripts, read artifacts, hash files, open interfaces, or claim live evidence.

mokomoking2501/isoproject/operator_handoff_report verifies the project-level operator handoff report policy for the bottom-layer operator acceptance artifact declared by the run plan and attached by commissioning. It consumes runner-supplied artifact facts and an exact required stdout line set, so stale or contradictory machine-readable PASS lines are rejected before the handoff report is accepted. It also preserves operator acceptance failure detail, including bottom-layer chain and provider handoff counts, so the later package report can identify the failed gate without reopening artifacts or scripts. File reads, SHA256 calculation, provider command execution, NIC access, and TShark remain outside the framework.

mokomoking2501/isoproject/operator_evidence_package is the package-level handoff gate above the run plan, commissioning report, operator acceptance report, and operator handoff report. It verifies the three package artifacts and outer stdout facts as a single engineering evidence package while keeping execution and artifact collection in external runners. The package and runner intake reports also preserve detailed operator package failure counts, so the final intake gate does not collapse provider/operator or bottom-layer chain failures to a single boolean. Runner intake stdout facts and provider CLI coverage stdout facts are kept as separate exact line sets, which prevents external runners from mixing stale or contradictory machine-readable PASS lines into the same evidence stream. Those required stdout line builders now expose dynamic sub-gate pass/failure counts for run plan, commissioning, operator acceptance, operator handoff, runner suite, provider CLI coverage, provider harness artifacts, and operator package status; scripts only collect the resulting lines.

The same package also exposes the project-level runner intake gate for the three retained top-level scripts: run-native.ps1, run-linux.ps1, and run-stm32.ps1. It consumes D77 script-transition and thin-runner suite reports plus runner-supplied summary artifact/stdout facts, so MoonBit owns the intake oracle while PowerShell remains limited to execution and collection. It keeps the thin-runner suite's missing/duplicate/unexpected runner counts and summary/stdout/file-fact failure counts visible in the final intake report, so runner evidence failures are not collapsed to one boolean. The intake gate also requires the no-live fieldbus_core/cmd/main harness-coverage artifact/stdout facts, keeping provider CLI coverage in the same project handoff path without importing fieldbus_core or executing provider commands. It additionally requires the four provider handoff summary artifacts that run-native.ps1 now collects for MoonECAT and PROFINET scan: one run-plan summary and one exec summary per provider. This keeps the final project intake gate aware of the provider handoff chain while the scripts remain execute/collect wrappers and the summary correctness oracles stay in MoonBit tests.

mokomoking2501/isoproject/workbench_server is the first E5 workbench-server contract. It follows the profinet console resource-server pattern at the contract level: shared route inventory, stable line output, an explicit allow_live gate, and exact Windows/Npcap interface confirmation before any live route may later open a NIC. It does not implement the native HTTP listener, frontend bundle, or live route execution. The CLI entry moon run --target native isoproject/cmd/main -- workbench-server-plan ... prints the deployment plan that a later native workbench server should expose. Even when the plan is live-ready, this command only emits handoff commands and keeps server_executes_live_io_now=false and live_evidence_claim=false.

mokomoking2501/isoproject/workbench_view projects the typed engineering model into the stable JSON resources isoproject.workbench.project-tree.v1 and isoproject.workbench.tasks.v1. Task periods and deadlines stay lossless decimal strings, and configuration is kept separate from runtime observation: configuration_status=configured does not imply a running task, so E5C1 always reports runtime_status=not-observed and live_observed=false. The same package owns the E5C2 isoproject.workbench.live-action.v1 response state machine. Its request DTO now comes from mokomoking2501/isoproject/workbench_shared. The response adapter converts E5A gate results into 400 invalid-json, 403 refused, 422blocked, or 202 handoff-ready without executing the returned command preview.

mokomoking2501/isoproject/workbench_runtime owns the E5C3A runner-fed observation contract and in-memory current-state store. A complete observation batch names the project, source/session, monotonically increasing sequence, all configured tasks, diagnostic events, and artifact facts. The store rejects malformed, invalid, and stale input as typed 400, 422, and 409 responses; accepted batches return 202. Period, jitter, age, and observation timestamps remain decimal strings in JSON. Every accepted batch is still self-reported: runner_reported=true, runtime_observation_verified=false, and live_evidence_claim=false. The package does not authenticate a producer, read artifact files, verify hashes, execute a runtime, or open a network interface.

mokomoking2501/isoproject/workbench_contract_monitor owns the E5C4 object-contract read model and trace ingest store. It projects the configured axis_authority_contracts() registry, binds every trace batch to the current project, contract asset, runner source/session, and runtime sequence, then runs the real isocontract.ObjectMonitor. A pass verdict requires a non-empty trace that covers every bound object and has no contract or timeout-action issues; partial coverage remains partial. Timing checks compare event age (raw_ns - vtime_ns) with max-age, validity, and deadline limits. Expected timeout actions are evaluated as matched, missing, mismatch, duplicate, or orphan, and a matched action never removes the timing violation that triggered it. The store is deterministic but not a trust boundary: accepted input remains runner_reported=true, trace_source_verified=false, and live_evidence_claim=false.

mokomoking2501/isoproject/workbench_runner owns the E5C3B1 local deterministic runner session and bounded replay journal. A start request selects a non-negative seed, one of nominal, stale-command, out-of-order-status, or injected-fault, and a browser replay pace. The background task calls the actual fieldbus_core/cia402_scenario_loop.run_cia402_scenario implementation, writes start/final observations through the E5C3A store, projects the complete scenario trace into the E5C4 axis contracts, and keeps scenario conformance separate from the project contract verdict. Its 64-entry journal uses a process-wide monotonic stream sequence and reports current-session first/oldest/newest, retained, dropped, and cursor-gap counts. Consumers read independent cursor slices, so a slow or disconnected WebSocket does not block the runner. pace_ms controls trace replay to the browser only; it is not a real task period or timing measurement. actual_runner_executed=true only confirms that the local finite scenario function ran. Every runner resource, action, event, and gap frame still sets runtime_observation_verified=false, trace_source_verified=false, and live_evidence_claim=false.

mokomoking2501/isoproject/workbench_report owns the E5C8 process-local offline report store. Generation is accepted only when the request matches the active project id/revision and a completed or failed deterministic runner session whose runtime snapshot, evidence resource, and Object Contract monitor share the same source/session/runtime/monitor sequences. The report combines the E4 plan and evidence slots with runner/runtime/contract verdicts, uses the runner seed for a reproducible E3 plan digest, and records offline-pass or offline-fail without claiming commissioning completion. A repeated request for the same terminal session is idempotent; distinct sessions enter a newest-first 16-entry FIFO. Collection resources are defensive copies. Export returns deterministic JSON with the original E4 report text as an attachment and does not write a server file. Project activation and backend restart clear this process-local history. An internal runner failure without positive runtime/monitor terminal sequences and a completed Object Contract evaluation remains not-ready; a fully evaluated injected-fault session is exported as offline-fail. Every report keeps commissioning_complete=false, runtime_observation_verified=false, trace_source_verified=false, and live_evidence_claim=false.

mokomoking2501/isoproject/workbench_artifact_bundle owns the E5C9 process-local offline artifact bundle. It combines exactly three stable members in order: the active canonical project JSON, the E3 deterministic run-plan text rebuilt with the retained report seed, and the typed E5C8 commissioning-report export. The manifest binds project id/revision/document digest, plan id/seed, report sequence/session/digest, member UTF-8 byte counts, member integrity digests, payload bytes, a monotonic bundle sequence, and an overall deterministic integrity digest. Generation is idempotent for the same report identity and replaces the single current bundle for a newer retained report. Export requires revision, report sequence, session, and bundle digest. Import verification is bounded to 262,144 wire bytes, rechecks all member/digest/no-live invariants and the active project context, and never mutates current state. Integrity verified means only deterministic content equality; it is not SHA256 provenance, source authentication, live evidence, or commissioning completion. Activation and backend restart clear the current bundle.

E5C10 adds detached bundle inspection and project import preview without weakening that contextual verify route. POST /api/project/document/import-artifact accepts the same bounded bundle JSON, verifies the fixed member manifest, deterministic digests, canonical embedded report, parseable/lint-ready project, and regenerated E3 plan without consulting the active bundle store. Only a fully inspected bundle is passed to the existing E5C6 project-document preview owner. The response reports the source project/revision/report/bundle identity, whether that source context is current, and the new preview sequence/digest. Import never activates a project, restores report or runner state, or promotes evidence; a later exact sequence+digest Activate remains mandatory.

mokomoking2501/isoproject/workbench_shared is the E5C7 target-independent HTTP contract owner. Its 40-entry WorkbenchApiEndpoint inventory fixes each API method, path, request media/field shape, response schema, status set, statefulness, stream classification, and no-live boundary. It also owns the browser write DTOs WorkbenchProjectActivateRequest, WorkbenchRunnerStartRequest, and WorkbenchLiveActionRequest, and WorkbenchCommissioningReportGenerateRequest, and WorkbenchOfflineArtifactBundleGenerateRequest, including parsers and static validators. Project activation, runner start, armed live-action handling, report generation, and artifact generation execute those shared validators before their feature-owned state checks. Domain packages still own their response/state machines but read route and schema values from shared.

E5C11 makes mokomoking2501/isoproject_workbench_frontend/app the complete JS-target UI owner. It compiles the document template, CSS, existing DOM/fetch/WebSocket behavior, shared manifest, typed DTO serializers, and operation guards into one app.js. A deterministic local postprocessor normalizes that release output, writes gzip with a fixed timestamp/OS header, and the backend embeds those bytes as the single GET /frontend.js resource. The native package now owns only a 353-byte mount shell plus HTTP/API handling; it no longer owns business UI behavior or serves a separate /workbench-contract.js. The page therefore calls IsoProjectWorkbenchApiRequests.projectActivate, runnerStart, liveAction, commissioningReportGenerate, and offlineArtifactBundleGenerate instead of maintaining a positional or handwritten request-field contract. Its transport helper also reads the HTTP method, request media type, allowed response status set, and structured export query from the same endpoint entry. Backend API responses pass through the shared status-set gate before they are written. GET /api/workbench/contract exposes the same typed manifest as JSON. The frontend response uses Content-Encoding: gzip, Vary: Accept-Encoding, Cache-Control: no-cache, text/javascript, and nosniff. isoproject uses moonbitlang/async@0.19.2 because the previous 0.16.8 sender discarded Content-Encoding; no manual raw-HTTP bypass is used.

E5C12 adds mokomoking2501/isoproject/workbench_response_boundary as the target-independent response consumer. Feature packages continue to own and serialize their domain response types, but those response types now also derive FromJson. Before browser state changes, IsoProjectWorkbenchApiResponses selects the type by endpoint id, rejects undeclared HTTP status, malformed or wrongly typed JSON, missing required fields, schema drift, and no-live boundary flips, then returns canonical JSON. REST state paths use apiJson; runner WebSocket messages use the same decoder with the stream endpoint. Attachment success bodies, raw artifact upload/download, and plain-text plan/live-route remain with their media owners. Typed attachment error JSON is readable but is marked state_write_allowed=false.

E6 adds mokomoking2501/isoproject/workbench_library_registry as a declaration-only inspection owner. It deterministically groups library assets and provider slots by package, expands application usages and device/provider bindings, and emits stable errors or warnings for duplicate identifiers, missing or ambiguous bindings, package mismatches, invalid symbols/relative paths, unused entries, and live-default declarations. GET /api/project/libraries projects the active project and revision; the frontend Libraries view accepts it only through the E5C12 typed response decoder. This surface does not inspect moon.work or moon.mod, resolve packages or exported symbols, read project paths, load a provider, or claim runtime/live evidence.

E7 adds mokomoking2501/isoproject/workbench_run_plan as the revision-bound inspection surface for the existing E3 RunTestPlan. E3 now canonicalizes runtime profile, task, application, and provider ordering before plan text and digest generation, and RunProfilePlan retains the declared evidence policy. The inspection resource exposes every profile binding and planned artifact, stable issues for invalid, duplicate, ambiguous, unused, real, or live-required profiles, and a deterministic instance id when duplicate profile ids would otherwise collide. GET /api/project/run-plans and the Profiles view remain declaration-only: all artifacts are offline, may_execute_live_io=false, and runtime/provider/ artifact-file verification fields remain false.

E8 adds mokomoking2501/isoproject/workbench_project_validation as the active-project lint and binding inspection owner. It preserves every IsoProject::lint() issue and count, builds a canonical declaration catalog, and resolves project-root, device-parent/provider, application-device/task/asset, task-profile, and asset-provider edges against declaration multiplicity. Missing or duplicate declarations therefore remain inspectable as missing-*/ambiguous-* bindings instead of being flattened into one lint counter. GET /api/project/validation and the Validation view bind this graph to the active project revision. The typed response gate recalculates declaration counts, binding resolution, issue accounting, and ordering before state write. Source files, provider runtime, runtime observation, NIC access, and live evidence all remain unverified or false.

E9 adds an explicit five-component LinkCapabilityVector to each device path: single initiator, bounded delay, total-order delivery, partition possibility, and clock-sync guarantee. JSON keeps it as a nested capabilities object and the YAML-like device row appends the same five values. Missing vectors, positive task deadlines on unbounded paths, and physical-clock tasks without physical sync are lint errors. E3 plan text and digest include the canonical vector plus derived E2/envelope/partition/absolute-time obligations. The revision-bound GET /api/project/capabilities resource and Capabilities view expose those declarations and bindings only. They do not infer capability from protocol or device family, inspect source files, connect a provider, observe a link, or claim that declared capability has been verified.

E10 binds application Object Contract declarations to those device capability vectors. TimingContract.clock_requirement is explicit and defaults to any; JSON and YAML-like contract input can require physical. An in-process catalog resolves only exact package id plus symbol pairs to defensive registry copies, so project asset source paths, protocol names, and device families are never used as discovery hints. Full project lint rejects unknown, duplicate, wrong-kind, or invalid registry bindings. Positive Object Contract max_age_ns or deadline_ns requires bounded delay, while a physical clock requirement needs physical synchronization. These checks participate in preview activation, E3 plan text/digest, the typed run-plan inspection, project validation, and the revision-bound GET /api/project/contract-requirements resource. The declaration catalog is static: it does not load source files or symbols, connect providers, observe a link, open a NIC, or claim verified capability or live evidence.

E11 binds each declared Protocol Contract role to one project device through ProtocolRoleBinding. An explicit package/symbol catalog resolves the real Concord GlobalProtocol and defensive copies, while the application Object Contract catalog resolves each message object. Full lint rejects missing, duplicate, unknown, or orphan role bindings; unresolved objects; sender-role authority mismatches; bounded message timing on either unbounded endpoint; and physical-clock requirements when either endpoint lacks physical sync. These checks gate preview activation and enter canonical E3 plan text/digest. The revision-bound GET /api/project/protocol-sessions resource and Protocols view show sessions, role/device capability bindings, message edges, and stable issues. Both catalogs are static: they do not load source files, connect a provider, execute the protocol, observe capability, open a NIC, or claim live evidence.

E12 projects every declared protocol role through the real GlobalProtocol::project() implementation. The protocol-session assessment publishes one canonical local projection per role, preserves terminal labels, and proves that every global message has exactly one sender send step and one receiver receive step with matching label, object, peer role/device, and global index. Each resolved projection carries an explicit device trace_node_id and a ready ProtocolMonitor binding plan. E3 plan text/digest, the typed run-plan summary, and the existing revision-bound Protocols resource and view expose these projections and local steps without adding endpoints or state resources. This is a static engineering projection only: monitor_executed=false and trace_source_verified=false; translating actual provider/runner events into sender and receiver local observations and executing the monitors remains a separate runtime binding task.

E13 adds that runtime binding for the deterministic CIA402 runner. A typed, revision-bound mapping store classifies each source event by exact label, direction, and optional payload digest; four exchange rules project every global edge into one sender Tx plus one receiver Rx, while terminal, mode, fault metadata, and reset events follow explicit terminal/recovery epoch rules. Both device-bound role monitors execute the real ProtocolMonitor, and GET /api/runtime/protocols exposes mapping coverage, source classifications, role verdicts, violations, and runtime/monitor provenance. The runner, Protocols view, typed response decoder, commissioning report, and offline artifact bundle consume the same verdict. Missing, duplicate, unmapped, out-of-order, wrong-node, and post-terminal input fail closed. This remains deterministic offline evidence: the mapper does not attach a provider, open a NIC, inspect Npcap frames, or set any trace/provider/live verification field.

E14 replaces the single hard-coded plan assumption with an explicit runtime mapping catalog and compiler. Every catalog entry binds one application, Protocol Contract asset, protocol id, source adapter, source-session namespace, terminal policy, and exact selector set. Exchange rules also declare the sender/receiver role and device expected from the E12 edge. Compilation rejects duplicate ids, targets, adapters, or selectors; missing/duplicate edges; unknown sessions, protocols, roles, devices, or terminal labels; missing terminal policy; unmapped project sessions; and cross-session selector leakage. The protocol monitor store retains an independent epoch for every compiled mapping, so two sessions in one revision may both use runtime/monitor sequence 1 and one mapping can reset without clearing the other. The GET /api/runtime/protocols resource exposes catalog plans/issues, all session epochs, and the selected session detail. Runner events/resources, commissioning reports, and offline artifacts retain the selected mapping, application, asset, and source-adapter identity. The production fixture still executes one deterministic CIA402 adapter; the two-session fixture proves local catalog and epoch behavior, not concurrent live providers.

E15 moves the production catalog source behind a provider-owned adapter manifest. mokomoking2501/fieldbus_core/cia402_scenario_loop owns and exports the default CIA402 declaration as typed JSON with schema/version, manifest/provider identity, mappings, exact rules, and a canonical SimDigest; declaration and rule order do not affect that digest. An in-memory defensive registry rejects malformed JSON, unsupported schema or version, digest mismatch, duplicate provider/manifest/mapping/adapter/rule/ selector ownership, cross-manifest conflicts, incomplete terminal policy, and all E14 compiler errors. The default monitor constructor now performs manifest decode, registry validation, and E14 compilation before creating epochs; new_with_catalog remains an explicitly labelled legacy-direct-catalog compatibility path. The v3 runtime resource, Protocols view, runner/WebSocket, and commissioning report retain manifest id, provider id, and digest alongside mapping provenance. Backend code does not accept a manifest file path, load a provider, open a NIC, or promote declaration integrity to runtime/live verification.

E16 makes that manifest choice an explicit project declaration instead of an implicit registry match. Every Protocol Contract session binds exactly one library provider slot, manifest id/provider/digest, source adapter, and selected mapping. JSON/YAML ingest and canonical project serialization preserve the binding in stable order; preview and activation reject missing, duplicate, ambiguous, orphaned, stale-digest, provider-mismatched, source-adapter-mismatched, or unbound manifest mappings. Libraries shows the declaration while Protocols shows its resolved status. The selected binding id and provider slot now travel with mapping plans, session epochs, runner resources/events, commissioning reports, and offline artifacts, so report provenance can be traced back to the activated project document. The registry remains a bounded in-memory backend composition input: project documents cannot name arbitrary files, load a provider, open a NIC, or claim provider/runtime/live verification.

Build the JS member before a clean native backend build:

moon build isoproject_workbench_frontend\app --target js --release moon run isoproject\workbench_backend --target native -- --host 127.0.0.1 --port 8765

mokomoking2501/isoproject/workbench_backend is the native HTTP listener and browser workbench around that contract. Run the two commands above from an x64 MSVC-initialized shell on Windows, and stop the foreground server with Ctrl-C. Open http://127.0.0.1:8765/ for the E5C1 project explorer, task monitor, and selection inspector. E5C2 adds manual Windows/Npcap interface input, exact confirmation, an explicit live-handoff toggle, PROFINET/MoonECAT scan controls, commissioning, and diagnostics capture controls. E5C5 calls the actual Lockwire Npcap interface-list report once at backend startup, projects a bounded typed inventory into the interface datalist, and exposes status, source, raw/retained counts, and a process-local sequence. The same origin serves GET /frontend.js, GET /api/workbench/contract, GET /api/project/document, bounded plain-text POST /api/project/document/preview, bounded raw-bundle POST /api/project/document/import-artifact, digest-bound POST /api/project/document/activate, GET /api/project/tree, GET /api/project/libraries, GET /api/project/run-plans, GET /api/project/validation, GET /api/project/capabilities, GET /api/project/contract-requirements, GET /api/project/protocol-sessions, GET /api/tasks, GET /api/runtime (an atomic resource bundle), GET /api/runtime/snapshot, GET /api/runtime/diagnostics, GET /api/runtime/evidence, POST /api/runtime/observations, GET /api/runtime/contracts, POST /api/runtime/contracts/observations, GET /api/runtime/protocols, GET /api/runtime/runner, POST /api/runtime/runner/start, POST /api/runtime/runner/stop, the cursor-resumable WebSocket GET /api/runtime/runner/stream?after=N&revision=R, GET /api/commissioning/reports, idempotent POST /api/commissioning/reports, attachment GET /api/commissioning/reports/export?project_revision=R&sequence=N&source_session_id=S, GET /api/commissioning/artifacts, idempotent POST /api/commissioning/artifacts, attachment GET /api/commissioning/artifacts/export?project_revision=R&report_sequence=N&source_session_id=S&bundle_integrity_digest=D, bounded POST /api/commissioning/artifacts/verify, GET /api/workbench/plan, read-only-current GET /api/interfaces, explicit host re-enumeration POST /api/interfaces/refresh, the legacy deliberately refused POST /api/workbench/live-route, and typed action handlers:

  • POST /api/provider/profinet/scan
  • POST /api/provider/moonecat/scan
  • POST /api/commissioning/live
  • POST /api/diagnostics/capture

The E5C6 project document panel accepts at most 64 KiB of JSON or the E2 YAML-like text format. Preview parses and lints without changing the active project, returns a canonical JSON digest and bounded issue list. Artifact import first completes detached internal inspection, then installs the embedded canonical project as the latest preview without changing the active revision; malformed, unsupported, tampered, and oversized bundles do not replace the latest preview. Activation requires the latest preview sequence plus exact digest. A successful activation increments the process-local project revision even when the project id is unchanged, then replaces the project tree/tasks and resets the runtime, diagnostics, evidence, contract monitor, deterministic runner session, runner journal, commissioning report history, and current offline artifact bundle. The activation response explicitly reports report_store_reset=true and artifact_bundle_store_reset=true. Runner start requests, resources, events, gap frames, session ids, and WebSocket cursors all carry that revision; stale stream revisions are rejected. Projects without exactly one fast-cycle task expose compatible_project=false and reject deterministic runner start. File input is read by the browser into the draft editor; the backend never receives or reads a filesystem path. The backend rejects an oversized declared Content-Length before reading the body, returns a complete 413 response, and closes that connection. For chunked or unknown-length requests it reads at most 65,537 body bytes, then returns 413 and closes without waiting for EOF or a terminating chunk. The 64 KiB limit is therefore also a transport memory and stalled-body bound. Artifact verify applies the same transport discipline at 262,144 bytes: a declared 262,145-byte body is rejected without draining it, while chunked or unknown-length input is read only through byte 262,145 before 413 and close.

Selecting an observed interface only fills the interface input; the operator must still type the exact confirmation independently and enable the live handoff gate. interfaces_observed=true means only that the exact name appears in the current retained host listing. It does not report link state, device presence, adapter open, protocol traffic, or capture evidence. A 202 handoff-ready response only exposes the E5A command preview and keeps server_executes_live_io_now=false, server_opens_nic_now=false, and live_evidence_claim=false. Interface resources additionally keep device_scan_performed=false, capture_performed=false, and runtime_observation_verified=false. The server still does not run the command, open a NIC, scan a device, capture a pcap, verify task timing, or claim live evidence. The browser loads the project validation graph, capability matrix, Object Contract requirements, Protocol Contract sessions and device-bound local monitor plans, library registry, and run-profile plan with each project refresh or activation. It polls the aggregate runtime bundle, atomic contract resource, runner resource, report collection, and artifact resource every two seconds. Tasks, Validation, Libraries, Profiles, Objects, Protocols, Runner, Diagnostics, Evidence, and Reports views expose the current sequences, coverage, declared object timing/capability compatibility, contract violations, timeout-action evaluations, runner session/verdict/journal state, report digest/history/blockers, and unverified source provenance. The eleven views are Tasks, Validation, Capabilities, Libraries, Profiles, Objects, Protocols, Runner, Diagnostics, Evidence, and Reports. The Runner tab can start and stop the local adapter and uses the WebSocket only for low-latency event display; REST resources remain the state truth. The Reports tab enables Generate only for a reportable terminal runner and downloads the retained current sequence through the shared export endpoint. It can also generate/download a three-member bundle and upload one for contextual integrity verification; project epoch plus report/bundle identity gates prevent stale async responses from crossing activation. A contract resource from an older runtime sequence is displayed as stale instead of being misreported as transport failure. Runtime, contract, runner, and report state is process-local and resets when the backend restarts. The Project panel separately imports a bundle as a detached preview, displays its source revision/context, keeps the editor draft across same-revision refresh, and still requires the existing Activate command. File-read and response awaits are project-epoch guarded; manual edit/preview clears import metadata and makes the older preview sequence stale. Binding a real Lockwire/MoonECAT/PROFINET provider remains E5C3B2; real Npcap/device/timing/pcap evidence remains E5V.

Run the repeatable localhost API check with powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\workbench-backend-api-smoke.ps1. The script builds the complete JS frontend and native package, starts the generated backend executable directly, decompresses and executes the single served bundle, checks typed builders plus shared method/media/status metadata, executes the typed response decoder against 20 served state resources, 15 action responses, two attachment errors, and actual runner WebSocket frames, verifies Npcap inventory refresh without opening an interface, and proves both declared Content-Length and chunked requests receive 413 plus Connection: close and EOF without completing their bodies. It then covers deterministic runner teardown, two independent revision-bound WebSocket consumers, first-create/idempotent report generation, deterministic attachment export, bundle generation/export/ roundtrip/tamper verification, 262,145-byte artifact verify bounds, 262,145-byte artifact import bounds, detached revision-1 bundle import into revision 2 without changing active report/artifact state, manual preview replacement, explicit import activation, active-runner activation refusal, report/bundle revision reset, old-stream rejection, different-project replacement, revision 1 through 4 Object Contract requirement and Protocol Contract session replacement, projection conservation and monitor-plan replacement, runtime protocol mapping/role-monitor execution, terminal/recovery accounting, catalog v2 identity and endpoint declarations, runner/report protocol verdict propagation, protocol-monitor epoch reset and replacement, detached-import non-mutation, and incompatible-runner binding. Ordinary HTTP status and validation matrices are intentionally left to Hurl. Each phase has a timeout; the script stops the process in finally and only reports PASS after the listener port has been released. When the standard Visual Studio installation is present, it initializes vcvars64.bat automatically.

Run the deterministic static artifact gate independently with powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\check-workbench-frontend-bundle.ps1. It builds the release JS once, postprocesses the same input twice, requires exact gzip byte/SHA256 equality and a fixed gzip header, decompresses the result, runs node --check, checks contract/UI/typed-response/race-gate tokens, rejects direct response.json() and JSON.parse(message.data) state paths, and rejects either old script route. This proves build reproducibility for the same input, not source authentication or live evidence.

Run the stateful black-box HTTP gate with powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\workbench-hurl-api.ps1. It generates an untracked 65,537-byte body under _build, builds the JS/native chain, starts the backend, and runs isoproject/workbench_backend/api.hurl. Hurl covers 200/202/400/403/404/409/413/422, shared project/live/report/artifact validation, the thin shell, gzip/cache/Vary response headers, decompressed single-bundle contract/UI/typed-response content, absence of raw state decode, old contract route 404, active-runner report refusal, missing exports, digest-bound activation, revision 1 to 2, report/bundle reset, stale stream/start rejection, revision-bound library-registry, run-plan, project-validation, capability-matrix, and Object/Protocol Contract replacement, including protocol role projections, local steps, device-bound monitor plans, initial/reset runtime protocol-monitor catalog v2 state, explicit sender/receiver endpoints, capability mismatch preview and activation refusal, malformed/unsupported detached import, plus all no-live response fields. The wrapper owns process cleanup and port release; the broader PowerShell lifecycle smoke remains responsible for WebSocket multi-consumer replay, stalled declared-length and chunked input, Npcap host inventory, successful report and bundle generation/export/verify/tamper gates, runner teardown, and revision 3/different-project behavior.

///|
test "isoproject fixture is lint clean" {
let project = iso_project_fixture()
assert_true(project.lint().passes())
}

#
Application

pub(all) struct Application {
id : String
label : String
device_id : String
task_ids : Array[String]
object_set_assets : Array[String]
protocol_session_assets : Array[String]
protocol_role_bindings : Array[ProtocolRoleBinding]
library_assets : Array[String]
} derive(Eq,
Debug
)

#
Application::make

fn Application::make(id~ : String, label~ : String, device_id~ : String, task_ids~ : Array[String], object_set_assets? : Array[String], protocol_session_assets? : Array[String], protocol_role_bindings? : Array[ProtocolRoleBinding], library_assets? : Array[String]) -> Application

#
ClockSyncGuarantee

pub(all) enum ClockSyncGuarantee {
NoSync
Causal
Windowed
Physical
} derive(Eq,
Debug
)

#
ClockSyncGuarantee::label

fn ClockSyncGuarantee::label(self : ClockSyncGuarantee) -> String

#
DeviceNode

pub(all) struct DeviceNode {
id : String
label : String
family : String
provider_slot : String
runtime_target : String
capabilities : LinkCapabilityVector?
parent_id : String?
} derive(Eq,
Debug
)

#
DeviceNode::make

fn DeviceNode::make(id~ : String, label~ : String, family~ : String, provider_slot~ : String, runtime_target~ : String, capabilities? : LinkCapabilityVector, parent_id? : String) -> DeviceNode

#
DeviceTree

pub(all) struct DeviceTree {
root_id : String
devices : Array[DeviceNode]
} derive(Eq,
Debug
)

#
DeviceTree::contains_device

fn DeviceTree::contains_device(self : DeviceTree, id : String) -> Bool

#
DeviceTree::empty

fn DeviceTree::empty() -> DeviceTree

#
DeviceTree::make

fn DeviceTree::make(root_id~ : String, devices~ : Array[DeviceNode]) -> DeviceTree

#
IsoProject

pub(all) struct IsoProject {
id : String
name : String
device_tree : DeviceTree
applications : Array[Application]
tasks : Array[TaskConfig]
libraries : LibraryRegistry
runtime_profiles : Array[RuntimeProfile]
} derive(Eq,
Debug
)

#
IsoProject::lint

#
IsoProject::make

fn IsoProject::make(id~ : String, name~ : String, device_tree~ : DeviceTree, applications~ : Array[Application], tasks~ : Array[TaskConfig], libraries~ : LibraryRegistry, runtime_profiles~ : Array[RuntimeProfile]) -> IsoProject

#
IsoProject::protocol_adapter_binding_declaration_issues

fn IsoProject::protocol_adapter_binding_declaration_issues(self : IsoProject) -> Array[ProjectLintIssue]

#
LibraryRegistry

pub(all) struct LibraryRegistry {
assets : Array[ProjectAssetRef]
provider_slots : Array[ProviderSlot]
protocol_adapter_bindings : Array[ProtocolAdapterBinding]
} derive(Eq,
Debug
)

#
LibraryRegistry::contains_asset

fn LibraryRegistry::contains_asset(self : LibraryRegistry, id : String) -> Bool

#
LibraryRegistry::contains_provider_slot

fn LibraryRegistry::contains_provider_slot(self : LibraryRegistry, id : String) -> Bool

#
LibraryRegistry::empty

#
LibraryRegistry::make

fn LibraryRegistry::make(assets~ : Array[ProjectAssetRef], provider_slots? : Array[ProviderSlot], protocol_adapter_bindings? : Array[ProtocolAdapterBinding]) -> LibraryRegistry

#
LinkCapabilityVector

pub(all) struct LinkCapabilityVector {
single_initiator : Bool
bounded_delay : Bool
total_order_delivery : Bool
partition_possible : Bool
clock_sync : ClockSyncGuarantee
} derive(Eq,
Debug
)

#
LinkCapabilityVector::absolute_time_allowed

fn LinkCapabilityVector::absolute_time_allowed(self : LinkCapabilityVector) -> Bool

#
LinkCapabilityVector::e2_strength

fn LinkCapabilityVector::e2_strength(self : LinkCapabilityVector) -> String

#
LinkCapabilityVector::envelope_kind

fn LinkCapabilityVector::envelope_kind(self : LinkCapabilityVector) -> String

#
LinkCapabilityVector::make

fn LinkCapabilityVector::make(single_initiator~ : Bool, bounded_delay~ : Bool, total_order_delivery~ : Bool, partition_possible~ : Bool, clock_sync~ : ClockSyncGuarantee) -> LinkCapabilityVector

#
LinkCapabilityVector::partition_faults_required

fn LinkCapabilityVector::partition_faults_required(self : LinkCapabilityVector) -> Bool

#
ProjectAssetKind

pub(all) enum ProjectAssetKind {
ObjectContractSet
ProtocolContract
FieldbusProfile
ProviderHarness
LockwireBackend
ReferenceRepository
} derive(Eq,
Debug
)

#
ProjectAssetKind::label

fn ProjectAssetKind::label(self : ProjectAssetKind) -> String

#
ProjectAssetRef

pub(all) struct ProjectAssetRef {
id : String
kind : ProjectAssetKind
package_id : String
symbol : String
source_path : String
provider_slot : String?
} derive(Eq,
Debug
)

#
ProjectAssetRef::make

fn ProjectAssetRef::make(id~ : String, kind~ : ProjectAssetKind, package_id~ : String, symbol~ : String, source_path~ : String, provider_slot? : String) -> ProjectAssetRef

#
ProjectLintIssue

pub(all) struct ProjectLintIssue {
code : String
target : String
message : String
} derive(Eq,
Debug
)

#
ProjectLintReport

pub(all) struct ProjectLintReport {
project_id : String
issue_count : Int
missing_device_count : Int
missing_application_count : Int
missing_task_count : Int
missing_library_count : Int
missing_capability_count : Int
invalid_binding_count : Int
incompatible_capability_binding_count : Int
unresolved_object_contract_registry_count : Int
incompatible_object_contract_binding_count : Int
unresolved_protocol_session_count : Int
incompatible_protocol_edge_count : Int
live_profile_count : Int
issues : Array[ProjectLintIssue]
} derive(Eq,
Debug
)

#
ProjectLintReport::passes

fn ProjectLintReport::passes(self : ProjectLintReport) -> Bool

#
ProtocolAdapterBinding

pub(all) struct ProtocolAdapterBinding {
id : String
application_id : String
protocol_asset_id : String
provider_slot_id : String
manifest_id : String
provider_id : String
source_adapter_id : String
manifest_digest : String
} derive(Eq,
Debug
)

#
ProtocolAdapterBinding::make

fn ProtocolAdapterBinding::make(id~ : String, application_id~ : String, protocol_asset_id~ : String, provider_slot_id~ : String, manifest_id~ : String, provider_id~ : String, source_adapter_id~ : String, manifest_digest~ : String) -> ProtocolAdapterBinding

#
ProtocolRoleBinding

pub(all) struct ProtocolRoleBinding {
protocol_asset_id : String
role : String
device_id : String
} derive(Eq,
Debug
)

#
ProtocolRoleBinding::make

fn ProtocolRoleBinding::make(protocol_asset_id~ : String, role~ : String, device_id~ : String) -> ProtocolRoleBinding

#
ProviderSlot

pub(all) struct ProviderSlot {
id : String
provider_id : String
package_id : String
descriptor_path : String
live_default_allowed : Bool
} derive(Eq,
Debug
)

#
ProviderSlot::make

fn ProviderSlot::make(id~ : String, provider_id~ : String, package_id~ : String, descriptor_path~ : String, live_default_allowed? : Bool) -> ProviderSlot

#
RuntimeProfile

pub(all) struct RuntimeProfile {
id : String
kind : RuntimeProfileKind
lockwire_backend : String
trace_route : String
evidence_policy : String
live_io_required : Bool
} derive(Eq,
Debug
)

#
RuntimeProfile::make

fn RuntimeProfile::make(id~ : String, kind~ : RuntimeProfileKind, lockwire_backend~ : String, trace_route? : String, evidence_policy? : String, live_io_required? : Bool) -> RuntimeProfile

#
RuntimeProfileKind

pub(all) enum RuntimeProfileKind {
SimNative
SimWasm
Replay
FaultCampaign
RealLinux
RealEmbedded
} derive(Eq,
Debug
)

#
RuntimeProfileKind::is_offline

fn RuntimeProfileKind::is_offline(self : RuntimeProfileKind) -> Bool

#
RuntimeProfileKind::label

fn RuntimeProfileKind::label(self : RuntimeProfileKind) -> String

#
TaskConfig

pub(all) struct TaskConfig {
id : String
runtime_profile_id : String
period_ns : Int64?
priority : Int
deadline_ns : Int64?
clock_domain : String
} derive(Eq,
Debug
)

#
TaskConfig::make

fn TaskConfig::make(id~ : String, runtime_profile_id~ : String, period_ns? : Int64, priority? : Int, deadline_ns? : Int64, clock_domain? : String) -> TaskConfig

#
iso_project_fixture

fn iso_project_fixture() -> IsoProject

#
iso_project_library_registry_fixture

fn iso_project_library_registry_fixture() -> LibraryRegistry

#
package_id

fn package_id() -> String

Source Files