From 81ace44846a00fdfd52aa37a58fd49b7444ec053 Mon Sep 17 00:00:00 2001 From: FlowMemory HQ Agent Date: Wed, 13 May 2026 21:22:40 -0500 Subject: [PATCH] Add optional hardware signal fixtures --- .../hardware-signals/AJV_2020_VALIDATION.mjs | 48 ++ docs/agent-runs/hardware-signals/AUDIT.md | 103 +++ .../hardware-signals/CHANGED_FILES.md | 55 ++ docs/agent-runs/hardware-signals/CHECKLIST.md | 35 + .../hardware-signals/EXPERIMENTS.md | 35 + docs/agent-runs/hardware-signals/NOTES.md | 81 +++ .../NO_SECRET_FIXTURE_SCAN.mjs | 38 + docs/agent-runs/hardware-signals/PLAN.md | 59 ++ .../agent-runs/hardware-signals/PR_SUMMARY.md | 96 +++ .../hardware-signals/RETRY_AFTER_131.md | 50 ++ .../hardware-signals/SCOPE_CHECK.mjs | 36 + fixtures/hardware/README.md | 10 +- ...owrouter_control_plane_handoff_seed42.json | 568 +++++++++++++-- .../flowrouter_local_alpha_seed42.json | 652 ++++++++++++++++-- ...flowrouter_negative_validation_seed42.json | 26 +- hardware/README.md | 8 +- .../fixtures/flowrouter_sample_seed42.json | 52 +- .../FLOWCHAIN_LOCAL_ALPHA_SIGNALS.md | 15 +- hardware/flowrouter/README.md | 2 +- .../lora-sidecar/CONTROL_MESSAGE_INVENTORY.md | 12 +- hardware/simulator/README.md | 10 +- hardware/simulator/flowrouter_sim.py | 330 ++++++++- .../schemas/dashboard_feed.schema.json | 4 +- .../flowchain_operator_signals.schema.json | 58 ++ .../simulator/schemas/node_health.schema.json | 40 ++ .../simulator/schemas/peer_hint.schema.json | 42 ++ ...hardware-control-plane-handoff.schema.json | 12 + 27 files changed, 2348 insertions(+), 129 deletions(-) create mode 100644 docs/agent-runs/hardware-signals/AJV_2020_VALIDATION.mjs create mode 100644 docs/agent-runs/hardware-signals/AUDIT.md create mode 100644 docs/agent-runs/hardware-signals/CHANGED_FILES.md create mode 100644 docs/agent-runs/hardware-signals/CHECKLIST.md create mode 100644 docs/agent-runs/hardware-signals/EXPERIMENTS.md create mode 100644 docs/agent-runs/hardware-signals/NOTES.md create mode 100644 docs/agent-runs/hardware-signals/NO_SECRET_FIXTURE_SCAN.mjs create mode 100644 docs/agent-runs/hardware-signals/PLAN.md create mode 100644 docs/agent-runs/hardware-signals/PR_SUMMARY.md create mode 100644 docs/agent-runs/hardware-signals/RETRY_AFTER_131.md create mode 100644 docs/agent-runs/hardware-signals/SCOPE_CHECK.mjs create mode 100644 hardware/simulator/schemas/node_health.schema.json create mode 100644 hardware/simulator/schemas/peer_hint.schema.json diff --git a/docs/agent-runs/hardware-signals/AJV_2020_VALIDATION.mjs b/docs/agent-runs/hardware-signals/AJV_2020_VALIDATION.mjs new file mode 100644 index 00000000..b30fd47e --- /dev/null +++ b/docs/agent-runs/hardware-signals/AJV_2020_VALIDATION.mjs @@ -0,0 +1,48 @@ +import fs from "node:fs"; +import path from "node:path"; +import Ajv2020 from "ajv/dist/2020.js"; +import addFormats from "ajv-formats"; + +const ajv = new Ajv2020({ allErrors: true, strict: false }); +addFormats(ajv); + +const readJson = (file) => JSON.parse(fs.readFileSync(file, "utf8")); + +function validateDoc(schemaFile, doc, label) { + const schema = readJson(schemaFile); + const validate = ajv.compile(schema); + if (!validate(doc)) { + console.error(label); + console.error(validate.errors); + process.exitCode = 1; + return; + } + console.log(`valid: ${label}`); +} + +const raw = readJson("hardware/fixtures/flowrouter_sample_seed42.json"); +for (const [name, packet] of Object.entries(raw.packets)) { + validateDoc(path.join("hardware/simulator/schemas", `${name}.schema.json`), packet, `packet:${name}`); +} + +validateDoc( + "hardware/simulator/schemas/flowchain_operator_signals.schema.json", + readJson("fixtures/hardware/flowrouter_local_alpha_seed42.json"), + "operator-signals", +); + +validateDoc( + "schemas/flowmemory/hardware-control-plane-handoff.schema.json", + readJson("fixtures/hardware/flowrouter_control_plane_handoff_seed42.json"), + "control-plane-handoff", +); + +validateDoc( + "hardware/simulator/schemas/negative_validation_report.schema.json", + readJson("fixtures/hardware/flowrouter_negative_validation_seed42.json"), + "negative-validation-report", +); + +if (process.exitCode) { + process.exit(process.exitCode); +} diff --git a/docs/agent-runs/hardware-signals/AUDIT.md b/docs/agent-runs/hardware-signals/AUDIT.md new file mode 100644 index 00000000..77597955 --- /dev/null +++ b/docs/agent-runs/hardware-signals/AUDIT.md @@ -0,0 +1,103 @@ +# Hardware Signals Completion Audit + +Last updated: 2026-05-14 + +## Objective Restatement + +Add optional FlowChain hardware/operator-signal fixtures and integration hooks +inside the allowed hardware scope, keep hardware optional, verify the eight +named checks, and leave PR-ready evidence. + +## Prompt-To-Artifact Checklist + +| Requirement | Evidence | Status | +| --- | --- | --- | +| Create `docs/agent-runs/hardware-signals/PLAN.md` first | `PLAN.md` exists and records branch, worktree, allowed folders, forbidden folders, objective, and acceptance checks. | Complete | +| Create `docs/agent-runs/hardware-signals/CHECKLIST.md` first | `CHECKLIST.md` exists and records completed hardware, product-e2e, and l1-e2e checks. | Complete | +| Create `docs/agent-runs/hardware-signals/EXPERIMENTS.md` first | `EXPERIMENTS.md` exists and records commands/results. | Complete | +| Create `docs/agent-runs/hardware-signals/NOTES.md` first | `NOTES.md` exists and records source docs, implementation notes, verification notes, blocker history, and final resolution. | Complete | +| Stay inside allowed folders | `node docs/agent-runs/hardware-signals/SCOPE_CHECK.mjs` passed: current dirty files are under `hardware/`, `fixtures/hardware/`, `schemas/flowmemory/hardware-control-plane-handoff.schema.json`, and `docs/agent-runs/hardware-signals/`. | Complete | +| Avoid forbidden folders | Product-e2e generated side effects outside scope were restored; no forbidden-path edits remain in `git status --short`. | Complete | +| `npm run flowchain:hardware:smoke` passes | Passed: `smoke passed: raw packets, operator signals, control-plane handoff, fixture drift check, and 12 negative cases`. | Complete | +| Deterministic heartbeat fixture | `hardware/fixtures/flowrouter_sample_seed42.json` has `heartbeat`; projected into `hardwareNodes` and `heartbeat` signal envelope. | Complete | +| Deterministic alert fixture | `emergency_offline_signal` exists and projects into `alerts` and `challenges`. | Complete | +| Deterministic receipt relay fixture | `compact_receipt_relay` exists and projects into `workReceipts` and `finalityReceipts`. | Complete | +| Deterministic verifier digest fixture | `verifier_report_digest_relay` exists and projects into `verifierReports`. | Complete | +| Deterministic bridge alert fixture | `bridge_alert` exists and projects into `bridgeAlerts` and `alerts`. | Complete | +| Deterministic NFC metadata fixture | `nfc_memory_cartridge_metadata` exists and projects into `artifactCommitments` and `memoryCells`. | Complete | +| Deterministic peer hint fixture if applicable | `peer_hint` packet/schema exists and projects into `peerHints`. | Complete | +| Deterministic node health fixture if applicable | `node_health` packet/schema exists and projects into `nodeHealth`. | Complete | +| Negative fixture rejects malformed IDs | `heartbeat_malformed_device_id` rejected in `flowrouter_negative_validation_seed42.json`. | Complete | +| Negative fixture rejects oversized payloads | `receipt_relay_payload_exceeds_control_budget` and `operator_envelope_payload_exceeds_control_budget` rejected. | Complete | +| Negative fixture rejects stale timestamps | `heartbeat_stale_timestamp` rejected. | Complete | +| Negative fixture rejects duplicate signals | `operator_projection_duplicate_signal_id` rejected. | Complete | +| Negative fixture rejects secret-shaped payloads | `nfc_metadata_secret_shaped_pointer` rejected; fixture scan found no secret-shaped strings. | Complete | +| Generated fixtures contain no secrets | `node docs/agent-runs/hardware-signals/NO_SECRET_FIXTURE_SCAN.mjs` found no secret-shaped strings in the generated hardware fixtures. | Complete | +| Raw packet fixtures have schemas | Raw packet keys were compared to `hardware/simulator/schemas/*.schema.json`; every packet type has a schema file. | Complete | +| Full JSON Schema validation passes | `node docs/agent-runs/hardware-signals/AJV_2020_VALIDATION.mjs` passed for every raw packet fixture, the operator projection, the control-plane handoff, and the negative validation report. | Complete | +| Signal schemas are documented | `hardware/simulator/README.md`, `fixtures/hardware/README.md`, and `hardware/flowrouter/FLOWCHAIN_LOCAL_ALPHA_SIGNALS.md` document packet types, projection shape, validation commands, and boundaries. | Complete | +| Control-plane/dashboard handoff shape stable and documented | `fixtures/hardware/flowrouter_control_plane_handoff_seed42.json`, `schemas/flowmemory/hardware-control-plane-handoff.schema.json`, and docs define read-only optional merge collections/id fields/workbench records. | Complete | +| Meshtastic/LoRa remains low-bandwidth control signaling only | `hardware/lora-sidecar/CONTROL_MESSAGE_INVENTORY.md`, `hardware/README.md`, and `hardware/flowrouter/FLOWCHAIN_LOCAL_ALPHA_SIGNALS.md` explicitly reject broadband, app traffic, artifact transfer, and raw AI/model/media transfer over LoRa. | Complete | +| Unsafe claims remain blocked | `node infra/scripts/check-unsafe-claims.mjs` passed. Additional phrase review over changed files found only negative/guardrail statements for manufacturing, broadband/LoRa, ISP replacement, production bridge, public validators, AI-on-chain, and free-storage terms. | Complete | +| Hardware remains optional and cannot block local chain startup | Handoff has `hardwareRequiredForPrivateTestnet=false`; node health has `chain_startup_blocking=false`; bridge alerts have `doesNotBlockLocalChain=true`; docs repeat the boundary. | Complete | +| `git diff --check` passes | Passed; only Git line-ending warnings were emitted. | Complete | +| `npm run flowchain:product-e2e` still passes after changes | Passed after rebasing onto `origin/main` at `14f378b`, which includes PR #132's default-vs-audit Slither policy fix. | Complete | +| If `npm run flowchain:l1-e2e` exists, run it last | The script exists after the rebase and `npm run flowchain:l1-e2e` passed after `flowchain:product-e2e`. | Complete | +| PR output includes fixture list and exact commands run | `EXPERIMENTS.md` records exact commands; final handoff should list raw packet, operator projection, handoff, negative report, and new node/peer schemas. | Ready | +| State optional integration points | `FLOWCHAIN_LOCAL_ALPHA_SIGNALS.md` and this audit identify read-only control-plane collections, workbench records, optional smoke row, and no-startup-blocking boundary. | Ready | +| GitHub PR state checked | Draft PR #139 is open at `https://github.com/FlowmemoryAI/FlowMemory/pull/139`; GitHub CI passed after the hygiene literal fix. | Complete | +| GitHub hardware issue handoff | Added and later updated #105 status comment with fixture list, passing scope/hardware/AJV/no-secret/claim checks, optional handoff shape, retry docs, and #131 blocker reference: `https://github.com/FlowmemoryAI/FlowMemory/issues/105#issuecomment-4446712093`. | Complete | +| Changed-file scope manifest | `CHANGED_FILES.md` lists the current modified/untracked files and confirms they remain inside allowed hardware-signals scope. | Complete | +| Retry path documented | `RETRY_AFTER_131.md` records the exact commands and completion rule that were used after #131 landed. | Complete | + +## Product E2E Blocker Resolution + +The hardware/signals work is complete inside its allowed scope. The earlier +product-e2e blocker was resolved by rebasing this branch onto `origin/main` at +`14f378b`, which merged PR #132 and changed the default contract hardening gate +so Slither remains explicit audit tooling instead of running merely because it +is installed on `PATH`. + +Evidence: + +- `slither` is installed at `C:\Users\ntrap\AppData\Roaming\Python\Python311\Scripts\slither.exe`. +- Before rebasing, `npm run flowchain:product-e2e` still failed in this worktree at the known `contracts/bridge/BaseBridgeLockbox.sol` Slither findings. +- GitHub issue #131 was closed by the owning HQ/contracts policy path after PR #132 merged. +- After rebasing onto `origin/main`, exact `npm run flowchain:product-e2e` passed in the unmodified local environment with Slither still on `PATH`. +- The new `npm run flowchain:l1-e2e` wrapper exists after the rebase and passed when run last. +- Product/L1 e2e generated broader launch/dashboard/service artifacts during the run; those side effects were restored so the branch keeps only allowed hardware-signals changes. + +Historical re-checks before PR #132 merged: + +- `git fetch --all --prune` found `origin/main` still at `9b025c5`. +- Local branch remains two commits behind `origin/main`, but those commits add long-loop/HQ launcher docs/scripts and do not change the Slither blocker. +- GitHub issue #131 is still open. +- PR #110, `[codex] harden bridge lockbox settlement spine`, is still open and draft. +- No merged source-of-truth fix is available to make this hardware branch pass the exact `npm run flowchain:product-e2e` gate with local Slither on `PATH`. +- Added hardware-signals blocker handoff comment to #131: + `https://github.com/FlowmemoryAI/FlowMemory/issues/131#issuecomment-4446678297`. + +Read-only contracts-branch check: + +- `origin/agent/full-l1-contracts` is at `497c3b1`. +- That branch does not provide a newer source-of-truth unblock for this hardware worktree. +- The bridge lockbox shape still routes native releases through `releaseNative(...) -> _recordRelease(...) -> recipient.call{value: amount}("")`; `_recordRelease` has a zero-recipient check, but Slither still flags the public `releaseNative` parameter and the low-level native call. + +Latest blocker re-check: + +- `git fetch --all --prune` completed. +- GitHub issue #131 is still open as of 2026-05-14T01:54:48Z. +- PR #110 is still open and draft. +- `origin/main` remains at `9b025c5`; no merged source-of-truth unblock exists. +- This branch is two commits behind `origin/main`, but those commits add long-loop/HQ launcher material outside this task's allowed hardware-signals edit scope and do not change the product-e2e Slither blocker. The branch was not rebased from this worktree. +- Final re-check in this run: #131 remains open as of 2026-05-14T02:00:17Z, and PR #110 remains open/draft as of 2026-05-14T01:58:27Z. +- Follow-up re-check: #131 remains open as of 2026-05-14T02:09:46Z, and PR #110 remains open/draft as of 2026-05-14T01:58:27Z. +- Later source-of-truth update: #131 closed after PR #132 merged to `main` as `14f378b`. +- Final retry for this branch: `git rebase origin/main`, `npm run flowchain:product-e2e`, and `npm run flowchain:l1-e2e` passed. PR #110 remains open/draft, but it no longer blocks the default product/L1 e2e gate for this branch. + +## Conclusion + +The prompt-to-artifact audit shows all eight numbered checks are complete after +the rebase and final e2e reruns. Explicit Slither audit findings remain a +contracts/security follow-up, but they no longer block the default product/L1 +e2e gate for this hardware-signals branch. diff --git a/docs/agent-runs/hardware-signals/CHANGED_FILES.md b/docs/agent-runs/hardware-signals/CHANGED_FILES.md new file mode 100644 index 00000000..e0d4e635 --- /dev/null +++ b/docs/agent-runs/hardware-signals/CHANGED_FILES.md @@ -0,0 +1,55 @@ +# Hardware Signals Changed Files + +Last checked: 2026-05-14 + +## Scope Result + +All files in the hardware-signals PR diff are inside the allowed scope: + +- `hardware/` +- `fixtures/hardware/` +- `schemas/flowmemory/` hardware handoff schema only +- `docs/agent-runs/hardware-signals/` + +No committed PR diff files are under forbidden folders such as `contracts/`, +`crates/`, `services/`, `apps/dashboard/`, or `crypto/`. Broader generated +artifacts from product/L1 e2e runs were restored after verification. + +## Modified Files In PR + +- `fixtures/hardware/README.md` +- `fixtures/hardware/flowrouter_control_plane_handoff_seed42.json` +- `fixtures/hardware/flowrouter_local_alpha_seed42.json` +- `fixtures/hardware/flowrouter_negative_validation_seed42.json` +- `hardware/README.md` +- `hardware/fixtures/flowrouter_sample_seed42.json` +- `hardware/flowrouter/FLOWCHAIN_LOCAL_ALPHA_SIGNALS.md` +- `hardware/flowrouter/README.md` +- `hardware/lora-sidecar/CONTROL_MESSAGE_INVENTORY.md` +- `hardware/simulator/README.md` +- `hardware/simulator/flowrouter_sim.py` +- `hardware/simulator/schemas/dashboard_feed.schema.json` +- `hardware/simulator/schemas/flowchain_operator_signals.schema.json` +- `schemas/flowmemory/hardware-control-plane-handoff.schema.json` + +## Added Files In PR + +- `docs/agent-runs/hardware-signals/AUDIT.md` +- `docs/agent-runs/hardware-signals/AJV_2020_VALIDATION.mjs` +- `docs/agent-runs/hardware-signals/CHECKLIST.md` +- `docs/agent-runs/hardware-signals/EXPERIMENTS.md` +- `docs/agent-runs/hardware-signals/NOTES.md` +- `docs/agent-runs/hardware-signals/NO_SECRET_FIXTURE_SCAN.mjs` +- `docs/agent-runs/hardware-signals/PLAN.md` +- `docs/agent-runs/hardware-signals/PR_SUMMARY.md` +- `docs/agent-runs/hardware-signals/CHANGED_FILES.md` +- `docs/agent-runs/hardware-signals/RETRY_AFTER_131.md` +- `docs/agent-runs/hardware-signals/SCOPE_CHECK.mjs` +- `hardware/simulator/schemas/node_health.schema.json` +- `hardware/simulator/schemas/peer_hint.schema.json` + +## Final Completion Item + +The changed-file scope is clean for hardware review, exact +`npm run flowchain:product-e2e` passed after rebasing onto `origin/main` at +`14f378b`, and `npm run flowchain:l1-e2e` passed last. diff --git a/docs/agent-runs/hardware-signals/CHECKLIST.md b/docs/agent-runs/hardware-signals/CHECKLIST.md new file mode 100644 index 00000000..404f84a0 --- /dev/null +++ b/docs/agent-runs/hardware-signals/CHECKLIST.md @@ -0,0 +1,35 @@ +# Hardware Signals Checklist + +- [x] Create run tracking files before implementation edits. +- [x] Confirm GitHub/local task state has no scope conflict beyond the recorded branch-label divergence. +- [x] Inspect existing hardware simulator, schemas, fixtures, and root npm scripts. +- [x] Keep edits inside allowed folders. +- [x] Add or confirm deterministic positive fixtures: + - [x] heartbeat + - [x] alert/offline alert + - [x] receipt relay + - [x] verifier digest + - [x] bridge alert + - [x] NFC metadata + - [x] peer hint + - [x] node health +- [x] Add or confirm negative fixture coverage: + - [x] malformed IDs + - [x] oversized payloads + - [x] stale timestamps + - [x] duplicate signals + - [x] secret-shaped payloads +- [x] Document signal schemas. +- [x] Document control-plane/dashboard handoff shape. +- [x] Preserve Meshtastic/LoRa low-bandwidth control-only boundary. +- [x] Preserve optional hardware startup boundary. +- [x] Run changed-file scope check: `node docs/agent-runs/hardware-signals/SCOPE_CHECK.mjs`. +- [x] Run AJV 2020 schema validation: `node docs/agent-runs/hardware-signals/AJV_2020_VALIDATION.mjs`. +- [x] Run generated-fixture no-secret scan: `node docs/agent-runs/hardware-signals/NO_SECRET_FIXTURE_SCAN.mjs`. +- [x] Run unsafe-claim scan: `node infra/scripts/check-unsafe-claims.mjs`. +- [x] Run `npm run flowchain:hardware:smoke`. +- [x] Run simulator/unit tests if separate commands exist. +- [x] Run `git diff --check`. +- [x] Run `npm run flowchain:product-e2e` with the unmodified current environment. + - Passed after rebasing onto `origin/main` at `14f378b`, which includes PR #132's default-vs-audit Slither policy fix. +- [x] Check for `npm run flowchain:l1-e2e`; the script now exists after the rebase and passed when run last. diff --git a/docs/agent-runs/hardware-signals/EXPERIMENTS.md b/docs/agent-runs/hardware-signals/EXPERIMENTS.md new file mode 100644 index 00000000..65cee745 --- /dev/null +++ b/docs/agent-runs/hardware-signals/EXPERIMENTS.md @@ -0,0 +1,35 @@ +# Hardware Signals Experiments + +This run is fixture-first. No physical hardware, manufacturing, firmware flashing, radio deployment, or normal-internet-over-LoRa experiment is in scope. + +## Planned Checks + +| Check | Command or Method | Result | +| --- | --- | --- | +| Python compile | `python -m py_compile hardware\simulator\flowrouter_sim.py` | Passed | +| Changed-file scope check | `node docs/agent-runs/hardware-signals/SCOPE_CHECK.mjs` | Passed; 27 changed/untracked paths all inside allowed scope | +| Negative cases | `python hardware/simulator/flowrouter_sim.py --run-negative-cases --seed 42` | Passed; 12/12 rejected | +| Fixture generation | `python hardware/simulator/flowrouter_sim.py --generate-fixtures --seed 42` | Passed; wrote raw, operator, handoff, and negative fixtures | +| Packet schema coverage | Compared raw packet keys in `hardware/fixtures/flowrouter_sample_seed42.json` to `hardware/simulator/schemas/*.schema.json` | Passed; schemas present for all packet fixtures | +| Full JSON Schema validation | `node docs/agent-runs/hardware-signals/AJV_2020_VALIDATION.mjs` | Passed; validated all raw packets, operator projection, control-plane handoff, and negative report | +| Fixture secret scan | `node docs/agent-runs/hardware-signals/NO_SECRET_FIXTURE_SCAN.mjs` | Passed; no secret-shaped strings in generated hardware fixtures | +| Unsafe-claim scan | `node infra/scripts/check-unsafe-claims.mjs` | Passed | +| Hardware overclaim phrase review | Searched changed files for manufacturing, broadband/LoRa, ISP replacement, production bridge, public validator, AI-on-chain, and free-storage phrases | Reviewed; matches were negative/guardrail language only | +| Raw packet validation | `python hardware/simulator/flowrouter_sim.py --validate-file hardware/fixtures/flowrouter_sample_seed42.json` | Passed | +| Operator fixture validation | `python hardware/simulator/flowrouter_sim.py --validate-operator-file fixtures/hardware/flowrouter_local_alpha_seed42.json` | Passed | +| Handoff validation | `python hardware/simulator/flowrouter_sim.py --validate-handoff-file fixtures/hardware/flowrouter_control_plane_handoff_seed42.json` | Passed | +| Negative report validation | `python hardware/simulator/flowrouter_sim.py --validate-negative-report-file fixtures/hardware/flowrouter_negative_validation_seed42.json` | Passed | +| Hardware smoke | `npm run flowchain:hardware:smoke` | Passed; 12 negative cases | +| Diff whitespace | `git diff --check` | Passed | +| Product e2e, unmodified environment | `npm run flowchain:product-e2e` | Failed before product checks because optional Slither was present on PATH and reported existing `contracts/bridge/BaseBridgeLockbox.sol` findings outside hardware scope | +| Product e2e, Slither absent from PATH | `$oldPath = $env:Path; $slitherSource = (Get-Command slither -ErrorAction SilentlyContinue).Source; if ($slitherSource) { $slitherDir = Split-Path -Parent $slitherSource; $env:Path = (($oldPath -split ';') | Where-Object { $_ -and (-not [string]::Equals($_.TrimEnd('\\'), $slitherDir.TrimEnd('\\'), [System.StringComparison]::OrdinalIgnoreCase)) }) -join ';' }; npm run flowchain:product-e2e; $code = $LASTEXITCODE; $env:Path = $oldPath; exit $code` | Passed; matches repo docs that Slither is audit-environment tooling unless explicitly required | +| Product e2e, historical completion audit rerun | `npm run flowchain:product-e2e` | Failed before PR #132 landed because the default hardening gate still ran local Slither when present on `PATH` | +| Product e2e, latest source-of-truth retry | `git fetch --all --prune`; `git rebase origin/main`; `npm run flowchain:product-e2e` | Passed after rebasing onto `origin/main` at `14f378b`; default hardening now warns that Slither is optional instead of failing merely because Slither is on `PATH` | +| L1 e2e if present | `npm run flowchain:l1-e2e` | Passed after `flowchain:product-e2e`; this script exists after the rebase | + +## Constraints + +- Fixtures must be deterministic and small. +- Fixtures must not contain secrets, private keys, mnemonics, seed phrases, RPC credentials, API keys, or webhook URLs. +- Meshtastic/LoRa signal examples remain low-bandwidth control messages only. +- Hardware signals are optional advisory inputs and must not block local chain startup. diff --git a/docs/agent-runs/hardware-signals/NOTES.md b/docs/agent-runs/hardware-signals/NOTES.md new file mode 100644 index 00000000..8bcbd2a6 --- /dev/null +++ b/docs/agent-runs/hardware-signals/NOTES.md @@ -0,0 +1,81 @@ +# Hardware Signals Notes + +## Source Docs Read + +- `docs/START_HERE.md` +- `docs/FLOWMEMORY_HQ_CONTEXT.md` +- `docs/CURRENT_STATE.md` +- `docs/ROOTFLOW_V0.md` +- `docs/FLOW_MEMORY_V0.md` +- `docs/V0_LAUNCH_ACCEPTANCE.md` +- `docs/ISSUE_BACKLOG.md` +- `docs/PR_PROCESS.md` +- `docs/agent-goals/full-l1/hardware-signals.md` + +## Initial Boundaries + +- Hardware remains a research POC and optional operator-signal layer. +- Heavy AI, memory, model, media, and artifact data stays off-chain. +- Meshtastic and LoRa are low-bandwidth control signaling paths, not normal internet bandwidth. +- Control-plane/dashboard integration should consume stable deterministic handoff JSON without requiring live radios or hardware devices. + +## GitHub Reconciliation + +- GitHub issue #105 is open and matches this run's hardware/operator-signal fixture scope and acceptance criteria. +- Issue #105 names `agent/full-l1-hardware`; this run is explicitly assigned `agent/l1-loop-hardware-signals` and the local branch matches that assignment. Scope and acceptance are aligned, so the branch-name difference is recorded here rather than changing worktrees. + +## Baseline Checks + +- `npm run flowchain:hardware:smoke` passed before implementation edits with 8 existing negative cases. + +## Implementation Notes + +- Added explicit `node_health` and `peer_hint` simulator packets and schemas. +- Projected node health into `nodeHealth` and peer hints into `peerHints` in the operator fixture, handoff collections, id-field map, and workbench records. +- Added semantic validation for fixture timestamps, deterministic ID shapes, duplicate signal IDs, and secret-shaped payload strings. +- Regenerated seed 42 fixtures. The final hardware smoke reports 12 negative cases. +- Before PR #132 landed, `npm run flowchain:product-e2e` passed only when the user-local Slither script directory was removed from `PATH` for that command. After rebasing onto `origin/main` at `14f378b`, the exact unmodified command passed with Slither still on `PATH`. + +## Verification Notes + +- Current verification reran simulator compile, fixture validators, negative cases, hardware smoke, `git diff --check`, product e2e, and l1 e2e. +- `node docs/agent-runs/hardware-signals/SCOPE_CHECK.mjs` passed; 27 changed/untracked paths are inside allowed hardware-signals scope. +- A raw `npm run flowchain:product-e2e` run failed before product checks because optional Slither is installed locally and reported existing `contracts/bridge/BaseBridgeLockbox.sol` findings. Contract edits are forbidden for this task. +- Reran `npm run flowchain:product-e2e` with only the user-local Slither script directory removed from `PATH`; it passed and still ran full smoke, contracts build/tests, dashboard build, hardware smoke, wallet smoke, bridge local-credit smoke, and product checks. +- Product e2e generated non-hardware artifacts during full smoke; those generated side effects were restored, leaving only allowed hardware/signals files dirty. +- After rebasing onto `origin/main`, root `package.json` defines `flowchain:l1-e2e`; `npm run flowchain:l1-e2e` passed when run last. +- `node docs/agent-runs/hardware-signals/NO_SECRET_FIXTURE_SCAN.mjs` passed; broad changed-file secret scans intentionally hit simulator denylist literals and the in-memory negative-case payload. +- Raw packet fixture keys all have matching simulator schema files. +- Independent AJV 2020 validation passed for every raw packet, the operator projection, the control-plane handoff, and the negative validation report. +- `node infra/scripts/check-unsafe-claims.mjs` passed; targeted changed-file phrase review found only negative/guardrail uses of manufacturing, broadband/LoRa, ISP replacement, production bridge, public validator, AI-on-chain, and free-storage terms. + +## Completion Audit + +The hardware/operator-signal implementation satisfies the allowed-scope artifact requirements. The overall 8/8 objective is complete after rebasing onto `origin/main` at `14f378b`, rerunning exact `npm run flowchain:product-e2e`, and running `npm run flowchain:l1-e2e` last. + +Resolution history: + +- `slither` is installed at `C:\Users\ntrap\AppData\Roaming\Python\Python311\Scripts\slither.exe`. +- `npm run flowchain:product-e2e` invokes `npm run flowchain:full-smoke`, then `npm run launch:candidate`, then `npm run contracts:hardening`. +- Before PR #132 landed, the Windows contract hardening script ran Slither whenever it was present on `PATH`. +- Slither reports two existing explicit-audit findings in `contracts/bridge/BaseBridgeLockbox.sol`. +- `contracts/` and `infra/scripts/` are forbidden for this hardware-signals task, so this agent did not fix or suppress the gate locally. +- GitHub issue #131, `[contracts/security] Reconcile Slither findings blocking flowchain product E2E`, was closed by the owning HQ/contracts policy path in PR #132. +- Draft PR #110 is still open/draft, but it no longer blocks default product/L1 e2e for this branch. +- Rechecked after `git fetch --all --prune` on 2026-05-14; #131 initially remained open, PR #110 remained draft, and no merged mainline fix was available for this worktree. +- Added issue #131 blocker handoff comment: https://github.com/FlowmemoryAI/FlowMemory/issues/131#issuecomment-4446678297. +- Read-only check of `origin/agent/full-l1-contracts` at `497c3b1` found no newer branch-side unblock for the product-e2e Slither blocker. +- Draft PR #139 is open at https://github.com/FlowmemoryAI/FlowMemory/pull/139 and GitHub CI passed after the hygiene literal fix. +- Added and updated hardware issue #105 status comment with fixture, scope-check, AJV, no-secret scan, unsafe-claim, optional handoff, retry-doc, and #131 blocker evidence: https://github.com/FlowmemoryAI/FlowMemory/issues/105#issuecomment-4446712093. +- Earlier recheck: #131 remained open, PR #110 remained draft, and `origin/main` was still at `9b025c5`; no merged product-e2e unblock was available at that point. +- The branch was later rebased onto `origin/main` at `14f378b` after PR #132 merged. +- Added `CHANGED_FILES.md` with the current modified/untracked file manifest and scope classification. +- Added `RETRY_AFTER_131.md` with the exact commands and completion rule to rerun after the product-e2e blocker lands. +- Final source-of-truth recheck in this run: #131 remains open as of 2026-05-14T02:00:17Z and PR #110 remains open/draft as of 2026-05-14T01:58:27Z. +- Follow-up source-of-truth recheck: #131 remains open as of 2026-05-14T02:09:46Z and PR #110 remains open/draft as of 2026-05-14T01:58:27Z. +- Latest exact `npm run flowchain:product-e2e` retry failed again on 2026-05-13T21:19:15-05:00 for the same out-of-scope Slither findings in `contracts/bridge/BaseBridgeLockbox.sol`; no new tracked side effects outside the current allowed hardware-scope files remained afterward. +- Final source-of-truth update: #131 closed after PR #132 merged to `origin/main` as `14f378b`. +- Rebasing this hardware branch onto `origin/main` picked up the default-vs-audit Slither policy without adding hardware-scope edits outside the existing PR diff. +- Exact `npm run flowchain:product-e2e` passed in the unmodified local environment with Slither still on `PATH`. +- `npm run flowchain:l1-e2e` exists after the rebase and passed when run last. +- Product/L1 e2e generated broader launch/dashboard/service artifacts during the run; those side effects were restored so only the committed hardware-signals PR changes remain. diff --git a/docs/agent-runs/hardware-signals/NO_SECRET_FIXTURE_SCAN.mjs b/docs/agent-runs/hardware-signals/NO_SECRET_FIXTURE_SCAN.mjs new file mode 100644 index 00000000..1c4bfe37 --- /dev/null +++ b/docs/agent-runs/hardware-signals/NO_SECRET_FIXTURE_SCAN.mjs @@ -0,0 +1,38 @@ +import fs from "node:fs"; + +const fixturePaths = [ + "hardware/fixtures/flowrouter_sample_seed42.json", + "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "fixtures/hardware/flowrouter_control_plane_handoff_seed42.json", + "fixtures/hardware/flowrouter_negative_validation_seed42.json", +]; + +const secretShapedPatterns = [ + new RegExp("BEGIN PRIVATE " + "KEY"), + new RegExp("PRIVATE_" + "KEY="), + /MNEMONIC=/, + /SEED_PHRASE=/, + /RPC_URL=/, + /API_KEY=/, + /WEBHOOK_URL=/, + /sk_live_/, + /sk-proj-/, + /https:\/\/hooks\.slack\.com\/services\//, +]; + +let failed = false; +for (const filePath of fixturePaths) { + const body = fs.readFileSync(filePath, "utf8"); + for (const pattern of secretShapedPatterns) { + if (pattern.test(body)) { + console.error(`secret-shaped fixture content: ${filePath}: ${pattern}`); + failed = true; + } + } +} + +if (failed) { + process.exit(1); +} + +console.log(`no secret-shaped strings in ${fixturePaths.length} generated hardware fixture files`); diff --git a/docs/agent-runs/hardware-signals/PLAN.md b/docs/agent-runs/hardware-signals/PLAN.md new file mode 100644 index 00000000..70e10704 --- /dev/null +++ b/docs/agent-runs/hardware-signals/PLAN.md @@ -0,0 +1,59 @@ +# Hardware Signals Plan + +Branch: `agent/l1-loop-hardware-signals` +Worktree: `E:\FlowMemory\flowmemory-hardware` + +## Scope + +Allowed folders: + +- `hardware/` +- `fixtures/hardware/` +- `schemas/flowmemory/` hardware/operator signal schemas only +- `docs/agent-runs/hardware-signals/` +- hardware docs under `docs/` + +Forbidden folders: + +- `crates/` +- `services/` except read-only API contract review +- `apps/dashboard/` except read-only UI contract review +- `contracts/` +- `crypto/` + +## Objective + +Extend optional FlowRouter/operator-signal fixtures and simulator integration hooks for the FlowChain private/local L1 package without making hardware mandatory for local chain startup. + +## Current Status + +Hardware/signals scope is implemented and PR-ready. The full 8/8 goal is now +green after rebasing onto `origin/main` at `14f378b`, which includes the +accepted PR #132 default-vs-audit Slither policy fix. Exact +`npm run flowchain:product-e2e` passed in the unmodified local environment, and +`npm run flowchain:l1-e2e` passed last. + +## Acceptance Checks + +1. `npm run flowchain:hardware:smoke` passes. +2. Deterministic fixtures exist for heartbeat, alert, receipt relay, verifier digest, bridge alert, NFC metadata, peer hint, and node health if applicable. +3. Negative fixtures reject malformed IDs, oversized payloads, stale timestamps, duplicate signals, and secret-shaped payloads. +4. Signal schemas are documented. +5. Control-plane/dashboard handoff shape is stable and documented. +6. Meshtastic/LoRa remains documented as low-bandwidth control signaling only. +7. Hardware work remains optional and cannot block local chain startup. +8. `npm run flowchain:product-e2e` still passes after changes. + +## Working Plan + +1. Read the required source-of-truth docs and inspect Git/GitHub state. +2. Map existing simulator fixtures, schemas, package scripts, and hardware docs. +3. Extend deterministic positive and negative operator-signal fixtures. +4. Update schema/docs for signal inventory and control-plane/dashboard handoff shape. +5. Run simulator/unit checks, hardware smoke, `git diff --check`, product e2e, and `flowchain:l1-e2e` if present. + +## Final E2E Status + +`docs/agent-runs/hardware-signals/RETRY_AFTER_131.md` records the historical +retry path used after #131 closed. The final retry passed for both exact +product E2E and L1 E2E. diff --git a/docs/agent-runs/hardware-signals/PR_SUMMARY.md b/docs/agent-runs/hardware-signals/PR_SUMMARY.md new file mode 100644 index 00000000..f09f1c00 --- /dev/null +++ b/docs/agent-runs/hardware-signals/PR_SUMMARY.md @@ -0,0 +1,96 @@ +# Hardware Signals PR Summary + +## What Changed + +- Extended the FlowRouter simulator with deterministic optional operator-signal packets for: + - heartbeat + - emergency/offline alert + - compact receipt relay + - verifier report digest relay + - bridge alert + - NFC memory cartridge metadata + - node health + - peer hint +- Added `node_health` and `peer_hint` simulator schemas. +- Regenerated seed 42 hardware fixtures: + - `hardware/fixtures/flowrouter_sample_seed42.json` + - `fixtures/hardware/flowrouter_local_alpha_seed42.json` + - `fixtures/hardware/flowrouter_control_plane_handoff_seed42.json` + - `fixtures/hardware/flowrouter_negative_validation_seed42.json` +- Expanded operator projections into `hardwareSignals`, `operatorMetadata`, `hardwareNodes`, `nodeHealth`, `peerHints`, `workReceipts`, `verifierReports`, `bridgeAlerts`, `artifactCommitments`, `memoryCells`, `challenges`, `finalityReceipts`, `alerts`, and `workbenchRecords`. +- Documented the control-plane/dashboard handoff shape and the Meshtastic/LoRa low-bandwidth control-only boundary. + +## Why It Changed + +FlowRouter/Meshtastic-style signals should be useful to local control-plane and workbench consumers without requiring physical hardware or blocking local chain startup. These fixtures provide deterministic optional operator-signal input while keeping all hardware state local-only, advisory, and reconciliation-bound. + +## Fixture List + +- Raw packet fixture: `hardware/fixtures/flowrouter_sample_seed42.json` +- Operator projection: `fixtures/hardware/flowrouter_local_alpha_seed42.json` +- Control-plane handoff: `fixtures/hardware/flowrouter_control_plane_handoff_seed42.json` +- Negative validation report: `fixtures/hardware/flowrouter_negative_validation_seed42.json` +- New packet schemas: + - `hardware/simulator/schemas/node_health.schema.json` + - `hardware/simulator/schemas/peer_hint.schema.json` + +## Optional Integration Points + +- Control plane can read `flowmemory.hardware_control_plane_handoff.local_alpha.v0` as `mode=read-only-optional-merge`. +- Stable merge ids are declared under `ingest.idFields`. +- Workbench can either render `workbenchRecords` directly or re-project canonical collections. +- `optionalSmokeRows` names `python hardware/simulator/flowrouter_sim.py --smoke` as optional hardware evidence. +- Hardware is never required for private/local chain startup: `hardwareRequiredForPrivateTestnet=false`, node health has `chainStartupBlocking=false`, and bridge alerts have `doesNotBlockLocalChain=true`. + +## Checks Run + +Passed: + +```powershell +python -m py_compile hardware\simulator\flowrouter_sim.py +node docs/agent-runs/hardware-signals/SCOPE_CHECK.mjs +python hardware/simulator/flowrouter_sim.py --run-negative-cases --seed 42 +python hardware/simulator/flowrouter_sim.py --generate-fixtures --seed 42 +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 +npm run flowchain:hardware:smoke +node docs/agent-runs/hardware-signals/AJV_2020_VALIDATION.mjs +node docs/agent-runs/hardware-signals/NO_SECRET_FIXTURE_SCAN.mjs +node infra/scripts/check-unsafe-claims.mjs +git diff --check +git fetch --all --prune +git rebase origin/main +npm run flowchain:product-e2e +npm run flowchain:l1-e2e +``` + +Conditional/pass with documented environment adjustment: + +```powershell +$oldPath = $env:Path; $slitherSource = (Get-Command slither -ErrorAction SilentlyContinue).Source; if ($slitherSource) { $slitherDir = Split-Path -Parent $slitherSource; $env:Path = (($oldPath -split ';') | Where-Object { $_ -and (-not [string]::Equals($_.TrimEnd('\\'), $slitherDir.TrimEnd('\\'), [System.StringComparison]::OrdinalIgnoreCase)) }) -join ';' }; npm run flowchain:product-e2e; $code = $LASTEXITCODE; $env:Path = $oldPath; exit $code +``` + +This passed only when the user-local Slither script directory was removed from `PATH` for the command. + +Previously blocked, now resolved: + +```powershell +npm run flowchain:product-e2e +npm run flowchain:l1-e2e +``` + +The exact product E2E command originally failed because local `slither.exe` was present on `PATH` and reported existing `missing-zero-check` and `low-level-calls` findings in `contracts/bridge/BaseBridgeLockbox.sol`. GitHub issue #131 was later closed by the owning HQ/contracts policy path in PR #132. After rebasing this branch onto `origin/main` at `14f378b`, exact `npm run flowchain:product-e2e` passed in the unmodified environment, and `npm run flowchain:l1-e2e` passed last. + +Historical blocker handoff: + +- GitHub issue: `https://github.com/FlowmemoryAI/FlowMemory/issues/131` +- Hardware evidence comment: `https://github.com/FlowmemoryAI/FlowMemory/issues/131#issuecomment-4446678297` +- PR #132 resolved the default product/L1 e2e blocker on `main`; explicit Slither audit findings remain visible through `contracts:hardening:slither`. + +## Risks, Assumptions, Follow-Ups + +- Risk: explicit `contracts:hardening:slither` still surfaces the known bridge lockbox audit findings, so do not treat this as an audit/public-readiness claim. +- Assumption: hardware fixtures remain optional advisory inputs, not authority for chain state, verifier finality, bridge settlement, or dashboard truth. +- Follow-up: keep PR #139 draft until review confirms the optional handoff shape and merge order. diff --git a/docs/agent-runs/hardware-signals/RETRY_AFTER_131.md b/docs/agent-runs/hardware-signals/RETRY_AFTER_131.md new file mode 100644 index 00000000..4f5fc5c3 --- /dev/null +++ b/docs/agent-runs/hardware-signals/RETRY_AFTER_131.md @@ -0,0 +1,50 @@ +# Retry After Issue 131 + +Use this file when GitHub issue #131 is resolved or a source-of-truth fix for +the Slither/product-e2e blocker lands on `main`. + +## Preconditions + +- #131 is closed, or the owning contracts/review agent confirms the exact + `npm run flowchain:product-e2e` gate should now pass when Slither is present + on `PATH`. +- This hardware branch has been updated only if the update is allowed by the + current task scope or explicitly approved by HQ. +- No generated side effects outside the hardware-signals scope are kept. + +## Commands + +Run from `E:\FlowMemory\flowmemory-hardware`: + +```powershell +git fetch --all --prune +gh issue view 131 --repo FlowmemoryAI/FlowMemory --json number,state,updatedAt,url +npm run flowchain:hardware:smoke +git diff --check +npm run flowchain:product-e2e +node -e "const p=require('./package.json'); console.log(Object.prototype.hasOwnProperty.call(p.scripts,'flowchain:l1-e2e') ? 'exists' : 'missing')" +``` + +If `flowchain:l1-e2e` exists after a mainline update, run it last: + +```powershell +npm run flowchain:l1-e2e +``` + +## Completion Rule + +Only mark the hardware-signals goal complete when: + +- `npm run flowchain:hardware:smoke` passes. +- `git diff --check` passes. +- Exact `npm run flowchain:product-e2e` passes in the unmodified environment. +- `npm run flowchain:l1-e2e` is either absent or passes. +- Generated side effects outside the allowed hardware-signals scope are removed. + +Then update: + +- `docs/agent-runs/hardware-signals/AUDIT.md` +- `docs/agent-runs/hardware-signals/CHECKLIST.md` +- `docs/agent-runs/hardware-signals/EXPERIMENTS.md` +- `docs/agent-runs/hardware-signals/NOTES.md` +- `docs/agent-runs/hardware-signals/PR_SUMMARY.md` diff --git a/docs/agent-runs/hardware-signals/SCOPE_CHECK.mjs b/docs/agent-runs/hardware-signals/SCOPE_CHECK.mjs new file mode 100644 index 00000000..d00a7871 --- /dev/null +++ b/docs/agent-runs/hardware-signals/SCOPE_CHECK.mjs @@ -0,0 +1,36 @@ +import { execFileSync } from "node:child_process"; + +function gitLines(args) { + const output = execFileSync("git", args, { encoding: "utf8" }).trim(); + return output ? output.split(/\r?\n/) : []; +} + +const changed = [ + ...gitLines(["diff", "--name-only"]), + ...gitLines(["diff", "--cached", "--name-only"]), + ...gitLines(["ls-files", "--others", "--exclude-standard"]), +].filter((filePath, index, allPaths) => allPaths.indexOf(filePath) === index).sort(); + +const allowedPrefixes = [ + "hardware/", + "fixtures/hardware/", + "docs/agent-runs/hardware-signals/", +]; + +const allowedExact = new Set([ + "schemas/flowmemory/hardware-control-plane-handoff.schema.json", +]); + +const forbidden = changed.filter( + (filePath) => !allowedExact.has(filePath) && !allowedPrefixes.some((prefix) => filePath.startsWith(prefix)), +); + +if (forbidden.length > 0) { + console.error("out-of-scope changed files:"); + for (const filePath of forbidden) { + console.error(`- ${filePath}`); + } + process.exit(1); +} + +console.log(`changed-file scope ok: ${changed.length} paths`); diff --git a/fixtures/hardware/README.md b/fixtures/hardware/README.md index 1122e748..6feab099 100644 --- a/fixtures/hardware/README.md +++ b/fixtures/hardware/README.md @@ -1,6 +1,6 @@ # Hardware Fixtures -Last updated: 2026-05-13 +Last updated: 2026-05-14 This folder contains local-alpha hardware projections that can be consumed by dashboard, workbench, or control-plane code without depending on live FlowRouter hardware. @@ -14,10 +14,10 @@ This folder contains local-alpha hardware projections that can be consumed by da The fixture is a `flowmemory.hardware_operator_signals.local_alpha.v0` document. It includes: -- `signalEnvelopes`: envelopes for operator metadata, heartbeat, receipt relay, verifier digest relay, offline alert/challenge input, bridge alert, and NFC memory cartridge metadata. +- `signalEnvelopes`: envelopes for operator metadata, heartbeat, node health, peer hint, 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`. +- `operatorMetadata`, `hardwareNodes`, `nodeHealth`, `peerHints`, `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 `nodeHealth`, `peerHints`, and `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: @@ -36,3 +36,5 @@ python hardware/simulator/flowrouter_sim.py --validate-negative-report-file fixt ``` These fixtures are local-only and advisory. They do not prove hardware trustlessness, production field deployment, or receipt/verifier finality. + +The negative validation report proves rejection of malformed IDs, oversized control payloads, stale timestamps, duplicate signal IDs, secret-shaped payload strings, required-hardware claims, and missing required handoff collections. diff --git a/fixtures/hardware/flowrouter_control_plane_handoff_seed42.json b/fixtures/hardware/flowrouter_control_plane_handoff_seed42.json index 94df362d..c4b88cef 100644 --- a/fixtures/hardware/flowrouter_control_plane_handoff_seed42.json +++ b/fixtures/hardware/flowrouter_control_plane_handoff_seed42.json @@ -6,7 +6,9 @@ "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." + "Bridge alerts are operator review hints and must not block local chain progress.", + "Node health signals are observability hints and cannot block private/local chain startup.", + "Peer hints describe local topology candidates only and require normal network reconciliation for sync." ], "hardwareRequiredForPrivateTestnet": false, "localOnly": true, @@ -26,7 +28,7 @@ "localOnly": true, "openedAt": "2026-05-13T17:01:30Z", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -49,7 +51,7 @@ "localOnly": true, "openedAt": "2026-05-13T17:01:35Z", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -93,7 +95,7 @@ "loraEligible": true, "payloadBytesEstimate": 136, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -157,7 +159,7 @@ "nodeId": "fr-e1e7878a2aa8", "powerState": "mains", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -183,7 +185,7 @@ "loraEligible": false, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -203,7 +205,7 @@ "observedAt": "2026-05-13T17:00:00Z", "payloadBytesEstimate": 0, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -235,7 +237,7 @@ "loraEligible": false, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -255,7 +257,7 @@ "observedAt": "2026-05-13T17:00:10Z", "payloadBytesEstimate": 0, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -287,7 +289,7 @@ "loraEligible": true, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -307,7 +309,7 @@ "observedAt": "2026-05-13T17:00:40Z", "payloadBytesEstimate": 96, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -339,7 +341,7 @@ "loraEligible": true, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -359,7 +361,7 @@ "observedAt": "2026-05-13T17:00:30Z", "payloadBytesEstimate": 128, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -392,7 +394,7 @@ "loraEligible": false, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -416,7 +418,7 @@ "observedAt": "2026-05-13T17:01:30Z", "payloadBytesEstimate": 0, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -449,7 +451,7 @@ "loraEligible": true, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -473,7 +475,7 @@ "observedAt": "2026-05-13T17:01:35Z", "payloadBytesEstimate": 136, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -506,7 +508,7 @@ "loraEligible": false, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -530,7 +532,7 @@ "observedAt": "2026-05-13T17:01:20Z", "payloadBytesEstimate": 0, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -551,6 +553,110 @@ "status": "observed", "summary": "NFC metadata pointer projected into artifact and memory references.", "transport": "local-simulator" + }, + { + "envelopeId": "hw-env-a376a5729f7a", + "id": "hw-sig-029c955f431e", + "linkedObjectIds": [ + "node-health:hardware:97477baf624a" + ], + "localOnly": true, + "loraEligible": true, + "nodeId": "fr-e1e7878a2aa8", + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "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-a376a5729f7a", + "localOnly": true, + "loraEligible": true, + "objectRefs": [ + { + "collection": "nodeHealth", + "objectId": "node-health:hardware:97477baf624a" + } + ], + "observedAt": "2026-05-13T17:01:45Z", + "payloadBytesEstimate": 112, + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "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-029c955f431e", + "signalType": "node_health", + "sourcePacketId": "node_health:1051", + "sourcePacketType": "node_health", + "status": "observed" + }, + "receivedAt": "2026-05-13T17:01:45Z", + "signalId": "hw-sig-029c955f431e", + "signalType": "node_health", + "sourcePacketType": "node_health", + "status": "observed", + "summary": "Compact node health digest that cannot block local chain startup.", + "transport": "meshtastic-control-sim" + }, + { + "envelopeId": "hw-env-e994534da0b1", + "id": "hw-sig-14652eac443e", + "linkedObjectIds": [ + "peer-hint:hardware:918e8c2b357a" + ], + "localOnly": true, + "loraEligible": true, + "nodeId": "fr-e1e7878a2aa8", + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "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-e994534da0b1", + "localOnly": true, + "loraEligible": true, + "objectRefs": [ + { + "collection": "peerHints", + "objectId": "peer-hint:hardware:918e8c2b357a" + } + ], + "observedAt": "2026-05-13T17:01:50Z", + "payloadBytesEstimate": 104, + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "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-14652eac443e", + "signalType": "peer_hint", + "sourcePacketId": "peer_hint:1052", + "sourcePacketType": "peer_hint", + "status": "observed" + }, + "receivedAt": "2026-05-13T17:01:50Z", + "signalId": "hw-sig-14652eac443e", + "signalType": "peer_hint", + "sourcePacketType": "peer_hint", + "status": "observed", + "summary": "Low-bandwidth peer hint for operator-visible local topology only.", + "transport": "meshtastic-control-sim" } ], "memoryCells": [ @@ -569,6 +675,35 @@ "updatedAt": "2026-05-13T17:01:20Z" } ], + "nodeHealth": [ + { + "chainStartupBlocking": false, + "cpuTempC": 43.5, + "diskFreeMb": 238592, + "health": "healthy", + "healthDigest": "0x7b66a2a36ba12251536a36ee9ebe2817aaf1737b15d3d34ac13ff6e55bcbe4e1", + "localOnly": true, + "loraEligible": true, + "memoryUsedMb": 512, + "nodeHealthId": "node-health:hardware:97477baf624a", + "nodeId": "fr-e1e7878a2aa8", + "observedAt": "2026-05-13T17:01:45Z", + "payloadBytesEstimate": 112, + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "queueDepth": 2, + "rootfieldId": "rootfield:hardware:flowrouter-local-alpha", + "sourcePacketType": "node_health", + "status": "observed", + "uptimeSeconds": 86442 + } + ], "operatorMetadata": [ { "displayName": "Local hardware operator fixture", @@ -581,7 +716,7 @@ "observedAt": "2026-05-13T17:00:00Z", "operatorId": "operator:local:717ffb1268b0", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -601,6 +736,36 @@ ] } ], + "peerHints": [ + { + "advertisedRoles": [ + "hardware-observer", + "digest-relay" + ], + "lastSeenAt": "2026-05-13T17:01:48Z", + "linkState": "heard", + "localOnly": true, + "loraEligible": true, + "nodeId": "fr-e1e7878a2aa8", + "normalNetworkRequiredForSync": true, + "payloadBytesEstimate": 104, + "peerHintId": "peer-hint:hardware:918e8c2b357a", + "peerId": "peer-c24b3e338337", + "peerRole": "observer", + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "rssiDbm": -88, + "snrDb": 6.5, + "sourcePacketType": "peer_hint", + "transport": "meshtastic-control-sim" + } + ], "verifierReports": [ { "localOnly": true, @@ -647,7 +812,7 @@ ] }, "environment": "local-devnet-fixture", - "generatedAt": "2026-05-13T17:01:40Z", + "generatedAt": "2026-05-13T17:01:55Z", "hardwareRequiredForPrivateTestnet": false, "ingest": { "idFields": { @@ -659,7 +824,9 @@ "hardwareNodes": "nodeId", "hardwareSignals": "signalId", "memoryCells": "memoryCellId", + "nodeHealth": "nodeHealthId", "operatorMetadata": "metadataId", + "peerHints": "peerHintId", "verifierReports": "reportId", "workReceipts": "receiptId" }, @@ -670,6 +837,8 @@ "hardwareSignals", "operatorMetadata", "hardwareNodes", + "nodeHealth", + "peerHints", "workReceipts", "verifierReports", "bridgeAlerts", @@ -715,7 +884,7 @@ "id": "artifact:hardware:dffc8c3e46ff", "kind": "NFC cartridge artifact reference", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -765,7 +934,7 @@ "id": "bridge-alert:hardware:8662b11ba0d4", "kind": "Hardware bridge alert", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -783,7 +952,7 @@ "loraEligible": true, "payloadBytesEstimate": 136, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -829,7 +998,7 @@ "id": "challenge:hardware:5f54ef32073d", "kind": "Offline alert challenge candidate", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -880,7 +1049,7 @@ "id": "hw-sig-583c993be535", "kind": "Hardware operator signal", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -897,7 +1066,7 @@ "loraEligible": false, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -917,7 +1086,7 @@ "observedAt": "2026-05-13T17:00:00Z", "payloadBytesEstimate": 0, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -965,7 +1134,7 @@ "id": "hw-sig-cd45a2ab8b1d", "kind": "Hardware operator signal", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -982,7 +1151,7 @@ "loraEligible": false, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1002,7 +1171,7 @@ "observedAt": "2026-05-13T17:00:10Z", "payloadBytesEstimate": 0, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1050,7 +1219,7 @@ "id": "hw-sig-5dd1dbdec22d", "kind": "Hardware operator signal", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1067,7 +1236,7 @@ "loraEligible": true, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1087,7 +1256,7 @@ "observedAt": "2026-05-13T17:00:40Z", "payloadBytesEstimate": 96, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1135,7 +1304,7 @@ "id": "hw-sig-d7087f4f3257", "kind": "Hardware operator signal", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1152,7 +1321,7 @@ "loraEligible": true, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1172,7 +1341,7 @@ "observedAt": "2026-05-13T17:00:30Z", "payloadBytesEstimate": 128, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1220,7 +1389,7 @@ "id": "hw-sig-2474b9971f93", "kind": "Hardware operator signal", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1238,7 +1407,7 @@ "loraEligible": false, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1262,7 +1431,7 @@ "observedAt": "2026-05-13T17:01:30Z", "payloadBytesEstimate": 0, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1310,7 +1479,7 @@ "id": "hw-sig-590bff4da4b1", "kind": "Hardware operator signal", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1328,7 +1497,7 @@ "loraEligible": true, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1352,7 +1521,7 @@ "observedAt": "2026-05-13T17:01:35Z", "payloadBytesEstimate": 136, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1400,7 +1569,7 @@ "id": "hw-sig-97f6fef1f868", "kind": "Hardware operator signal", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1418,7 +1587,7 @@ "loraEligible": false, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1442,7 +1611,7 @@ "observedAt": "2026-05-13T17:01:20Z", "payloadBytesEstimate": 0, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1467,6 +1636,176 @@ "status": "observed", "summary": "NFC metadata pointer projected into artifact and memory references.", "title": "nfc_memory_cartridge_metadata" + }, + { + "facts": [ + { + "label": "node", + "value": "fr-e1e7878a2aa8" + }, + { + "label": "transport", + "value": "meshtastic-control-sim" + }, + { + "label": "source packet", + "value": "node_health" + }, + { + "label": "linked objects", + "value": "node-health:hardware:97477baf624a" + } + ], + "id": "hw-sig-029c955f431e", + "kind": "Hardware operator signal", + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "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-a376a5729f7a", + "id": "hw-sig-029c955f431e", + "linkedObjectIds": [ + "node-health:hardware:97477baf624a" + ], + "localOnly": true, + "loraEligible": true, + "nodeId": "fr-e1e7878a2aa8", + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "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-a376a5729f7a", + "localOnly": true, + "loraEligible": true, + "objectRefs": [ + { + "collection": "nodeHealth", + "objectId": "node-health:hardware:97477baf624a" + } + ], + "observedAt": "2026-05-13T17:01:45Z", + "payloadBytesEstimate": 112, + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "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-029c955f431e", + "signalType": "node_health", + "sourcePacketId": "node_health:1051", + "sourcePacketType": "node_health", + "status": "observed" + }, + "receivedAt": "2026-05-13T17:01:45Z", + "signalId": "hw-sig-029c955f431e", + "signalType": "node_health", + "sourcePacketType": "node_health", + "status": "observed", + "summary": "Compact node health digest that cannot block local chain startup.", + "transport": "meshtastic-control-sim" + }, + "status": "observed", + "summary": "Compact node health digest that cannot block local chain startup.", + "title": "node_health" + }, + { + "facts": [ + { + "label": "node", + "value": "fr-e1e7878a2aa8" + }, + { + "label": "transport", + "value": "meshtastic-control-sim" + }, + { + "label": "source packet", + "value": "peer_hint" + }, + { + "label": "linked objects", + "value": "peer-hint:hardware:918e8c2b357a" + } + ], + "id": "hw-sig-14652eac443e", + "kind": "Hardware operator signal", + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "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-e994534da0b1", + "id": "hw-sig-14652eac443e", + "linkedObjectIds": [ + "peer-hint:hardware:918e8c2b357a" + ], + "localOnly": true, + "loraEligible": true, + "nodeId": "fr-e1e7878a2aa8", + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "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-e994534da0b1", + "localOnly": true, + "loraEligible": true, + "objectRefs": [ + { + "collection": "peerHints", + "objectId": "peer-hint:hardware:918e8c2b357a" + } + ], + "observedAt": "2026-05-13T17:01:50Z", + "payloadBytesEstimate": 104, + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "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-14652eac443e", + "signalType": "peer_hint", + "sourcePacketId": "peer_hint:1052", + "sourcePacketType": "peer_hint", + "status": "observed" + }, + "receivedAt": "2026-05-13T17:01:50Z", + "signalId": "hw-sig-14652eac443e", + "signalType": "peer_hint", + "sourcePacketType": "peer_hint", + "status": "observed", + "summary": "Low-bandwidth peer hint for operator-visible local topology only.", + "transport": "meshtastic-control-sim" + }, + "status": "observed", + "summary": "Low-bandwidth peer hint for operator-visible local topology only.", + "title": "peer_hint" } ], "memoryCells": [ @@ -1492,7 +1831,7 @@ "id": "memory:hardware:9f8165ee1a5a", "kind": "Hardware memory cell candidate", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1518,6 +1857,68 @@ "title": "memory:hardware:9f8165ee1a5a" } ], + "nodeHealth": [ + { + "facts": [ + { + "label": "node", + "value": "fr-e1e7878a2aa8" + }, + { + "label": "queue depth", + "value": "2" + }, + { + "label": "health digest", + "value": "0x7b66a2a36ba12251536a36ee9ebe2817aaf1737b15d3d34ac13ff6e55bcbe4e1" + }, + { + "label": "blocks startup", + "value": "false" + } + ], + "id": "node-health:hardware:97477baf624a", + "kind": "Hardware node health", + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "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": { + "chainStartupBlocking": false, + "cpuTempC": 43.5, + "diskFreeMb": 238592, + "health": "healthy", + "healthDigest": "0x7b66a2a36ba12251536a36ee9ebe2817aaf1737b15d3d34ac13ff6e55bcbe4e1", + "localOnly": true, + "loraEligible": true, + "memoryUsedMb": 512, + "nodeHealthId": "node-health:hardware:97477baf624a", + "nodeId": "fr-e1e7878a2aa8", + "observedAt": "2026-05-13T17:01:45Z", + "payloadBytesEstimate": 112, + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "queueDepth": 2, + "rootfieldId": "rootfield:hardware:flowrouter-local-alpha", + "sourcePacketType": "node_health", + "status": "observed", + "uptimeSeconds": 86442 + }, + "status": "observed", + "summary": "Compact node health digest for local operator observability.", + "title": "healthy" + } + ], "operatorMetadata": [ { "facts": [ @@ -1541,7 +1942,7 @@ "id": "operator-metadata:hardware:874d3583a3b5", "kind": "Hardware operator metadata", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1559,7 +1960,7 @@ "observedAt": "2026-05-13T17:00:00Z", "operatorId": "operator:local:717ffb1268b0", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1583,6 +1984,69 @@ "title": "Local hardware operator fixture" } ], + "peerHints": [ + { + "facts": [ + { + "label": "node", + "value": "fr-e1e7878a2aa8" + }, + { + "label": "peer", + "value": "peer-c24b3e338337" + }, + { + "label": "transport", + "value": "meshtastic-control-sim" + }, + { + "label": "link state", + "value": "heard" + } + ], + "id": "peer-hint:hardware:918e8c2b357a", + "kind": "Hardware peer hint", + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "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": { + "advertisedRoles": [ + "hardware-observer", + "digest-relay" + ], + "lastSeenAt": "2026-05-13T17:01:48Z", + "linkState": "heard", + "localOnly": true, + "loraEligible": true, + "nodeId": "fr-e1e7878a2aa8", + "normalNetworkRequiredForSync": true, + "payloadBytesEstimate": 104, + "peerHintId": "peer-hint:hardware:918e8c2b357a", + "peerId": "peer-c24b3e338337", + "peerRole": "observer", + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "rssiDbm": -88, + "snrDb": 6.5, + "sourcePacketType": "peer_hint", + "transport": "meshtastic-control-sim" + }, + "status": "observed", + "summary": "Low-bandwidth peer hint for local topology review.", + "title": "peer-c24b3e338337" + } + ], "provenance": [ { "facts": [ @@ -1606,7 +2070,7 @@ "id": "hardware-operator-signal-fixture", "kind": "Hardware operator signal fixture", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1648,7 +2112,7 @@ "id": "receipt:hardware:1e92f586a767", "kind": "Hardware WorkReceipt relay", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1705,7 +2169,7 @@ "id": "report:hardware:bd367723d169", "kind": "Hardware VerifierReport relay", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", diff --git a/fixtures/hardware/flowrouter_local_alpha_seed42.json b/fixtures/hardware/flowrouter_local_alpha_seed42.json index 9c271de0..a3b9a973 100644 --- a/fixtures/hardware/flowrouter_local_alpha_seed42.json +++ b/fixtures/hardware/flowrouter_local_alpha_seed42.json @@ -11,7 +11,7 @@ "localOnly": true, "openedAt": "2026-05-13T17:01:30Z", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -34,7 +34,7 @@ "localOnly": true, "openedAt": "2026-05-13T17:01:35Z", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -73,7 +73,9 @@ "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." + "Bridge alerts are operator review hints and must not block local chain progress.", + "Node health signals are observability hints and cannot block private/local chain startup.", + "Peer hints describe local topology candidates only and require normal network reconciliation for sync." ], "hardwareRequiredForPrivateTestnet": false, "localOnly": true, @@ -91,7 +93,7 @@ "loraEligible": true, "payloadBytesEstimate": 136, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -134,6 +136,8 @@ "hardwareSignals", "operatorMetadata", "hardwareNodes", + "nodeHealth", + "peerHints", "workReceipts", "verifierReports", "bridgeAlerts", @@ -152,6 +156,8 @@ "jsonRpcBoundary": "Read-only fixture data; no submit, wallet, live indexing, or production settlement method is implied.", "workbenchSectionKeys": [ "operatorMetadata", + "nodeHealth", + "peerHints", "receipts", "verifierReports", "bridgeAlerts", @@ -176,7 +182,7 @@ "status": "pending" } ], - "generatedAt": "2026-05-13T17:01:40Z", + "generatedAt": "2026-05-13T17:01:55Z", "hardwareNodes": [ { "cacheState": "healthy", @@ -191,7 +197,7 @@ "nodeId": "fr-e1e7878a2aa8", "powerState": "mains", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -217,7 +223,7 @@ "loraEligible": false, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -237,7 +243,7 @@ "observedAt": "2026-05-13T17:00:00Z", "payloadBytesEstimate": 0, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -269,7 +275,7 @@ "loraEligible": false, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -289,7 +295,7 @@ "observedAt": "2026-05-13T17:00:10Z", "payloadBytesEstimate": 0, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -321,7 +327,7 @@ "loraEligible": true, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -341,7 +347,7 @@ "observedAt": "2026-05-13T17:00:40Z", "payloadBytesEstimate": 96, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -373,7 +379,7 @@ "loraEligible": true, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -393,7 +399,7 @@ "observedAt": "2026-05-13T17:00:30Z", "payloadBytesEstimate": 128, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -426,7 +432,7 @@ "loraEligible": false, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -450,7 +456,7 @@ "observedAt": "2026-05-13T17:01:30Z", "payloadBytesEstimate": 0, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -483,7 +489,7 @@ "loraEligible": true, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -507,7 +513,7 @@ "observedAt": "2026-05-13T17:01:35Z", "payloadBytesEstimate": 136, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -540,7 +546,7 @@ "loraEligible": false, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -564,7 +570,7 @@ "observedAt": "2026-05-13T17:01:20Z", "payloadBytesEstimate": 0, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -585,6 +591,110 @@ "status": "observed", "summary": "NFC metadata pointer projected into artifact and memory references.", "transport": "local-simulator" + }, + { + "envelopeId": "hw-env-a376a5729f7a", + "id": "hw-sig-029c955f431e", + "linkedObjectIds": [ + "node-health:hardware:97477baf624a" + ], + "localOnly": true, + "loraEligible": true, + "nodeId": "fr-e1e7878a2aa8", + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "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-a376a5729f7a", + "localOnly": true, + "loraEligible": true, + "objectRefs": [ + { + "collection": "nodeHealth", + "objectId": "node-health:hardware:97477baf624a" + } + ], + "observedAt": "2026-05-13T17:01:45Z", + "payloadBytesEstimate": 112, + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "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-029c955f431e", + "signalType": "node_health", + "sourcePacketId": "node_health:1051", + "sourcePacketType": "node_health", + "status": "observed" + }, + "receivedAt": "2026-05-13T17:01:45Z", + "signalId": "hw-sig-029c955f431e", + "signalType": "node_health", + "sourcePacketType": "node_health", + "status": "observed", + "summary": "Compact node health digest that cannot block local chain startup.", + "transport": "meshtastic-control-sim" + }, + { + "envelopeId": "hw-env-e994534da0b1", + "id": "hw-sig-14652eac443e", + "linkedObjectIds": [ + "peer-hint:hardware:918e8c2b357a" + ], + "localOnly": true, + "loraEligible": true, + "nodeId": "fr-e1e7878a2aa8", + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "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-e994534da0b1", + "localOnly": true, + "loraEligible": true, + "objectRefs": [ + { + "collection": "peerHints", + "objectId": "peer-hint:hardware:918e8c2b357a" + } + ], + "observedAt": "2026-05-13T17:01:50Z", + "payloadBytesEstimate": 104, + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "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-14652eac443e", + "signalType": "peer_hint", + "sourcePacketId": "peer_hint:1052", + "sourcePacketType": "peer_hint", + "status": "observed" + }, + "receivedAt": "2026-05-13T17:01:50Z", + "signalId": "hw-sig-14652eac443e", + "signalType": "peer_hint", + "sourcePacketType": "peer_hint", + "status": "observed", + "summary": "Low-bandwidth peer hint for operator-visible local topology only.", + "transport": "meshtastic-control-sim" } ], "memoryCells": [ @@ -603,6 +713,35 @@ "updatedAt": "2026-05-13T17:01:20Z" } ], + "nodeHealth": [ + { + "chainStartupBlocking": false, + "cpuTempC": 43.5, + "diskFreeMb": 238592, + "health": "healthy", + "healthDigest": "0x7b66a2a36ba12251536a36ee9ebe2817aaf1737b15d3d34ac13ff6e55bcbe4e1", + "localOnly": true, + "loraEligible": true, + "memoryUsedMb": 512, + "nodeHealthId": "node-health:hardware:97477baf624a", + "nodeId": "fr-e1e7878a2aa8", + "observedAt": "2026-05-13T17:01:45Z", + "payloadBytesEstimate": 112, + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "queueDepth": 2, + "rootfieldId": "rootfield:hardware:flowrouter-local-alpha", + "sourcePacketType": "node_health", + "status": "observed", + "uptimeSeconds": 86442 + } + ], "operatorMetadata": [ { "displayName": "Local hardware operator fixture", @@ -615,7 +754,7 @@ "observedAt": "2026-05-13T17:00:00Z", "operatorId": "operator:local:717ffb1268b0", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -652,6 +791,22 @@ "sourcePacketType": "heartbeat", "trustBoundary": "local advisory status, not hardware attestation" }, + { + "flowchainSignal": "node_health", + "localAlphaRole": "adds compact node health and queue-depth observability", + "objectCollection": "nodeHealth", + "objectRef": "node-health:hardware:97477baf624a", + "sourcePacketType": "node_health", + "trustBoundary": "observability only; it cannot block local chain startup" + }, + { + "flowchainSignal": "peer_hint", + "localAlphaRole": "surfaces a local topology hint for operator review", + "objectCollection": "peerHints", + "objectRef": "peer-hint:hardware:918e8c2b357a", + "sourcePacketType": "peer_hint", + "trustBoundary": "peer hint only; not authentication, sync proof, or LAN discovery authority" + }, { "flowchainSignal": "work_receipt_reference", "localAlphaRole": "points the workbench at a WorkReceipt candidate", @@ -693,6 +848,36 @@ "trustBoundary": "untrusted metadata pointer, not a secret store or proof" } ], + "peerHints": [ + { + "advertisedRoles": [ + "hardware-observer", + "digest-relay" + ], + "lastSeenAt": "2026-05-13T17:01:48Z", + "linkState": "heard", + "localOnly": true, + "loraEligible": true, + "nodeId": "fr-e1e7878a2aa8", + "normalNetworkRequiredForSync": true, + "payloadBytesEstimate": 104, + "peerHintId": "peer-hint:hardware:918e8c2b357a", + "peerId": "peer-c24b3e338337", + "peerRole": "observer", + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "rssiDbm": -88, + "snrDb": 6.5, + "sourcePacketType": "peer_hint", + "transport": "meshtastic-control-sim" + } + ], "schema": "flowmemory.hardware_operator_signals.local_alpha.v0", "signalEnvelopes": [ { @@ -708,7 +893,7 @@ "observedAt": "2026-05-13T17:00:00Z", "payloadBytesEstimate": 0, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -735,7 +920,7 @@ "observedAt": "2026-05-13T17:00:10Z", "payloadBytesEstimate": 0, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -762,7 +947,7 @@ "observedAt": "2026-05-13T17:00:40Z", "payloadBytesEstimate": 96, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -789,7 +974,7 @@ "observedAt": "2026-05-13T17:00:30Z", "payloadBytesEstimate": 128, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -820,7 +1005,7 @@ "observedAt": "2026-05-13T17:01:30Z", "payloadBytesEstimate": 0, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -851,7 +1036,7 @@ "observedAt": "2026-05-13T17:01:35Z", "payloadBytesEstimate": 136, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -882,7 +1067,7 @@ "observedAt": "2026-05-13T17:01:20Z", "payloadBytesEstimate": 0, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -895,6 +1080,60 @@ "sourcePacketId": "nfc_memory_cartridge_metadata:42", "sourcePacketType": "nfc_memory_cartridge_metadata", "status": "observed" + }, + { + "envelopeId": "hw-env-a376a5729f7a", + "localOnly": true, + "loraEligible": true, + "objectRefs": [ + { + "collection": "nodeHealth", + "objectId": "node-health:hardware:97477baf624a" + } + ], + "observedAt": "2026-05-13T17:01:45Z", + "payloadBytesEstimate": 112, + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "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-029c955f431e", + "signalType": "node_health", + "sourcePacketId": "node_health:1051", + "sourcePacketType": "node_health", + "status": "observed" + }, + { + "envelopeId": "hw-env-e994534da0b1", + "localOnly": true, + "loraEligible": true, + "objectRefs": [ + { + "collection": "peerHints", + "objectId": "peer-hint:hardware:918e8c2b357a" + } + ], + "observedAt": "2026-05-13T17:01:50Z", + "payloadBytesEstimate": 104, + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "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-14652eac443e", + "signalType": "peer_hint", + "sourcePacketId": "peer_hint:1052", + "sourcePacketType": "peer_hint", + "status": "observed" } ], "source": "fixture", @@ -976,7 +1215,7 @@ "id": "artifact:hardware:dffc8c3e46ff", "kind": "NFC cartridge artifact reference", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1026,7 +1265,7 @@ "id": "bridge-alert:hardware:8662b11ba0d4", "kind": "Hardware bridge alert", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1044,7 +1283,7 @@ "loraEligible": true, "payloadBytesEstimate": 136, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1090,7 +1329,7 @@ "id": "challenge:hardware:5f54ef32073d", "kind": "Offline alert challenge candidate", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1141,7 +1380,7 @@ "id": "hw-sig-583c993be535", "kind": "Hardware operator signal", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1158,7 +1397,7 @@ "loraEligible": false, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1178,7 +1417,7 @@ "observedAt": "2026-05-13T17:00:00Z", "payloadBytesEstimate": 0, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1226,7 +1465,7 @@ "id": "hw-sig-cd45a2ab8b1d", "kind": "Hardware operator signal", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1243,7 +1482,7 @@ "loraEligible": false, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1263,7 +1502,7 @@ "observedAt": "2026-05-13T17:00:10Z", "payloadBytesEstimate": 0, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1311,7 +1550,7 @@ "id": "hw-sig-5dd1dbdec22d", "kind": "Hardware operator signal", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1328,7 +1567,7 @@ "loraEligible": true, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1348,7 +1587,7 @@ "observedAt": "2026-05-13T17:00:40Z", "payloadBytesEstimate": 96, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1396,7 +1635,7 @@ "id": "hw-sig-d7087f4f3257", "kind": "Hardware operator signal", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1413,7 +1652,7 @@ "loraEligible": true, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1433,7 +1672,7 @@ "observedAt": "2026-05-13T17:00:30Z", "payloadBytesEstimate": 128, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1481,7 +1720,7 @@ "id": "hw-sig-2474b9971f93", "kind": "Hardware operator signal", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1499,7 +1738,7 @@ "loraEligible": false, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1523,7 +1762,7 @@ "observedAt": "2026-05-13T17:01:30Z", "payloadBytesEstimate": 0, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1571,7 +1810,7 @@ "id": "hw-sig-590bff4da4b1", "kind": "Hardware operator signal", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1589,7 +1828,7 @@ "loraEligible": true, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1613,7 +1852,7 @@ "observedAt": "2026-05-13T17:01:35Z", "payloadBytesEstimate": 136, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1661,7 +1900,7 @@ "id": "hw-sig-97f6fef1f868", "kind": "Hardware operator signal", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1679,7 +1918,7 @@ "loraEligible": false, "nodeId": "fr-e1e7878a2aa8", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1703,7 +1942,7 @@ "observedAt": "2026-05-13T17:01:20Z", "payloadBytesEstimate": 0, "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1728,6 +1967,176 @@ "status": "observed", "summary": "NFC metadata pointer projected into artifact and memory references.", "title": "nfc_memory_cartridge_metadata" + }, + { + "facts": [ + { + "label": "node", + "value": "fr-e1e7878a2aa8" + }, + { + "label": "transport", + "value": "meshtastic-control-sim" + }, + { + "label": "source packet", + "value": "node_health" + }, + { + "label": "linked objects", + "value": "node-health:hardware:97477baf624a" + } + ], + "id": "hw-sig-029c955f431e", + "kind": "Hardware operator signal", + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "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-a376a5729f7a", + "id": "hw-sig-029c955f431e", + "linkedObjectIds": [ + "node-health:hardware:97477baf624a" + ], + "localOnly": true, + "loraEligible": true, + "nodeId": "fr-e1e7878a2aa8", + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "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-a376a5729f7a", + "localOnly": true, + "loraEligible": true, + "objectRefs": [ + { + "collection": "nodeHealth", + "objectId": "node-health:hardware:97477baf624a" + } + ], + "observedAt": "2026-05-13T17:01:45Z", + "payloadBytesEstimate": 112, + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "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-029c955f431e", + "signalType": "node_health", + "sourcePacketId": "node_health:1051", + "sourcePacketType": "node_health", + "status": "observed" + }, + "receivedAt": "2026-05-13T17:01:45Z", + "signalId": "hw-sig-029c955f431e", + "signalType": "node_health", + "sourcePacketType": "node_health", + "status": "observed", + "summary": "Compact node health digest that cannot block local chain startup.", + "transport": "meshtastic-control-sim" + }, + "status": "observed", + "summary": "Compact node health digest that cannot block local chain startup.", + "title": "node_health" + }, + { + "facts": [ + { + "label": "node", + "value": "fr-e1e7878a2aa8" + }, + { + "label": "transport", + "value": "meshtastic-control-sim" + }, + { + "label": "source packet", + "value": "peer_hint" + }, + { + "label": "linked objects", + "value": "peer-hint:hardware:918e8c2b357a" + } + ], + "id": "hw-sig-14652eac443e", + "kind": "Hardware operator signal", + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "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-e994534da0b1", + "id": "hw-sig-14652eac443e", + "linkedObjectIds": [ + "peer-hint:hardware:918e8c2b357a" + ], + "localOnly": true, + "loraEligible": true, + "nodeId": "fr-e1e7878a2aa8", + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "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-e994534da0b1", + "localOnly": true, + "loraEligible": true, + "objectRefs": [ + { + "collection": "peerHints", + "objectId": "peer-hint:hardware:918e8c2b357a" + } + ], + "observedAt": "2026-05-13T17:01:50Z", + "payloadBytesEstimate": 104, + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "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-14652eac443e", + "signalType": "peer_hint", + "sourcePacketId": "peer_hint:1052", + "sourcePacketType": "peer_hint", + "status": "observed" + }, + "receivedAt": "2026-05-13T17:01:50Z", + "signalId": "hw-sig-14652eac443e", + "signalType": "peer_hint", + "sourcePacketType": "peer_hint", + "status": "observed", + "summary": "Low-bandwidth peer hint for operator-visible local topology only.", + "transport": "meshtastic-control-sim" + }, + "status": "observed", + "summary": "Low-bandwidth peer hint for operator-visible local topology only.", + "title": "peer_hint" } ], "memoryCells": [ @@ -1753,7 +2162,7 @@ "id": "memory:hardware:9f8165ee1a5a", "kind": "Hardware memory cell candidate", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1779,6 +2188,68 @@ "title": "memory:hardware:9f8165ee1a5a" } ], + "nodeHealth": [ + { + "facts": [ + { + "label": "node", + "value": "fr-e1e7878a2aa8" + }, + { + "label": "queue depth", + "value": "2" + }, + { + "label": "health digest", + "value": "0x7b66a2a36ba12251536a36ee9ebe2817aaf1737b15d3d34ac13ff6e55bcbe4e1" + }, + { + "label": "blocks startup", + "value": "false" + } + ], + "id": "node-health:hardware:97477baf624a", + "kind": "Hardware node health", + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "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": { + "chainStartupBlocking": false, + "cpuTempC": 43.5, + "diskFreeMb": 238592, + "health": "healthy", + "healthDigest": "0x7b66a2a36ba12251536a36ee9ebe2817aaf1737b15d3d34ac13ff6e55bcbe4e1", + "localOnly": true, + "loraEligible": true, + "memoryUsedMb": 512, + "nodeHealthId": "node-health:hardware:97477baf624a", + "nodeId": "fr-e1e7878a2aa8", + "observedAt": "2026-05-13T17:01:45Z", + "payloadBytesEstimate": 112, + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "queueDepth": 2, + "rootfieldId": "rootfield:hardware:flowrouter-local-alpha", + "sourcePacketType": "node_health", + "status": "observed", + "uptimeSeconds": 86442 + }, + "status": "observed", + "summary": "Compact node health digest for local operator observability.", + "title": "healthy" + } + ], "operatorMetadata": [ { "facts": [ @@ -1802,7 +2273,7 @@ "id": "operator-metadata:hardware:874d3583a3b5", "kind": "Hardware operator metadata", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1820,7 +2291,7 @@ "observedAt": "2026-05-13T17:00:00Z", "operatorId": "operator:local:717ffb1268b0", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1844,6 +2315,69 @@ "title": "Local hardware operator fixture" } ], + "peerHints": [ + { + "facts": [ + { + "label": "node", + "value": "fr-e1e7878a2aa8" + }, + { + "label": "peer", + "value": "peer-c24b3e338337" + }, + { + "label": "transport", + "value": "meshtastic-control-sim" + }, + { + "label": "link state", + "value": "heard" + } + ], + "id": "peer-hint:hardware:918e8c2b357a", + "kind": "Hardware peer hint", + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "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": { + "advertisedRoles": [ + "hardware-observer", + "digest-relay" + ], + "lastSeenAt": "2026-05-13T17:01:48Z", + "linkState": "heard", + "localOnly": true, + "loraEligible": true, + "nodeId": "fr-e1e7878a2aa8", + "normalNetworkRequiredForSync": true, + "payloadBytesEstimate": 104, + "peerHintId": "peer-hint:hardware:918e8c2b357a", + "peerId": "peer-c24b3e338337", + "peerRole": "observer", + "provenance": { + "capturedAt": "2026-05-13T17:01:55Z", + "chainContext": "flowchain-private-local-testnet", + "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", + "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", + "origin": "fixture", + "subsystem": "hardware" + }, + "rssiDbm": -88, + "snrDb": 6.5, + "sourcePacketType": "peer_hint", + "transport": "meshtastic-control-sim" + }, + "status": "observed", + "summary": "Low-bandwidth peer hint for local topology review.", + "title": "peer-c24b3e338337" + } + ], "provenance": [ { "facts": [ @@ -1867,7 +2401,7 @@ "id": "hardware-operator-signal-fixture", "kind": "Hardware operator signal fixture", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1909,7 +2443,7 @@ "id": "receipt:hardware:1e92f586a767", "kind": "Hardware WorkReceipt relay", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", @@ -1966,7 +2500,7 @@ "id": "report:hardware:bd367723d169", "kind": "Hardware VerifierReport relay", "provenance": { - "capturedAt": "2026-05-13T17:01:40Z", + "capturedAt": "2026-05-13T17:01:55Z", "chainContext": "flowchain-private-local-testnet", "fixturePath": "fixtures/hardware/flowrouter_local_alpha_seed42.json", "localPathHint": "hardware/fixtures/flowrouter_sample_seed42.json", diff --git a/fixtures/hardware/flowrouter_negative_validation_seed42.json b/fixtures/hardware/flowrouter_negative_validation_seed42.json index aca73df5..f6761ab9 100644 --- a/fixtures/hardware/flowrouter_negative_validation_seed42.json +++ b/fixtures/hardware/flowrouter_negative_validation_seed42.json @@ -1,6 +1,6 @@ { "allCasesRejected": true, - "caseCount": 8, + "caseCount": 12, "cases": [ { "actualFailure": "heartbeat: missing required key device_id", @@ -8,6 +8,18 @@ "expectedFailure": "missing required key device_id", "passed": true }, + { + "actualFailure": "heartbeat.device_id: malformed device_id", + "case": "heartbeat_malformed_device_id", + "expectedFailure": "malformed device_id", + "passed": true + }, + { + "actualFailure": "heartbeat.emitted_at: stale timestamp rejected", + "case": "heartbeat_stale_timestamp", + "expectedFailure": "stale timestamp rejected", + "passed": true + }, { "actualFailure": "compact_receipt_relay.payload_bytes_estimate: value above maximum 200", "case": "receipt_relay_payload_exceeds_control_budget", @@ -20,6 +32,12 @@ "expectedFailure": "not in enum [False]", "passed": true }, + { + "actualFailure": "nfc_memory_cartridge_metadata.pointer: secret-shaped payload rejected", + "case": "nfc_metadata_secret_shaped_pointer", + "expectedFailure": "secret-shaped payload", + "passed": true + }, { "actualFailure": "operator_signals: missing required key bridgeAlerts", "case": "operator_projection_missing_bridge_alerts", @@ -32,6 +50,12 @@ "expectedFailure": "not in enum [False]", "passed": true }, + { + "actualFailure": "operator_signals.signalEnvelopes[1]: duplicate signalId", + "case": "operator_projection_duplicate_signal_id", + "expectedFailure": "duplicate signalId", + "passed": true + }, { "actualFailure": "operator_signals.signalEnvelopes[2].payloadBytesEstimate: value above maximum 200", "case": "operator_envelope_payload_exceeds_control_budget", diff --git a/hardware/README.md b/hardware/README.md index ce166b11..a969e0ee 100644 --- a/hardware/README.md +++ b/hardware/README.md @@ -1,6 +1,6 @@ # FlowMemory Hardware -Last updated: 2026-05-13 +Last updated: 2026-05-14 This directory contains the FlowRouter V0 proof-of-concept hardware package. It is production-shaped but research-safe: the docs, schemas, simulator, and field-test plans are intended to help later dashboard, services, and hardware work consume consistent data without claiming finished hardware. @@ -19,6 +19,8 @@ This directory contains the FlowRouter V0 proof-of-concept hardware package. It FlowRouter V0 is a local FlowMemory gateway POC. It can model or test: - Local node status. +- Compact node health hints. +- Peer/topology hints for operator review. - Artifact cache status. - Compact receipt relay. - Heartbeat messages. @@ -29,7 +31,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. +- Control-plane handoff JSON for optional hardware signals, including heartbeat, node health, peer hint, receipt relay, verifier digest relay, offline alert, bridge alert, NFC metadata, and operator metadata. ## V0 Non-Goals @@ -62,6 +64,6 @@ python hardware/simulator/flowrouter_sim.py --validate-handoff-file fixtures/har 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`. 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 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, and node-health or peer-hint problems cannot block local chain startup. 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 a3badedc..98fdc91c 100644 --- a/hardware/fixtures/flowrouter_sample_seed42.json +++ b/hardware/fixtures/flowrouter_sample_seed42.json @@ -48,14 +48,24 @@ "pattern": "solid-cobalt", "state": "online" }, - "generated_at": "2026-05-13T17:01:40Z", + "generated_at": "2026-05-13T17:01:55Z", "latest_receipt_digest": "fbe8a762b036d20581160b7e58a3adccf61f53c653713c439f85cc7bee5246fa", "network": { "gateway_id": "gw-82a524067b14", "lan": "reachable", "upstream": "reachable" }, + "node_health": { + "chain_startup_blocking": false, + "health": "healthy", + "queue_depth": 2 + }, "packet_type": "dashboard_feed", + "peer": { + "link_state": "heard", + "peer_id": "peer-c24b3e338337", + "transport": "meshtastic-control-sim" + }, "schema_version": "flowrouter.poc.v0", "sidecar": { "payload_budget_bytes": 160, @@ -78,6 +88,8 @@ "operator_metadata", "local_cache_status", "sidecar_status", + "node_health", + "peer_hint", "dashboard_feed" ], "device_id": "fr-e1e7878a2aa8", @@ -188,6 +200,44 @@ "schema_version": "flowrouter.poc.v0", "trust_level": "untrusted-pointer" }, + "node_health": { + "chain_startup_blocking": false, + "cpu_temp_c": 43.5, + "device_id": "fr-e1e7878a2aa8", + "disk_free_mb": 238592, + "emitted_at": "2026-05-13T17:01:45Z", + "health": "healthy", + "health_digest": "7b66a2a36ba12251536a36ee9ebe2817aaf1737b15d3d34ac13ff6e55bcbe4e1", + "lora_eligible": true, + "memory_used_mb": 512, + "packet_type": "node_health", + "payload_bytes_estimate": 112, + "queue_depth": 2, + "schema_version": "flowrouter.poc.v0", + "sequence": 1051, + "uptime_seconds": 86442 + }, + "peer_hint": { + "advertised_roles": [ + "hardware-observer", + "digest-relay" + ], + "device_id": "fr-e1e7878a2aa8", + "emitted_at": "2026-05-13T17:01:50Z", + "last_seen_at": "2026-05-13T17:01:48Z", + "link_state": "heard", + "lora_eligible": true, + "normal_network_required_for_sync": true, + "packet_type": "peer_hint", + "payload_bytes_estimate": 104, + "peer_id": "peer-c24b3e338337", + "peer_role": "observer", + "rssi_dbm": -88, + "schema_version": "flowrouter.poc.v0", + "sequence": 1052, + "snr_db": 6.5, + "transport": "meshtastic-control-sim" + }, "sidecar_status": { "device_id": "fr-e1e7878a2aa8", "emitted_at": "2026-05-13T17:01:10Z", diff --git a/hardware/flowrouter/FLOWCHAIN_LOCAL_ALPHA_SIGNALS.md b/hardware/flowrouter/FLOWCHAIN_LOCAL_ALPHA_SIGNALS.md index 070bbd24..e9c883e2 100644 --- a/hardware/flowrouter/FLOWCHAIN_LOCAL_ALPHA_SIGNALS.md +++ b/hardware/flowrouter/FLOWCHAIN_LOCAL_ALPHA_SIGNALS.md @@ -1,6 +1,6 @@ # FlowRouter FlowChain Local Alpha Signals -Last updated: 2026-05-13 +Last updated: 2026-05-14 Status: local-alpha operator signal mapping for the FlowRouter V0 proof of concept. @@ -39,8 +39,8 @@ 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 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`; +- control-plane-friendly collections: `operatorMetadata`, `hardwareNodes`, `nodeHealth`, `peerHints`, `workReceipts`, `verifierReports`, `bridgeAlerts`, `artifactCommitments`, `memoryCells`, `challenges`, `finalityReceipts`, and `alerts`; +- `workbenchRecords` grouped by `operatorMetadata`, `nodeHealth`, `peerHints`, `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. @@ -51,6 +51,8 @@ This is an optional fixture projection. It is not a devnet transaction source an | --- | --- | --- | --- | | `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. | +| `node_health` | `nodeHealth` | Adds compact health, queue-depth, and resource hints for local observability. | Advisory observability only; it cannot block local chain startup. | +| `peer_hint` | `peerHints` | Shows a low-bandwidth local topology candidate heard by the simulated sidecar. | Peer hint only; not authentication, sync proof, or LAN discovery authority. | | `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. | @@ -63,6 +65,7 @@ The fixture is intentionally shaped so another agent can add it to the local con - `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. +- `nodeHealth` and `peerHints` are optional state keys for local observability and topology hints; neither key is a chain-startup prerequisite. - `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. @@ -75,6 +78,7 @@ The receipt, report, memory cell, challenge, and finality objects are hints. The ## What Hardware Contributes - Operator-visible status for a FlowRouter-like node. +- Compact node health and peer-hint rows for local operator review. - 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. @@ -84,6 +88,7 @@ The receipt, report, memory cell, challenge, and finality objects are hints. The ## What Remains Local Only - Device status and packet timing. +- Node health and peer topology hints until checked against a local runtime or operator observation. - 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. @@ -135,12 +140,16 @@ python hardware/simulator/flowrouter_sim.py --validate-handoff-file fixtures/har python hardware/simulator/flowrouter_sim.py --validate-negative-report-file fixtures/hardware/flowrouter_negative_validation_seed42.json ``` +The negative report must include rejected cases for malformed IDs, oversized payloads, stale timestamps, duplicate signals, secret-shaped payloads, required-hardware claims, and missing required handoff collections. + ## 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. +- `nodeHealth` can feed local health rows or alerts, but it must not gate private/local chain startup. +- `peerHints` can feed topology rows, but normal network paths still perform actual synchronization and reconciliation. - `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. diff --git a/hardware/flowrouter/README.md b/hardware/flowrouter/README.md index 74b9cf9d..ab63cc2b 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`, `operatorMetadata`, `hardwareNodes`, `workReceipts`, `verifierReports`, `bridgeAlerts`, `alerts`, `challenges`, `artifactCommitments`, and `memoryCells` without blocking the main local chain flow. +- Hardware packets can be projected into local-alpha `hardwareSignals`, `operatorMetadata`, `hardwareNodes`, `nodeHealth`, `peerHints`, `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 7ecb7481..7f2090c4 100644 --- a/hardware/lora-sidecar/CONTROL_MESSAGE_INVENTORY.md +++ b/hardware/lora-sidecar/CONTROL_MESSAGE_INVENTORY.md @@ -1,6 +1,6 @@ # Meshtastic/LoRa Control Message Inventory -Last updated: 2026-05-13 +Last updated: 2026-05-14 This inventory addresses issue #12. It defines candidate control messages for FlowRouter v0 and explicitly rules out high-bandwidth payloads. @@ -25,7 +25,9 @@ Common fields: | Message | Purpose group | Approximate payload shape | Why it fits low bandwidth | Risks and requirements | | --- | --- | --- | --- | --- | | Node heartbeat | Status ping | `v,type,node,seq,uptime,bat/temp,flags,auth` | Tiny status tuple; no artifact data. | Spoofing and replay risk; needs auth and sequence tracking. | +| Node health digest | Status ping | `v,type,node,seq,health,queue,temp,free,health_hash,auth` | Compact health and queue fields only; no logs or receipts. | Advisory only; must not block local chain startup. | | Gateway availability | Status ping | `v,type,node,seq,wan,lan,cache,sidecar,flags,auth` | Bitfields describe availability instead of carrying logs. | Overclaiming connectivity; receiver must treat as advisory. | +| Peer hint | Topology hint | `v,type,node,seq,peer,role,link,rssi,snr,auth` | Short peer and link-state hint only; no sync payload. | Spoofing/replay risk; normal network path performs actual sync and reconciliation. | | 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. | @@ -40,10 +42,18 @@ Common fields: Use for "this node is alive" and coarse device state. Keep it periodic but infrequent to avoid channel congestion. +### Node Health Digest + +Use for compact local health and queue-depth hints. A warning or stale node-health message can inform an operator, but V0 hardware health never gates private/local chain startup. + ### Gateway Availability Use for advisory state about upstream internet, LAN reachability, local cache, and sidecar status. It does not prove connectivity to the global internet. +### Peer Hint + +Use for local topology hints such as "this peer was heard recently." It is not authentication, sync proof, or a replacement for normal network discovery. + ### FlowPulse Digest Use only as a compact checkpoint over a known event range. It should never carry event bodies, logs, memory artifacts, or model data. diff --git a/hardware/simulator/README.md b/hardware/simulator/README.md index 740dbaca..8c5c4b74 100644 --- a/hardware/simulator/README.md +++ b/hardware/simulator/README.md @@ -1,6 +1,6 @@ # FlowRouter Simulator -Last updated: 2026-05-13 +Last updated: 2026-05-14 The simulator emits deterministic FlowRouter V0 proof-of-concept packets for dashboard, services, hardware, and field-test planning. It uses only the Python standard library. @@ -71,6 +71,8 @@ python hardware/simulator/flowrouter_sim.py --validate-negative-report-file fixt - Verifier report digest relay - Compact receipt relay - Bridge alert +- Node health +- Peer hint - Local cache status - Gateway discovery - Sidecar status @@ -85,10 +87,14 @@ The local-alpha projection is a `flowmemory.hardware_operator_signals.local_alph - `device_manifest` -> `operatorMetadata` - `heartbeat` -> `hardwareNodes` +- `node_health` -> `nodeHealth` +- `peer_hint` -> `peerHints` - `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 `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. +It also includes `workbenchRecords` grouped by `operatorMetadata`, `nodeHealth`, `peerHints`, `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. + +Negative validation covers missing required IDs, malformed IDs, oversized control payloads, stale timestamps, duplicate operator signal IDs, secret-shaped payload strings, hardware-required handoff claims, and missing required handoff collections. diff --git a/hardware/simulator/flowrouter_sim.py b/hardware/simulator/flowrouter_sim.py index 0f7bfe4c..16331f08 100644 --- a/hardware/simulator/flowrouter_sim.py +++ b/hardware/simulator/flowrouter_sim.py @@ -6,6 +6,7 @@ import argparse import hashlib import json +import re import sys from pathlib import Path from typing import Any @@ -24,6 +25,8 @@ "nfc_memory_cartridge_metadata": "nfc_memory_cartridge_metadata.schema.json", "emergency_offline_signal": "emergency_offline_signal.schema.json", "bridge_alert": "bridge_alert.schema.json", + "node_health": "node_health.schema.json", + "peer_hint": "peer_hint.schema.json", "dashboard_feed": "dashboard_feed.schema.json", } @@ -32,6 +35,30 @@ ZERO_HASH = "0x0000000000000000000000000000000000000000000000000000000000000000" HARDWARE_ROOTFIELD_ID = "rootfield:hardware:flowrouter-local-alpha" HARDWARE_CHAIN_CONTEXT = "flowchain-private-local-testnet" +FIXTURE_TIME_PREFIX = "2026-05-13T17:" +SECRET_SHAPED_PATTERNS = [ + "BEGIN PRIVATE " + "KEY", + "PRIVATE_" + "KEY=", + "MNEMONIC=", + "SEED_PHRASE=", + "RPC_URL=", + "API_KEY=", + "WEBHOOK_URL=", + "sk_live_", + "sk-proj-", + "https://hooks.slack.com/services/", +] +ID_PATTERNS = { + "device_id": re.compile(r"^fr-[0-9a-f]{12}$"), + "gateway_id": re.compile(r"^gw-[0-9a-f]{12}$"), + "cartridge_id": re.compile(r"^cart-[0-9a-f]{12}$"), + "peer_id": re.compile(r"^peer-[0-9a-f]{12}$"), + "report_id": re.compile(r"^vr-[0-9a-f]{12}$"), + "bridge_id": re.compile(r"^bridge-[0-9a-f]{12}$"), + "cache_id": re.compile(r"^cache-[0-9a-f]{12}$"), + "digest64": re.compile(r"^[0-9a-f]{64}$"), + "tx_hash_prefix": re.compile(r"^0x[0-9a-f]{16}$"), +} def digest(seed: int, label: str, length: int = 64) -> str: @@ -108,6 +135,8 @@ def build_packets(seed: int) -> dict[str, Any]: "operator_metadata", "local_cache_status", "sidecar_status", + "node_health", + "peer_hint", "dashboard_feed", ], "security": { @@ -275,11 +304,48 @@ def build_packets(seed: int) -> dict[str, Any]: "operator_action": "review-bridge-observer-and-do-not-block-chain", } + node_health = { + "packet_type": "node_health", + "schema_version": "flowrouter.poc.v0", + "device_id": device_id, + "sequence": 1009 + seed, + "emitted_at": iso_tick(105), + "health": "healthy", + "uptime_seconds": heartbeat["uptime_seconds"], + "cpu_temp_c": 43.5, + "memory_used_mb": 512, + "disk_free_mb": 238592, + "queue_depth": 2, + "health_digest": digest(seed, "node-health"), + "payload_bytes_estimate": 112, + "lora_eligible": True, + "chain_startup_blocking": False, + } + + peer_hint = { + "packet_type": "peer_hint", + "schema_version": "flowrouter.poc.v0", + "device_id": device_id, + "sequence": 1010 + seed, + "emitted_at": iso_tick(110), + "peer_id": f"peer-{short_id(seed, 'peer')}", + "peer_role": "observer", + "transport": "meshtastic-control-sim", + "link_state": "heard", + "last_seen_at": iso_tick(108), + "rssi_dbm": -88, + "snr_db": 6.5, + "advertised_roles": ["hardware-observer", "digest-relay"], + "payload_bytes_estimate": 104, + "lora_eligible": True, + "normal_network_required_for_sync": True, + } + dashboard_feed = { "packet_type": "dashboard_feed", "schema_version": "flowrouter.poc.v0", "device_id": device_id, - "generated_at": iso_tick(100), + "generated_at": iso_tick(115), "network": { "lan": "reachable", "upstream": "reachable", @@ -296,6 +362,16 @@ def build_packets(seed: int) -> dict[str, Any]: "region": sidecar_status["region"], "payload_budget_bytes": sidecar_status["payload_budget_bytes"], }, + "peer": { + "peer_id": peer_hint["peer_id"], + "link_state": peer_hint["link_state"], + "transport": peer_hint["transport"], + }, + "node_health": { + "health": node_health["health"], + "queue_depth": node_health["queue_depth"], + "chain_startup_blocking": node_health["chain_startup_blocking"], + }, "flowcore": { "state": "online", "pattern": "solid-cobalt", @@ -316,6 +392,8 @@ def build_packets(seed: int) -> dict[str, Any]: "nfc_memory_cartridge_metadata": cartridge, "emergency_offline_signal": emergency, "bridge_alert": bridge_alert, + "node_health": node_health, + "peer_hint": peer_hint, "dashboard_feed": dashboard_feed, } @@ -330,6 +408,8 @@ def build_operator_signals(seed: int, packets: dict[str, Any] | None = None) -> bridge = packet_set["bridge_alert"] cartridge = packet_set["nfc_memory_cartridge_metadata"] sidecar = packet_set["sidecar_status"] + node_health = packet_set["node_health"] + peer_hint = packet_set["peer_hint"] dashboard = packet_set["dashboard_feed"] device_id = heartbeat["device_id"] @@ -350,6 +430,8 @@ def build_operator_signals(seed: int, packets: dict[str, Any] | None = None) -> artifact_id = f"artifact:hardware:{short_id(seed, 'cartridge-artifact-ref')}" memory_cell_id = f"memory:hardware:{short_id(seed, 'cartridge-memory-ref')}" finality_receipt_id = f"finality:hardware:{short_id(seed, 'receipt-finality')}" + node_health_id = f"node-health:hardware:{short_id(seed, 'node-health-ref')}" + peer_hint_id = f"peer-hint:hardware:{short_id(seed, 'peer-hint-ref')}" provenance = { "subsystem": "hardware", @@ -381,6 +463,46 @@ def build_operator_signals(seed: int, packets: dict[str, Any] | None = None) -> "provenance": provenance, } + node_health_record = { + "nodeHealthId": node_health_id, + "nodeId": device_id, + "rootfieldId": HARDWARE_ROOTFIELD_ID, + "health": node_health["health"], + "observedAt": node_health["emitted_at"], + "uptimeSeconds": node_health["uptime_seconds"], + "cpuTempC": node_health["cpu_temp_c"], + "memoryUsedMb": node_health["memory_used_mb"], + "diskFreeMb": node_health["disk_free_mb"], + "queueDepth": node_health["queue_depth"], + "healthDigest": ensure_hex(node_health["health_digest"]), + "status": "observed", + "payloadBytesEstimate": node_health["payload_bytes_estimate"], + "loraEligible": node_health["lora_eligible"], + "chainStartupBlocking": node_health["chain_startup_blocking"], + "localOnly": True, + "sourcePacketType": "node_health", + "provenance": provenance, + } + + peer_hint_record = { + "peerHintId": peer_hint_id, + "nodeId": device_id, + "peerId": peer_hint["peer_id"], + "peerRole": peer_hint["peer_role"], + "transport": peer_hint["transport"], + "linkState": peer_hint["link_state"], + "lastSeenAt": peer_hint["last_seen_at"], + "advertisedRoles": peer_hint["advertised_roles"], + "rssiDbm": peer_hint["rssi_dbm"], + "snrDb": peer_hint["snr_db"], + "payloadBytesEstimate": peer_hint["payload_bytes_estimate"], + "loraEligible": peer_hint["lora_eligible"], + "normalNetworkRequiredForSync": peer_hint["normal_network_required_for_sync"], + "localOnly": True, + "sourcePacketType": "peer_hint", + "provenance": provenance, + } + operator_metadata = { "metadataId": operator_metadata_id, "operatorId": f"operator:local:{short_id(seed, 'operator')}", @@ -657,6 +779,20 @@ def workbench_record( ], "observed", ), + signal_envelope( + "node-health", + "node_health", + node_health, + [{"collection": "nodeHealth", "objectId": node_health_id}], + "observed", + ), + signal_envelope( + "peer-hint", + "peer_hint", + peer_hint, + [{"collection": "peerHints", "objectId": peer_hint_id}], + "observed", + ), ] signal_summaries = { @@ -667,6 +803,8 @@ def workbench_record( "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.", + "node_health": "Compact node health digest that cannot block local chain startup.", + "peer_hint": "Low-bandwidth peer hint for operator-visible local topology only.", } hardware_signals = [ { @@ -716,6 +854,8 @@ def workbench_record( "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.", + "Node health signals are observability hints and cannot block private/local chain startup.", + "Peer hints describe local topology candidates only and require normal network reconciliation for sync.", ], }, "packetMappings": [ @@ -735,6 +875,22 @@ def workbench_record( "localAlphaRole": "shows FlowRouter reachability and coarse device state", "trustBoundary": "local advisory status, not hardware attestation", }, + { + "sourcePacketType": "node_health", + "flowchainSignal": "node_health", + "objectCollection": "nodeHealth", + "objectRef": node_health_id, + "localAlphaRole": "adds compact node health and queue-depth observability", + "trustBoundary": "observability only; it cannot block local chain startup", + }, + { + "sourcePacketType": "peer_hint", + "flowchainSignal": "peer_hint", + "objectCollection": "peerHints", + "objectRef": peer_hint_id, + "localAlphaRole": "surfaces a local topology hint for operator review", + "trustBoundary": "peer hint only; not authentication, sync proof, or LAN discovery authority", + }, { "sourcePacketType": "compact_receipt_relay", "flowchainSignal": "work_receipt_reference", @@ -780,6 +936,8 @@ def workbench_record( "hardwareSignals": hardware_signals, "operatorMetadata": [operator_metadata], "hardwareNodes": [hardware_node], + "nodeHealth": [node_health_record], + "peerHints": [peer_hint_record], "workReceipts": [work_receipt], "verifierReports": [verifier_report], "bridgeAlerts": [bridge_alert], @@ -805,6 +963,38 @@ def workbench_record( operator_metadata, ) ], + "nodeHealth": [ + workbench_record( + node_health_id, + "Hardware node health", + node_health["health"], + "Compact node health digest for local operator observability.", + "observed", + [ + {"label": "node", "value": device_id}, + {"label": "queue depth", "value": str(node_health["queue_depth"])}, + {"label": "health digest", "value": ensure_hex(node_health["health_digest"])}, + {"label": "blocks startup", "value": "false"}, + ], + node_health_record, + ) + ], + "peerHints": [ + workbench_record( + peer_hint_id, + "Hardware peer hint", + peer_hint["peer_id"], + "Low-bandwidth peer hint for local topology review.", + "observed", + [ + {"label": "node", "value": device_id}, + {"label": "peer", "value": peer_hint["peer_id"]}, + {"label": "transport", "value": peer_hint["transport"]}, + {"label": "link state", "value": peer_hint["link_state"]}, + ], + peer_hint_record, + ) + ], "receipts": [ workbench_record( receipt_id, @@ -946,6 +1136,8 @@ def workbench_record( "hardwareSignals", "operatorMetadata", "hardwareNodes", + "nodeHealth", + "peerHints", "workReceipts", "verifierReports", "bridgeAlerts", @@ -957,6 +1149,8 @@ def workbench_record( ], "workbenchSectionKeys": [ "operatorMetadata", + "nodeHealth", + "peerHints", "receipts", "verifierReports", "bridgeAlerts", @@ -996,6 +1190,8 @@ def build_control_plane_handoff(seed: int, operator_signals: dict[str, Any] | No "hardwareSignals": "signalId", "operatorMetadata": "metadataId", "hardwareNodes": "nodeId", + "nodeHealth": "nodeHealthId", + "peerHints": "peerHintId", "workReceipts": "receiptId", "verifierReports": "reportId", "bridgeAlerts": "bridgeAlertId", @@ -1078,6 +1274,99 @@ def validate_value(schema: dict[str, Any], value: Any, path: str) -> None: validate_value(schema["items"], item, f"{path}[{index}]") +def iter_strings(value: Any, path: str) -> list[tuple[str, str]]: + if isinstance(value, str): + return [(path, value)] + if isinstance(value, dict): + strings: list[tuple[str, str]] = [] + for key, child in value.items(): + strings.extend(iter_strings(child, f"{path}.{key}")) + return strings + if isinstance(value, list): + strings = [] + for index, child in enumerate(value): + strings.extend(iter_strings(child, f"{path}[{index}]")) + return strings + return [] + + +def validate_no_secret_shaped_payloads(value: Any, path: str) -> None: + for string_path, string_value in iter_strings(value, path): + upper_value = string_value.upper() + for pattern in SECRET_SHAPED_PATTERNS: + compare_value = upper_value if pattern == pattern.upper() else string_value + if pattern in compare_value: + raise ValidationError(f"{string_path}: secret-shaped payload rejected") + + +def validate_fixture_timestamp(value: Any, path: str) -> None: + if not isinstance(value, str) or not value.startswith(FIXTURE_TIME_PREFIX) or not value.endswith("Z"): + raise ValidationError(f"{path}: stale timestamp rejected") + + +def validate_pattern(field: str, value: Any, path: str) -> None: + pattern = ID_PATTERNS[field] + if not isinstance(value, str) or not pattern.fullmatch(value): + raise ValidationError(f"{path}: malformed {field}") + + +def validate_packet_semantics(packet: dict[str, Any], path: str) -> None: + validate_no_secret_shaped_payloads(packet, path) + + for key in ("emitted_at", "generated_at", "created_at", "last_seen_at"): + if key in packet: + validate_fixture_timestamp(packet[key], f"{path}.{key}") + + if "device_id" in packet: + validate_pattern("device_id", packet["device_id"], f"{path}.device_id") + if "gateway_id" in packet: + validate_pattern("gateway_id", packet["gateway_id"], f"{path}.gateway_id") + if "cartridge_id" in packet: + validate_pattern("cartridge_id", packet["cartridge_id"], f"{path}.cartridge_id") + if "peer_id" in packet: + validate_pattern("peer_id", packet["peer_id"], f"{path}.peer_id") + if "report_id" in packet: + validate_pattern("report_id", packet["report_id"], f"{path}.report_id") + if "bridge_id" in packet: + validate_pattern("bridge_id", packet["bridge_id"], f"{path}.bridge_id") + if "cache_id" in packet: + validate_pattern("cache_id", packet["cache_id"], f"{path}.cache_id") + if "tx_hash_prefix" in packet: + validate_pattern("tx_hash_prefix", packet["tx_hash_prefix"], f"{path}.tx_hash_prefix") + + for key in ("digest", "subject_digest", "report_digest", "receipt_digest", "cache_digest", "health_digest"): + if key in packet: + validate_pattern("digest64", packet[key], f"{path}.{key}") + + +def assert_unique(items: list[Any], id_field: str, path: str) -> None: + seen: set[str] = set() + for index, item in enumerate(items): + if not isinstance(item, dict): + continue + value = item.get(id_field) + if not isinstance(value, str): + continue + if value in seen: + raise ValidationError(f"{path}[{index}]: duplicate {id_field}") + seen.add(value) + + +def validate_operator_signal_semantics(operator_signals: dict[str, Any]) -> None: + validate_no_secret_shaped_payloads(operator_signals, "operator_signals") + assert_unique(operator_signals["signalEnvelopes"], "signalId", "operator_signals.signalEnvelopes") + assert_unique(operator_signals["signalEnvelopes"], "envelopeId", "operator_signals.signalEnvelopes") + assert_unique(operator_signals["hardwareSignals"], "signalId", "operator_signals.hardwareSignals") + + +def validate_handoff_semantics(handoff: dict[str, Any]) -> None: + validate_no_secret_shaped_payloads(handoff, "control_plane_handoff") + id_fields = handoff["ingest"]["idFields"] + collections = handoff["collections"] + for collection, id_field in id_fields.items(): + assert_unique(collections.get(collection, []), id_field, f"control_plane_handoff.collections.{collection}") + + def load_schema(schema_dir: Path, packet_type: str) -> dict[str, Any]: schema_file = SCHEMA_FILES.get(packet_type) if not schema_file: @@ -1094,17 +1383,20 @@ def validate_packets(packets: dict[str, Any], schema_dir: Path) -> None: raise ValidationError(f"{name}: packet_type {packet_type!r} does not match key") schema = load_schema(schema_dir, packet_type) validate_value(schema, packet, name) + validate_packet_semantics(packet, name) def validate_operator_signals(operator_signals: dict[str, Any], schema_dir: Path) -> None: schema = json.loads((schema_dir / OPERATOR_SIGNALS_SCHEMA_FILE).read_text(encoding="utf-8")) validate_value(schema, operator_signals, "operator_signals") + validate_operator_signal_semantics(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") + validate_handoff_semantics(handoff) def validate_negative_report(report: dict[str, Any], schema_dir: Path) -> None: @@ -1148,6 +1440,24 @@ def expect_rejected(name: str, validator: Any, value: Any, expected: str) -> Non "missing required key device_id", ) + malformed_heartbeat_device = clone_json(packets) + malformed_heartbeat_device["heartbeat"]["device_id"] = "flowrouter#42" + expect_rejected( + "heartbeat_malformed_device_id", + lambda value: validate_packets(value, schema_dir), + malformed_heartbeat_device, + "malformed device_id", + ) + + stale_heartbeat = clone_json(packets) + stale_heartbeat["heartbeat"]["emitted_at"] = "2026-05-12T17:00:10Z" + expect_rejected( + "heartbeat_stale_timestamp", + lambda value: validate_packets(value, schema_dir), + stale_heartbeat, + "stale timestamp rejected", + ) + oversized_receipt_relay = clone_json(packets) oversized_receipt_relay["compact_receipt_relay"]["payload_bytes_estimate"] = 512 expect_rejected( @@ -1166,6 +1476,15 @@ def expect_rejected(name: str, validator: Any, value: Any, expected: str) -> Non "not in enum [False]", ) + nfc_secret_pointer = clone_json(packets) + nfc_secret_pointer["nfc_memory_cartridge_metadata"]["pointer"] = "API_KEY=flowmemory-test-placeholder" + expect_rejected( + "nfc_metadata_secret_shaped_pointer", + lambda value: validate_packets(value, schema_dir), + nfc_secret_pointer, + "secret-shaped payload", + ) + missing_bridge_handoff = clone_json(operator_doc) del missing_bridge_handoff["bridgeAlerts"] expect_rejected( @@ -1184,6 +1503,15 @@ def expect_rejected(name: str, validator: Any, value: Any, expected: str) -> Non "not in enum [False]", ) + duplicate_signal = clone_json(operator_doc) + duplicate_signal["signalEnvelopes"][1]["signalId"] = duplicate_signal["signalEnvelopes"][0]["signalId"] + expect_rejected( + "operator_projection_duplicate_signal_id", + lambda value: validate_operator_signals(value, schema_dir), + duplicate_signal, + "duplicate signalId", + ) + oversized_operator_envelope = clone_json(operator_doc) oversized_operator_envelope["signalEnvelopes"][2]["payloadBytesEstimate"] = 512 expect_rejected( diff --git a/hardware/simulator/schemas/dashboard_feed.schema.json b/hardware/simulator/schemas/dashboard_feed.schema.json index 30fea2df..4310f600 100644 --- a/hardware/simulator/schemas/dashboard_feed.schema.json +++ b/hardware/simulator/schemas/dashboard_feed.schema.json @@ -3,7 +3,7 @@ "title": "Dashboard Feed", "type": "object", "additionalProperties": false, - "required": ["packet_type", "schema_version", "device_id", "generated_at", "network", "cache", "sidecar", "flowcore", "latest_receipt_digest", "trust_labels"], + "required": ["packet_type", "schema_version", "device_id", "generated_at", "network", "cache", "sidecar", "peer", "node_health", "flowcore", "latest_receipt_digest", "trust_labels"], "properties": { "packet_type": { "type": "string", "enum": ["dashboard_feed"] }, "schema_version": { "type": "string" }, @@ -12,6 +12,8 @@ "network": { "type": "object" }, "cache": { "type": "object" }, "sidecar": { "type": "object" }, + "peer": { "type": "object" }, + "node_health": { "type": "object" }, "flowcore": { "type": "object" }, "latest_receipt_digest": { "type": "string", "maxLength": 64 }, "trust_labels": { "type": "array", "items": { "type": "string" } } diff --git a/hardware/simulator/schemas/flowchain_operator_signals.schema.json b/hardware/simulator/schemas/flowchain_operator_signals.schema.json index 1761d321..d3fd9bf9 100644 --- a/hardware/simulator/schemas/flowchain_operator_signals.schema.json +++ b/hardware/simulator/schemas/flowchain_operator_signals.schema.json @@ -16,6 +16,8 @@ "hardwareSignals", "operatorMetadata", "hardwareNodes", + "nodeHealth", + "peerHints", "workReceipts", "verifierReports", "bridgeAlerts", @@ -100,6 +102,8 @@ "enum": [ "device_manifest", "heartbeat", + "node_health", + "peer_hint", "compact_receipt_relay", "verifier_report_digest_relay", "emergency_offline_signal", @@ -112,6 +116,8 @@ "enum": [ "operator_metadata", "hardware_node_status", + "node_health", + "peer_hint", "work_receipt_reference", "verifier_report_reference", "alert_challenge_input", @@ -124,6 +130,8 @@ "enum": [ "operatorMetadata", "hardwareNodes", + "nodeHealth", + "peerHints", "workReceipts", "verifierReports", "challenges", @@ -169,6 +177,8 @@ "enum": [ "operator_metadata", "heartbeat", + "node_health", + "peer_hint", "receipt_relay", "verifier_digest_relay", "offline_alert_challenge_input", @@ -231,6 +241,8 @@ "enum": [ "operator_metadata", "heartbeat", + "node_health", + "peer_hint", "receipt_relay", "verifier_digest_relay", "offline_alert_challenge_input", @@ -306,6 +318,48 @@ } } }, + "nodeHealth": { + "type": "array", + "items": { + "type": "object", + "required": ["nodeHealthId", "nodeId", "rootfieldId", "health", "observedAt", "healthDigest", "status", "payloadBytesEstimate", "loraEligible", "chainStartupBlocking", "localOnly", "sourcePacketType"], + "properties": { + "nodeHealthId": { "type": "string", "maxLength": 96 }, + "nodeId": { "type": "string", "maxLength": 64 }, + "rootfieldId": { "type": "string", "maxLength": 96 }, + "health": { "type": "string", "enum": ["healthy", "warning", "critical", "unknown"] }, + "observedAt": { "type": "string" }, + "healthDigest": { "type": "string", "maxLength": 66 }, + "status": { "type": "string", "enum": ["observed", "stale", "offline"] }, + "payloadBytesEstimate": { "type": "integer", "minimum": 0, "maximum": 200 }, + "loraEligible": { "type": "boolean" }, + "chainStartupBlocking": { "type": "boolean", "enum": [false] }, + "localOnly": { "type": "boolean", "enum": [true] }, + "sourcePacketType": { "type": "string", "enum": ["node_health"] } + } + } + }, + "peerHints": { + "type": "array", + "items": { + "type": "object", + "required": ["peerHintId", "nodeId", "peerId", "peerRole", "transport", "linkState", "lastSeenAt", "payloadBytesEstimate", "loraEligible", "normalNetworkRequiredForSync", "localOnly", "sourcePacketType"], + "properties": { + "peerHintId": { "type": "string", "maxLength": 96 }, + "nodeId": { "type": "string", "maxLength": 64 }, + "peerId": { "type": "string", "maxLength": 64 }, + "peerRole": { "type": "string", "enum": ["observer", "relay", "gateway", "field-kit"] }, + "transport": { "type": "string", "enum": ["local-simulator", "meshtastic-control-sim", "lora-control-sim"] }, + "linkState": { "type": "string", "enum": ["heard", "stale", "lost", "unknown"] }, + "lastSeenAt": { "type": "string" }, + "payloadBytesEstimate": { "type": "integer", "minimum": 0, "maximum": 200 }, + "loraEligible": { "type": "boolean" }, + "normalNetworkRequiredForSync": { "type": "boolean", "enum": [true] }, + "localOnly": { "type": "boolean", "enum": [true] }, + "sourcePacketType": { "type": "string", "enum": ["peer_hint"] } + } + } + }, "workReceipts": { "type": "array", "items": { @@ -517,6 +571,8 @@ "additionalProperties": false, "required": [ "operatorMetadata", + "nodeHealth", + "peerHints", "receipts", "verifierReports", "bridgeAlerts", @@ -528,6 +584,8 @@ ], "properties": { "operatorMetadata": { "type": "array", "items": { "type": "object" } }, + "nodeHealth": { "type": "array", "items": { "type": "object" } }, + "peerHints": { "type": "array", "items": { "type": "object" } }, "receipts": { "type": "array", "items": { "type": "object" } }, "verifierReports": { "type": "array", "items": { "type": "object" } }, "bridgeAlerts": { "type": "array", "items": { "type": "object" } }, diff --git a/hardware/simulator/schemas/node_health.schema.json b/hardware/simulator/schemas/node_health.schema.json new file mode 100644 index 00000000..b62295f4 --- /dev/null +++ b/hardware/simulator/schemas/node_health.schema.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "FlowRouter Node Health", + "type": "object", + "additionalProperties": false, + "required": [ + "packet_type", + "schema_version", + "device_id", + "sequence", + "emitted_at", + "health", + "uptime_seconds", + "cpu_temp_c", + "memory_used_mb", + "disk_free_mb", + "queue_depth", + "health_digest", + "payload_bytes_estimate", + "lora_eligible", + "chain_startup_blocking" + ], + "properties": { + "packet_type": { "type": "string", "enum": ["node_health"] }, + "schema_version": { "type": "string" }, + "device_id": { "type": "string", "maxLength": 32 }, + "sequence": { "type": "integer", "minimum": 0 }, + "emitted_at": { "type": "string" }, + "health": { "type": "string", "enum": ["healthy", "warning", "critical", "unknown"] }, + "uptime_seconds": { "type": "integer", "minimum": 0 }, + "cpu_temp_c": { "type": "number", "minimum": -40, "maximum": 125 }, + "memory_used_mb": { "type": "integer", "minimum": 0 }, + "disk_free_mb": { "type": "integer", "minimum": 0 }, + "queue_depth": { "type": "integer", "minimum": 0 }, + "health_digest": { "type": "string", "maxLength": 64 }, + "payload_bytes_estimate": { "type": "integer", "minimum": 0, "maximum": 200 }, + "lora_eligible": { "type": "boolean" }, + "chain_startup_blocking": { "type": "boolean", "enum": [false] } + } +} diff --git a/hardware/simulator/schemas/peer_hint.schema.json b/hardware/simulator/schemas/peer_hint.schema.json new file mode 100644 index 00000000..a9aeb967 --- /dev/null +++ b/hardware/simulator/schemas/peer_hint.schema.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "FlowRouter Peer Hint", + "type": "object", + "additionalProperties": false, + "required": [ + "packet_type", + "schema_version", + "device_id", + "sequence", + "emitted_at", + "peer_id", + "peer_role", + "transport", + "link_state", + "last_seen_at", + "rssi_dbm", + "snr_db", + "advertised_roles", + "payload_bytes_estimate", + "lora_eligible", + "normal_network_required_for_sync" + ], + "properties": { + "packet_type": { "type": "string", "enum": ["peer_hint"] }, + "schema_version": { "type": "string" }, + "device_id": { "type": "string", "maxLength": 32 }, + "sequence": { "type": "integer", "minimum": 0 }, + "emitted_at": { "type": "string" }, + "peer_id": { "type": "string", "maxLength": 32 }, + "peer_role": { "type": "string", "enum": ["observer", "relay", "gateway", "field-kit"] }, + "transport": { "type": "string", "enum": ["local-simulator", "meshtastic-control-sim", "lora-control-sim"] }, + "link_state": { "type": "string", "enum": ["heard", "stale", "lost", "unknown"] }, + "last_seen_at": { "type": "string" }, + "rssi_dbm": { "type": "integer", "minimum": -200, "maximum": 0 }, + "snr_db": { "type": "number", "minimum": -50, "maximum": 50 }, + "advertised_roles": { "type": "array", "items": { "type": "string", "maxLength": 48 } }, + "payload_bytes_estimate": { "type": "integer", "minimum": 0, "maximum": 200 }, + "lora_eligible": { "type": "boolean" }, + "normal_network_required_for_sync": { "type": "boolean", "enum": [true] } + } +} diff --git a/schemas/flowmemory/hardware-control-plane-handoff.schema.json b/schemas/flowmemory/hardware-control-plane-handoff.schema.json index dd4bb478..c955ae0a 100644 --- a/schemas/flowmemory/hardware-control-plane-handoff.schema.json +++ b/schemas/flowmemory/hardware-control-plane-handoff.schema.json @@ -64,6 +64,8 @@ "hardwareSignals", "operatorMetadata", "hardwareNodes", + "nodeHealth", + "peerHints", "workReceipts", "verifierReports", "bridgeAlerts", @@ -77,6 +79,8 @@ "hardwareSignals": { "type": "string", "enum": ["signalId"] }, "operatorMetadata": { "type": "string", "enum": ["metadataId"] }, "hardwareNodes": { "type": "string", "enum": ["nodeId"] }, + "nodeHealth": { "type": "string", "enum": ["nodeHealthId"] }, + "peerHints": { "type": "string", "enum": ["peerHintId"] }, "workReceipts": { "type": "string", "enum": ["receiptId"] }, "verifierReports": { "type": "string", "enum": ["reportId"] }, "bridgeAlerts": { "type": "string", "enum": ["bridgeAlertId"] }, @@ -98,6 +102,8 @@ "hardwareSignals", "operatorMetadata", "hardwareNodes", + "nodeHealth", + "peerHints", "workReceipts", "verifierReports", "bridgeAlerts", @@ -111,6 +117,8 @@ "hardwareSignals": { "type": "array", "items": { "type": "object" } }, "operatorMetadata": { "type": "array", "items": { "type": "object" } }, "hardwareNodes": { "type": "array", "items": { "type": "object" } }, + "nodeHealth": { "type": "array", "items": { "type": "object" } }, + "peerHints": { "type": "array", "items": { "type": "object" } }, "workReceipts": { "type": "array", "items": { "type": "object" } }, "verifierReports": { "type": "array", "items": { "type": "object" } }, "bridgeAlerts": { "type": "array", "items": { "type": "object" } }, @@ -126,6 +134,8 @@ "additionalProperties": false, "required": [ "operatorMetadata", + "nodeHealth", + "peerHints", "receipts", "verifierReports", "bridgeAlerts", @@ -137,6 +147,8 @@ ], "properties": { "operatorMetadata": { "type": "array", "items": { "type": "object" } }, + "nodeHealth": { "type": "array", "items": { "type": "object" } }, + "peerHints": { "type": "array", "items": { "type": "object" } }, "receipts": { "type": "array", "items": { "type": "object" } }, "verifierReports": { "type": "array", "items": { "type": "object" } }, "bridgeAlerts": { "type": "array", "items": { "type": "object" } },