Industrial Connections
This guide describes work brought from the former 0.67 branch into the planned 0.71.0 release, not a released or hardware-qualified capability. Selected HTTP-host integration supports OPC UA…
Industrial connections
This guide describes work brought from the former 0.67 branch into the planned 0.71.0 release, not a released or hardware-qualified capability. Selected HTTP-host integration supports OPC UA and catalog-approved MQTT/Sparkplug observation ingress to scalar model targets and latest-reading tables, plus catalog-approved legacy Modbus connections. Version-four resources add raw CAN to the selected HTTP model-lifecycle owner; live receive still requires Linux and is not yet Linux/hardware qualified. Version-five resources add passive J1939 through that same owner. Version-six resources add BACnet/IP and MS/TP observation activation through an operator-supplied, digest-pinned Unix driver. Version-seven resources add the selected generic MQTT 5 QoS 1 byte-observation profile described below. Safe remapping, additional MQTT profiles, and outbound commands remain unfinished.
BACnet uses a separate version-two observation contract. A complete notification retains its device, object, property and array identities, explicit value types, and one restart-safe position. All selected values enter the queue together; a malformed property or insufficient capacity refuses the whole notification. The existing endpoint journal owns persistence before acknowledgement. Missing status flags remain uncertain rather than being presented as a good reading, and read errors remain bad-quality diagnostics. Exact integer values are preserved. Legacy BACnet saved data retains its old interpretation; automatic migration is not available. A production BACnet wire stack, wire interoperability, non-Unix process transport and device qualification remain unfinished. Do not interpret the notification contract or its portable tests as live BACnet support, and do not delete saved state to force a version change.
The host SDK now has an explicitly constructed BACnet session owner over an injected driver interface. It admits exact direct-IP or MS/TP peer inventories, read plans, typed bindings, COV subscriptions, and resource/time limits before calling a driver. It keeps a single notification pending until the journal permits release, renews subscriptions separately, and stops on expired leases, missing or repeated driver receipts, unapproved data, and ambiguous release. Reconnects continue Grid's saved observation positions rather than reusing wire invoke IDs. Read responses and unconfirmed COV request only local-capacity release, never a confirmed-service acknowledgement. The SDK now includes a separately selected Unix process transport. On connect, it verifies the approved executable fingerprint, runs a private copy, and uses bounded request/response messages with timeouts. Stalled or ambiguous operations stop the instance; recovery cannot silently retry a possibly completed action. It starts no process or worker merely because an ordinary model is opened. Compiled model/resource activation now composes that driver with the existing endpoint owner, journal, protected recovery checkpoints and disk coordination layer. The separately packaged BACnet stack, non-Unix process transport, and wire/device qualification remain required. The subprocess test fixture is not a BACnet stack and establishes no live IP or serial support. Process isolation is not a sandbox for hostile executables; libraries and other deployment inputs still need separate approval. Peer and device identifiers are routing identities, not authentication. Routed BACnet addressing is not inferred from direct peers. Direct SDK callers must bind configuration to a saved resource identity; the version-six catalog binds all approved peers, read/COV definitions, type and normalization policies, durability and driver inputs into the model incarnation. Reusing saved state after changing approved peers or reading definitions is not supported migration.
BACnet resources
Use bacnet-ip-legacy (grid.protocol.bacnet-ip-legacy@2.0.0) or
bacnet-mstp-legacy (grid.protocol.bacnet-mstp-legacy@2.0.0) for observation
subscriptions. Both have the external driver contract grid.bacnet.driver/v1
and expose no write, publish or control operation. They are unauthenticated
legacy links for an operator-controlled network or serial bus.
reading = CONNECTOR("bacnet-ip-legacy", {
"endpointId": "building", "connection": "approved-building",
"address": {"deviceInstance": 7, "objectType": 2, "objectInstance": 3,
"propertyId": 85, "valueType": "unsigned"},
"deliveryProfile": "event-ingress", "industrialProjection": "scalar-value-v1"
})
The address contains exact non-wildcard device/object/property identifiers and
an explicit scalar type. Optional arrayIndex distinguishes a whole property
from element zero (the array length). It does not contain an executable,
socket, serial path, credential or read/subscription policy.
An operator's version-six manifest adds bacnetEndpoints. Each resource names
the approved modelId, incarnation, exact endpoint package pin and connection,
typed adapter configuration, driver, tagDefinitions, ingress and
durability. The adapter supplies the direct-IP bind or MS/TP serial settings,
exact peers, typed property bindings, read plans and COV subscriptions, bounded
request/poll/pending/service-gap limits, and notification capacity. The driver
supplies an absolute executable, its lowercase SHA-256, bounded arguments and
request/response sizes. Its file permissions and actual bytes are verified
before launch, and the worker runs its private verified copy.
Normalization definitions must exactly match the approved bindings; SDK callers
derive their address identity with bacnet_tag_address. Compiled routes may
select only approved property/type identities. Duplicate direct-IP bind
ownership, overlapping wildcard binds and shared MS/TP/Modbus serial paths are
refused. A selected worker cadence at or beyond the service-gap bound is refused
before checkpoint or worker creation; actual delays still fail closed.
The existing industrial startup settings and disk coordination layer topology apply. Version one through five manifests keep their existing encodings and recovery identities. Version six changes the resource identity explicitly; it does not migrate saved data. Selected composition has authored IPC-to-compiled formula and recovery fixtures; their execution is still pending at this source. Those fixtures do not provide BACnet wire or hardware evidence.
CAN/J1939 preparation adds a separate version-two reading contract. It identifies standard versus extended frames and Classical CAN versus CAN FD explicitly, preserves exact signed and unsigned integers, and gives assembled J1939 readings an exact message/source/destination identity. Bit offsets use explicitly selected linear LSB0 or MSB0 numbering, not an assumed DBC convention. Invalid or incomplete selected readings are refused as a whole without advancing saved state. Existing CAN journals retain their original meaning; they cannot silently become v2 state. The host SDK now binds approved raw CAN signals to compiled models through the existing managed endpoint, recovery, and peer-delivery owners. The version-four resource catalog now supplies HTTP model approval. Its adapter implements receive-only Classical CAN/CAN FD through Linux SocketCAN; construction is inert and other platforms refuse connection. The host must provide an exact interface, approved signals, a receive-buffer budget, a 1–1000 ms maximum polling gap, and whether locally originated traffic is allowed. It never configures the bus or sends device commands. Detected loss, malformed data, bus errors, and exceeded polling/acknowledgement deadlines stop the connection and require explicit recovery. Local acknowledgement releases Grid capacity, not a device replay guarantee. Native Linux/virtual-CAN execution and hardware qualification remain outstanding; portable tests and Linux type checks alone do not qualify a device. J1939 now has an explicitly selected host SDK combining bounded assembly with the existing CAN capture and journal lifecycle. It observes broadcasts and passive request/response transfers, sends no acknowledgement or device command, and now has compiled model/package/catalog activation with explicit host limits. Native Linux/device qualification remains unfinished. Do not delete saved state to force a version change. CAN identifiers and local socket filters do not authenticate a sending device; this SDK is for an operator-controlled trusted bus, not an encrypted protocol.
The SDK's exact grid.protocol.can-raw version-two package (can-raw alias)
accepts only observation subscriptions. Every signal explicitly specifies the
arbitration ID, standard/extended identifier, Classical/FD format, bit offset,
bit length/order, value type, scale, and offset. Interface and socket settings
are host-owned, never address fields. SDK integrators must bind approved
interface, filtering, timing, tag policies, and durability into the plan's
recovery incarnation. Reusing an old incarnation for changed host resources is
not supported. HTTP integrations use the frozen catalog described below to
enforce those resource identities rather than trusting direct SDK callers.
The selected worker rejects a configured polling interval at or beyond the
receive-gap limit; actual scheduling delays and queue pressure can still exceed
that limit and require explicit recovery. No new scheduler or evaluator is added.
The new CAN format also preserves integer precision when deciding whether a reading changed enough to publish or crossed an alarm threshold. Adjacent large integers are not collapsed by converting them to floating point. Other protocol and legacy CAN recovery semantics are unchanged.
Modbus preparation adds a separate version-two observation format that keeps coils, discrete inputs, holding registers, and input registers distinct. A failed read affects only its requested range; byte and word order are explicit. Legacy saved frames keep their original interpretation and cannot silently become v2 recovery state. This format alone does not open a TCP or serial connection. Preserve existing saved state; migration is unfinished.
Host SDK integrators can now explicitly construct the native Modbus adapter for legacy TCP or RTU serial reads. It polls only declared bindings, keeps a bounded read plan, and uses the existing endpoint journal/supervisor interfaces. These legacy profiles have no device authentication or encryption: use only on a trusted, operator-controlled network or serial bus. They are not Modbus Security. Version-three industrial resources admit approved Modbus connections through the existing HTTP model-lifecycle owner. After a TCP timeout the connection is discarded before retry; an ambiguous serial exchange requires explicit recovery because RTU replies have no transaction ID. No writes, coils changes, or physical commands are exposed. Real-device, serial-timing, and cross-platform qualification remain unfinished.
The managed SDK integration adds exact legacy TCP and RTU package identities, explicit reading interpretation, and reuse of Grid's endpoint lifecycle, protected checkpoints, and peer delivery. A model address names the unit, address space, zero-based address, type, byte/word order, scale, and offset; Boolean readings require an explicit bit. Host integrators must approve the transport and bind its resources into the recovery identity. Grid refuses changed reading definitions or recovery identities against an old checkpoint; this SDK boundary relies on the integrator to identify changed host resources. The version-three resource catalog enforces those resource identities for the selected HTTP host, rather than relying on a direct SDK caller.
Standalone MQTT/Sparkplug adapter users: new observations use versioned Grid positions rather than device birth counters for recovery ordering. Legacy journals remain readable, but the live adapter refuses legacy saved cursors; explicit migration is not yet available. Preserve journals and checkpoints. Deleting saved state or downgrading after writing new cursors is not a supported migration. Catalog-backed MQTT uses the version-two configuration described below.
The standalone Sparkplug adapter also requires an explicit host-approved group/node/device inventory and bounded retained-state settings. It no longer subscribes to the whole broker namespace. Approve every active device on each selected edge; an unknown device or exhausted state budget stops the connection visibly. Subscriptions do not replace broker authentication or access controls. The same inventory is required by catalog-backed MQTT activation.
Host integrators can attach an approved Sparkplug adapter to Grid's existing managed endpoint and peer-delivery path. Direct SDK callers supply trusted broker settings and a resource-bound recovery identity. The HTTP host obtains them from the operator's version-two resource manifest; a model's connection name is not permission to open an arbitrary broker or reuse changed resources under an old checkpoint.
What changes for a model
A connection receives device readings, records them for recovery, and hands them to Grid's peer network. That network delivers them to the model's existing input path; ordinary Grid formulas then recalculate. It does not introduce another calculation engine.
For example:
# Approved device reading
temperature = CONNECTOR("opc-ua", {
"endpointId": "line-1",
"connection": "factory-opcua",
"address": {"nodeId": "ns=2;s=Temperature"},
"deliveryProfile": "event-ingress",
"industrialProjection": "scalar-value-v1"
})
# Keep unavailable/bad-quality input visible rather than disguising it as zero.
adjusted = temperature + 3
The connection name is an operator-approved alias, not a URL or credential.
Compiling this model alone does not open a connection. The operator must approve
the exact model ID, endpoint, protocol package, and monitored nodes at startup.
The default projection is a complete observation record; this example explicitly
requests only the scalar value. Bad-quality scalar observations produce an
INDUSTRIAL_BAD_QUALITY error rather than silently retaining an old good reading.
Record projections retain quality, times, sequence, and alarm information;
64-bit integers use exact decimal strings. Scalar integers that cannot be
represented exactly as a Grid number are refused rather than rounded.
Reading tables
The canonical examples/industrial/opcua-observation-table.grid example
uses TABLE Readings = CONNECTOR("opc-ua", {...}). The industrial owner creates
the fixed version-one schema and uses the existing peer-delivered table update path.
Do not supply generic provider sourceSymbol, sourceKind, tableSchema,
tableOperation, key, or keyColumns settings: these are refused. The selected HTTP
composition currently requires one authored connector per table, including
case-insensitive aliases; it does not combine multiple declarations into one table.
Each (endpointId, tagId) identifies one latest-reading row. This is not an
append-only history: a later reading replaces that tag's row, and an identical
retry does not add another row.
| Columns | Meaning |
|---|---|
rowSchemaVersion |
Physical table representation version, currently 1. |
endpointId, tagId, deliveryId |
Source identity and exact delivery position. |
sourceTimeMs, serverTimeMs, effectiveTimeMs, sourceSequence |
Exact decimal text; missing source time/sequence is null. Text sorting is not numeric time ordering. |
valueKind |
Original value type, including signed/unsigned integers and bytes. |
numberValue, integerValue, booleanValue, textValue |
Convenience columns for good-quality readings. Integers remain decimal text; only the matching column is populated. Bytes have no convenience column. |
qualityStatus, qualityDetail |
Current quality and available explanation. |
observationJson |
Complete observation as JSON text, including original value, quality, timestamps, sequence, and alarm transition. |
Bad or uncertain quality clears all convenience value columns; the full
original observation remains in observationJson. A value-type change also
clears the previous type's column. Filter/check qualityStatus explicitly:
an aggregate that ignores blank fields is not a healthy-reading indication.
Existing table schemas must pass additive migration preflight. An incompatible
schema or another active peer-delivery source for the same table refuses activation.
Old retained nested-object table payloads are not silently converted; conflicting
replay bytes require explicit recovery/migration, which is still unfinished.
Operator selection
The selected HTTP host requires all four startup settings together:
| Setting | Required value |
|---|---|
GRID_INDUSTRIAL_RESOURCES |
Absolute path to the bounded, operator-owned resource manifest. |
GRID_INDUSTRIAL_STATE_DIR |
Absolute durable directory for endpoint journals and checkpoints. |
GRID_INDUSTRIAL_CHECKPOINT_KEY_FILE |
Absolute path to an operator-provisioned raw key, 32–4096 bytes. |
GRID_INDUSTRIAL_CHECKPOINT_KEY_ID |
Stable, nonempty key identity, at most 256 characters, without control characters. |
No key is generated or inferred by Grid. On Unix, the key file must be a regular
file owned by the current host user, with mode 0600 and one hard link; symlinks
are refused. Keep the key and its identity available across restarts. Windows
ACL/reparse-point qualification is outstanding. Deployment-owned parent
directories must be trusted and protected from replacement.
The local integration requires disk-backed peer delivery, frozen fanout receipts,
post-drain-v1 or post-drain-v2 cutover, and synchronous delivery persistence:
GRID_BACKBONE_MODE=disk, GRID_BACKBONE_FROZEN_FANOUT_RECEIPTS=1,
GRID_BACKBONE_FROZEN_FANOUT_CUTOVER=post-drain-v1, and
GRID_BACKBONE_DELIVERIES_FSYNC_INTERVAL_MS=0.
Configure a durable GRID_BACKBONE_STATE_DIR and model storage as well.
For selected local lifecycle-v2 source completion, use
GRID_BACKBONE_FROZEN_FANOUT_CUTOVER=post-drain-v2 instead. The source checkpoint
retains the receiver's durable proof before the source advances and confirms its
receiver horizon. It waits for retained local input/table delivery completion;
this is not a new claim that a device observation has already reached the model.
A bounded pending queue supplies backpressure while completion is outstanding.
Do not select either cutover over undrained older deliveries or change the receipt profile over a saved endpoint checkpoint. These migrations currently refuse. Lifecycle-v3, exact-node HTTP v2 and DDIL combinations remain unsupported; Grid does not silently fall back to a weaker receipt. Native source-cut fixtures for the new local-v2 composition are authored but have not yet been executed.
The resource manifest is a version-one IndustrialResourceManifest with an
endpoints array. Each entry binds modelId, incarnation, the exact compiled
endpoint declaration/package pin, endpointUrl, applicationUri,
applicationPki, optional userCertificate, driver, nodes, ingress, and
durability. Provision it through the host resource contract; do not copy a
digest from another build or put credentials in the model. The current resolver
accepts the secure default SignAndEncrypt/Basic256Sha256 profile and anonymous
or X509 user identity. Generic endpoint options and secret-slot maps are refused
until their semantics are implemented. The driver is separately packaged and
SHA-256 pinned; the ordinary Grid process does not embed an OPC UA SDK.
Changing approved resources changes the protected connection identity, including changes to unused approvals in the same model. An old checkpoint cannot silently be reused with a new server, credential reference, executable, or normalization policy. Explicit migration tooling is still required; deleting a checkpoint is not a supported reset procedure.
Generic MQTT 5 byte observations
Version-seven mqttV5Endpoints resources admit consume-only exact topics under
grid.protocol.mqtt-v5@5.0.0. Deployment supplies a canonical IPv4 broker,
client ID, pinned TLS 1.3 mutual authentication, and positive finite session
expiry. The model supplies a topic, optional qos: 1, optional retain approval,
and event-ingress. Payloads remain bytes inside ordinary observation records;
no JSON, numeric or text decoding is implied.
A resource approves at most 256 topics, 1024 bytes per topic, 65536 payload bytes, 131072 packet bytes and one pending publication. The complete subscription must fit that packet budget. It uses persistent sessions, ReceiveMaximum 1, disabled topic aliases and manual acknowledgements. QoS 0/2, secondary PUBLISH properties, MQTT 3.1.1, DNS/IPv6 brokers and outbound publishing refuse this selected profile. Earlier manifest versions one through six retain their bytes and identities. Version seven changes the identity explicitly and performs no migration.
A saved cursor requires the broker to report its existing session. Session loss refuses before Subscribe or PUBACK; an existing broker session without a saved cursor also requires explicit reconciliation. Grid journals a complete validated publication before PUBACK and waits for the actual local network write and flush. This does not prove the broker received that acknowledgement. A failed or late acknowledgement quarantines capture while the saved fact remains replayable.
Delivery is at least once. Packet IDs and DUP flags are evidence rather than unique event IDs: a broker redelivery may produce another captured observation. Unchanged payloads retain distinct captured positions. Native fixtures cover initial capture, commit-before-ACK, restart, redelivery, session loss, bounded pressure, refused input and catalog activation; their execution is pending in the coordinated validation operation. These are scripted broker fixtures, not production broker interoperability or measured performance.
MQTT/Sparkplug resources
For Sparkplug, use manifest version 2, keeping OPC UA entries in endpoints
and adding sparkplugEndpoints. Each Sparkplug entry binds the model and
endpoint declaration to a broker, exact approved group/node/device inventory,
metric mappings, normalization policy, and bounded queues. The canonical
examples/industrial/sparkplug-observation.grid model selects factory-broker;
the operator must supply that exact approved connection.
examples/industrial/sparkplug-observation-table.grid uses the same connection
for the fixed latest-reading table, with a quality-filtered query and dependent
row count. Restart fixtures exercise both examples through the host's existing
delivery pump using retained readings from a mutually authenticated test broker;
they are not certification of a production broker or device.
The initial profile requires mutual TLS 1.3. Supply PEM trust certificates,
client certificate chain, and private key through the broker's tls fields:
trustCertificates, clientCertificateChain, and clientPrivateKey, each with
an absolute path and exact lowercase sha256. These files are operator
resources, not model fields. On Unix they must be owner-controlled, regular,
single-linked files; keys must have no group/world permissions. Symlinks are
refused. Keep their parent directories trusted. TLS verifies the configured
broker host and certificate chain; no system-root, plaintext, password, or
verification-bypass fallback is supplied by this profile.
Configure the broker to require and authorize Grid's client certificate. Grid
supplies its certificate when requested and verifies the broker; supplying client
material does not make an external broker enforce authentication or access rules.
Broker client IDs and Sparkplug host application IDs must be unique among connections to the same broker, including aliases that reach the same server. Approve every active device on each selected edge. All approved edges remain subscribed to preserve their birth/sequence state; only selected, approved metrics are delivered to the model.
The configuration is frozen at startup. Changing a certificate, key pin, broker, device inventory, or policy changes its recovery identity. Existing version-one OPC UA configurations retain their old identity, but upgrading a model to version two requires explicit migration; deleting saved state is not a supported shortcut. TLS packet fixtures do not establish named-broker or hardware support.
Modbus resources
Use manifest version 3 and modbusEndpoints; keep OPC UA resources in
endpoints and Sparkplug resources in sparkplugEndpoints. The canonical
examples/industrial/modbus-observation.grid selects factory-modbus, unit 1,
holding register 10 (zero-based), with an explicit engineering conversion.
Compilation does not open the connection. The operator must supply matching
model, endpoint, connection alias, and exact protocol package approval.
examples/industrial/modbus-observation-table.grid uses the same approved
connection and register interpretation for a latest-reading table. Its query
selects only good-quality numeric readings. Each endpoint/tag updates its own
row; this table is not a history archive.
Each Modbus resource includes:
| Field | Operator-owned approval |
|---|---|
modelId, incarnation, endpoint |
Exact model and endpoint declaration, including the package pin and connection alias. |
transport |
legacy_tcp with an exact IP socket address, or legacy_rtu with path, baud_rate, and parity. No DNS or device discovery. |
points |
Complete approved point addresses, each with absoluteDeadband and optional alarms. |
polling |
Poll interval, request/connect deadlines, maximum registers and bits per read. |
ingress, durability |
Bounded loss-refusing queues and the operator's persistence policy. |
The approved address must include the unit, address space, zero-based address, value type, byte and word order, scale, offset, and a Boolean bit when applicable. A model cannot change these interpretations or read unapproved points. Only the selected subset is polled; approving an unused point does not poll it. Polling interval is the minimum pause after each read, not an entire scan's period. TCP/RTU remain unauthenticated and unencrypted; these are not Modbus Security profiles and cannot substitute for deployment-owned network controls.
Serial paths require one catalog owner across models. Put multiple unit IDs
on the same bus under that endpoint. Actual connection still checks the serial
path and exclusive port ownership. On Unix, use a non-symlink character device
under /dev with trusted parent directories. Path approval is not proof of the
physical device's identity. RTU faults with ambiguous replies require explicit
recovery; Grid does not silently retry on an uncertain serial session.
The catalog allows at most 64 endpoints and 4096 approved points/nodes/metrics across all families, within its 1 MiB manifest limit. Changing transport, polling, points (including unused approvals), interpretation, alarm policy, or durability changes the protected recovery identity. Point reordering does not. Versions one and two retain their exact old identities. Switching an existing manifest to version three changes affected model identities and requires explicit migration, which remains unfinished; do not delete saved state to force the change. Selected scalar/table process-recovery tests are described below. Real hardware, physical serial timing, cross-platform, power-loss, and wider restart-fault qualification remain separate work.
J1939 resources and host SDK boundary
The j1939 model alias pins the receive-only grid.protocol.j1939 package
version 1.0.0. The canonical scalar and latest-reading-table examples are
examples/industrial/j1939-observation.grid and
examples/industrial/j1939-observation-table.grid. Each address declares PGN,
source and destination addresses, bit offset/length/order, value type, scale,
and offset. These examples demonstrate an approved conversion, not a standardized
SAE signal dictionary. Static addresses identify observation scope, not an
authenticated device or a dynamically tracked J1939 NAME.
Use manifest version 5 with the existing canEndpoints list. Supply the same
capture, point, normalization, and durability fields as raw CAN, plus this
required operator-owned field (shown as a resource fragment):
{
"j1939Reassembly": {
"maxSessions": 2,
"maxPayloadBytes": 3570,
"maxSessionAgeMs": 5000
}
}
Raw CAN resources must omit this field; J1939 resources must provide it. All three limits participate in protected recovery identity, including changes that only increase capacity. Versions one through four keep their old identity encoding. Moving existing resources to version five requires explicit migration, which remains unfinished; never delete saved state to force activation. Models cannot put assembly policy or interface settings in their signal address. The worker interval must be shorter than the receive-gap bound, 550 ms, and the selected absolute session age; actual scheduling delays can still cause refusal.
Direct host integrators may use
industrial::can::j1939_adapter::J1939Adapter. Its J1939AdapterConfig contains
shared capture settings and explicit reassembly limits. Capture settings are the same endpoint,
interface, receive budget, polling gap, and local-origin policy used by raw CAN,
but its bindings must exclusively identify J1939 PGN/source/destination signals.
The raw CAN constructor and can-raw package still refuse J1939 bindings.
Reassembly requires limits for simultaneous sessions (1..64), aggregate payload bytes (9..114240), and absolute session age (1..120000 ms). Each TP message is bounded to 1785 bytes. Admission expands application and required transport identifiers across all eight priorities and refuses more than 512 exact socket filters. Multiple signals sharing a message share those filters. No wildcard bus access, bus configuration, transmission, ETP, or J1939 FD is enabled.
Only complete messages become journalable observations. Broadcasts complete on their final data packet; directed transfers require the peer's observed completion acknowledgement. Another ECU must perform the active receiver role. Grid's later local acknowledgement releases capture capacity after its journal append; it is not a bus acknowledgement. Incomplete messages are discarded on restart, not reconstructed from orphan fragments. Detected loss, ambiguity, session expiry, and capacity exhaustion require explicit recovery.
Directed TP can carry a PDU2 PGN, so the corrected v2 reading contract retains that transport destination for payloads of 9..1785 bytes. Single-frame PDU2 remains broadcast-only. Existing valid saved bytes keep their meaning, but an older reader may refuse the newly admitted directed form; do not force a downgrade by deleting saved state. SDK callers remain responsible for binding all capture/reassembly settings to their protected recovery identity. The frozen version-five catalog enforces that binding for selected HTTP model activation. The same CAN worker, journal, checkpoint, and peer-delivery owners are reused. Selected-startup scalar/table tests now recover saved J1939 observations across independent host processes. This does not establish live Linux capture or hardware qualification; the fixture supplies a complete saved reading, not packets.
Raw CAN resources
Use manifest version 4 and canEndpoints, alongside the existing family
lists. The canonical examples/industrial/can-observation.grid selects an
approved signal on factory-can: standard Classical CAN ID 291, the first
16 bits in linear LSB0 order, with an explicit engineering conversion.
examples/industrial/can-observation-table.grid uses the same approved signal
with the fixed latest-reading table schema and explicit quality filtering.
The model supplies no interface, bus speed, transmit operation, or credential.
Compiling it does not open a socket.
Each CAN resource includes:
| Field | Operator-owned approval |
|---|---|
modelId, incarnation, endpoint |
Exact model and endpoint declaration, package pin, and connection alias. |
interface |
Exact existing Linux interface name. Grid does not configure or bring up the bus. |
receiveBufferBytes |
Requested socket receive buffer, from 4096 to 1048576 bytes. Kernel accounting may report up to twice this request. |
maxPollGapMs |
Maximum receive/local-acknowledgement gap, 1–1000 ms. The HTTP worker's configured interval must be shorter; its current default is 50 ms. |
acceptLocalFrames |
Whether traffic originated by other local sockets is allowed. Grid's adapter never transmits. |
points |
Complete approved addresses, with absoluteDeadband and optional alarms. |
ingress, durability |
Bounded loss-refusing queues and persistence policy. |
Only selected signals enter the adapter. IDs, identifier/frame formats, bit ranges/order, value types, scale, and offset must exactly match an approval. Changing the interface, receive policy, unused approvals, or normalization settings changes the recovery identity; reordering points does not. Independent approved endpoints may observe the same bus; this does not grant sender identity or exclusive bus ownership. Each endpoint has its own bounded receive socket. The existing aggregate limits still apply: 64 endpoints, 4096 approved signals/ points/nodes/metrics, and a 1 MiB manifest. Each CAN endpoint permits at most 512 distinct identifier/format filters.
Versions one, two, and three preserve their existing recovery identity encodings. Switching a manifest to version four is not an automatic migration; preserve saved state until explicit migration is supported. Queue pressure and storage delays cannot pause a physical CAN bus. Loss/gap detection requires recovery, not a claim of complete capture or bus-level acknowledgement. Non-Linux live connections refuse; the portable recovery fixture uses a saved journal record and a missing interface, not a real CAN source. It checks that already captured work can reach the HTTP peer-delivery pump while live connection failure is visible. Scalar and table selected-startup/process-recovery tests are described below. Native Linux, broader fault coverage, and physical-device evidence remain open.
Status and recovery
Selected model summaries expose industrialConnections: startup readiness,
whether explicit recovery is required, and per-endpoint state/counters.
Readiness is not evidence of a healthy device session. Peer-delivery admission counts
are not counts of readings already applied to a model.
- Connected: the endpoint is receiving through its selected driver.
- Backpressured: pending readings are retained and new polling is slowed or paused until peer delivery has capacity.
- Reconnecting: a recognized disconnection is waiting for bounded retry.
- Quarantined: identity, configuration, integrity, or durability requires explicit investigation; Grid does not report success or weaken security.
- Stopped: polling has ended. Preserved work remains recoverable.
Normal host shutdown joins workers and preserves pending observations. An in-flight driver call or disk flush must finish first, so this is not a hard real-time shutdown guarantee. Approved-model deletion and binding remapping are currently refused until explicit source-retirement/migration support exists.
Source journaling defaults to a bounded one-second flush interval. That is not a zero-loss power-failure promise for source acknowledgements; select interval zero when synchronous source persistence is required. Before exporting readings, the bridge fences its journal and requires a durable peer-delivery receipt. A crash can replay a reading; stable identities make an identical replay safe. There is no claim of exactly-once physical delivery.
Scope and evidence
The canonical scalar and table examples are compiled in the host test suite. Separate host processes exercise startup and saved-model recovery against the same durable state: a reading retained before shutdown is applied after restart and its dependent calculation updates. Table tests check the complete stored observation, the quality-filtered query result, and that the generic connector path did not create a second source. Tests also check that a failed model save stops connection workers and that a corrupt approved model refuses startup.
The OPC UA driver is a process-protocol fixture, not an OPC UA server or evidence of OPC UA certificate validation. Sparkplug tests use a mutually authenticated TLS test broker. Modbus tests use an independent TCP fixture that sends one register reading across all host processes, then withholds replies. The capture host exits without normal shutdown after durable peer-delivery admission. The next host reports the device read timeout yet applies the retained reading to the scalar model or table. Re-admission of the original journal record is allowed; it must not create a second retained delivery. The table keeps its original delivery/source position and the quality-filtered query returns the saved value.
Raw CAN and J1939 each have scalar and table scenarios spanning four independent host processes. After dormant model compilation, the fixture writes one synthetic complete observation into the endpoint journal. The first host admits it to peer delivery and exits without normal shutdown. On restart, normal model/table rehydration applies the retained reading while a deliberately missing interface quarantines capture. Startup readiness does not make that device healthy. No new reading or duplicate table source is created, and table schemas are provisioned by the normal selected startup path. Later processes verify that quarantined model edits preserve saved source bytes and that corrupt saved models refuse startup. This is saved-observation recovery evidence, not live CAN/J1939 traffic.
These tests do not establish listening-HTTP behavior, power-loss durability, complete wire conformance, physical-device support, or measured performance budgets. Named-device, physical serial, and cross-platform qualification remain separate work.
Industrial connections are off unless explicitly selected. An ordinary model does not acquire an industrial driver, journal, worker, or polling timer. The selected Process command profile below is separate from connection capture; ordinary formula recalculation never issues a physical command. This is not a PLC scan-loop replacement, safety system, or hard-real-time control service.
Explicit Modbus command operations
The first outbound profile is legacy-modbus-tcp-single-dispatch. It uses the
existing Process operation owner and the installed ops:grid-industrial/modbus-effects@1.0.0
package. Importing the package or opening a model sends no device command.
Only an explicitly started Process can call write_coil (function 05) or
write_register (function 06). This is a legacy TCP profile, without Modbus
Security or device authentication.
The operator selects a durable native Process recovery directory and model
Table root, then supplies GRID_PROCESS_INDUSTRIAL_EFFECTS_JSON. Each grant
names one verified principal and model, an exact numeric IPv4 endpoint,
resource generation, timeout, and permitted function/unit/address/value range.
Runtime configuration JSON, workbook source, browser requests and package
metadata cannot grant this authority. For example:
[{"principal":"operator-id","model":"plant","resources":[{
"name":"pump","profile":"legacy-modbus-tcp-single-dispatch",
"address":"192.0.2.10:502","generation":1,"timeoutMs":1500,
"operations":[{"function":"write_register","unit":1,"address":7,
"minimum":10,"maximum":30}]
}]}]
USE "ops:grid-industrial/modbus-effects@1.0.0" AS device
PROCESS setpoint()
device.write_register("pump", 1, 7, 21)
END
requested = setpoint()
A fresh Start requires that exact native principal/model scope and the current ordinary hosted model. Protected or licensed models refuse this profile because their current device-effect authority is not installed. Existing enterprise Process-route restrictions remain in force.
Before device bytes leave, the original native Process wait and InFlight
attempt are durable. Each nonblocking socket write is fenced against the exact
root, source namespace, native writer generation, attempt, current model and
resource grant. Stop ordered before a write prevents it; Stop after any bytes
leave makes the original outcome uncertain. Waiting for the response holds no
root writer or model publication permit, so Stop does not wait for the device.
Start freezes its original hosted record, compiled formula and publication generation while
preparing the plan. The same identity governs owner binding, each physical
write and result retention. Replacing the model under the same ID, even with
the same source, refuses the old operation; it never borrows successor authority.
Short record/compiled formula locks span only a nonblocking write or local result commit.
A replacement between partial writes or before response retention leaves the
original outcome Unknown and never reconnects or resends.
The original host lifetime is retained only by the live admitted root. After
restart, these device-bearing Process plans cannot resume or fork, including a
plan that waited before its first write or skipped the device branch. This profile
refuses before rebinding or creating a new attempt until a durable original-host
lifetime witness is implemented. Existing retained inspection and accepted Start
receipts remain available; ordinary Process recovery uses its existing path.
There is one connection and one function request, without reconnect or retry.
true means the receiver returned the exact function/address/value protocol
echo. It does not prove downstream equipment reached a physical state. false
means a matching protocol exception 01, 02 or 03 rejected the request. A lost,
late, malformed or mismatched response, another exception, cancellation,
authority change or ambiguous durable commit leaves the original operation
Unknown. Restart preserves its original evidence and does not redispatch it.
Readback and operator acknowledgement cannot settle that original uncertainty;
no reconciliation capability is installed in this first profile.
Inputs and each retained result/evidence body are bounded to 4 KiB. Configuration is at most 64 KiB, 32 principal/model grants, 16 resources per grant and 1,024 exact command rules in total. Unit IDs are 1–247, addresses and register values are exact unsigned 16-bit integers, and coil grants select the allowed Boolean values. The total connect/request window is at most 2.5 seconds and the Process operation deadline at most 10 seconds.
The native source fixtures cover protocol replies, restart without another command, exact allowlist refusal, durable intent before egress, Stop before and after send, and cancellation after a real partial TCP frame. They are authored and awaiting the grouped native validation run; physical device conformance, power loss and performance budgets are not established by this packet. Other Modbus functions/transports, protected or enterprise effect joins, BACnet/OPC UA/ MQTT/CAN writes, remote effect-job activation, original Unknown reconciliation and industrial remapping transactions remain separate implementation work.