diff --git a/fixtures/hardware/README.md b/fixtures/hardware/README.md index 930c8bf2..1122e748 100644 --- a/fixtures/hardware/README.md +++ b/fixtures/hardware/README.md @@ -7,21 +7,32 @@ This folder contains local-alpha hardware projections that can be consumed by da ## Fixtures - `flowrouter_local_alpha_seed42.json`: deterministic FlowRouter-to-FlowChain operator signal projection generated from `hardware/fixtures/flowrouter_sample_seed42.json`. +- `flowrouter_control_plane_handoff_seed42.json`: read-only optional hardware handoff shaped for local control-plane ingestion. +- `flowrouter_negative_validation_seed42.json`: deterministic report proving malformed hardware/operator handoff cases are rejected. ## Shape The fixture is a `flowmemory.hardware_operator_signals.local_alpha.v0` document. It includes: -- `signalEnvelopes`: one envelope for heartbeat, receipt relay, verifier digest relay, offline alert/challenge input, and NFC memory cartridge metadata. -- `hardwareSignals`: direct workbench/control-plane signal records for the same five envelopes. -- `hardwareNodes`, `workReceipts`, `verifierReports`, `artifactCommitments`, `memoryCells`, `challenges`, `finalityReceipts`, and `alerts`: control-plane-friendly local fixture collections. +- `signalEnvelopes`: envelopes for operator metadata, heartbeat, receipt relay, verifier digest relay, offline alert/challenge input, bridge alert, and NFC memory cartridge metadata. +- `hardwareSignals`: direct workbench/control-plane signal records for the same envelopes. +- `operatorMetadata`, `hardwareNodes`, `workReceipts`, `verifierReports`, `bridgeAlerts`, `artifactCommitments`, `memoryCells`, `challenges`, `finalityReceipts`, and `alerts`: control-plane-friendly local fixture collections. - `workbenchRecords`: ready-to-render records grouped by workbench section keys, including `hardwareSignals`. - `boundary`: explicit local-only, advisory, optional-hardware limitations. +The handoff fixture is a `flowmemory.hardware_control_plane_handoff.local_alpha.v0` document. It mirrors the stable control-plane state keys under `collections`, declares read-only merge id fields, and carries an optional full-smoke row: + +```powershell +python hardware/simulator/flowrouter_sim.py --smoke +``` + ## Validation ```powershell +python hardware/simulator/flowrouter_sim.py --smoke --seed 42 python hardware/simulator/flowrouter_sim.py --validate-operator-file fixtures/hardware/flowrouter_local_alpha_seed42.json +python hardware/simulator/flowrouter_sim.py --validate-handoff-file fixtures/hardware/flowrouter_control_plane_handoff_seed42.json +python hardware/simulator/flowrouter_sim.py --validate-negative-report-file fixtures/hardware/flowrouter_negative_validation_seed42.json ``` These fixtures are local-only and advisory. They do not prove hardware trustlessness, production field deployment, or receipt/verifier finality. diff --git a/fixtures/hardware/flowrouter_control_plane_handoff_seed42.json b/fixtures/hardware/flowrouter_control_plane_handoff_seed42.json new file mode 100644 index 00000000..94df362d --- /dev/null +++ b/fixtures/hardware/flowrouter_control_plane_handoff_seed42.json @@ -0,0 +1,1739 @@ +{ + "boundary": { + "advisory": true, + "claimLimitations": [ + "Hardware-originated references are hints until reconciled by normal indexer, receipt, and verifier paths.", + "LoRa and Meshtastic packets carry compact control signals, not artifacts, model data, media, or raw memory.", + "NFC cartridge metadata is an untrusted pointer until checked against expected commitments.", + "Emergency offline signals are operator alerts or challenge inputs only; they do not execute remote actions.", + "Bridge alerts are operator review hints and must not block local chain progress." + ], + "hardwareRequiredForPrivateTestnet": false, + "localOnly": true, + "normalNetworkReconciliationRequired": true + }, + "chainId": "flowmemory-local-alpha", + "collections": { + "alerts": [ + { + "id": "hw-alert-f187cbc304cb", + "incidentId": "hw-alert-f187cbc304cb", + "linkedObjectIds": [ + "fr-e1e7878a2aa8", + "receipt:hardware:1e92f586a767", + "challenge:hardware:5f54ef32073d" + ], + "localOnly": true, + "openedAt": "2026-05-13T17:01:30Z", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "recommendedAction": "check-upstream-and-power", + "severity": "warning", + "sourcePacketType": "emergency_offline_signal", + "status": "unresolved", + "summary": "Upstream unavailable; LAN dashboard and local cache still reachable.", + "title": "UPSTREAM_LOSS" + }, + { + "id": "hw-alert-008de71ad207", + "incidentId": "hw-alert-008de71ad207", + "linkedObjectIds": [ + "bridge-alert:hardware:8662b11ba0d4" + ], + "localOnly": true, + "openedAt": "2026-05-13T17:01:35Z", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "recommendedAction": "review-bridge-observer-and-do-not-block-chain", + "severity": "warning", + "sourcePacketType": "bridge_alert", + "status": "unresolved", + "summary": "Bridge observer digest lag detected; local chain continues while operator reviews.", + "title": "LOCKBOX_OBSERVER_LAG" + } + ], + "artifactCommitments": [ + { + "artifactId": "artifact:hardware:dffc8c3e46ff", + "availabilityStatus": "metadata-only", + "cartridgeId": "cart-0cfd3cfcb210", + "commitment": "0x7bb78aa4de0935712809a8e46e13b832ba5a761416731e51aeb02d753bbca5b0", + "containsSecrets": false, + "expiresAt": "2026-06-13T17:00:00Z", + "label": "field-test-cache-alpha", + "localOnly": true, + "rootfieldId": "rootfield:hardware:flowrouter-local-alpha", + "sourcePacketType": "nfc_memory_cartridge_metadata", + "status": "observed", + "trustLevel": "untrusted-pointer", + "uriHint": "flowmemory://cache/4549dba629bc" + } + ], + "bridgeAlerts": [ + { + "alertCode": "LOCKBOX_OBSERVER_LAG", + "blockHint": 1200024, + "bridgeAlertId": "bridge-alert:hardware:8662b11ba0d4", + "bridgeId": "bridge-3489980d98a3", + "doesNotBlockLocalChain": true, + "eventDigest": "0xaa5bbd37b946a917cd73aa5394466e71c463cf28e5a1091c474139e23cfe236f", + "localOnly": true, + "loraEligible": true, + "payloadBytesEstimate": 136, + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "recommendedAction": "review-bridge-observer-and-do-not-block-chain", + "resolutionState": "operator-review-required", + "rootfieldId": "rootfield:hardware:flowrouter-local-alpha", + "severity": "warning", + "sourceChain": "flowchain-local-alpha", + "sourcePacketType": "bridge_alert", + "status": "unresolved", + "subjectId": "lockbox:dfce05f88cae", + "summary": "Bridge observer digest lag detected; local chain continues while operator reviews.", + "targetChain": "base-sepolia-sim" + } + ], + "challenges": [ + { + "challengeId": "challenge:hardware:5f54ef32073d", + "doesNotExecuteRemoteAction": true, + "localOnly": true, + "openedAt": "2026-05-13T17:01:30Z", + "openedBy": "hardware-node:fr-e1e7878a2aa8", + "reason": "offline-alert-candidate", + "receiptId": "receipt:hardware:1e92f586a767", + "recommendedAction": "check-upstream-and-power", + "reportId": "report:hardware:bd367723d169", + "sourcePacketType": "emergency_offline_signal", + "status": "pending", + "summary": "Upstream unavailable; LAN dashboard and local cache still reachable.", + "targetId": "receipt:hardware:1e92f586a767", + "ttlSeconds": 900 + } + ], + "finalityReceipts": [ + { + "finalityReceiptId": "finality:hardware:e28633303a6b", + "finalityStatus": "local-pending", + "localOnly": true, + "objectId": "receipt:hardware:1e92f586a767", + "receiptId": "receipt:hardware:1e92f586a767", + "rootfieldId": "rootfield:hardware:flowrouter-local-alpha", + "settlement": "local-fixture", + "sourcePacketType": "compact_receipt_relay", + "status": "pending" + } + ], + "hardwareNodes": [ + { + "cacheState": "healthy", + "callsign": "FlowRouter local-alpha fixture", + "firmware": "flowrouter.poc.v0", + "flowcoreState": "online", + "id": "fr-e1e7878a2aa8", + "lastHeartbeatAt": "2026-05-13T17:00:10Z", + "linkedWorkLaneId": "CHECKPOINT_STORAGE", + "localOnly": true, + "locationHint": "local lab fixture", + "nodeId": "fr-e1e7878a2aa8", + "powerState": "mains", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "role": "router", + "sidecarState": "ready", + "sourcePacketType": "heartbeat", + "status": "verified", + "transport": "local-wifi+meshtastic-sidecar-sim", + "warnings": [] + } + ], + "hardwareSignals": [ + { + "envelopeId": "hw-env-adac8a11bc47", + "id": "hw-sig-583c993be535", + "linkedObjectIds": [ + "operator-metadata:hardware:874d3583a3b5" + ], + "localOnly": true, + "loraEligible": false, + "nodeId": "fr-e1e7878a2aa8", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "rawEnvelope": { + "envelopeId": "hw-env-adac8a11bc47", + "localOnly": true, + "loraEligible": false, + "objectRefs": [ + { + "collection": "operatorMetadata", + "objectId": "operator-metadata:hardware:874d3583a3b5" + } + ], + "observedAt": "2026-05-13T17:00:00Z", + "payloadBytesEstimate": 0, + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "schema": "flowmemory.hardware_operator_signal_envelope.local_alpha.v0", + "signalId": "hw-sig-583c993be535", + "signalType": "operator_metadata", + "sourcePacketId": "device_manifest:42", + "sourcePacketType": "device_manifest", + "status": "observed" + }, + "receivedAt": "2026-05-13T17:00:00Z", + "signalId": "hw-sig-583c993be535", + "signalType": "operator_metadata", + "sourcePacketType": "device_manifest", + "status": "observed", + "summary": "Local operator metadata for optional hardware fixture ingestion.", + "transport": "local-simulator" + }, + { + "envelopeId": "hw-env-69eab6533abb", + "id": "hw-sig-cd45a2ab8b1d", + "linkedObjectIds": [ + "fr-e1e7878a2aa8" + ], + "localOnly": true, + "loraEligible": false, + "nodeId": "fr-e1e7878a2aa8", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "rawEnvelope": { + "envelopeId": "hw-env-69eab6533abb", + "localOnly": true, + "loraEligible": false, + "objectRefs": [ + { + "collection": "hardwareNodes", + "objectId": "fr-e1e7878a2aa8" + } + ], + "observedAt": "2026-05-13T17:00:10Z", + "payloadBytesEstimate": 0, + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "schema": "flowmemory.hardware_operator_signal_envelope.local_alpha.v0", + "signalId": "hw-sig-cd45a2ab8b1d", + "signalType": "heartbeat", + "sourcePacketId": "heartbeat:1042", + "sourcePacketType": "heartbeat", + "status": "observed" + }, + "receivedAt": "2026-05-13T17:00:10Z", + "signalId": "hw-sig-cd45a2ab8b1d", + "signalType": "heartbeat", + "sourcePacketType": "heartbeat", + "status": "observed", + "summary": "FlowRouter heartbeat and coarse node state.", + "transport": "local-simulator" + }, + { + "envelopeId": "hw-env-88c288e8803c", + "id": "hw-sig-5dd1dbdec22d", + "linkedObjectIds": [ + "receipt:hardware:1e92f586a767" + ], + "localOnly": true, + "loraEligible": true, + "nodeId": "fr-e1e7878a2aa8", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "rawEnvelope": { + "envelopeId": "hw-env-88c288e8803c", + "localOnly": true, + "loraEligible": true, + "objectRefs": [ + { + "collection": "workReceipts", + "objectId": "receipt:hardware:1e92f586a767" + } + ], + "observedAt": "2026-05-13T17:00:40Z", + "payloadBytesEstimate": 96, + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "schema": "flowmemory.hardware_operator_signal_envelope.local_alpha.v0", + "signalId": "hw-sig-5dd1dbdec22d", + "signalType": "receipt_relay", + "sourcePacketId": "compact_receipt_relay:1045", + "sourcePacketType": "compact_receipt_relay", + "status": "unresolved" + }, + "receivedAt": "2026-05-13T17:00:40Z", + "signalId": "hw-sig-5dd1dbdec22d", + "signalType": "receipt_relay", + "sourcePacketType": "compact_receipt_relay", + "status": "unresolved", + "summary": "Compact WorkReceipt digest relay awaiting normal reconciliation.", + "transport": "meshtastic-control-sim" + }, + { + "envelopeId": "hw-env-3c18d09ea961", + "id": "hw-sig-d7087f4f3257", + "linkedObjectIds": [ + "report:hardware:bd367723d169" + ], + "localOnly": true, + "loraEligible": true, + "nodeId": "fr-e1e7878a2aa8", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "rawEnvelope": { + "envelopeId": "hw-env-3c18d09ea961", + "localOnly": true, + "loraEligible": true, + "objectRefs": [ + { + "collection": "verifierReports", + "objectId": "report:hardware:bd367723d169" + } + ], + "observedAt": "2026-05-13T17:00:30Z", + "payloadBytesEstimate": 128, + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "schema": "flowmemory.hardware_operator_signal_envelope.local_alpha.v0", + "signalId": "hw-sig-d7087f4f3257", + "signalType": "verifier_digest_relay", + "sourcePacketId": "verifier_report_digest_relay:1044", + "sourcePacketType": "verifier_report_digest_relay", + "status": "unresolved" + }, + "receivedAt": "2026-05-13T17:00:30Z", + "signalId": "hw-sig-d7087f4f3257", + "signalType": "verifier_digest_relay", + "sourcePacketType": "verifier_report_digest_relay", + "status": "unresolved", + "summary": "Compact VerifierReport digest relay awaiting the full report.", + "transport": "meshtastic-control-sim" + }, + { + "envelopeId": "hw-env-2d165bbf5812", + "id": "hw-sig-2474b9971f93", + "linkedObjectIds": [ + "hw-alert-f187cbc304cb", + "challenge:hardware:5f54ef32073d" + ], + "localOnly": true, + "loraEligible": false, + "nodeId": "fr-e1e7878a2aa8", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "rawEnvelope": { + "envelopeId": "hw-env-2d165bbf5812", + "localOnly": true, + "loraEligible": false, + "objectRefs": [ + { + "collection": "alerts", + "objectId": "hw-alert-f187cbc304cb" + }, + { + "collection": "challenges", + "objectId": "challenge:hardware:5f54ef32073d" + } + ], + "observedAt": "2026-05-13T17:01:30Z", + "payloadBytesEstimate": 0, + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "schema": "flowmemory.hardware_operator_signal_envelope.local_alpha.v0", + "signalId": "hw-sig-2474b9971f93", + "signalType": "offline_alert_challenge_input", + "sourcePacketId": "emergency_offline_signal:1049", + "sourcePacketType": "emergency_offline_signal", + "status": "pending" + }, + "receivedAt": "2026-05-13T17:01:30Z", + "signalId": "hw-sig-2474b9971f93", + "signalType": "offline_alert_challenge_input", + "sourcePacketType": "emergency_offline_signal", + "status": "pending", + "summary": "Offline alert that can seed a local challenge candidate.", + "transport": "local-simulator" + }, + { + "envelopeId": "hw-env-6b84ec9e2c77", + "id": "hw-sig-590bff4da4b1", + "linkedObjectIds": [ + "bridge-alert:hardware:8662b11ba0d4", + "hw-alert-008de71ad207" + ], + "localOnly": true, + "loraEligible": true, + "nodeId": "fr-e1e7878a2aa8", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "rawEnvelope": { + "envelopeId": "hw-env-6b84ec9e2c77", + "localOnly": true, + "loraEligible": true, + "objectRefs": [ + { + "collection": "bridgeAlerts", + "objectId": "bridge-alert:hardware:8662b11ba0d4" + }, + { + "collection": "alerts", + "objectId": "hw-alert-008de71ad207" + } + ], + "observedAt": "2026-05-13T17:01:35Z", + "payloadBytesEstimate": 136, + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "schema": "flowmemory.hardware_operator_signal_envelope.local_alpha.v0", + "signalId": "hw-sig-590bff4da4b1", + "signalType": "bridge_alert", + "sourcePacketId": "bridge_alert:1050", + "sourcePacketType": "bridge_alert", + "status": "unresolved" + }, + "receivedAt": "2026-05-13T17:01:35Z", + "signalId": "hw-sig-590bff4da4b1", + "signalType": "bridge_alert", + "sourcePacketType": "bridge_alert", + "status": "unresolved", + "summary": "Compact bridge observer alert that does not block local chain progress.", + "transport": "meshtastic-control-sim" + }, + { + "envelopeId": "hw-env-3787edd45bfa", + "id": "hw-sig-97f6fef1f868", + "linkedObjectIds": [ + "artifact:hardware:dffc8c3e46ff", + "memory:hardware:9f8165ee1a5a" + ], + "localOnly": true, + "loraEligible": false, + "nodeId": "fr-e1e7878a2aa8", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "rawEnvelope": { + "envelopeId": "hw-env-3787edd45bfa", + "localOnly": true, + "loraEligible": false, + "objectRefs": [ + { + "collection": "artifactCommitments", + "objectId": "artifact:hardware:dffc8c3e46ff" + }, + { + "collection": "memoryCells", + "objectId": "memory:hardware:9f8165ee1a5a" + } + ], + "observedAt": "2026-05-13T17:01:20Z", + "payloadBytesEstimate": 0, + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "schema": "flowmemory.hardware_operator_signal_envelope.local_alpha.v0", + "signalId": "hw-sig-97f6fef1f868", + "signalType": "nfc_memory_cartridge_metadata", + "sourcePacketId": "nfc_memory_cartridge_metadata:42", + "sourcePacketType": "nfc_memory_cartridge_metadata", + "status": "observed" + }, + "receivedAt": "2026-05-13T17:01:20Z", + "signalId": "hw-sig-97f6fef1f868", + "signalType": "nfc_memory_cartridge_metadata", + "sourcePacketType": "nfc_memory_cartridge_metadata", + "status": "observed", + "summary": "NFC metadata pointer projected into artifact and memory references.", + "transport": "local-simulator" + } + ], + "memoryCells": [ + { + "artifactId": "artifact:hardware:dffc8c3e46ff", + "currentRoot": "0x7bb78aa4de0935712809a8e46e13b832ba5a761416731e51aeb02d753bbca5b0", + "latestRoot": "0x7bb78aa4de0935712809a8e46e13b832ba5a761416731e51aeb02d753bbca5b0", + "localOnly": true, + "memoryCellId": "memory:hardware:9f8165ee1a5a", + "receiptId": "receipt:hardware:1e92f586a767", + "resolutionState": "untrusted-metadata-only", + "rootfieldId": "rootfield:hardware:flowrouter-local-alpha", + "sourcePacketType": "nfc_memory_cartridge_metadata", + "status": "observed", + "summary": "NFC cartridge metadata pointer projected into a local memory cell candidate.", + "updatedAt": "2026-05-13T17:01:20Z" + } + ], + "operatorMetadata": [ + { + "displayName": "Local hardware operator fixture", + "hardwareRequiredForPrivateTestnet": false, + "localOnly": true, + "metadataId": "operator-metadata:hardware:874d3583a3b5", + "metadataSource": "device_manifest", + "noSecrets": true, + "nodeId": "fr-e1e7878a2aa8", + "observedAt": "2026-05-13T17:00:00Z", + "operatorId": "operator:local:717ffb1268b0", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "radioPayloadBudgetBytes": 160, + "roles": [ + "hardware_observer", + "fixture_relay" + ], + "rootfieldId": "rootfield:hardware:flowrouter-local-alpha", + "sourcePacketType": "device_manifest", + "transportPreferences": [ + "local-simulator", + "meshtastic-control-sim" + ] + } + ], + "verifierReports": [ + { + "localOnly": true, + "loraEligible": true, + "payloadBytesEstimate": 128, + "reasonCodes": [ + "hardware_digest_relay_only" + ], + "receiptId": "receipt:hardware:1e92f586a767", + "relayReportId": "vr-14d051ae590e", + "reportDigest": "0xb988417bf67c48bab7208ee1fe0531d66fbcb45a1be1b853b70423239c4c8870", + "reportId": "report:hardware:bd367723d169", + "resolutionState": "needs-full-report", + "rootfieldId": "rootfield:hardware:flowrouter-local-alpha", + "sourcePacketType": "verifier_report_digest_relay", + "status": "unresolved", + "subjectDigest": "0x1d5a28ff5af6335dccc9412d01d434aa58bf0870011f781bd614a5266740da3b", + "verifierId": "hardware-relay:fr-e1e7878a2aa8" + } + ], + "workReceipts": [ + { + "artifactCommitment": "0x7bb78aa4de0935712809a8e46e13b832ba5a761416731e51aeb02d753bbca5b0", + "chain": "base-sepolia-sim", + "inputRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "localOnly": true, + "locatorHint": { + "blockHint": 1200012, + "logIndexHint": 3, + "txHashPrefix": "0xea723a0a09aa25b7" + }, + "loraEligible": true, + "outputRoot": "0xfbe8a762b036d20581160b7e58a3adccf61f53c653713c439f85cc7bee5246fa", + "payloadBytesEstimate": 96, + "receiptDigest": "0xfbe8a762b036d20581160b7e58a3adccf61f53c653713c439f85cc7bee5246fa", + "receiptId": "receipt:hardware:1e92f586a767", + "resolutionState": "needs-normal-network-reconciliation", + "rootfieldId": "rootfield:hardware:flowrouter-local-alpha", + "ruleSet": "flowmemory.hardware.operator_signal.local_alpha.v0", + "sourcePacketType": "compact_receipt_relay", + "status": "unresolved", + "workerId": "hardware-node:fr-e1e7878a2aa8" + } + ] + }, + "environment": "local-devnet-fixture", + "generatedAt": "2026-05-13T17:01:40Z", + "hardwareRequiredForPrivateTestnet": false, + "ingest": { + "idFields": { + "alerts": "incidentId", + "artifactCommitments": "artifactId", + "bridgeAlerts": "bridgeAlertId", + "challenges": "challengeId", + "finalityReceipts": "finalityReceiptId", + "hardwareNodes": "nodeId", + "hardwareSignals": "signalId", + "memoryCells": "memoryCellId", + "operatorMetadata": "metadataId", + "verifierReports": "reportId", + "workReceipts": "receiptId" + }, + "localOnly": true, + "mergePolicy": "replace-by-stable-id", + "normalNetworkReconciliationRequired": true, + "stateKeys": [ + "hardwareSignals", + "operatorMetadata", + "hardwareNodes", + "workReceipts", + "verifierReports", + "bridgeAlerts", + "artifactCommitments", + "memoryCells", + "challenges", + "finalityReceipts", + "alerts" + ] + }, + "mode": "read-only-optional-merge", + "optionalSmokeRows": [ + { + "command": "python hardware/simulator/flowrouter_sim.py --smoke", + "hardwareRequired": false, + "label": "Validate optional hardware operator signal fixtures", + "requiredForChainProgress": false + } + ], + "schema": "flowmemory.hardware_control_plane_handoff.local_alpha.v0", + "sourceFixture": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "workbenchRecords": { + "artifacts": [ + { + "facts": [ + { + "label": "cartridge", + "value": "cart-0cfd3cfcb210" + }, + { + "label": "pointer", + "value": "flowmemory://cache/4549dba629bc" + }, + { + "label": "commitment", + "value": "0x7bb78aa4de0935712809a8e46e13b832ba5a761416731e51aeb02d753bbca5b0" + }, + { + "label": "expires", + "value": "2026-06-13T17:00:00Z" + } + ], + "id": "artifact:hardware:dffc8c3e46ff", + "kind": "NFC cartridge artifact reference", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "raw": { + "artifactId": "artifact:hardware:dffc8c3e46ff", + "availabilityStatus": "metadata-only", + "cartridgeId": "cart-0cfd3cfcb210", + "commitment": "0x7bb78aa4de0935712809a8e46e13b832ba5a761416731e51aeb02d753bbca5b0", + "containsSecrets": false, + "expiresAt": "2026-06-13T17:00:00Z", + "label": "field-test-cache-alpha", + "localOnly": true, + "rootfieldId": "rootfield:hardware:flowrouter-local-alpha", + "sourcePacketType": "nfc_memory_cartridge_metadata", + "status": "observed", + "trustLevel": "untrusted-pointer", + "uriHint": "flowmemory://cache/4549dba629bc" + }, + "status": "observed", + "summary": "NFC cartridge metadata pointer; content is untrusted until commitment checks pass.", + "title": "field-test-cache-alpha" + } + ], + "bridgeAlerts": [ + { + "facts": [ + { + "label": "bridge", + "value": "bridge-3489980d98a3" + }, + { + "label": "source", + "value": "flowchain-local-alpha" + }, + { + "label": "target", + "value": "base-sepolia-sim" + }, + { + "label": "digest", + "value": "0xaa5bbd37b946a917cd73aa5394466e71c463cf28e5a1091c474139e23cfe236f" + } + ], + "id": "bridge-alert:hardware:8662b11ba0d4", + "kind": "Hardware bridge alert", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "raw": { + "alertCode": "LOCKBOX_OBSERVER_LAG", + "blockHint": 1200024, + "bridgeAlertId": "bridge-alert:hardware:8662b11ba0d4", + "bridgeId": "bridge-3489980d98a3", + "doesNotBlockLocalChain": true, + "eventDigest": "0xaa5bbd37b946a917cd73aa5394466e71c463cf28e5a1091c474139e23cfe236f", + "localOnly": true, + "loraEligible": true, + "payloadBytesEstimate": 136, + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "recommendedAction": "review-bridge-observer-and-do-not-block-chain", + "resolutionState": "operator-review-required", + "rootfieldId": "rootfield:hardware:flowrouter-local-alpha", + "severity": "warning", + "sourceChain": "flowchain-local-alpha", + "sourcePacketType": "bridge_alert", + "status": "unresolved", + "subjectId": "lockbox:dfce05f88cae", + "summary": "Bridge observer digest lag detected; local chain continues while operator reviews.", + "targetChain": "base-sepolia-sim" + }, + "status": "unresolved", + "summary": "Bridge observer digest lag detected; local chain continues while operator reviews.", + "title": "LOCKBOX_OBSERVER_LAG" + } + ], + "challenges": [ + { + "facts": [ + { + "label": "target", + "value": "receipt:hardware:1e92f586a767" + }, + { + "label": "report", + "value": "report:hardware:bd367723d169" + }, + { + "label": "ttl seconds", + "value": "900" + }, + { + "label": "action", + "value": "check-upstream-and-power" + } + ], + "id": "challenge:hardware:5f54ef32073d", + "kind": "Offline alert challenge candidate", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "raw": { + "challengeId": "challenge:hardware:5f54ef32073d", + "doesNotExecuteRemoteAction": true, + "localOnly": true, + "openedAt": "2026-05-13T17:01:30Z", + "openedBy": "hardware-node:fr-e1e7878a2aa8", + "reason": "offline-alert-candidate", + "receiptId": "receipt:hardware:1e92f586a767", + "recommendedAction": "check-upstream-and-power", + "reportId": "report:hardware:bd367723d169", + "sourcePacketType": "emergency_offline_signal", + "status": "pending", + "summary": "Upstream unavailable; LAN dashboard and local cache still reachable.", + "targetId": "receipt:hardware:1e92f586a767", + "ttlSeconds": 900 + }, + "status": "pending", + "summary": "Upstream unavailable; LAN dashboard and local cache still reachable.", + "title": "UPSTREAM_LOSS" + } + ], + "hardwareSignals": [ + { + "facts": [ + { + "label": "node", + "value": "fr-e1e7878a2aa8" + }, + { + "label": "transport", + "value": "local-simulator" + }, + { + "label": "source packet", + "value": "device_manifest" + }, + { + "label": "linked objects", + "value": "operator-metadata:hardware:874d3583a3b5" + } + ], + "id": "hw-sig-583c993be535", + "kind": "Hardware operator signal", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "raw": { + "envelopeId": "hw-env-adac8a11bc47", + "id": "hw-sig-583c993be535", + "linkedObjectIds": [ + "operator-metadata:hardware:874d3583a3b5" + ], + "localOnly": true, + "loraEligible": false, + "nodeId": "fr-e1e7878a2aa8", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "rawEnvelope": { + "envelopeId": "hw-env-adac8a11bc47", + "localOnly": true, + "loraEligible": false, + "objectRefs": [ + { + "collection": "operatorMetadata", + "objectId": "operator-metadata:hardware:874d3583a3b5" + } + ], + "observedAt": "2026-05-13T17:00:00Z", + "payloadBytesEstimate": 0, + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "schema": "flowmemory.hardware_operator_signal_envelope.local_alpha.v0", + "signalId": "hw-sig-583c993be535", + "signalType": "operator_metadata", + "sourcePacketId": "device_manifest:42", + "sourcePacketType": "device_manifest", + "status": "observed" + }, + "receivedAt": "2026-05-13T17:00:00Z", + "signalId": "hw-sig-583c993be535", + "signalType": "operator_metadata", + "sourcePacketType": "device_manifest", + "status": "observed", + "summary": "Local operator metadata for optional hardware fixture ingestion.", + "transport": "local-simulator" + }, + "status": "observed", + "summary": "Local operator metadata for optional hardware fixture ingestion.", + "title": "operator_metadata" + }, + { + "facts": [ + { + "label": "node", + "value": "fr-e1e7878a2aa8" + }, + { + "label": "transport", + "value": "local-simulator" + }, + { + "label": "source packet", + "value": "heartbeat" + }, + { + "label": "linked objects", + "value": "fr-e1e7878a2aa8" + } + ], + "id": "hw-sig-cd45a2ab8b1d", + "kind": "Hardware operator signal", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "raw": { + "envelopeId": "hw-env-69eab6533abb", + "id": "hw-sig-cd45a2ab8b1d", + "linkedObjectIds": [ + "fr-e1e7878a2aa8" + ], + "localOnly": true, + "loraEligible": false, + "nodeId": "fr-e1e7878a2aa8", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "rawEnvelope": { + "envelopeId": "hw-env-69eab6533abb", + "localOnly": true, + "loraEligible": false, + "objectRefs": [ + { + "collection": "hardwareNodes", + "objectId": "fr-e1e7878a2aa8" + } + ], + "observedAt": "2026-05-13T17:00:10Z", + "payloadBytesEstimate": 0, + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "schema": "flowmemory.hardware_operator_signal_envelope.local_alpha.v0", + "signalId": "hw-sig-cd45a2ab8b1d", + "signalType": "heartbeat", + "sourcePacketId": "heartbeat:1042", + "sourcePacketType": "heartbeat", + "status": "observed" + }, + "receivedAt": "2026-05-13T17:00:10Z", + "signalId": "hw-sig-cd45a2ab8b1d", + "signalType": "heartbeat", + "sourcePacketType": "heartbeat", + "status": "observed", + "summary": "FlowRouter heartbeat and coarse node state.", + "transport": "local-simulator" + }, + "status": "observed", + "summary": "FlowRouter heartbeat and coarse node state.", + "title": "heartbeat" + }, + { + "facts": [ + { + "label": "node", + "value": "fr-e1e7878a2aa8" + }, + { + "label": "transport", + "value": "meshtastic-control-sim" + }, + { + "label": "source packet", + "value": "compact_receipt_relay" + }, + { + "label": "linked objects", + "value": "receipt:hardware:1e92f586a767" + } + ], + "id": "hw-sig-5dd1dbdec22d", + "kind": "Hardware operator signal", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "raw": { + "envelopeId": "hw-env-88c288e8803c", + "id": "hw-sig-5dd1dbdec22d", + "linkedObjectIds": [ + "receipt:hardware:1e92f586a767" + ], + "localOnly": true, + "loraEligible": true, + "nodeId": "fr-e1e7878a2aa8", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "rawEnvelope": { + "envelopeId": "hw-env-88c288e8803c", + "localOnly": true, + "loraEligible": true, + "objectRefs": [ + { + "collection": "workReceipts", + "objectId": "receipt:hardware:1e92f586a767" + } + ], + "observedAt": "2026-05-13T17:00:40Z", + "payloadBytesEstimate": 96, + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "schema": "flowmemory.hardware_operator_signal_envelope.local_alpha.v0", + "signalId": "hw-sig-5dd1dbdec22d", + "signalType": "receipt_relay", + "sourcePacketId": "compact_receipt_relay:1045", + "sourcePacketType": "compact_receipt_relay", + "status": "unresolved" + }, + "receivedAt": "2026-05-13T17:00:40Z", + "signalId": "hw-sig-5dd1dbdec22d", + "signalType": "receipt_relay", + "sourcePacketType": "compact_receipt_relay", + "status": "unresolved", + "summary": "Compact WorkReceipt digest relay awaiting normal reconciliation.", + "transport": "meshtastic-control-sim" + }, + "status": "unresolved", + "summary": "Compact WorkReceipt digest relay awaiting normal reconciliation.", + "title": "receipt_relay" + }, + { + "facts": [ + { + "label": "node", + "value": "fr-e1e7878a2aa8" + }, + { + "label": "transport", + "value": "meshtastic-control-sim" + }, + { + "label": "source packet", + "value": "verifier_report_digest_relay" + }, + { + "label": "linked objects", + "value": "report:hardware:bd367723d169" + } + ], + "id": "hw-sig-d7087f4f3257", + "kind": "Hardware operator signal", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "raw": { + "envelopeId": "hw-env-3c18d09ea961", + "id": "hw-sig-d7087f4f3257", + "linkedObjectIds": [ + "report:hardware:bd367723d169" + ], + "localOnly": true, + "loraEligible": true, + "nodeId": "fr-e1e7878a2aa8", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "rawEnvelope": { + "envelopeId": "hw-env-3c18d09ea961", + "localOnly": true, + "loraEligible": true, + "objectRefs": [ + { + "collection": "verifierReports", + "objectId": "report:hardware:bd367723d169" + } + ], + "observedAt": "2026-05-13T17:00:30Z", + "payloadBytesEstimate": 128, + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "schema": "flowmemory.hardware_operator_signal_envelope.local_alpha.v0", + "signalId": "hw-sig-d7087f4f3257", + "signalType": "verifier_digest_relay", + "sourcePacketId": "verifier_report_digest_relay:1044", + "sourcePacketType": "verifier_report_digest_relay", + "status": "unresolved" + }, + "receivedAt": "2026-05-13T17:00:30Z", + "signalId": "hw-sig-d7087f4f3257", + "signalType": "verifier_digest_relay", + "sourcePacketType": "verifier_report_digest_relay", + "status": "unresolved", + "summary": "Compact VerifierReport digest relay awaiting the full report.", + "transport": "meshtastic-control-sim" + }, + "status": "unresolved", + "summary": "Compact VerifierReport digest relay awaiting the full report.", + "title": "verifier_digest_relay" + }, + { + "facts": [ + { + "label": "node", + "value": "fr-e1e7878a2aa8" + }, + { + "label": "transport", + "value": "local-simulator" + }, + { + "label": "source packet", + "value": "emergency_offline_signal" + }, + { + "label": "linked objects", + "value": "hw-alert-f187cbc304cb, challenge:hardware:5f54ef32073d" + } + ], + "id": "hw-sig-2474b9971f93", + "kind": "Hardware operator signal", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "raw": { + "envelopeId": "hw-env-2d165bbf5812", + "id": "hw-sig-2474b9971f93", + "linkedObjectIds": [ + "hw-alert-f187cbc304cb", + "challenge:hardware:5f54ef32073d" + ], + "localOnly": true, + "loraEligible": false, + "nodeId": "fr-e1e7878a2aa8", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "rawEnvelope": { + "envelopeId": "hw-env-2d165bbf5812", + "localOnly": true, + "loraEligible": false, + "objectRefs": [ + { + "collection": "alerts", + "objectId": "hw-alert-f187cbc304cb" + }, + { + "collection": "challenges", + "objectId": "challenge:hardware:5f54ef32073d" + } + ], + "observedAt": "2026-05-13T17:01:30Z", + "payloadBytesEstimate": 0, + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "schema": "flowmemory.hardware_operator_signal_envelope.local_alpha.v0", + "signalId": "hw-sig-2474b9971f93", + "signalType": "offline_alert_challenge_input", + "sourcePacketId": "emergency_offline_signal:1049", + "sourcePacketType": "emergency_offline_signal", + "status": "pending" + }, + "receivedAt": "2026-05-13T17:01:30Z", + "signalId": "hw-sig-2474b9971f93", + "signalType": "offline_alert_challenge_input", + "sourcePacketType": "emergency_offline_signal", + "status": "pending", + "summary": "Offline alert that can seed a local challenge candidate.", + "transport": "local-simulator" + }, + "status": "pending", + "summary": "Offline alert that can seed a local challenge candidate.", + "title": "offline_alert_challenge_input" + }, + { + "facts": [ + { + "label": "node", + "value": "fr-e1e7878a2aa8" + }, + { + "label": "transport", + "value": "meshtastic-control-sim" + }, + { + "label": "source packet", + "value": "bridge_alert" + }, + { + "label": "linked objects", + "value": "bridge-alert:hardware:8662b11ba0d4, hw-alert-008de71ad207" + } + ], + "id": "hw-sig-590bff4da4b1", + "kind": "Hardware operator signal", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "raw": { + "envelopeId": "hw-env-6b84ec9e2c77", + "id": "hw-sig-590bff4da4b1", + "linkedObjectIds": [ + "bridge-alert:hardware:8662b11ba0d4", + "hw-alert-008de71ad207" + ], + "localOnly": true, + "loraEligible": true, + "nodeId": "fr-e1e7878a2aa8", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "rawEnvelope": { + "envelopeId": "hw-env-6b84ec9e2c77", + "localOnly": true, + "loraEligible": true, + "objectRefs": [ + { + "collection": "bridgeAlerts", + "objectId": "bridge-alert:hardware:8662b11ba0d4" + }, + { + "collection": "alerts", + "objectId": "hw-alert-008de71ad207" + } + ], + "observedAt": "2026-05-13T17:01:35Z", + "payloadBytesEstimate": 136, + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "schema": "flowmemory.hardware_operator_signal_envelope.local_alpha.v0", + "signalId": "hw-sig-590bff4da4b1", + "signalType": "bridge_alert", + "sourcePacketId": "bridge_alert:1050", + "sourcePacketType": "bridge_alert", + "status": "unresolved" + }, + "receivedAt": "2026-05-13T17:01:35Z", + "signalId": "hw-sig-590bff4da4b1", + "signalType": "bridge_alert", + "sourcePacketType": "bridge_alert", + "status": "unresolved", + "summary": "Compact bridge observer alert that does not block local chain progress.", + "transport": "meshtastic-control-sim" + }, + "status": "unresolved", + "summary": "Compact bridge observer alert that does not block local chain progress.", + "title": "bridge_alert" + }, + { + "facts": [ + { + "label": "node", + "value": "fr-e1e7878a2aa8" + }, + { + "label": "transport", + "value": "local-simulator" + }, + { + "label": "source packet", + "value": "nfc_memory_cartridge_metadata" + }, + { + "label": "linked objects", + "value": "artifact:hardware:dffc8c3e46ff, memory:hardware:9f8165ee1a5a" + } + ], + "id": "hw-sig-97f6fef1f868", + "kind": "Hardware operator signal", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "raw": { + "envelopeId": "hw-env-3787edd45bfa", + "id": "hw-sig-97f6fef1f868", + "linkedObjectIds": [ + "artifact:hardware:dffc8c3e46ff", + "memory:hardware:9f8165ee1a5a" + ], + "localOnly": true, + "loraEligible": false, + "nodeId": "fr-e1e7878a2aa8", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "rawEnvelope": { + "envelopeId": "hw-env-3787edd45bfa", + "localOnly": true, + "loraEligible": false, + "objectRefs": [ + { + "collection": "artifactCommitments", + "objectId": "artifact:hardware:dffc8c3e46ff" + }, + { + "collection": "memoryCells", + "objectId": "memory:hardware:9f8165ee1a5a" + } + ], + "observedAt": "2026-05-13T17:01:20Z", + "payloadBytesEstimate": 0, + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "schema": "flowmemory.hardware_operator_signal_envelope.local_alpha.v0", + "signalId": "hw-sig-97f6fef1f868", + "signalType": "nfc_memory_cartridge_metadata", + "sourcePacketId": "nfc_memory_cartridge_metadata:42", + "sourcePacketType": "nfc_memory_cartridge_metadata", + "status": "observed" + }, + "receivedAt": "2026-05-13T17:01:20Z", + "signalId": "hw-sig-97f6fef1f868", + "signalType": "nfc_memory_cartridge_metadata", + "sourcePacketType": "nfc_memory_cartridge_metadata", + "status": "observed", + "summary": "NFC metadata pointer projected into artifact and memory references.", + "transport": "local-simulator" + }, + "status": "observed", + "summary": "NFC metadata pointer projected into artifact and memory references.", + "title": "nfc_memory_cartridge_metadata" + } + ], + "memoryCells": [ + { + "facts": [ + { + "label": "rootfield", + "value": "rootfield:hardware:flowrouter-local-alpha" + }, + { + "label": "latest root", + "value": "0x7bb78aa4de0935712809a8e46e13b832ba5a761416731e51aeb02d753bbca5b0" + }, + { + "label": "receipt", + "value": "receipt:hardware:1e92f586a767" + }, + { + "label": "artifact", + "value": "artifact:hardware:dffc8c3e46ff" + } + ], + "id": "memory:hardware:9f8165ee1a5a", + "kind": "Hardware memory cell candidate", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "raw": { + "artifactId": "artifact:hardware:dffc8c3e46ff", + "currentRoot": "0x7bb78aa4de0935712809a8e46e13b832ba5a761416731e51aeb02d753bbca5b0", + "latestRoot": "0x7bb78aa4de0935712809a8e46e13b832ba5a761416731e51aeb02d753bbca5b0", + "localOnly": true, + "memoryCellId": "memory:hardware:9f8165ee1a5a", + "receiptId": "receipt:hardware:1e92f586a767", + "resolutionState": "untrusted-metadata-only", + "rootfieldId": "rootfield:hardware:flowrouter-local-alpha", + "sourcePacketType": "nfc_memory_cartridge_metadata", + "status": "observed", + "summary": "NFC cartridge metadata pointer projected into a local memory cell candidate.", + "updatedAt": "2026-05-13T17:01:20Z" + }, + "status": "observed", + "summary": "Projected from NFC cartridge metadata for local operator inspection.", + "title": "memory:hardware:9f8165ee1a5a" + } + ], + "operatorMetadata": [ + { + "facts": [ + { + "label": "operator", + "value": "operator:local:717ffb1268b0" + }, + { + "label": "node", + "value": "fr-e1e7878a2aa8" + }, + { + "label": "hardware required", + "value": "false" + }, + { + "label": "payload budget", + "value": "160" + } + ], + "id": "operator-metadata:hardware:874d3583a3b5", + "kind": "Hardware operator metadata", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "raw": { + "displayName": "Local hardware operator fixture", + "hardwareRequiredForPrivateTestnet": false, + "localOnly": true, + "metadataId": "operator-metadata:hardware:874d3583a3b5", + "metadataSource": "device_manifest", + "noSecrets": true, + "nodeId": "fr-e1e7878a2aa8", + "observedAt": "2026-05-13T17:00:00Z", + "operatorId": "operator:local:717ffb1268b0", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "radioPayloadBudgetBytes": 160, + "roles": [ + "hardware_observer", + "fixture_relay" + ], + "rootfieldId": "rootfield:hardware:flowrouter-local-alpha", + "sourcePacketType": "device_manifest", + "transportPreferences": [ + "local-simulator", + "meshtastic-control-sim" + ] + }, + "status": "observed", + "summary": "Local-only metadata for the optional hardware signal fixture issuer.", + "title": "Local hardware operator fixture" + } + ], + "provenance": [ + { + "facts": [ + { + "label": "packet fixture", + "value": "hardware/fixtures/flowrouter_sample_seed42.json" + }, + { + "label": "schema", + "value": "flowmemory.hardware_operator_signals.local_alpha.v0" + }, + { + "label": "seed", + "value": "42" + }, + { + "label": "hardware required", + "value": "false" + } + ], + "id": "hardware-operator-signal-fixture", + "kind": "Hardware operator signal fixture", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "raw": { + "sourcePaths": { + "handoffFixture": "fixtures/hardware/flowrouter_control_plane_handoff_seed42.json", + "operatorFixture": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "packetFixture": "hardware/fixtures/flowrouter_sample_seed42.json" + } + }, + "status": "verified", + "summary": "Deterministic optional hardware signal projection for control-plane/workbench import.", + "title": "fixtures/hardware/flowrouter_local_alpha_seed42.json" + } + ], + "receipts": [ + { + "facts": [ + { + "label": "rootfield", + "value": "rootfield:hardware:flowrouter-local-alpha" + }, + { + "label": "receipt digest", + "value": "0xfbe8a762b036d20581160b7e58a3adccf61f53c653713c439f85cc7bee5246fa" + }, + { + "label": "block hint", + "value": "1200012" + }, + { + "label": "tx prefix", + "value": "0xea723a0a09aa25b7" + } + ], + "id": "receipt:hardware:1e92f586a767", + "kind": "Hardware WorkReceipt relay", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "raw": { + "artifactCommitment": "0x7bb78aa4de0935712809a8e46e13b832ba5a761416731e51aeb02d753bbca5b0", + "chain": "base-sepolia-sim", + "inputRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "localOnly": true, + "locatorHint": { + "blockHint": 1200012, + "logIndexHint": 3, + "txHashPrefix": "0xea723a0a09aa25b7" + }, + "loraEligible": true, + "outputRoot": "0xfbe8a762b036d20581160b7e58a3adccf61f53c653713c439f85cc7bee5246fa", + "payloadBytesEstimate": 96, + "receiptDigest": "0xfbe8a762b036d20581160b7e58a3adccf61f53c653713c439f85cc7bee5246fa", + "receiptId": "receipt:hardware:1e92f586a767", + "resolutionState": "needs-normal-network-reconciliation", + "rootfieldId": "rootfield:hardware:flowrouter-local-alpha", + "ruleSet": "flowmemory.hardware.operator_signal.local_alpha.v0", + "sourcePacketType": "compact_receipt_relay", + "status": "unresolved", + "workerId": "hardware-node:fr-e1e7878a2aa8" + }, + "status": "unresolved", + "summary": "Compact hardware receipt relay awaiting normal network reconciliation.", + "title": "receipt:hardware:1e92f586a767" + } + ], + "verifierReports": [ + { + "facts": [ + { + "label": "relay report id", + "value": "vr-14d051ae590e" + }, + { + "label": "report digest", + "value": "0xb988417bf67c48bab7208ee1fe0531d66fbcb45a1be1b853b70423239c4c8870" + }, + { + "label": "subject digest", + "value": "0x1d5a28ff5af6335dccc9412d01d434aa58bf0870011f781bd614a5266740da3b" + }, + { + "label": "result", + "value": "unresolved" + } + ], + "id": "report:hardware:bd367723d169", + "kind": "Hardware VerifierReport relay", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "raw": { + "localOnly": true, + "loraEligible": true, + "payloadBytesEstimate": 128, + "reasonCodes": [ + "hardware_digest_relay_only" + ], + "receiptId": "receipt:hardware:1e92f586a767", + "relayReportId": "vr-14d051ae590e", + "reportDigest": "0xb988417bf67c48bab7208ee1fe0531d66fbcb45a1be1b853b70423239c4c8870", + "reportId": "report:hardware:bd367723d169", + "resolutionState": "needs-full-report", + "rootfieldId": "rootfield:hardware:flowrouter-local-alpha", + "sourcePacketType": "verifier_report_digest_relay", + "status": "unresolved", + "subjectDigest": "0x1d5a28ff5af6335dccc9412d01d434aa58bf0870011f781bd614a5266740da3b", + "verifierId": "hardware-relay:fr-e1e7878a2aa8" + }, + "status": "unresolved", + "summary": "Compact verifier report digest relay; full report is still required.", + "title": "report:hardware:bd367723d169" + } + ] + } +} diff --git a/fixtures/hardware/flowrouter_local_alpha_seed42.json b/fixtures/hardware/flowrouter_local_alpha_seed42.json index 6ac960cd..9c271de0 100644 --- a/fixtures/hardware/flowrouter_local_alpha_seed42.json +++ b/fixtures/hardware/flowrouter_local_alpha_seed42.json @@ -24,6 +24,29 @@ "status": "unresolved", "summary": "Upstream unavailable; LAN dashboard and local cache still reachable.", "title": "UPSTREAM_LOSS" + }, + { + "id": "hw-alert-008de71ad207", + "incidentId": "hw-alert-008de71ad207", + "linkedObjectIds": [ + "bridge-alert:hardware:8662b11ba0d4" + ], + "localOnly": true, + "openedAt": "2026-05-13T17:01:35Z", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "recommendedAction": "review-bridge-observer-and-do-not-block-chain", + "severity": "warning", + "sourcePacketType": "bridge_alert", + "status": "unresolved", + "summary": "Bridge observer digest lag detected; local chain continues while operator reviews.", + "title": "LOCKBOX_OBSERVER_LAG" } ], "artifactCommitments": [ @@ -49,12 +72,44 @@ "Hardware-originated references are hints until reconciled by normal indexer, receipt, and verifier paths.", "LoRa and Meshtastic packets carry compact control signals, not artifacts, model data, media, or raw memory.", "NFC cartridge metadata is an untrusted pointer until checked against expected commitments.", - "Emergency offline signals are operator alerts or challenge inputs only; they do not execute remote actions." + "Emergency offline signals are operator alerts or challenge inputs only; they do not execute remote actions.", + "Bridge alerts are operator review hints and must not block local chain progress." ], "hardwareRequiredForPrivateTestnet": false, "localOnly": true, "normalNetworkReconciliationRequired": true }, + "bridgeAlerts": [ + { + "alertCode": "LOCKBOX_OBSERVER_LAG", + "blockHint": 1200024, + "bridgeAlertId": "bridge-alert:hardware:8662b11ba0d4", + "bridgeId": "bridge-3489980d98a3", + "doesNotBlockLocalChain": true, + "eventDigest": "0xaa5bbd37b946a917cd73aa5394466e71c463cf28e5a1091c474139e23cfe236f", + "localOnly": true, + "loraEligible": true, + "payloadBytesEstimate": 136, + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "recommendedAction": "review-bridge-observer-and-do-not-block-chain", + "resolutionState": "operator-review-required", + "rootfieldId": "rootfield:hardware:flowrouter-local-alpha", + "severity": "warning", + "sourceChain": "flowchain-local-alpha", + "sourcePacketType": "bridge_alert", + "status": "unresolved", + "subjectId": "lockbox:dfce05f88cae", + "summary": "Bridge observer digest lag detected; local chain continues while operator reviews.", + "targetChain": "base-sepolia-sim" + } + ], "chainId": "flowmemory-local-alpha", "challenges": [ { @@ -77,19 +132,29 @@ "compatibility": { "controlPlaneStateKeys": [ "hardwareSignals", + "operatorMetadata", "hardwareNodes", "workReceipts", "verifierReports", + "bridgeAlerts", "artifactCommitments", "memoryCells", "challenges", "finalityReceipts", "alerts" ], + "flowchainFullSmokeOptionalRow": { + "command": "python hardware/simulator/flowrouter_sim.py --smoke", + "hardwareRequired": false, + "label": "Validate optional hardware operator signal fixtures", + "requiredForChainProgress": false + }, "jsonRpcBoundary": "Read-only fixture data; no submit, wallet, live indexing, or production settlement method is implied.", "workbenchSectionKeys": [ + "operatorMetadata", "receipts", "verifierReports", + "bridgeAlerts", "artifacts", "memoryCells", "challenges", @@ -142,6 +207,58 @@ } ], "hardwareSignals": [ + { + "envelopeId": "hw-env-adac8a11bc47", + "id": "hw-sig-583c993be535", + "linkedObjectIds": [ + "operator-metadata:hardware:874d3583a3b5" + ], + "localOnly": true, + "loraEligible": false, + "nodeId": "fr-e1e7878a2aa8", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "rawEnvelope": { + "envelopeId": "hw-env-adac8a11bc47", + "localOnly": true, + "loraEligible": false, + "objectRefs": [ + { + "collection": "operatorMetadata", + "objectId": "operator-metadata:hardware:874d3583a3b5" + } + ], + "observedAt": "2026-05-13T17:00:00Z", + "payloadBytesEstimate": 0, + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "schema": "flowmemory.hardware_operator_signal_envelope.local_alpha.v0", + "signalId": "hw-sig-583c993be535", + "signalType": "operator_metadata", + "sourcePacketId": "device_manifest:42", + "sourcePacketType": "device_manifest", + "status": "observed" + }, + "receivedAt": "2026-05-13T17:00:00Z", + "signalId": "hw-sig-583c993be535", + "signalType": "operator_metadata", + "sourcePacketType": "device_manifest", + "status": "observed", + "summary": "Local operator metadata for optional hardware fixture ingestion.", + "transport": "local-simulator" + }, { "envelopeId": "hw-env-69eab6533abb", "id": "hw-sig-cd45a2ab8b1d", @@ -355,6 +472,63 @@ "summary": "Offline alert that can seed a local challenge candidate.", "transport": "local-simulator" }, + { + "envelopeId": "hw-env-6b84ec9e2c77", + "id": "hw-sig-590bff4da4b1", + "linkedObjectIds": [ + "bridge-alert:hardware:8662b11ba0d4", + "hw-alert-008de71ad207" + ], + "localOnly": true, + "loraEligible": true, + "nodeId": "fr-e1e7878a2aa8", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "rawEnvelope": { + "envelopeId": "hw-env-6b84ec9e2c77", + "localOnly": true, + "loraEligible": true, + "objectRefs": [ + { + "collection": "bridgeAlerts", + "objectId": "bridge-alert:hardware:8662b11ba0d4" + }, + { + "collection": "alerts", + "objectId": "hw-alert-008de71ad207" + } + ], + "observedAt": "2026-05-13T17:01:35Z", + "payloadBytesEstimate": 136, + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "schema": "flowmemory.hardware_operator_signal_envelope.local_alpha.v0", + "signalId": "hw-sig-590bff4da4b1", + "signalType": "bridge_alert", + "sourcePacketId": "bridge_alert:1050", + "sourcePacketType": "bridge_alert", + "status": "unresolved" + }, + "receivedAt": "2026-05-13T17:01:35Z", + "signalId": "hw-sig-590bff4da4b1", + "signalType": "bridge_alert", + "sourcePacketType": "bridge_alert", + "status": "unresolved", + "summary": "Compact bridge observer alert that does not block local chain progress.", + "transport": "meshtastic-control-sim" + }, { "envelopeId": "hw-env-3787edd45bfa", "id": "hw-sig-97f6fef1f868", @@ -429,7 +603,47 @@ "updatedAt": "2026-05-13T17:01:20Z" } ], + "operatorMetadata": [ + { + "displayName": "Local hardware operator fixture", + "hardwareRequiredForPrivateTestnet": false, + "localOnly": true, + "metadataId": "operator-metadata:hardware:874d3583a3b5", + "metadataSource": "device_manifest", + "noSecrets": true, + "nodeId": "fr-e1e7878a2aa8", + "observedAt": "2026-05-13T17:00:00Z", + "operatorId": "operator:local:717ffb1268b0", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "radioPayloadBudgetBytes": 160, + "roles": [ + "hardware_observer", + "fixture_relay" + ], + "rootfieldId": "rootfield:hardware:flowrouter-local-alpha", + "sourcePacketType": "device_manifest", + "transportPreferences": [ + "local-simulator", + "meshtastic-control-sim" + ] + } + ], "packetMappings": [ + { + "flowchainSignal": "operator_metadata", + "localAlphaRole": "names the local optional hardware operator fixture issuer", + "objectCollection": "operatorMetadata", + "objectRef": "operator-metadata:hardware:874d3583a3b5", + "sourcePacketType": "device_manifest", + "trustBoundary": "local metadata only; no wallet, secret, or production operator claim" + }, { "flowchainSignal": "hardware_node_status", "localAlphaRole": "shows FlowRouter reachability and coarse device state", @@ -462,6 +676,14 @@ "sourcePacketType": "emergency_offline_signal", "trustBoundary": "local operator attention only; no public emergency-service claim" }, + { + "flowchainSignal": "bridge_observer_alert", + "localAlphaRole": "surfaces bridge-observer lag without blocking the local chain", + "objectCollection": "bridgeAlerts", + "objectRef": "bridge-alert:hardware:8662b11ba0d4", + "sourcePacketType": "bridge_alert", + "trustBoundary": "digest alert only; no production bridge readiness or settlement claim" + }, { "flowchainSignal": "artifact_memory_reference", "localAlphaRole": "connects cartridge metadata to an artifact or memory reference", @@ -473,6 +695,33 @@ ], "schema": "flowmemory.hardware_operator_signals.local_alpha.v0", "signalEnvelopes": [ + { + "envelopeId": "hw-env-adac8a11bc47", + "localOnly": true, + "loraEligible": false, + "objectRefs": [ + { + "collection": "operatorMetadata", + "objectId": "operator-metadata:hardware:874d3583a3b5" + } + ], + "observedAt": "2026-05-13T17:00:00Z", + "payloadBytesEstimate": 0, + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "schema": "flowmemory.hardware_operator_signal_envelope.local_alpha.v0", + "signalId": "hw-sig-583c993be535", + "signalType": "operator_metadata", + "sourcePacketId": "device_manifest:42", + "sourcePacketType": "device_manifest", + "status": "observed" + }, { "envelopeId": "hw-env-69eab6533abb", "localOnly": true, @@ -585,6 +834,37 @@ "sourcePacketType": "emergency_offline_signal", "status": "pending" }, + { + "envelopeId": "hw-env-6b84ec9e2c77", + "localOnly": true, + "loraEligible": true, + "objectRefs": [ + { + "collection": "bridgeAlerts", + "objectId": "bridge-alert:hardware:8662b11ba0d4" + }, + { + "collection": "alerts", + "objectId": "hw-alert-008de71ad207" + } + ], + "observedAt": "2026-05-13T17:01:35Z", + "payloadBytesEstimate": 136, + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "schema": "flowmemory.hardware_operator_signal_envelope.local_alpha.v0", + "signalId": "hw-sig-590bff4da4b1", + "signalType": "bridge_alert", + "sourcePacketId": "bridge_alert:1050", + "sourcePacketType": "bridge_alert", + "status": "unresolved" + }, { "envelopeId": "hw-env-3787edd45bfa", "localOnly": true, @@ -619,7 +899,11 @@ ], "source": "fixture", "sourcePaths": { + "handoffFixture": "fixtures/hardware/flowrouter_control_plane_handoff_seed42.json", + "handoffSchema": "schemas/flowmemory/hardware-control-plane-handoff.schema.json", "mappingDoc": "hardware/flowrouter/FLOWCHAIN_LOCAL_ALPHA_SIGNALS.md", + "negativeReport": "fixtures/hardware/flowrouter_negative_validation_seed42.json", + "negativeReportSchema": "hardware/simulator/schemas/negative_validation_report.schema.json", "operatorFixture": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "operatorSchema": "hardware/simulator/schemas/flowchain_operator_signals.schema.json", "packetFixture": "hardware/fixtures/flowrouter_sample_seed42.json" @@ -719,6 +1003,70 @@ "title": "field-test-cache-alpha" } ], + "bridgeAlerts": [ + { + "facts": [ + { + "label": "bridge", + "value": "bridge-3489980d98a3" + }, + { + "label": "source", + "value": "flowchain-local-alpha" + }, + { + "label": "target", + "value": "base-sepolia-sim" + }, + { + "label": "digest", + "value": "0xaa5bbd37b946a917cd73aa5394466e71c463cf28e5a1091c474139e23cfe236f" + } + ], + "id": "bridge-alert:hardware:8662b11ba0d4", + "kind": "Hardware bridge alert", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "raw": { + "alertCode": "LOCKBOX_OBSERVER_LAG", + "blockHint": 1200024, + "bridgeAlertId": "bridge-alert:hardware:8662b11ba0d4", + "bridgeId": "bridge-3489980d98a3", + "doesNotBlockLocalChain": true, + "eventDigest": "0xaa5bbd37b946a917cd73aa5394466e71c463cf28e5a1091c474139e23cfe236f", + "localOnly": true, + "loraEligible": true, + "payloadBytesEstimate": 136, + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "recommendedAction": "review-bridge-observer-and-do-not-block-chain", + "resolutionState": "operator-review-required", + "rootfieldId": "rootfield:hardware:flowrouter-local-alpha", + "severity": "warning", + "sourceChain": "flowchain-local-alpha", + "sourcePacketType": "bridge_alert", + "status": "unresolved", + "subjectId": "lockbox:dfce05f88cae", + "summary": "Bridge observer digest lag detected; local chain continues while operator reviews.", + "targetChain": "base-sepolia-sim" + }, + "status": "unresolved", + "summary": "Bridge observer digest lag detected; local chain continues while operator reviews.", + "title": "LOCKBOX_OBSERVER_LAG" + } + ], "challenges": [ { "facts": [ @@ -771,6 +1119,91 @@ } ], "hardwareSignals": [ + { + "facts": [ + { + "label": "node", + "value": "fr-e1e7878a2aa8" + }, + { + "label": "transport", + "value": "local-simulator" + }, + { + "label": "source packet", + "value": "device_manifest" + }, + { + "label": "linked objects", + "value": "operator-metadata:hardware:874d3583a3b5" + } + ], + "id": "hw-sig-583c993be535", + "kind": "Hardware operator signal", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "raw": { + "envelopeId": "hw-env-adac8a11bc47", + "id": "hw-sig-583c993be535", + "linkedObjectIds": [ + "operator-metadata:hardware:874d3583a3b5" + ], + "localOnly": true, + "loraEligible": false, + "nodeId": "fr-e1e7878a2aa8", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "rawEnvelope": { + "envelopeId": "hw-env-adac8a11bc47", + "localOnly": true, + "loraEligible": false, + "objectRefs": [ + { + "collection": "operatorMetadata", + "objectId": "operator-metadata:hardware:874d3583a3b5" + } + ], + "observedAt": "2026-05-13T17:00:00Z", + "payloadBytesEstimate": 0, + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "schema": "flowmemory.hardware_operator_signal_envelope.local_alpha.v0", + "signalId": "hw-sig-583c993be535", + "signalType": "operator_metadata", + "sourcePacketId": "device_manifest:42", + "sourcePacketType": "device_manifest", + "status": "observed" + }, + "receivedAt": "2026-05-13T17:00:00Z", + "signalId": "hw-sig-583c993be535", + "signalType": "operator_metadata", + "sourcePacketType": "device_manifest", + "status": "observed", + "summary": "Local operator metadata for optional hardware fixture ingestion.", + "transport": "local-simulator" + }, + "status": "observed", + "summary": "Local operator metadata for optional hardware fixture ingestion.", + "title": "operator_metadata" + }, { "facts": [ { @@ -1116,6 +1549,96 @@ "summary": "Offline alert that can seed a local challenge candidate.", "title": "offline_alert_challenge_input" }, + { + "facts": [ + { + "label": "node", + "value": "fr-e1e7878a2aa8" + }, + { + "label": "transport", + "value": "meshtastic-control-sim" + }, + { + "label": "source packet", + "value": "bridge_alert" + }, + { + "label": "linked objects", + "value": "bridge-alert:hardware:8662b11ba0d4, hw-alert-008de71ad207" + } + ], + "id": "hw-sig-590bff4da4b1", + "kind": "Hardware operator signal", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "raw": { + "envelopeId": "hw-env-6b84ec9e2c77", + "id": "hw-sig-590bff4da4b1", + "linkedObjectIds": [ + "bridge-alert:hardware:8662b11ba0d4", + "hw-alert-008de71ad207" + ], + "localOnly": true, + "loraEligible": true, + "nodeId": "fr-e1e7878a2aa8", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "rawEnvelope": { + "envelopeId": "hw-env-6b84ec9e2c77", + "localOnly": true, + "loraEligible": true, + "objectRefs": [ + { + "collection": "bridgeAlerts", + "objectId": "bridge-alert:hardware:8662b11ba0d4" + }, + { + "collection": "alerts", + "objectId": "hw-alert-008de71ad207" + } + ], + "observedAt": "2026-05-13T17:01:35Z", + "payloadBytesEstimate": 136, + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "schema": "flowmemory.hardware_operator_signal_envelope.local_alpha.v0", + "signalId": "hw-sig-590bff4da4b1", + "signalType": "bridge_alert", + "sourcePacketId": "bridge_alert:1050", + "sourcePacketType": "bridge_alert", + "status": "unresolved" + }, + "receivedAt": "2026-05-13T17:01:35Z", + "signalId": "hw-sig-590bff4da4b1", + "signalType": "bridge_alert", + "sourcePacketType": "bridge_alert", + "status": "unresolved", + "summary": "Compact bridge observer alert that does not block local chain progress.", + "transport": "meshtastic-control-sim" + }, + "status": "unresolved", + "summary": "Compact bridge observer alert that does not block local chain progress.", + "title": "bridge_alert" + }, { "facts": [ { @@ -1256,6 +1779,71 @@ "title": "memory:hardware:9f8165ee1a5a" } ], + "operatorMetadata": [ + { + "facts": [ + { + "label": "operator", + "value": "operator:local:717ffb1268b0" + }, + { + "label": "node", + "value": "fr-e1e7878a2aa8" + }, + { + "label": "hardware required", + "value": "false" + }, + { + "label": "payload budget", + "value": "160" + } + ], + "id": "operator-metadata:hardware:874d3583a3b5", + "kind": "Hardware operator metadata", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "raw": { + "displayName": "Local hardware operator fixture", + "hardwareRequiredForPrivateTestnet": false, + "localOnly": true, + "metadataId": "operator-metadata:hardware:874d3583a3b5", + "metadataSource": "device_manifest", + "noSecrets": true, + "nodeId": "fr-e1e7878a2aa8", + "observedAt": "2026-05-13T17:00:00Z", + "operatorId": "operator:local:717ffb1268b0", + "provenance": { + "capturedAt": "2026-05-13T17:01:40Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "radioPayloadBudgetBytes": 160, + "roles": [ + "hardware_observer", + "fixture_relay" + ], + "rootfieldId": "rootfield:hardware:flowrouter-local-alpha", + "sourcePacketType": "device_manifest", + "transportPreferences": [ + "local-simulator", + "meshtastic-control-sim" + ] + }, + "status": "observed", + "summary": "Local-only metadata for the optional hardware signal fixture issuer.", + "title": "Local hardware operator fixture" + } + ], "provenance": [ { "facts": [ @@ -1288,6 +1876,7 @@ }, "raw": { "sourcePaths": { + "handoffFixture": "fixtures/hardware/flowrouter_control_plane_handoff_seed42.json", "operatorFixture": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "packetFixture": "hardware/fixtures/flowrouter_sample_seed42.json" } diff --git a/fixtures/hardware/flowrouter_negative_validation_seed42.json b/fixtures/hardware/flowrouter_negative_validation_seed42.json new file mode 100644 index 00000000..aca73df5 --- /dev/null +++ b/fixtures/hardware/flowrouter_negative_validation_seed42.json @@ -0,0 +1,57 @@ +{ + "allCasesRejected": true, + "caseCount": 8, + "cases": [ + { + "actualFailure": "heartbeat: missing required key device_id", + "case": "heartbeat_missing_device_id", + "expectedFailure": "missing required key device_id", + "passed": true + }, + { + "actualFailure": "compact_receipt_relay.payload_bytes_estimate: value above maximum 200", + "case": "receipt_relay_payload_exceeds_control_budget", + "expectedFailure": "value above maximum 200", + "passed": true + }, + { + "actualFailure": "nfc_memory_cartridge_metadata.contains_secrets: value True not in enum [False]", + "case": "nfc_metadata_claims_secret_storage", + "expectedFailure": "not in enum [False]", + "passed": true + }, + { + "actualFailure": "operator_signals: missing required key bridgeAlerts", + "case": "operator_projection_missing_bridge_alerts", + "expectedFailure": "missing required key bridgeAlerts", + "passed": true + }, + { + "actualFailure": "operator_signals.boundary.hardwareRequiredForPrivateTestnet: value True not in enum [False]", + "case": "operator_projection_requires_hardware", + "expectedFailure": "not in enum [False]", + "passed": true + }, + { + "actualFailure": "operator_signals.signalEnvelopes[2].payloadBytesEstimate: value above maximum 200", + "case": "operator_envelope_payload_exceeds_control_budget", + "expectedFailure": "value above maximum 200", + "passed": true + }, + { + "actualFailure": "control_plane_handoff.hardwareRequiredForPrivateTestnet: value True not in enum [False]", + "case": "control_plane_handoff_requires_hardware", + "expectedFailure": "not in enum [False]", + "passed": true + }, + { + "actualFailure": "control_plane_handoff.collections: missing required key hardwareSignals", + "case": "control_plane_handoff_missing_hardware_signals", + "expectedFailure": "missing required key hardwareSignals", + "passed": true + } + ], + "generatedAt": "2026-05-13T17:02:10Z", + "schema": "flowmemory.hardware_negative_validation.local_alpha.v0", + "seed": 42 +} diff --git a/hardware/README.md b/hardware/README.md index eb561f44..ce166b11 100644 --- a/hardware/README.md +++ b/hardware/README.md @@ -29,6 +29,7 @@ FlowRouter V0 is a local FlowMemory gateway POC. It can model or test: - FlowCore light-pipe status. - Enclosure measurement direction. - FlowChain local-alpha operator signals derived from hardware packets, including optional control-plane/workbench fixture collections. +- Control-plane handoff JSON for optional hardware signals, including heartbeat, receipt relay, verifier digest relay, offline alert, bridge alert, NFC metadata, and operator metadata. ## V0 Non-Goals @@ -48,17 +49,19 @@ FlowRouter V0 does not: Generate and validate deterministic simulator output: ```powershell -python hardware/simulator/flowrouter_sim.py --seed 42 --out hardware/fixtures/flowrouter_sample_seed42.json -python hardware/simulator/flowrouter_sim.py --validate-file hardware/fixtures/flowrouter_sample_seed42.json +python hardware/simulator/flowrouter_sim.py --generate-fixtures --seed 42 +python hardware/simulator/flowrouter_sim.py --smoke --seed 42 ``` -Generate and validate the local-alpha FlowChain operator-signal projection: +Validate individual generated files: ```powershell -python hardware/simulator/flowrouter_sim.py --seed 42 --out hardware/fixtures/flowrouter_sample_seed42.json --operator-out fixtures/hardware/flowrouter_local_alpha_seed42.json +python hardware/simulator/flowrouter_sim.py --validate-file hardware/fixtures/flowrouter_sample_seed42.json python hardware/simulator/flowrouter_sim.py --validate-operator-file fixtures/hardware/flowrouter_local_alpha_seed42.json +python hardware/simulator/flowrouter_sim.py --validate-handoff-file fixtures/hardware/flowrouter_control_plane_handoff_seed42.json +python hardware/simulator/flowrouter_sim.py --validate-negative-report-file fixtures/hardware/flowrouter_negative_validation_seed42.json ``` -The operator projection emits `flowmemory.hardware_operator_signals.local_alpha.v0`, with local-only `signalEnvelopes`, a direct `hardwareSignals` view, control-plane-style collections, and `workbenchRecords`. Hardware remains optional for the private/local testnet path. +The operator projection emits `flowmemory.hardware_operator_signals.local_alpha.v0`, with local-only `signalEnvelopes`, a direct `hardwareSignals` view, control-plane-style collections, and `workbenchRecords`. The handoff fixture emits `flowmemory.hardware_control_plane_handoff.local_alpha.v0` and is shaped for read-only optional control-plane ingestion. Hardware remains optional for the private/local testnet path. The simulator uses only the Python standard library. diff --git a/hardware/fixtures/flowrouter_sample_seed42.json b/hardware/fixtures/flowrouter_sample_seed42.json index 0e44f216..a3badedc 100644 --- a/hardware/fixtures/flowrouter_sample_seed42.json +++ b/hardware/fixtures/flowrouter_sample_seed42.json @@ -1,6 +1,26 @@ { "generated_at": "2026-05-13T17:02:00Z", "packets": { + "bridge_alert": { + "alert_code": "LOCKBOX_OBSERVER_LAG", + "block_hint": 1200024, + "bridge_id": "bridge-3489980d98a3", + "device_id": "fr-e1e7878a2aa8", + "digest": "aa5bbd37b946a917cd73aa5394466e71c463cf28e5a1091c474139e23cfe236f", + "emitted_at": "2026-05-13T17:01:35Z", + "lora_eligible": true, + "operator_action": "review-bridge-observer-and-do-not-block-chain", + "packet_type": "bridge_alert", + "payload_bytes_estimate": 136, + "schema_version": "flowrouter.poc.v0", + "sequence": 1050, + "severity": "warning", + "source_chain": "flowchain-local-alpha", + "subject_id": "lockbox:dfce05f88cae", + "summary": "Bridge observer digest lag detected; local chain continues while operator reviews.", + "target_chain": "base-sepolia-sim", + "verification_state": "advisory" + }, "compact_receipt_relay": { "block_hint": 1200012, "chain": "base-sepolia-sim", @@ -53,6 +73,9 @@ "heartbeat", "gateway_discovery", "compact_receipt_relay", + "verifier_report_digest_relay", + "bridge_alert", + "operator_metadata", "local_cache_status", "sidecar_status", "dashboard_feed" diff --git a/hardware/flowrouter/FLOWCHAIN_LOCAL_ALPHA_SIGNALS.md b/hardware/flowrouter/FLOWCHAIN_LOCAL_ALPHA_SIGNALS.md index c894ae3e..070bbd24 100644 --- a/hardware/flowrouter/FLOWCHAIN_LOCAL_ALPHA_SIGNALS.md +++ b/hardware/flowrouter/FLOWCHAIN_LOCAL_ALPHA_SIGNALS.md @@ -20,6 +20,12 @@ The FlowChain/workbench projection is: fixtures/hardware/flowrouter_local_alpha_seed42.json ``` +The read-only control-plane handoff is: + +```text +fixtures/hardware/flowrouter_control_plane_handoff_seed42.json +``` + The projection is generated by `hardware/simulator/flowrouter_sim.py` from the same seed as the raw packet fixture. It is local-only, advisory, and schema-validated by: ```text @@ -32,9 +38,9 @@ The projection uses the local private-testnet fixture style where applicable: - top-level `schema`, `generatedAt`, `chainId`, `environment`, and `source`; - `signalEnvelopes` with stable local signal ids, source packet references, object references, status, provenance, and payload-size hints; -- `hardwareSignals` as direct workbench/control-plane hardware signal rows for the same five envelopes; -- control-plane-friendly collections: `hardwareNodes`, `workReceipts`, `verifierReports`, `artifactCommitments`, `memoryCells`, `challenges`, `finalityReceipts`, and `alerts`; -- `workbenchRecords` grouped by `receipts`, `verifierReports`, `artifacts`, `memoryCells`, `challenges`, `hardwareSignals`, and `provenance`; +- `hardwareSignals` as direct workbench/control-plane hardware signal rows for the same envelopes; +- control-plane-friendly collections: `operatorMetadata`, `hardwareNodes`, `workReceipts`, `verifierReports`, `bridgeAlerts`, `artifactCommitments`, `memoryCells`, `challenges`, `finalityReceipts`, and `alerts`; +- `workbenchRecords` grouped by `operatorMetadata`, `receipts`, `verifierReports`, `bridgeAlerts`, `artifacts`, `memoryCells`, `challenges`, `hardwareSignals`, and `provenance`; - `boundary.hardwareRequiredForPrivateTestnet = false`. This is an optional fixture projection. It is not a devnet transaction source and it does not make hardware required for the private/local chain, indexer, verifier, control-plane, or workbench to run. @@ -43,10 +49,12 @@ This is an optional fixture projection. It is not a devnet transaction source an | FlowRouter packet | Projection collection | Why it is useful | Boundary | | --- | --- | --- | --- | +| `device_manifest` | `operatorMetadata` | Names the local optional hardware fixture issuer and radio/control-plane constraints. | Local metadata only; not a wallet, key, or production operator claim. | | `heartbeat` | `hardwareNodes` | Shows FlowRouter reachability, power, cache, sidecar, and FlowCore state to an operator. | Local advisory status only; not hardware attestation. | | `compact_receipt_relay` | `workReceipts` and `finalityReceipts` | Gives the workbench a compact WorkReceipt candidate using receipt digest plus block, tx-prefix, and log-index hints. | Requires normal network/indexer reconciliation before it can be trusted. | | `verifier_report_digest_relay` | `verifierReports` | Gives the workbench a VerifierReport candidate using report id, report digest, subject digest, and result. | Digest relay is not the full verifier report. | | `emergency_offline_signal` | `alerts` and `challenges` | Creates a local operator alert and optional challenge input when upstream or local conditions degrade. | Candidate input only; it does not execute remote commands or claim public emergency-service reliability. | +| `bridge_alert` | `bridgeAlerts` and `alerts` | Surfaces compact bridge-observer lag or mismatch signals for operator review. | Digest alert only; it never blocks local chain progress or claims production bridge readiness. | | `nfc_memory_cartridge_metadata` | `artifactCommitments` and `memoryCells` | Lets a cartridge label, pointer, digest, and expiration become an artifact/memory reference for operator workflows. | Untrusted metadata pointer only; it is not a secret store or proof. | ## Control-Plane And Workbench Consumption @@ -54,7 +62,10 @@ This is an optional fixture projection. It is not a devnet transaction source an The fixture is intentionally shaped so another agent can add it to the local control-plane without creating a second hardware pipeline: - `compatibility.controlPlaneStateKeys` names the collections a local read API can expose or merge. +- `fixtures/hardware/flowrouter_control_plane_handoff_seed42.json` mirrors those state keys under `collections` with stable id fields and read-only merge policy. - `workReceipts`, `verifierReports`, `artifactCommitments`, `memoryCells`, `challenges`, and `finalityReceipts` use the same object names the private/local workbench already scans for in fixture state. +- `bridgeAlerts` is present as a compact operator alert stream for bridge observer issues; it does not submit, settle, or pause local chain activity. +- `operatorMetadata` is local fixture metadata only and contains no secrets. - `hardwareSignals` is present as a direct collection for workbench hardware-signal sections that do not want to re-project `signalEnvelopes`. - `workbenchRecords` provides ready-to-render records when a UI chooses not to re-project the raw collections. - Every projected object keeps `sourcePacketType`, `localOnly`, and provenance back to `hardware/fixtures/flowrouter_sample_seed42.json`. @@ -65,6 +76,7 @@ The receipt, report, memory cell, challenge, and finality objects are hints. The - Operator-visible status for a FlowRouter-like node. - Compact receipt and verifier-report breadcrumbs during degraded connectivity. +- Compact bridge observer alert breadcrumbs during local review. - A local alert signal that can seed dashboard/operator attention. - A physical metadata path for memory/artifact references through NFC cartridge reads. - A low-bandwidth control-signal fixture that workbench, dashboard, or control-plane code can import. @@ -73,6 +85,7 @@ The receipt, report, memory cell, challenge, and finality objects are hints. The - Device status and packet timing. - Receipt and verifier digest relays until reconciled against normal receipt, indexer, and verifier data. +- Bridge alert digests until reconciled by normal bridge observer and operator workflows. - NFC labels, pointers, and cartridge ids until checked against expected commitments. - Emergency/offline alert state until a local operator or later challenge workflow acts on it. - Any simulated radio, cache, display, FlowCore, and cartridge state. @@ -92,10 +105,16 @@ The receipt, report, memory cell, challenge, and finality objects are hints. The Generate the raw packet fixture and the local-alpha projection: ```powershell -python hardware/simulator/flowrouter_sim.py --seed 42 --out hardware/fixtures/flowrouter_sample_seed42.json --operator-out fixtures/hardware/flowrouter_local_alpha_seed42.json +python hardware/simulator/flowrouter_sim.py --generate-fixtures --seed 42 ``` -This command is the simulator smoke path because the generator validates both outputs before writing them. +The generator validates the raw packet fixture, operator projection, handoff fixture, and negative validation report before writing them. + +Run the stricter simulator smoke path: + +```powershell +python hardware/simulator/flowrouter_sim.py --smoke --seed 42 +``` Validate the raw simulator packet fixture: @@ -109,12 +128,21 @@ Validate the FlowChain local-alpha projection: python hardware/simulator/flowrouter_sim.py --validate-operator-file fixtures/hardware/flowrouter_local_alpha_seed42.json ``` +Validate the FlowChain control-plane handoff and negative validation report: + +```powershell +python hardware/simulator/flowrouter_sim.py --validate-handoff-file fixtures/hardware/flowrouter_control_plane_handoff_seed42.json +python hardware/simulator/flowrouter_sim.py --validate-negative-report-file fixtures/hardware/flowrouter_negative_validation_seed42.json +``` + ## Integration Notes - A dashboard, workbench, or control-plane can read `packetMappings` to understand which packet produced each local-alpha object. - `hardwareSignals` can feed workbench hardware-signal tables directly. +- `operatorMetadata` can seed local fixture issuer rows. - `hardwareNodes` can feed hardware node cards or operator status rows. - `workReceipts` and `verifierReports` are breadcrumbs for later reconciliation; they are not final evidence. +- `bridgeAlerts` can seed local bridge observer alert rows without blocking the local chain. - `alerts` and `challenges` can seed local operator attention without blocking the rest of the local flow. - `artifactCommitments` and `memoryCells` can seed artifact or memory-reference panels while keeping cartridge contents untrusted. - `workbenchRecords` can be rendered directly by a fixture-backed workbench, or ignored if the workbench re-projects the canonical collections itself. diff --git a/hardware/flowrouter/README.md b/hardware/flowrouter/README.md index a86565cf..74b9cf9d 100644 --- a/hardware/flowrouter/README.md +++ b/hardware/flowrouter/README.md @@ -58,6 +58,6 @@ V0 uses certified router/radio hardware and off-the-shelf compute. It does not i - A local operator can tell whether the node has upstream internet, LAN availability, cache health, power/thermal status, and sidecar status. - A second node can receive compact Meshtastic status or digest messages during degraded IP connectivity. - Cached state is clearly marked local-only until verified through normal network, indexer, or chain-derived paths. -- Hardware packets can be projected into local-alpha `hardwareSignals`, `hardwareNodes`, `workReceipts`, `verifierReports`, `alerts`, `challenges`, `artifactCommitments`, and `memoryCells` without blocking the main local chain flow. +- Hardware packets can be projected into local-alpha `hardwareSignals`, `operatorMetadata`, `hardwareNodes`, `workReceipts`, `verifierReports`, `bridgeAlerts`, `alerts`, `challenges`, `artifactCommitments`, and `memoryCells` without blocking the main local chain flow. - The prototype can be measured for thermal, power, serviceability, and enclosure-fit constraints. - The docs make it difficult to overclaim bandwidth, production readiness, trustlessness, or regulatory status. diff --git a/hardware/lora-sidecar/CONTROL_MESSAGE_INVENTORY.md b/hardware/lora-sidecar/CONTROL_MESSAGE_INVENTORY.md index 26ebf095..7ecb7481 100644 --- a/hardware/lora-sidecar/CONTROL_MESSAGE_INVENTORY.md +++ b/hardware/lora-sidecar/CONTROL_MESSAGE_INVENTORY.md @@ -29,6 +29,7 @@ Common fields: | FlowPulse digest | Compact digest | `v,type,node,seq,chain,from,to,digest32,count,flags,auth` | Sends a hash over a FlowPulse range, not events. | Digest cannot be trusted until verified by indexer/receipts. | | Artifact availability digest | Compact digest | `v,type,node,seq,namespace,digest32,count,bytes_class,ttl,auth` | Announces cache hints only; no artifacts. | May leak inventory metadata; needs privacy review. | | Compact receipt reference | Compact receipt reference | `v,type,node,seq,chain,block_hint,tx_hash_prefix/log_hint,receipt_hash,auth` | Carries short pointer and hash, not receipt body. | Prefix collisions and stale hints; full verification requires normal network path. | +| Bridge alert digest | Compact bridge alert | `v,type,node,seq,bridge,src,dst,code,digest32,block_hint,auth` | Sends a bridge-observer alert code and digest, not bridge payloads or settlement state. | Advisory only; must not block local chain progress or imply production bridge readiness. | | Field diagnostic | Field diagnostic | `v,type,node,seq,temp,power_class,rssi,snr,loss,flags,auth` | Numeric summary only. | Sensor accuracy, spoofing, and replay risk. | | Emergency/local signal | Emergency signal | `v,type,node,seq,code,priority,ttl,location_hint?,auth` | Short code and optional coarse hint. | Abuse risk; no public emergency-service claim. | | Operator command warning | Operator command | `v,type,node,seq,command_id,intent,ttl,auth` | Intent marker only; no scripts or payloads. | Must not execute privileged action in v0; needs strong auth, authorization, replay protection, and audit before any future action. | @@ -55,6 +56,10 @@ Use to say "this cache may have content matching this digest or namespace." It m Use as a breadcrumb for later reconciliation. It can include a chain id, block hint, short transaction/log hint, and receipt hash. It is not final proof by itself. +### Bridge Alert Digest + +Use as a compact local operator warning when a bridge observer reports lag, mismatch, or stale relay state. It is a review hint only; the local private chain keeps running and normal bridge observer workflows must reconcile the digest. + ### Field Diagnostic Use for temperature, power class, RSSI/SNR, message loss, and degraded-state flags. It helps correlate field notes after the test. diff --git a/hardware/simulator/README.md b/hardware/simulator/README.md index ed1be3db..740dbaca 100644 --- a/hardware/simulator/README.md +++ b/hardware/simulator/README.md @@ -10,13 +10,33 @@ The simulator emits deterministic FlowRouter V0 proof-of-concept packets for das python hardware/simulator/flowrouter_sim.py --seed 42 --out hardware/fixtures/flowrouter_sample_seed42.json ``` -## Generate Local-Alpha Operator Signals +## Generate Canonical Fixtures ```powershell -python hardware/simulator/flowrouter_sim.py --seed 42 --out hardware/fixtures/flowrouter_sample_seed42.json --operator-out fixtures/hardware/flowrouter_local_alpha_seed42.json +python hardware/simulator/flowrouter_sim.py --generate-fixtures --seed 42 ``` -This is also the simulator smoke command: generation validates the raw packet fixture and the local-alpha operator projection before writing either output file. +This writes the raw packet fixture, the local-alpha operator projection, the control-plane handoff fixture, and the negative validation report. + +The PowerShell wrapper is: + +```powershell +powershell -NoProfile -ExecutionPolicy Bypass -File hardware/simulator/flowrouter-generate-fixtures.ps1 +``` + +## Simulator Smoke + +```powershell +python hardware/simulator/flowrouter_sim.py --smoke --seed 42 +``` + +The smoke command validates all canonical fixtures, compares them against deterministic regenerated output, and runs negative validation cases. + +The PowerShell wrapper is: + +```powershell +powershell -NoProfile -ExecutionPolicy Bypass -File hardware/simulator/flowrouter-smoke.ps1 +``` ## Validate Existing Fixture @@ -30,6 +50,19 @@ python hardware/simulator/flowrouter_sim.py --validate-file hardware/fixtures/fl python hardware/simulator/flowrouter_sim.py --validate-operator-file fixtures/hardware/flowrouter_local_alpha_seed42.json ``` +## Validate Control-Plane Handoff + +```powershell +python hardware/simulator/flowrouter_sim.py --validate-handoff-file fixtures/hardware/flowrouter_control_plane_handoff_seed42.json +``` + +## Validate Negative Cases + +```powershell +python hardware/simulator/flowrouter_sim.py --run-negative-cases --seed 42 +python hardware/simulator/flowrouter_sim.py --validate-negative-report-file fixtures/hardware/flowrouter_negative_validation_seed42.json +``` + ## Packet Types - Device manifest @@ -37,6 +70,7 @@ python hardware/simulator/flowrouter_sim.py --validate-operator-file fixtures/ha - FlowPulse digest relay - Verifier report digest relay - Compact receipt relay +- Bridge alert - Local cache status - Gateway discovery - Sidecar status @@ -49,10 +83,12 @@ The packets are JSON versions of compact, binary-inspired fields. They are not p The local-alpha projection is a `flowmemory.hardware_operator_signals.local_alpha.v0` document. It uses camelCase, a top-level `schema`, local-only `signalEnvelopes`, a direct `hardwareSignals` view, and workbench/control-plane-ready collections: +- `device_manifest` -> `operatorMetadata` - `heartbeat` -> `hardwareNodes` - `compact_receipt_relay` -> `workReceipts` - `verifier_report_digest_relay` -> `verifierReports` - `emergency_offline_signal` -> `alerts` and `challenges` +- `bridge_alert` -> `bridgeAlerts` and `alerts` - `nfc_memory_cartridge_metadata` -> `artifactCommitments` and `memoryCells` -It also includes `workbenchRecords` grouped by `receipts`, `verifierReports`, `artifacts`, `memoryCells`, `challenges`, `hardwareSignals`, and `provenance`. These projection objects are local-only and advisory until reconciled through normal FlowMemory indexer, receipt, verifier, or operator workflows. +It also includes `workbenchRecords` grouped by `operatorMetadata`, `receipts`, `verifierReports`, `bridgeAlerts`, `artifacts`, `memoryCells`, `challenges`, `hardwareSignals`, and `provenance`. The companion `flowmemory.hardware_control_plane_handoff.local_alpha.v0` fixture carries the same state keys under `collections` plus an optional `flowchain:full-smoke` row that runs `python hardware/simulator/flowrouter_sim.py --smoke`. These projection objects are local-only and advisory until reconciled through normal FlowMemory indexer, receipt, verifier, or operator workflows. diff --git a/hardware/simulator/flowrouter-generate-fixtures.ps1 b/hardware/simulator/flowrouter-generate-fixtures.ps1 new file mode 100644 index 00000000..ed88406d --- /dev/null +++ b/hardware/simulator/flowrouter-generate-fixtures.ps1 @@ -0,0 +1,11 @@ +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +$repoRoot = Resolve-Path -LiteralPath (Join-Path $PSScriptRoot "..\..") +Push-Location -LiteralPath $repoRoot +try { + python hardware/simulator/flowrouter_sim.py --generate-fixtures --seed 42 +} +finally { + Pop-Location +} diff --git a/hardware/simulator/flowrouter-smoke.ps1 b/hardware/simulator/flowrouter-smoke.ps1 new file mode 100644 index 00000000..e0ad4d4e --- /dev/null +++ b/hardware/simulator/flowrouter-smoke.ps1 @@ -0,0 +1,11 @@ +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +$repoRoot = Resolve-Path -LiteralPath (Join-Path $PSScriptRoot "..\..") +Push-Location -LiteralPath $repoRoot +try { + python hardware/simulator/flowrouter_sim.py --smoke --seed 42 +} +finally { + Pop-Location +} diff --git a/hardware/simulator/flowrouter_sim.py b/hardware/simulator/flowrouter_sim.py index 643bacf9..0f7bfe4c 100644 --- a/hardware/simulator/flowrouter_sim.py +++ b/hardware/simulator/flowrouter_sim.py @@ -11,6 +11,7 @@ from typing import Any +DEFAULT_SEED = 42 SCHEMA_FILES = { "device_manifest": "device_manifest.schema.json", "heartbeat": "heartbeat.schema.json", @@ -22,10 +23,12 @@ "sidecar_status": "sidecar_status.schema.json", "nfc_memory_cartridge_metadata": "nfc_memory_cartridge_metadata.schema.json", "emergency_offline_signal": "emergency_offline_signal.schema.json", + "bridge_alert": "bridge_alert.schema.json", "dashboard_feed": "dashboard_feed.schema.json", } OPERATOR_SIGNALS_SCHEMA_FILE = "flowchain_operator_signals.schema.json" +NEGATIVE_REPORT_SCHEMA_FILE = "negative_validation_report.schema.json" ZERO_HASH = "0x0000000000000000000000000000000000000000000000000000000000000000" HARDWARE_ROOTFIELD_ID = "rootfield:hardware:flowrouter-local-alpha" HARDWARE_CHAIN_CONTEXT = "flowchain-private-local-testnet" @@ -51,6 +54,26 @@ def iso_tick(offset_seconds: int) -> str: return f"2026-05-13T{base_hour:02d}:{minute:02d}:{second:02d}Z" +def default_raw_fixture_path(seed: int) -> Path: + return Path(f"hardware/fixtures/flowrouter_sample_seed{seed}.json") + + +def default_operator_fixture_path(seed: int) -> Path: + return Path(f"fixtures/hardware/flowrouter_local_alpha_seed{seed}.json") + + +def default_handoff_fixture_path(seed: int) -> Path: + return Path(f"fixtures/hardware/flowrouter_control_plane_handoff_seed{seed}.json") + + +def default_negative_report_path(seed: int) -> Path: + return Path(f"fixtures/hardware/flowrouter_negative_validation_seed{seed}.json") + + +def clone_json(value: Any) -> Any: + return json.loads(json.dumps(value, sort_keys=True)) + + def build_packets(seed: int) -> dict[str, Any]: device_id = f"fr-{short_id(seed, 'device')}" gateway_id = f"gw-{short_id(seed, 'gateway')}" @@ -58,6 +81,7 @@ def build_packets(seed: int) -> dict[str, Any]: receipt_digest = digest(seed, "receipt") flowpulse_digest = digest(seed, "flowpulse") verifier_digest = digest(seed, "verifier") + bridge_digest = digest(seed, "bridge-alert") cache_digest = digest(seed, "cache") manifest = { @@ -79,6 +103,9 @@ def build_packets(seed: int) -> dict[str, Any]: "heartbeat", "gateway_discovery", "compact_receipt_relay", + "verifier_report_digest_relay", + "bridge_alert", + "operator_metadata", "local_cache_status", "sidecar_status", "dashboard_feed", @@ -227,6 +254,27 @@ def build_packets(seed: int) -> dict[str, Any]: "operator_action": "check-upstream-and-power", } + bridge_alert = { + "packet_type": "bridge_alert", + "schema_version": "flowrouter.poc.v0", + "device_id": device_id, + "sequence": 1008 + seed, + "emitted_at": iso_tick(95), + "bridge_id": f"bridge-{short_id(seed, 'bridge')}", + "source_chain": "flowchain-local-alpha", + "target_chain": "base-sepolia-sim", + "alert_code": "LOCKBOX_OBSERVER_LAG", + "severity": "warning", + "subject_id": f"lockbox:{short_id(seed, 'bridge-subject')}", + "digest": bridge_digest, + "block_hint": 1200024, + "payload_bytes_estimate": 136, + "lora_eligible": True, + "verification_state": "advisory", + "summary": "Bridge observer digest lag detected; local chain continues while operator reviews.", + "operator_action": "review-bridge-observer-and-do-not-block-chain", + } + dashboard_feed = { "packet_type": "dashboard_feed", "schema_version": "flowrouter.poc.v0", @@ -267,28 +315,37 @@ def build_packets(seed: int) -> dict[str, Any]: "sidecar_status": sidecar_status, "nfc_memory_cartridge_metadata": cartridge, "emergency_offline_signal": emergency, + "bridge_alert": bridge_alert, "dashboard_feed": dashboard_feed, } def build_operator_signals(seed: int, packets: dict[str, Any] | None = None) -> dict[str, Any]: packet_set = packets if packets is not None else build_packets(seed) + manifest = packet_set["device_manifest"] heartbeat = packet_set["heartbeat"] receipt = packet_set["compact_receipt_relay"] verifier = packet_set["verifier_report_digest_relay"] emergency = packet_set["emergency_offline_signal"] + bridge = packet_set["bridge_alert"] cartridge = packet_set["nfc_memory_cartridge_metadata"] + sidecar = packet_set["sidecar_status"] dashboard = packet_set["dashboard_feed"] device_id = heartbeat["device_id"] generated_at = dashboard["generated_at"] packet_fixture_path = f"hardware/fixtures/flowrouter_sample_seed{seed}.json" operator_fixture_path = f"fixtures/hardware/flowrouter_local_alpha_seed{seed}.json" + handoff_fixture_path = f"fixtures/hardware/flowrouter_control_plane_handoff_seed{seed}.json" + negative_report_path = f"fixtures/hardware/flowrouter_negative_validation_seed{seed}.json" + operator_metadata_id = f"operator-metadata:hardware:{short_id(seed, 'operator-metadata')}" worker_id = f"hardware-node:{device_id}" verifier_id = f"hardware-relay:{device_id}" receipt_id = f"receipt:hardware:{short_id(seed, 'work-receipt-ref')}" verifier_report_id = f"report:hardware:{short_id(seed, 'verifier-report-ref')}" alert_id = f"hw-alert-{short_id(seed, 'offline-alert')}" + bridge_alert_id = f"bridge-alert:hardware:{short_id(seed, 'bridge-alert-ref')}" + bridge_incident_id = f"hw-alert-{short_id(seed, 'bridge-incident')}" challenge_id = f"challenge:hardware:{short_id(seed, 'offline-challenge')}" artifact_id = f"artifact:hardware:{short_id(seed, 'cartridge-artifact-ref')}" memory_cell_id = f"memory:hardware:{short_id(seed, 'cartridge-memory-ref')}" @@ -324,6 +381,24 @@ def build_operator_signals(seed: int, packets: dict[str, Any] | None = None) -> "provenance": provenance, } + operator_metadata = { + "metadataId": operator_metadata_id, + "operatorId": f"operator:local:{short_id(seed, 'operator')}", + "nodeId": device_id, + "rootfieldId": HARDWARE_ROOTFIELD_ID, + "displayName": "Local hardware operator fixture", + "roles": ["hardware_observer", "fixture_relay"], + "transportPreferences": ["local-simulator", "meshtastic-control-sim"], + "hardwareRequiredForPrivateTestnet": False, + "noSecrets": True, + "radioPayloadBudgetBytes": sidecar["payload_budget_bytes"], + "metadataSource": "device_manifest", + "observedAt": manifest["generated_at"], + "localOnly": True, + "sourcePacketType": "device_manifest", + "provenance": provenance, + } + work_receipt = { "receiptId": receipt_id, "rootfieldId": HARDWARE_ROOTFIELD_ID, @@ -364,6 +439,29 @@ def build_operator_signals(seed: int, packets: dict[str, Any] | None = None) -> "sourcePacketType": "verifier_report_digest_relay", } + bridge_alert = { + "bridgeAlertId": bridge_alert_id, + "bridgeId": bridge["bridge_id"], + "rootfieldId": HARDWARE_ROOTFIELD_ID, + "severity": bridge["severity"], + "alertCode": bridge["alert_code"], + "sourceChain": bridge["source_chain"], + "targetChain": bridge["target_chain"], + "subjectId": bridge["subject_id"], + "eventDigest": ensure_hex(bridge["digest"]), + "blockHint": bridge["block_hint"], + "status": "unresolved", + "resolutionState": "operator-review-required", + "summary": bridge["summary"], + "recommendedAction": bridge["operator_action"], + "payloadBytesEstimate": bridge["payload_bytes_estimate"], + "loraEligible": bridge["lora_eligible"], + "localOnly": True, + "doesNotBlockLocalChain": True, + "sourcePacketType": "bridge_alert", + "provenance": provenance, + } + artifact = { "artifactId": artifact_id, "rootfieldId": HARDWARE_ROOTFIELD_ID, @@ -439,6 +537,21 @@ def build_operator_signals(seed: int, packets: dict[str, Any] | None = None) -> "provenance": provenance, } + bridge_incident = { + "id": bridge_incident_id, + "incidentId": bridge_incident_id, + "severity": bridge["severity"], + "title": bridge["alert_code"], + "summary": bridge["summary"], + "openedAt": bridge["emitted_at"], + "linkedObjectIds": [bridge_alert_id], + "recommendedAction": bridge["operator_action"], + "status": "unresolved", + "localOnly": True, + "sourcePacketType": "bridge_alert", + "provenance": provenance, + } + def signal_envelope( label: str, signal_type: str, @@ -448,6 +561,7 @@ def signal_envelope( ) -> dict[str, Any]: packet_type = packet["packet_type"] sequence = packet.get("sequence", seed) + observed_at = packet.get("emitted_at", packet.get("created_at", packet.get("generated_at", generated_at))) return { "schema": "flowmemory.hardware_operator_signal_envelope.local_alpha.v0", "envelopeId": f"hw-env-{short_id(seed, f'{label}-envelope')}", @@ -455,7 +569,7 @@ def signal_envelope( "signalType": signal_type, "sourcePacketType": packet_type, "sourcePacketId": f"{packet_type}:{sequence}", - "observedAt": packet.get("emitted_at", packet.get("created_at", generated_at)), + "observedAt": observed_at, "status": status, "localOnly": True, "payloadBytesEstimate": packet.get("payload_bytes_estimate", 0), @@ -485,6 +599,13 @@ def workbench_record( } signal_envelopes = [ + signal_envelope( + "operator-metadata", + "operator_metadata", + manifest, + [{"collection": "operatorMetadata", "objectId": operator_metadata_id}], + "observed", + ), signal_envelope( "heartbeat", "heartbeat", @@ -516,6 +637,16 @@ def workbench_record( ], "pending", ), + signal_envelope( + "bridge-alert", + "bridge_alert", + bridge, + [ + {"collection": "bridgeAlerts", "objectId": bridge_alert_id}, + {"collection": "alerts", "objectId": bridge_incident_id}, + ], + "unresolved", + ), signal_envelope( "nfc-memory-cartridge", "nfc_memory_cartridge_metadata", @@ -529,10 +660,12 @@ def workbench_record( ] signal_summaries = { + "operator_metadata": "Local operator metadata for optional hardware fixture ingestion.", "heartbeat": "FlowRouter heartbeat and coarse node state.", "receipt_relay": "Compact WorkReceipt digest relay awaiting normal reconciliation.", "verifier_digest_relay": "Compact VerifierReport digest relay awaiting the full report.", "offline_alert_challenge_input": "Offline alert that can seed a local challenge candidate.", + "bridge_alert": "Compact bridge observer alert that does not block local chain progress.", "nfc_memory_cartridge_metadata": "NFC metadata pointer projected into artifact and memory references.", } hardware_signals = [ @@ -566,6 +699,10 @@ def workbench_record( "packetFixture": packet_fixture_path, "operatorFixture": operator_fixture_path, "operatorSchema": "hardware/simulator/schemas/flowchain_operator_signals.schema.json", + "handoffFixture": handoff_fixture_path, + "handoffSchema": "schemas/flowmemory/hardware-control-plane-handoff.schema.json", + "negativeReport": negative_report_path, + "negativeReportSchema": "hardware/simulator/schemas/negative_validation_report.schema.json", "mappingDoc": "hardware/flowrouter/FLOWCHAIN_LOCAL_ALPHA_SIGNALS.md", }, "boundary": { @@ -578,9 +715,18 @@ def workbench_record( "LoRa and Meshtastic packets carry compact control signals, not artifacts, model data, media, or raw memory.", "NFC cartridge metadata is an untrusted pointer until checked against expected commitments.", "Emergency offline signals are operator alerts or challenge inputs only; they do not execute remote actions.", + "Bridge alerts are operator review hints and must not block local chain progress.", ], }, "packetMappings": [ + { + "sourcePacketType": "device_manifest", + "flowchainSignal": "operator_metadata", + "objectCollection": "operatorMetadata", + "objectRef": operator_metadata_id, + "localAlphaRole": "names the local optional hardware operator fixture issuer", + "trustBoundary": "local metadata only; no wallet, secret, or production operator claim", + }, { "sourcePacketType": "heartbeat", "flowchainSignal": "hardware_node_status", @@ -613,6 +759,14 @@ def workbench_record( "localAlphaRole": "creates an operator alert and optional challenge input", "trustBoundary": "local operator attention only; no public emergency-service claim", }, + { + "sourcePacketType": "bridge_alert", + "flowchainSignal": "bridge_observer_alert", + "objectCollection": "bridgeAlerts", + "objectRef": bridge_alert_id, + "localAlphaRole": "surfaces bridge-observer lag without blocking the local chain", + "trustBoundary": "digest alert only; no production bridge readiness or settlement claim", + }, { "sourcePacketType": "nfc_memory_cartridge_metadata", "flowchainSignal": "artifact_memory_reference", @@ -624,15 +778,33 @@ def workbench_record( ], "signalEnvelopes": signal_envelopes, "hardwareSignals": hardware_signals, + "operatorMetadata": [operator_metadata], "hardwareNodes": [hardware_node], "workReceipts": [work_receipt], "verifierReports": [verifier_report], + "bridgeAlerts": [bridge_alert], "artifactCommitments": [artifact], "memoryCells": [memory_cell], "challenges": [challenge], "finalityReceipts": [finality_receipt], - "alerts": [alert], + "alerts": [alert, bridge_incident], "workbenchRecords": { + "operatorMetadata": [ + workbench_record( + operator_metadata_id, + "Hardware operator metadata", + operator_metadata["displayName"], + "Local-only metadata for the optional hardware signal fixture issuer.", + "observed", + [ + {"label": "operator", "value": operator_metadata["operatorId"]}, + {"label": "node", "value": operator_metadata["nodeId"]}, + {"label": "hardware required", "value": "false"}, + {"label": "payload budget", "value": str(operator_metadata["radioPayloadBudgetBytes"])}, + ], + operator_metadata, + ) + ], "receipts": [ workbench_record( receipt_id, @@ -665,6 +837,22 @@ def workbench_record( verifier_report, ) ], + "bridgeAlerts": [ + workbench_record( + bridge_alert_id, + "Hardware bridge alert", + bridge["alert_code"], + bridge["summary"], + "unresolved", + [ + {"label": "bridge", "value": bridge["bridge_id"]}, + {"label": "source", "value": bridge["source_chain"]}, + {"label": "target", "value": bridge["target_chain"]}, + {"label": "digest", "value": ensure_hex(bridge["digest"])}, + ], + bridge_alert, + ) + ], "artifacts": [ workbench_record( artifact_id, @@ -747,6 +935,7 @@ def workbench_record( "sourcePaths": { "packetFixture": packet_fixture_path, "operatorFixture": operator_fixture_path, + "handoffFixture": handoff_fixture_path, } }, ) @@ -755,9 +944,11 @@ def workbench_record( "compatibility": { "controlPlaneStateKeys": [ "hardwareSignals", + "operatorMetadata", "hardwareNodes", "workReceipts", "verifierReports", + "bridgeAlerts", "artifactCommitments", "memoryCells", "challenges", @@ -765,8 +956,10 @@ def workbench_record( "alerts", ], "workbenchSectionKeys": [ + "operatorMetadata", "receipts", "verifierReports", + "bridgeAlerts", "artifacts", "memoryCells", "challenges", @@ -774,10 +967,53 @@ def workbench_record( "provenance", ], "jsonRpcBoundary": "Read-only fixture data; no submit, wallet, live indexing, or production settlement method is implied.", + "flowchainFullSmokeOptionalRow": { + "label": "Validate optional hardware operator signal fixtures", + "command": "python hardware/simulator/flowrouter_sim.py --smoke", + "requiredForChainProgress": False, + "hardwareRequired": False, + }, }, } +def build_control_plane_handoff(seed: int, operator_signals: dict[str, Any] | None = None) -> dict[str, Any]: + signal_doc = operator_signals if operator_signals is not None else build_operator_signals(seed) + state_keys = signal_doc["compatibility"]["controlPlaneStateKeys"] + return { + "schema": "flowmemory.hardware_control_plane_handoff.local_alpha.v0", + "generatedAt": signal_doc["generatedAt"], + "chainId": signal_doc["chainId"], + "environment": signal_doc["environment"], + "sourceFixture": signal_doc["sourcePaths"]["operatorFixture"], + "hardwareRequiredForPrivateTestnet": False, + "mode": "read-only-optional-merge", + "boundary": signal_doc["boundary"], + "ingest": { + "stateKeys": state_keys, + "mergePolicy": "replace-by-stable-id", + "idFields": { + "hardwareSignals": "signalId", + "operatorMetadata": "metadataId", + "hardwareNodes": "nodeId", + "workReceipts": "receiptId", + "verifierReports": "reportId", + "bridgeAlerts": "bridgeAlertId", + "artifactCommitments": "artifactId", + "memoryCells": "memoryCellId", + "challenges": "challengeId", + "finalityReceipts": "finalityReceiptId", + "alerts": "incidentId", + }, + "localOnly": True, + "normalNetworkReconciliationRequired": True, + }, + "collections": {key: signal_doc[key] for key in state_keys}, + "workbenchRecords": signal_doc["workbenchRecords"], + "optionalSmokeRows": [signal_doc["compatibility"]["flowchainFullSmokeOptionalRow"]], + } + + class ValidationError(Exception): pass @@ -803,6 +1039,9 @@ def validate_value(schema: dict[str, Any], value: Any, path: str) -> None: if expected and not check_type(value, expected): raise ValidationError(f"{path}: expected {expected}, got {type(value).__name__}") + if "const" in schema and value != schema["const"]: + raise ValidationError(f"{path}: value {value!r} does not match const {schema['const']!r}") + if "enum" in schema and value not in schema["enum"]: raise ValidationError(f"{path}: value {value!r} not in enum {schema['enum']!r}") @@ -831,6 +1070,10 @@ def validate_value(schema: dict[str, Any], value: Any, path: str) -> None: validate_value(child, value[key], f"{path}.{key}") if isinstance(value, list) and "items" in schema: + if "minItems" in schema and len(value) < schema["minItems"]: + raise ValidationError(f"{path}: array shorter than {schema['minItems']}") + if "maxItems" in schema and len(value) > schema["maxItems"]: + raise ValidationError(f"{path}: array longer than {schema['maxItems']}") for index, item in enumerate(value): validate_value(schema["items"], item, f"{path}[{index}]") @@ -858,6 +1101,17 @@ def validate_operator_signals(operator_signals: dict[str, Any], schema_dir: Path validate_value(schema, operator_signals, "operator_signals") +def validate_control_plane_handoff(handoff: dict[str, Any], repo_root: Path) -> None: + schema_path = repo_root / "schemas" / "flowmemory" / "hardware-control-plane-handoff.schema.json" + schema = json.loads(schema_path.read_text(encoding="utf-8")) + validate_value(schema, handoff, "control_plane_handoff") + + +def validate_negative_report(report: dict[str, Any], schema_dir: Path) -> None: + schema = json.loads((schema_dir / NEGATIVE_REPORT_SCHEMA_FILE).read_text(encoding="utf-8")) + validate_value(schema, report, "negative_report") + + def output_document(seed: int) -> dict[str, Any]: return { "simulator": "flowrouter-v0-poc", @@ -867,18 +1121,155 @@ def output_document(seed: int) -> dict[str, Any]: } +def run_negative_cases(seed: int, schema_dir: Path, repo_root: Path) -> list[dict[str, Any]]: + packets = build_packets(seed) + operator_doc = build_operator_signals(seed, packets) + handoff_doc = build_control_plane_handoff(seed, operator_doc) + + cases: list[dict[str, Any]] = [] + + def expect_rejected(name: str, validator: Any, value: Any, expected: str) -> None: + try: + validator(value) + except ValidationError as exc: + message = str(exc) + if expected not in message: + raise ValidationError(f"{name}: expected error containing {expected!r}, got {message!r}") from exc + cases.append({"case": name, "expectedFailure": expected, "actualFailure": message, "passed": True}) + return + raise ValidationError(f"{name}: negative case unexpectedly passed validation") + + missing_heartbeat_device = clone_json(packets) + del missing_heartbeat_device["heartbeat"]["device_id"] + expect_rejected( + "heartbeat_missing_device_id", + lambda value: validate_packets(value, schema_dir), + missing_heartbeat_device, + "missing required key device_id", + ) + + oversized_receipt_relay = clone_json(packets) + oversized_receipt_relay["compact_receipt_relay"]["payload_bytes_estimate"] = 512 + expect_rejected( + "receipt_relay_payload_exceeds_control_budget", + lambda value: validate_packets(value, schema_dir), + oversized_receipt_relay, + "value above maximum 200", + ) + + nfc_secret_claim = clone_json(packets) + nfc_secret_claim["nfc_memory_cartridge_metadata"]["contains_secrets"] = True + expect_rejected( + "nfc_metadata_claims_secret_storage", + lambda value: validate_packets(value, schema_dir), + nfc_secret_claim, + "not in enum [False]", + ) + + missing_bridge_handoff = clone_json(operator_doc) + del missing_bridge_handoff["bridgeAlerts"] + expect_rejected( + "operator_projection_missing_bridge_alerts", + lambda value: validate_operator_signals(value, schema_dir), + missing_bridge_handoff, + "missing required key bridgeAlerts", + ) + + hardware_required = clone_json(operator_doc) + hardware_required["boundary"]["hardwareRequiredForPrivateTestnet"] = True + expect_rejected( + "operator_projection_requires_hardware", + lambda value: validate_operator_signals(value, schema_dir), + hardware_required, + "not in enum [False]", + ) + + oversized_operator_envelope = clone_json(operator_doc) + oversized_operator_envelope["signalEnvelopes"][2]["payloadBytesEstimate"] = 512 + expect_rejected( + "operator_envelope_payload_exceeds_control_budget", + lambda value: validate_operator_signals(value, schema_dir), + oversized_operator_envelope, + "value above maximum 200", + ) + + handoff_requires_hardware = clone_json(handoff_doc) + handoff_requires_hardware["hardwareRequiredForPrivateTestnet"] = True + expect_rejected( + "control_plane_handoff_requires_hardware", + lambda value: validate_control_plane_handoff(value, repo_root), + handoff_requires_hardware, + "not in enum [False]", + ) + + handoff_missing_hardware_signals = clone_json(handoff_doc) + del handoff_missing_hardware_signals["collections"]["hardwareSignals"] + expect_rejected( + "control_plane_handoff_missing_hardware_signals", + lambda value: validate_control_plane_handoff(value, repo_root), + handoff_missing_hardware_signals, + "missing required key hardwareSignals", + ) + + return cases + + +def build_negative_report(seed: int, cases: list[dict[str, Any]]) -> dict[str, Any]: + return { + "schema": "flowmemory.hardware_negative_validation.local_alpha.v0", + "generatedAt": iso_tick(130), + "seed": seed, + "caseCount": len(cases), + "allCasesRejected": all(case["passed"] for case in cases), + "cases": cases, + } + + +def write_json(path: Path, value: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(value, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + +def assert_matches_file(path: Path, expected: dict[str, Any]) -> None: + if not path.exists(): + raise ValidationError(f"missing fixture: {path}") + actual = json.loads(path.read_text(encoding="utf-8")) + if actual != expected: + raise ValidationError(f"fixture drift: {path}") + + def main() -> int: parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--seed", type=int, default=42, help="deterministic seed") + parser.add_argument("--seed", type=int, default=DEFAULT_SEED, help="deterministic seed") + parser.add_argument("--generate-fixtures", action="store_true", help="write the canonical raw, operator, handoff, and negative validation fixtures") + parser.add_argument("--smoke", action="store_true", help="validate canonical fixtures, check deterministic drift, and run negative cases") + parser.add_argument("--run-negative-cases", action="store_true", help="run in-memory negative validation cases") parser.add_argument("--out", type=Path, help="write generated JSON to this path") parser.add_argument("--operator-out", type=Path, help="write FlowChain local-alpha operator signal JSON to this path") + parser.add_argument("--handoff-out", type=Path, help="write control-plane handoff JSON to this path") + parser.add_argument("--negative-report-out", type=Path, help="write negative validation report JSON to this path") parser.add_argument("--validate-file", type=Path, help="validate an existing simulator JSON file") parser.add_argument("--validate-operator-file", type=Path, help="validate an existing FlowChain local-alpha operator signal JSON file") + parser.add_argument("--validate-handoff-file", type=Path, help="validate an existing hardware control-plane handoff JSON file") + parser.add_argument("--validate-negative-report-file", type=Path, help="validate an existing negative validation report JSON file") args = parser.parse_args() schema_dir = Path(__file__).resolve().parent / "schemas" + repo_root = Path(__file__).resolve().parents[2] try: + if args.validate_negative_report_file: + negative_report = json.loads(args.validate_negative_report_file.read_text(encoding="utf-8")) + validate_negative_report(negative_report, schema_dir) + print(f"valid: {args.validate_negative_report_file}") + return 0 + + if args.validate_handoff_file: + handoff_doc = json.loads(args.validate_handoff_file.read_text(encoding="utf-8")) + validate_control_plane_handoff(handoff_doc, repo_root) + print(f"valid: {args.validate_handoff_file}") + return 0 + if args.validate_operator_file: operator_doc = json.loads(args.validate_operator_file.read_text(encoding="utf-8")) validate_operator_signals(operator_doc, schema_dir) @@ -891,27 +1282,58 @@ def main() -> int: print(f"valid: {args.validate_file}") return 0 + if args.run_negative_cases: + cases = run_negative_cases(args.seed, schema_dir, repo_root) + print(f"negative cases passed: {len(cases)}") + return 0 + packets = build_packets(args.seed) - doc = { - "simulator": "flowrouter-v0-poc", - "seed": args.seed, - "generated_at": iso_tick(120), - "packets": packets, - } + doc = output_document(args.seed) validate_packets(doc["packets"], schema_dir) operator_doc = build_operator_signals(args.seed, packets) validate_operator_signals(operator_doc, schema_dir) + handoff_doc = build_control_plane_handoff(args.seed, operator_doc) + validate_control_plane_handoff(handoff_doc, repo_root) + negative_cases = run_negative_cases(args.seed, schema_dir, repo_root) + negative_report = build_negative_report(args.seed, negative_cases) + validate_negative_report(negative_report, schema_dir) + + if args.smoke: + assert_matches_file(default_raw_fixture_path(args.seed), doc) + assert_matches_file(default_operator_fixture_path(args.seed), operator_doc) + assert_matches_file(default_handoff_fixture_path(args.seed), handoff_doc) + assert_matches_file(default_negative_report_path(args.seed), negative_report) + print( + "smoke passed: raw packets, operator signals, control-plane handoff, " + f"fixture drift check, and {len(negative_cases)} negative cases" + ) + return 0 + encoded = json.dumps(doc, indent=2, sort_keys=True) + "\n" - if args.out: - args.out.parent.mkdir(parents=True, exist_ok=True) - args.out.write_text(encoded, encoding="utf-8") - print(f"wrote: {args.out}") + out_path = args.out + operator_out_path = args.operator_out + handoff_out_path = args.handoff_out + negative_report_out_path = args.negative_report_out + if args.generate_fixtures: + out_path = out_path or default_raw_fixture_path(args.seed) + operator_out_path = operator_out_path or default_operator_fixture_path(args.seed) + handoff_out_path = handoff_out_path or default_handoff_fixture_path(args.seed) + negative_report_out_path = negative_report_out_path or default_negative_report_path(args.seed) + + if out_path: + write_json(out_path, doc) + print(f"wrote: {out_path}") else: sys.stdout.write(encoded) - if args.operator_out: - args.operator_out.parent.mkdir(parents=True, exist_ok=True) - args.operator_out.write_text(json.dumps(operator_doc, indent=2, sort_keys=True) + "\n", encoding="utf-8") - print(f"wrote: {args.operator_out}") + if operator_out_path: + write_json(operator_out_path, operator_doc) + print(f"wrote: {operator_out_path}") + if handoff_out_path: + write_json(handoff_out_path, handoff_doc) + print(f"wrote: {handoff_out_path}") + if negative_report_out_path: + write_json(negative_report_out_path, negative_report) + print(f"wrote: {negative_report_out_path}") return 0 except (KeyError, json.JSONDecodeError, OSError, ValidationError) as exc: print(f"error: {exc}", file=sys.stderr) diff --git a/hardware/simulator/schemas/bridge_alert.schema.json b/hardware/simulator/schemas/bridge_alert.schema.json new file mode 100644 index 00000000..6c578be9 --- /dev/null +++ b/hardware/simulator/schemas/bridge_alert.schema.json @@ -0,0 +1,49 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Bridge Alert", + "type": "object", + "additionalProperties": false, + "required": [ + "packet_type", + "schema_version", + "device_id", + "sequence", + "emitted_at", + "bridge_id", + "source_chain", + "target_chain", + "alert_code", + "severity", + "subject_id", + "digest", + "block_hint", + "payload_bytes_estimate", + "lora_eligible", + "verification_state", + "summary", + "operator_action" + ], + "properties": { + "packet_type": { "type": "string", "enum": ["bridge_alert"] }, + "schema_version": { "type": "string" }, + "device_id": { "type": "string", "maxLength": 32 }, + "sequence": { "type": "integer", "minimum": 0 }, + "emitted_at": { "type": "string" }, + "bridge_id": { "type": "string", "maxLength": 48 }, + "source_chain": { "type": "string", "maxLength": 48 }, + "target_chain": { "type": "string", "maxLength": 48 }, + "alert_code": { + "type": "string", + "enum": ["LOCKBOX_OBSERVER_LAG", "BRIDGE_DIGEST_MISMATCH", "BRIDGE_RELAY_STALE"] + }, + "severity": { "type": "string", "enum": ["info", "warning", "critical"] }, + "subject_id": { "type": "string", "maxLength": 64 }, + "digest": { "type": "string", "maxLength": 64 }, + "block_hint": { "type": "integer", "minimum": 0 }, + "payload_bytes_estimate": { "type": "integer", "minimum": 0, "maximum": 200 }, + "lora_eligible": { "type": "boolean" }, + "verification_state": { "type": "string", "enum": ["advisory", "unresolved"] }, + "summary": { "type": "string", "maxLength": 160 }, + "operator_action": { "type": "string", "maxLength": 96 } + } +} diff --git a/hardware/simulator/schemas/flowchain_operator_signals.schema.json b/hardware/simulator/schemas/flowchain_operator_signals.schema.json index 05926abe..1761d321 100644 --- a/hardware/simulator/schemas/flowchain_operator_signals.schema.json +++ b/hardware/simulator/schemas/flowchain_operator_signals.schema.json @@ -14,9 +14,11 @@ "packetMappings", "signalEnvelopes", "hardwareSignals", + "operatorMetadata", "hardwareNodes", "workReceipts", "verifierReports", + "bridgeAlerts", "artifactCommitments", "memoryCells", "challenges", @@ -37,11 +39,24 @@ "sourcePaths": { "type": "object", "additionalProperties": false, - "required": ["packetFixture", "operatorFixture", "operatorSchema", "mappingDoc"], + "required": [ + "packetFixture", + "operatorFixture", + "operatorSchema", + "handoffFixture", + "handoffSchema", + "negativeReport", + "negativeReportSchema", + "mappingDoc" + ], "properties": { "packetFixture": { "type": "string", "maxLength": 128 }, "operatorFixture": { "type": "string", "maxLength": 128 }, "operatorSchema": { "type": "string", "maxLength": 128 }, + "handoffFixture": { "type": "string", "maxLength": 128 }, + "handoffSchema": { "type": "string", "maxLength": 128 }, + "negativeReport": { "type": "string", "maxLength": 128 }, + "negativeReportSchema": { "type": "string", "maxLength": 128 }, "mappingDoc": { "type": "string", "maxLength": 128 } } }, @@ -83,30 +98,36 @@ "sourcePacketType": { "type": "string", "enum": [ + "device_manifest", "heartbeat", "compact_receipt_relay", "verifier_report_digest_relay", "emergency_offline_signal", + "bridge_alert", "nfc_memory_cartridge_metadata" ] }, "flowchainSignal": { "type": "string", "enum": [ + "operator_metadata", "hardware_node_status", "work_receipt_reference", "verifier_report_reference", "alert_challenge_input", + "bridge_observer_alert", "artifact_memory_reference" ] }, "objectCollection": { "type": "string", "enum": [ + "operatorMetadata", "hardwareNodes", "workReceipts", "verifierReports", "challenges", + "bridgeAlerts", "artifactCommitments" ] }, @@ -146,10 +167,12 @@ "signalType": { "type": "string", "enum": [ + "operator_metadata", "heartbeat", "receipt_relay", "verifier_digest_relay", "offline_alert_challenge_input", + "bridge_alert", "nfc_memory_cartridge_metadata" ] }, @@ -206,10 +229,12 @@ "signalType": { "type": "string", "enum": [ + "operator_metadata", "heartbeat", "receipt_relay", "verifier_digest_relay", "offline_alert_challenge_input", + "bridge_alert", "nfc_memory_cartridge_metadata" ] }, @@ -226,6 +251,44 @@ } } }, + "operatorMetadata": { + "type": "array", + "items": { + "type": "object", + "required": [ + "metadataId", + "operatorId", + "nodeId", + "rootfieldId", + "displayName", + "roles", + "transportPreferences", + "hardwareRequiredForPrivateTestnet", + "noSecrets", + "radioPayloadBudgetBytes", + "metadataSource", + "observedAt", + "localOnly", + "sourcePacketType" + ], + "properties": { + "metadataId": { "type": "string", "maxLength": 96 }, + "operatorId": { "type": "string", "maxLength": 96 }, + "nodeId": { "type": "string", "maxLength": 64 }, + "rootfieldId": { "type": "string", "maxLength": 96 }, + "displayName": { "type": "string", "maxLength": 96 }, + "roles": { "type": "array", "items": { "type": "string", "maxLength": 48 } }, + "transportPreferences": { "type": "array", "items": { "type": "string", "maxLength": 48 } }, + "hardwareRequiredForPrivateTestnet": { "type": "boolean", "enum": [false] }, + "noSecrets": { "type": "boolean", "enum": [true] }, + "radioPayloadBudgetBytes": { "type": "integer", "minimum": 0, "maximum": 200 }, + "metadataSource": { "type": "string", "enum": ["device_manifest"] }, + "observedAt": { "type": "string" }, + "localOnly": { "type": "boolean", "enum": [true] }, + "sourcePacketType": { "type": "string", "enum": ["device_manifest"] } + } + } + }, "hardwareNodes": { "type": "array", "items": { @@ -311,6 +374,54 @@ } } }, + "bridgeAlerts": { + "type": "array", + "items": { + "type": "object", + "required": [ + "bridgeAlertId", + "bridgeId", + "rootfieldId", + "severity", + "alertCode", + "sourceChain", + "targetChain", + "subjectId", + "eventDigest", + "blockHint", + "status", + "resolutionState", + "summary", + "recommendedAction", + "payloadBytesEstimate", + "loraEligible", + "localOnly", + "doesNotBlockLocalChain", + "sourcePacketType" + ], + "properties": { + "bridgeAlertId": { "type": "string", "maxLength": 96 }, + "bridgeId": { "type": "string", "maxLength": 48 }, + "rootfieldId": { "type": "string", "maxLength": 96 }, + "severity": { "type": "string", "enum": ["info", "warning", "critical"] }, + "alertCode": { "type": "string", "maxLength": 64 }, + "sourceChain": { "type": "string", "maxLength": 48 }, + "targetChain": { "type": "string", "maxLength": 48 }, + "subjectId": { "type": "string", "maxLength": 64 }, + "eventDigest": { "type": "string", "maxLength": 66 }, + "blockHint": { "type": "integer", "minimum": 0 }, + "status": { "type": "string", "enum": ["unresolved", "pending", "verified", "failed"] }, + "resolutionState": { "type": "string", "enum": ["operator-review-required"] }, + "summary": { "type": "string", "maxLength": 180 }, + "recommendedAction": { "type": "string", "maxLength": 96 }, + "payloadBytesEstimate": { "type": "integer", "minimum": 0, "maximum": 200 }, + "loraEligible": { "type": "boolean" }, + "localOnly": { "type": "boolean", "enum": [true] }, + "doesNotBlockLocalChain": { "type": "boolean", "enum": [true] }, + "sourcePacketType": { "type": "string", "enum": ["bridge_alert"] } + } + } + }, "artifactCommitments": { "type": "array", "items": { @@ -397,17 +508,29 @@ "recommendedAction": { "type": "string", "maxLength": 96 }, "status": { "type": "string", "enum": ["unresolved", "pending", "verified", "failed"] }, "localOnly": { "type": "boolean", "enum": [true] }, - "sourcePacketType": { "type": "string", "enum": ["emergency_offline_signal"] } + "sourcePacketType": { "type": "string", "enum": ["emergency_offline_signal", "bridge_alert"] } } } }, "workbenchRecords": { "type": "object", "additionalProperties": false, - "required": ["receipts", "verifierReports", "artifacts", "memoryCells", "challenges", "hardwareSignals", "provenance"], + "required": [ + "operatorMetadata", + "receipts", + "verifierReports", + "bridgeAlerts", + "artifacts", + "memoryCells", + "challenges", + "hardwareSignals", + "provenance" + ], "properties": { + "operatorMetadata": { "type": "array", "items": { "type": "object" } }, "receipts": { "type": "array", "items": { "type": "object" } }, "verifierReports": { "type": "array", "items": { "type": "object" } }, + "bridgeAlerts": { "type": "array", "items": { "type": "object" } }, "artifacts": { "type": "array", "items": { "type": "object" } }, "memoryCells": { "type": "array", "items": { "type": "object" } }, "challenges": { "type": "array", "items": { "type": "object" } }, @@ -418,11 +541,22 @@ "compatibility": { "type": "object", "additionalProperties": false, - "required": ["controlPlaneStateKeys", "workbenchSectionKeys", "jsonRpcBoundary"], + "required": ["controlPlaneStateKeys", "workbenchSectionKeys", "jsonRpcBoundary", "flowchainFullSmokeOptionalRow"], "properties": { "controlPlaneStateKeys": { "type": "array", "items": { "type": "string", "maxLength": 64 } }, "workbenchSectionKeys": { "type": "array", "items": { "type": "string", "maxLength": 64 } }, - "jsonRpcBoundary": { "type": "string", "maxLength": 160 } + "jsonRpcBoundary": { "type": "string", "maxLength": 160 }, + "flowchainFullSmokeOptionalRow": { + "type": "object", + "additionalProperties": false, + "required": ["label", "command", "requiredForChainProgress", "hardwareRequired"], + "properties": { + "label": { "type": "string", "maxLength": 96 }, + "command": { "type": "string", "maxLength": 128 }, + "requiredForChainProgress": { "type": "boolean", "enum": [false] }, + "hardwareRequired": { "type": "boolean", "enum": [false] } + } + } } } } diff --git a/hardware/simulator/schemas/negative_validation_report.schema.json b/hardware/simulator/schemas/negative_validation_report.schema.json new file mode 100644 index 00000000..15ada7a8 --- /dev/null +++ b/hardware/simulator/schemas/negative_validation_report.schema.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "FlowRouter Negative Validation Report", + "type": "object", + "additionalProperties": false, + "required": ["schema", "generatedAt", "seed", "caseCount", "allCasesRejected", "cases"], + "properties": { + "schema": { "type": "string", "enum": ["flowmemory.hardware_negative_validation.local_alpha.v0"] }, + "generatedAt": { "type": "string" }, + "seed": { "type": "integer", "minimum": 0 }, + "caseCount": { "type": "integer", "minimum": 1 }, + "allCasesRejected": { "type": "boolean", "enum": [true] }, + "cases": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["case", "expectedFailure", "actualFailure", "passed"], + "properties": { + "case": { "type": "string", "maxLength": 96 }, + "expectedFailure": { "type": "string", "maxLength": 160 }, + "actualFailure": { "type": "string", "maxLength": 220 }, + "passed": { "type": "boolean", "enum": [true] } + } + } + } + } +} diff --git a/schemas/flowmemory/hardware-control-plane-handoff.schema.json b/schemas/flowmemory/hardware-control-plane-handoff.schema.json new file mode 100644 index 00000000..dd4bb478 --- /dev/null +++ b/schemas/flowmemory/hardware-control-plane-handoff.schema.json @@ -0,0 +1,165 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "flowmemory.hardware_control_plane_handoff.local_alpha.v0", + "title": "FlowMemory Hardware Control-Plane Handoff Local Alpha", + "type": "object", + "additionalProperties": false, + "required": [ + "schema", + "generatedAt", + "chainId", + "environment", + "sourceFixture", + "hardwareRequiredForPrivateTestnet", + "mode", + "boundary", + "ingest", + "collections", + "workbenchRecords", + "optionalSmokeRows" + ], + "properties": { + "schema": { "type": "string", "enum": ["flowmemory.hardware_control_plane_handoff.local_alpha.v0"] }, + "generatedAt": { "type": "string" }, + "chainId": { "type": "string", "enum": ["flowmemory-local-alpha"] }, + "environment": { "type": "string", "enum": ["local-devnet-fixture"] }, + "sourceFixture": { "type": "string", "maxLength": 128 }, + "hardwareRequiredForPrivateTestnet": { "type": "boolean", "enum": [false] }, + "mode": { "type": "string", "enum": ["read-only-optional-merge"] }, + "boundary": { + "type": "object", + "additionalProperties": false, + "required": [ + "localOnly", + "advisory", + "normalNetworkReconciliationRequired", + "hardwareRequiredForPrivateTestnet", + "claimLimitations" + ], + "properties": { + "localOnly": { "type": "boolean", "enum": [true] }, + "advisory": { "type": "boolean", "enum": [true] }, + "normalNetworkReconciliationRequired": { "type": "boolean", "enum": [true] }, + "hardwareRequiredForPrivateTestnet": { "type": "boolean", "enum": [false] }, + "claimLimitations": { "type": "array", "items": { "type": "string", "maxLength": 180 } } + } + }, + "ingest": { + "type": "object", + "additionalProperties": false, + "required": [ + "stateKeys", + "mergePolicy", + "idFields", + "localOnly", + "normalNetworkReconciliationRequired" + ], + "properties": { + "stateKeys": { "type": "array", "items": { "type": "string", "maxLength": 64 } }, + "mergePolicy": { "type": "string", "enum": ["replace-by-stable-id"] }, + "idFields": { + "type": "object", + "additionalProperties": false, + "required": [ + "hardwareSignals", + "operatorMetadata", + "hardwareNodes", + "workReceipts", + "verifierReports", + "bridgeAlerts", + "artifactCommitments", + "memoryCells", + "challenges", + "finalityReceipts", + "alerts" + ], + "properties": { + "hardwareSignals": { "type": "string", "enum": ["signalId"] }, + "operatorMetadata": { "type": "string", "enum": ["metadataId"] }, + "hardwareNodes": { "type": "string", "enum": ["nodeId"] }, + "workReceipts": { "type": "string", "enum": ["receiptId"] }, + "verifierReports": { "type": "string", "enum": ["reportId"] }, + "bridgeAlerts": { "type": "string", "enum": ["bridgeAlertId"] }, + "artifactCommitments": { "type": "string", "enum": ["artifactId"] }, + "memoryCells": { "type": "string", "enum": ["memoryCellId"] }, + "challenges": { "type": "string", "enum": ["challengeId"] }, + "finalityReceipts": { "type": "string", "enum": ["finalityReceiptId"] }, + "alerts": { "type": "string", "enum": ["incidentId"] } + } + }, + "localOnly": { "type": "boolean", "enum": [true] }, + "normalNetworkReconciliationRequired": { "type": "boolean", "enum": [true] } + } + }, + "collections": { + "type": "object", + "additionalProperties": false, + "required": [ + "hardwareSignals", + "operatorMetadata", + "hardwareNodes", + "workReceipts", + "verifierReports", + "bridgeAlerts", + "artifactCommitments", + "memoryCells", + "challenges", + "finalityReceipts", + "alerts" + ], + "properties": { + "hardwareSignals": { "type": "array", "items": { "type": "object" } }, + "operatorMetadata": { "type": "array", "items": { "type": "object" } }, + "hardwareNodes": { "type": "array", "items": { "type": "object" } }, + "workReceipts": { "type": "array", "items": { "type": "object" } }, + "verifierReports": { "type": "array", "items": { "type": "object" } }, + "bridgeAlerts": { "type": "array", "items": { "type": "object" } }, + "artifactCommitments": { "type": "array", "items": { "type": "object" } }, + "memoryCells": { "type": "array", "items": { "type": "object" } }, + "challenges": { "type": "array", "items": { "type": "object" } }, + "finalityReceipts": { "type": "array", "items": { "type": "object" } }, + "alerts": { "type": "array", "items": { "type": "object" } } + } + }, + "workbenchRecords": { + "type": "object", + "additionalProperties": false, + "required": [ + "operatorMetadata", + "receipts", + "verifierReports", + "bridgeAlerts", + "artifacts", + "memoryCells", + "challenges", + "hardwareSignals", + "provenance" + ], + "properties": { + "operatorMetadata": { "type": "array", "items": { "type": "object" } }, + "receipts": { "type": "array", "items": { "type": "object" } }, + "verifierReports": { "type": "array", "items": { "type": "object" } }, + "bridgeAlerts": { "type": "array", "items": { "type": "object" } }, + "artifacts": { "type": "array", "items": { "type": "object" } }, + "memoryCells": { "type": "array", "items": { "type": "object" } }, + "challenges": { "type": "array", "items": { "type": "object" } }, + "hardwareSignals": { "type": "array", "items": { "type": "object" } }, + "provenance": { "type": "array", "items": { "type": "object" } } + } + }, + "optionalSmokeRows": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["label", "command", "requiredForChainProgress", "hardwareRequired"], + "properties": { + "label": { "type": "string", "maxLength": 96 }, + "command": { "type": "string", "maxLength": 128 }, + "requiredForChainProgress": { "type": "boolean", "enum": [false] }, + "hardwareRequired": { "type": "boolean", "enum": [false] } + } + } + } + } +}