diff --git a/docs/FLOWCHAIN_OPERATOR_CHECKLIST.md b/docs/FLOWCHAIN_OPERATOR_CHECKLIST.md index 09b8cd25..c1dfeb00 100644 --- a/docs/FLOWCHAIN_OPERATOR_CHECKLIST.md +++ b/docs/FLOWCHAIN_OPERATOR_CHECKLIST.md @@ -35,10 +35,13 @@ Second-computer readiness check: ```powershell npm run flowchain:prereq +npm run flowchain:doctor npm run flowchain:init -npm run flowchain:start +npm run flowchain:node:start -- -MaxBlocks 3 -Wait +npm run flowchain:node:status npm run flowchain:demo npm run flowchain:export +npm run flowchain:production-l1:e2e ``` Run `npm run flowchain:full-smoke` when the machine has the full prerequisite set, @@ -48,6 +51,7 @@ Capped owner pilot preflight: ```powershell npm run flowchain:real-value-pilot:ops +npm run flowchain:bridge:live:check ``` Do not run live pilot actions until the owner has reviewed @@ -164,6 +168,7 @@ Run before handoff when dependencies are installed: ```powershell npm run flowchain:smoke npm run flowchain:full-smoke +npm run flowchain:production-l1:e2e git diff --check ``` diff --git a/docs/FLOWCHAIN_SECOND_COMPUTER_SETUP.md b/docs/FLOWCHAIN_SECOND_COMPUTER_SETUP.md index 32279fbf..fd0fe660 100644 --- a/docs/FLOWCHAIN_SECOND_COMPUTER_SETUP.md +++ b/docs/FLOWCHAIN_SECOND_COMPUTER_SETUP.md @@ -210,14 +210,20 @@ The final package should provide these root-level commands or documented equivalents: ```powershell +npm install +npm install --prefix apps/dashboard +npm install --prefix crypto npm run flowchain:prereq +npm run flowchain:doctor npm run flowchain:init -npm run flowchain:start -npm run flowchain:stop +npm run flowchain:node:start +npm run flowchain:node:status +npm run flowchain:node:stop npm run flowchain:demo npm run flowchain:smoke npm run flowchain:full-smoke npm run flowchain:product-e2e +npm run flowchain:production-l1:e2e npm run flowchain:real-value-pilot:ops npm run flowchain:real-value-pilot:emergency-stop npm run flowchain:real-value-pilot:export @@ -237,6 +243,10 @@ Current status: | `npm run flowchain:smoke` | Implemented for current private/local surfaces | Runs service tests, crypto validation, launch candidate, devnet tests, control-plane smoke, deterministic replay, dashboard build, hardware fixture, unsafe-claim scan, and no-secret export scan. | | `npm run flowchain:full-smoke` | Implemented acceptance gate | Wraps smoke, wallet CLI sign/verify, full-smoke report, no-secret scan, and `git diff --check`. | | `npm run flowchain:product-e2e` | Implemented product testnet gate | Wraps the full smoke and proves local account funding, token launch, DEX pool/liquidity/swap receipts, bridge-test records, control-plane product queries, workbench product surfaces, and no-secret API boundaries. | +| `npm run flowchain:production-l1:e2e` | Implemented private/local ops wrapper gate | Runs prereq, init, bounded node start/status, wallet, transfer, product, token/DEX, bridge mock, live-readiness refusal, control-plane smoke, dashboard build, export/import, restart recovery, no-secret scan, unsafe-claim scan, and evidence export. | +| `npm run flowchain:bridge:live:check` | Implemented fail-closed readiness check | Checks Base `8453` readiness only when env names are present; prints names, never values, and broadcasts nothing. | +| `npm run flowchain:emergency:stop-local` | Implemented emergency local stop wrapper | Requests node stop and prints/manualizes local service stop commands. | +| `npm run flowchain:emergency:export-evidence` | Implemented secret-scanned evidence export | Writes an ignored local evidence bundle under `devnet/local/production-l1-e2e/evidence/`. | | `npm run flowchain:real-value-pilot:ops` | Branch-local dry-run pilot ops proof | Parser-checks pilot scripts, proves dry-run needs no RPC or keys, verifies missing live env refusal, checks emergency-stop dry-run, and writes sanitized evidence export. | | `npm run flowchain:real-value-pilot:emergency-stop` | Branch-local guarded pause wrapper | Routes to the live `Pause` action after explicit acknowledgement, Base `8453` chain check, cap check, lockbox address check, and owner key check. | | `npm run flowchain:real-value-pilot:export` | Branch-local pilot evidence exporter | Writes a sanitized ignored bundle excluding Git metadata, dependency folders, build targets, local vaults, private-key files, and env files. | @@ -346,3 +356,36 @@ This setup guide is complete for the HQ/Ops wrapper layer and current private/local acceptance gate. The next evidence step is running `npm run flowchain:full-smoke` on a clean second computer and recording the generated report. + +## Current Final Wrapper Path + +For the latest ops wrapper proof, use: + +```powershell +npm install +npm install --prefix apps/dashboard +npm install --prefix crypto +npm run flowchain:production-l1:e2e +``` + +The command writes: + +```text +devnet/local/production-l1-e2e/flowchain-production-l1-e2e-report.json +devnet/local/production-l1-e2e/flowchain-production-l1-e2e-summary.md +devnet/local/production-l1-e2e/evidence/flowchain-production-l1-evidence.zip +``` + +For a no-GitHub-login transfer to a second computer, run: + +```powershell +npm run flowchain:second-computer:bundle +``` + +Then move the generated zip from `devnet/local/second-computer/` to the second +machine, extract it, install dependencies, and run: + +```powershell +npm run flowchain:second-computer:verify +npm run flowchain:production-l1:e2e +``` diff --git a/docs/FLOWCHAIN_TROUBLESHOOTING.md b/docs/FLOWCHAIN_TROUBLESHOOTING.md index f180d714..7b753326 100644 --- a/docs/FLOWCHAIN_TROUBLESHOOTING.md +++ b/docs/FLOWCHAIN_TROUBLESHOOTING.md @@ -38,6 +38,7 @@ From the repo root: ```powershell npm run flowchain:prereq +npm run flowchain:doctor ``` If that fails, fix the missing prerequisite before running init, demo, smoke, @@ -64,6 +65,12 @@ or workbench commands. | Cargo cannot overwrite a Windows `.exe` under `target` | A running node, old test process, or stale shell is locking Cargo build output. | Run `npm run flowchain:node:stop`, close old PowerShell windows, and retry. If the lock remains, reboot before deleting ignored local build output. | | Existing state blocks init | `devnet/local/state.json` already exists. | Run `npm run flowchain:demo`, or force reset with `powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-init.ps1 -Force`. | | Import refuses to overwrite state | Import protects existing local state by default. | Run `npm run flowchain:import -- --BundlePath -Force`. | +| Final wrapper says dashboard dependencies are missing | Dashboard package dependencies have not been installed. | Run `npm install --prefix apps/dashboard`. | +| Final wrapper says crypto package dependencies are missing | Crypto package dependencies have not been installed. | Run `npm install --prefix crypto`. | +| Final wrapper reports live readiness `blocked` | Live Base pilot env values are intentionally absent. | Run `npm run flowchain:bridge:live:check` after setting the required env names locally. | +| Final wrapper reports missing strict live proof commands | Contracts, bridge, or runtime proof commands have not merged yet. | Read `devnet/local/production-l1-e2e/flowchain-production-l1-e2e-report.json` and the owner rows for issues #133, #138, and #134. | +| Evidence export refuses a path | The export stage found an excluded or secret-shaped file. | Move env, vault, key, seed phrase, mnemonic, RPC credential, API key, or webhook files outside the evidence source and rerun `npm run flowchain:emergency:export-evidence`. | +| Import root mismatch | Restored state does not match the exported root. | Rerun `npm run flowchain:export`, import to a fresh state path, and inspect `devnet/local/production-l1-e2e/export-import-root-compare.json`. | ## Clean Local Reset @@ -186,6 +193,17 @@ npm run flowchain:real-value-pilot:ops | Pause or resume cannot broadcast | `cast` is missing, the owner key is missing, or the key is not the lockbox owner. | Install Foundry, verify `$env:FLOWCHAIN_BASE8453_DEPLOYER_PRIVATE_KEY` in the local shell, and rerun the action. | | Evidence export refuses a file | The evidence directory contains an env file, local vault, private-key file, build output, or secret-named path. | Move that file outside the evidence directory and rerun `npm run flowchain:real-value-pilot:export`. | +Current ops readiness check: + +```powershell +npm run flowchain:bridge:live:check +``` + +This command refuses missing acknowledgement, missing RPC URL, wrong Base chain +ID, missing or malformed lockbox, missing token address when token mode requires +one, missing or oversized caps, unsafe confirmation depth, and broad block +ranges. It prints env names only. + ## Smoke Evidence After a successful smoke run, check: diff --git a/docs/agent-runs/production-l1-ops/BACKUP_RESTORE_PROOF.md b/docs/agent-runs/production-l1-ops/BACKUP_RESTORE_PROOF.md new file mode 100644 index 00000000..4ec29183 --- /dev/null +++ b/docs/agent-runs/production-l1-ops/BACKUP_RESTORE_PROOF.md @@ -0,0 +1,24 @@ +# Backup Restore Proof + +Commands: + +```powershell +npm run flowchain:export +npm run flowchain:import -- --BundlePath devnet/local/export/flowchain-local-state.zip -StatePath devnet/local/production-l1-e2e/imported-state.json -Force +npm run flowchain:restart:verify +``` + +Latest comparison: + +```text +Original state root: 0x21be07858c24cc2ecb99fd5d2d0240aa251e13a0910455397855a993b549db6d +Imported state root: 0x21be07858c24cc2ecb99fd5d2d0240aa251e13a0910455397855a993b549db6d +Status: passed +``` + +Evidence: + +- `devnet/local/export/flowchain-local-state.zip` +- `devnet/local/production-l1-e2e/export-import-root-compare.json` +- `devnet/local/node-smoke/one-node-smoke-report.json` + diff --git a/docs/agent-runs/production-l1-ops/BASE_PILOT_GATE_PROOF.md b/docs/agent-runs/production-l1-ops/BASE_PILOT_GATE_PROOF.md new file mode 100644 index 00000000..d96bdbe5 --- /dev/null +++ b/docs/agent-runs/production-l1-ops/BASE_PILOT_GATE_PROOF.md @@ -0,0 +1,51 @@ +# Base Pilot Gate Proof + +Readiness command: + +```powershell +npm run flowchain:bridge:live:check +``` + +Latest readiness status: + +```text +status: blocked +baseChainId: 8453 +broadcasts: false +printsEnvValues: false +``` + +Missing env names: + +- `FLOWCHAIN_PILOT_OPERATOR_ACK` +- `FLOWCHAIN_BASE8453_RPC_URL` +- `FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS` +- `FLOWCHAIN_BASE8453_FROM_BLOCK` +- `FLOWCHAIN_BASE8453_TO_BLOCK` +- `FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI` +- `FLOWCHAIN_PILOT_TOTAL_CAP_WEI` +- `FLOWCHAIN_BASE8453_CONFIRMATION_DEPTH` + +Optional token-mode env names: + +- `FLOWCHAIN_BASE8453_TOKEN_MODE` +- `FLOWCHAIN_BASE8453_SUPPORTED_TOKEN` + +Refusal rules implemented: + +- Missing acknowledgement blocks readiness. +- Missing RPC URL blocks readiness. +- Wrong `eth_chainId` fails readiness; expected Base `8453`. +- Missing or malformed lockbox address blocks or fails readiness. +- Token mode requires a supported token address. +- Missing, zero, negative, or oversized caps fail readiness. +- Missing or unsafe confirmation depth blocks or fails readiness. +- Broad block ranges fail readiness. +- The check never prints live env values. + +Strict live pilot proof remains incomplete until these commands exist: + +- `npm run flowchain:real-value-pilot:contracts` +- `npm run flowchain:real-value-pilot:bridge` +- `npm run flowchain:real-value-pilot:runtime` + diff --git a/docs/agent-runs/production-l1-ops/CHECKLIST.md b/docs/agent-runs/production-l1-ops/CHECKLIST.md new file mode 100644 index 00000000..5778d0af --- /dev/null +++ b/docs/agent-runs/production-l1-ops/CHECKLIST.md @@ -0,0 +1,15 @@ +# Private/Local Ops Wrapper Checklist + +- [x] Read `AGENTS.md`. +- [x] Read `docs/START_HERE.md`. +- [x] Read `docs/FLOWMEMORY_HQ_CONTEXT.md`. +- [x] Read `docs/CURRENT_STATE.md`. +- [x] Read FlowChain second-computer, troubleshooting, and operator docs. +- [x] Read Rootflow, Flow Memory, and launch acceptance docs. +- [x] Inventory existing scripts and root commands. +- [x] Add missing tracking and proof docs. +- [x] Add final `flowchain:production-l1:e2e` command with explicit non-production boundaries. +- [x] Add command aliases for install, lifecycle, wallet, bridge, dashboard, storage, and emergency paths. +- [x] Add parser checks for changed PowerShell scripts. +- [x] Run required gates and record reports. +- [x] Write handoff. diff --git a/docs/agent-runs/production-l1-ops/COMMAND_MATRIX.md b/docs/agent-runs/production-l1-ops/COMMAND_MATRIX.md new file mode 100644 index 00000000..47570338 --- /dev/null +++ b/docs/agent-runs/production-l1-ops/COMMAND_MATRIX.md @@ -0,0 +1,46 @@ +# Command Matrix + +Boundary: `flowchain:production-l1:e2e` is a private/local ops wrapper command. It does not claim production readiness or live-funds readiness. + +| Command | Owner | Subsystem | Latest status | Evidence | +| --- | --- | --- | --- | --- | +| `npm run flowchain:prereq` | installer | install/prereq | passed | final report step `Prerequisite check` | +| `npm run flowchain:doctor` | ops | install/config/status | passed | `devnet/local/doctor/flowchain-doctor-report.json` | +| `npm run flowchain:init` | runtime/storage | local state | passed | final report step `Initialize local state` | +| `npm run flowchain:second-computer:bundle` | ops | offline bundle | command exists | `infra/scripts/flowchain-second-computer-bundle.ps1` | +| `npm run flowchain:second-computer:verify` | ops | second computer | command exists | `infra/scripts/flowchain-second-computer-verify.ps1` | +| `npm run flowchain:node:start` | runtime | node lifecycle | passed in bounded mode | final report step `Node start bounded` | +| `npm run flowchain:node:stop` | runtime | node lifecycle | command exists | existing stop wrapper | +| `npm run flowchain:node:status` | runtime | node lifecycle | passed | final report step `Node status` | +| `npm run flowchain:node:restart` | runtime | node lifecycle | command exists | `infra/scripts/flowchain-node-restart.ps1` | +| `npm run flowchain:node:logs` | runtime/ops | observability | command exists | `infra/scripts/flowchain-node-logs.ps1` | +| `npm run flowchain:wallet:e2e` | wallet/crypto | wallet | passed | `devnet/local/production-l1-e2e/wallet-e2e-report.json` | +| `npm run flowchain:wallet:transfer:e2e` | wallet/runtime | transfer | passed | `devnet/local/production-l1-e2e/wallet-transfer/wallet-transfer-e2e-report.json` | +| `npm run flowchain:product:e2e` | runtime/product | product flow | passed with `-SkipFullSmoke` after baseline | `devnet/local/product-e2e/flowchain-product-e2e-report.json` | +| `npm run flowchain:dex:e2e` | runtime/token-dex | token/DEX | passed | `devnet/local/production-l1-e2e/dex/dex-e2e-report.json` | +| `npm run flowchain:bridge:mock:e2e` | bridge-relayer | mock bridge | passed | final report step `Bridge mock pilot E2E` | +| `npm run flowchain:bridge:live:check` | bridge/ops | Base 8453 readiness | blocked on env | `devnet/local/production-l1-e2e/bridge-live-readiness-report.json` | +| `npm run flowchain:bridge:evidence:export` | ops/security | evidence | passed through emergency alias | `devnet/local/production-l1-e2e/evidence/flowchain-production-l1-evidence-export-report.json` | +| `npm run flowchain:bridge:emergency-stop` | bridge/ops | emergency | command exists | guarded pause wrapper | +| `npm run flowchain:control-plane:smoke` | control-plane | RPC/API | passed | final report step `Control-plane smoke` | +| `npm run flowchain:dashboard:build` | dashboard | workbench | passed | final report step `Dashboard build` | +| `npm run flowchain:dashboard:verify` | dashboard | workbench | command exists | build-backed verification | +| `npm run flowchain:export` | storage | backup/export | passed | final report step `Export local state` | +| `npm run flowchain:import` | storage | restore/import | passed | final report step `Import local state` | +| `npm run flowchain:restart:verify` | runtime/storage | restart recovery | passed | `devnet/local/node-smoke/one-node-smoke-report.json` | +| `npm run flowchain:l1:e2e` | integration | full local gate | passed | `devnet/local/full-smoke/flowchain-full-smoke-report.json` | +| `npm run flowchain:l1-e2e` | integration | compatibility alias | passed | explicit verification run passed | +| `npm run flowchain:real-value-pilot:e2e` | HQ/ops + subsystem owners | live pilot proof | incomplete by design | missing contracts, bridge-relayer, and runtime proof commands | +| `npm run flowchain:production-l1:e2e` | ops | final wrapper | passed with live blockers | `devnet/local/production-l1-e2e/flowchain-production-l1-e2e-report.json` | +| `npm run flowchain:no-secret:scan` | security | secret hygiene | passed | `devnet/local/production-l1-e2e/no-secret-scan-report.json` | +| `npm run flowchain:emergency:stop-local` | ops | emergency | command exists | stop-node plus port stop plan | +| `npm run flowchain:emergency:pause-bridge` | bridge/ops | emergency | command exists | guarded Base 8453 pause wrapper | +| `npm run flowchain:emergency:export-evidence` | ops/security | emergency/evidence | passed | evidence export report | +| `npm run flowchain:emergency:print-recovery` | ops | emergency/recovery | command exists | recovery report script | + +Missing strict live-pilot proof commands: + +- `flowchain:real-value-pilot:contracts`, owner `contracts`, reason: chain ID, lockbox, caps, pause, release/recovery, and replay proof; GitHub issue #133. +- `flowchain:real-value-pilot:bridge`, owner `bridge-relayer`, reason: Base observation, deterministic credit, duplicate handling, and withdrawal/release evidence; GitHub issue #138. +- `flowchain:real-value-pilot:runtime`, owner `chain-runtime`, reason: credit-once, restart, export/import preservation; GitHub issue #134. + diff --git a/docs/agent-runs/production-l1-ops/EMERGENCY_DRILL_PROOF.md b/docs/agent-runs/production-l1-ops/EMERGENCY_DRILL_PROOF.md new file mode 100644 index 00000000..afb46382 --- /dev/null +++ b/docs/agent-runs/production-l1-ops/EMERGENCY_DRILL_PROOF.md @@ -0,0 +1,28 @@ +# Emergency Drill Proof + +Drill command set: + +```powershell +npm run flowchain:emergency:stop-local +npm run flowchain:bridge:emergency-stop +npm run flowchain:emergency:export-evidence +npm run flowchain:emergency:print-recovery +``` + +Local stop behavior: + +- Requests the local node stop file through the runtime wrapper. +- Lists control-plane/dashboard process stop commands for ports `8787` and `5173`. +- Can stop known port processes when the underlying script is run with `-StopKnownPorts`. + +Bridge pause behavior: + +- Routes through `flowchain-real-value-pilot-emergency-stop.ps1`. +- Live mode requires Base 8453 env, acknowledgement, caps, lockbox, and owner key. +- Dry-run mode is covered by `npm run flowchain:real-value-pilot:ops`. + +Evidence: + +- Final evidence export passed. +- Recovery commands are printed by `npm run flowchain:emergency:print-recovery`. + diff --git a/docs/agent-runs/production-l1-ops/EMERGENCY_PROOF.md b/docs/agent-runs/production-l1-ops/EMERGENCY_PROOF.md new file mode 100644 index 00000000..158e92e1 --- /dev/null +++ b/docs/agent-runs/production-l1-ops/EMERGENCY_PROOF.md @@ -0,0 +1,27 @@ +# Emergency Proof + +Emergency command family: + +```powershell +npm run flowchain:emergency:stop-local +npm run flowchain:bridge:emergency-stop +npm run flowchain:emergency:pause-bridge +npm run flowchain:emergency:export-evidence +npm run flowchain:emergency:print-recovery +``` + +What each command does: + +- `flowchain:emergency:stop-local`: requests local node stop and prints/manualizes control-plane and dashboard port stop commands unless `-StopKnownPorts` is explicitly passed to the script. +- `flowchain:bridge:emergency-stop`: routes to the guarded Base 8453 pause action. +- `flowchain:emergency:pause-bridge`: same guarded pause path. +- `flowchain:emergency:export-evidence`: writes a secret-scanned evidence bundle. +- `flowchain:emergency:print-recovery`: writes and prints recovery commands. + +Latest evidence export: + +```text +Bundle: devnet/local/production-l1-e2e/evidence/flowchain-production-l1-evidence.zip +Status: passed +SHA256: 45295F89EDAAA1BFDCE7EE4A1E16AF285554CD12B2166682F456A353333B79FD +``` diff --git a/docs/agent-runs/production-l1-ops/EXPERIMENTS.md b/docs/agent-runs/production-l1-ops/EXPERIMENTS.md new file mode 100644 index 00000000..8d050c12 --- /dev/null +++ b/docs/agent-runs/production-l1-ops/EXPERIMENTS.md @@ -0,0 +1,15 @@ +# Private/Local Ops Wrapper Experiments + +This file records commands run while building the `flowchain:production-l1:e2e` ops wrapper. The wrapper is a private/local gate and does not claim production readiness. + +| Time | Command | Result | Notes | +| --- | --- | --- | --- | +| 2026-05-14 | `npm install`; `npm install --prefix apps/dashboard`; `npm install --prefix crypto` | passed | Installed local dependencies needed for strict smoke and dashboard build. | +| 2026-05-14 | PowerShell parser checks for changed scripts | passed | Parser checked new/changed ops scripts. | +| 2026-05-14 | `npm run flowchain:wallet:transfer:e2e` | passed | Local test-unit transfer recorded in devnet state. | +| 2026-05-14 | `npm run flowchain:dex:e2e` | passed | Product smoke proved token and DEX records. | +| 2026-05-14 | `npm run flowchain:production-l1:e2e` | passed with live blockers | Mock path passed; live Base pilot blocked on env and missing proof commands. | +| 2026-05-14 | `npm run flowchain:l1-e2e` | passed | Explicit compatibility alias verification. | +| 2026-05-14 | `npm run flowchain:real-value-pilot:e2e` | incomplete | Strict live pilot gate blocked by missing contracts, bridge, and runtime proof commands. | +| 2026-05-14 | `node infra/scripts/check-unsafe-claims.mjs` | passed | Claim scan clean. | +| 2026-05-14 | `git diff --check` | passed | Whitespace check clean. | diff --git a/docs/agent-runs/production-l1-ops/FINAL_E2E_PROOF.md b/docs/agent-runs/production-l1-ops/FINAL_E2E_PROOF.md new file mode 100644 index 00000000..ce7cbae4 --- /dev/null +++ b/docs/agent-runs/production-l1-ops/FINAL_E2E_PROOF.md @@ -0,0 +1,35 @@ +# Final E2E Proof + +Latest root command: + +```powershell +npm run flowchain:production-l1:e2e +``` + +Result: + +```text +FlowChain production-l1:e2e status: passed-with-live-blockers +Dashboard URL: http://127.0.0.1:5173/ +Data directory: E:\FlowMemory\flowmemory-prod-ops\devnet\local +Final report: E:\FlowMemory\flowmemory-prod-ops\devnet\local\production-l1-e2e\flowchain-production-l1-e2e-report.json +Evidence bundle: E:\FlowMemory\flowmemory-prod-ops\devnet\local\production-l1-e2e\evidence\flowchain-production-l1-evidence.zip +Bridge live readiness command: npm run flowchain:bridge:live:check +``` + +Machine-readable report summary: + +- Overall: `passed-with-live-blockers`. +- Mock-safe local path: `passed`. +- Live readiness: `blocked`. +- State root: `0x21be07858c24cc2ecb99fd5d2d0240aa251e13a0910455397855a993b549db6d`. +- Backup/restore root comparison: passed. +- No-secret scan: passed. +- Unsafe-claim scan: passed. + +Strict live pilot remains blocked by missing owner/env inputs and missing subsystem proof commands: + +- `flowchain:real-value-pilot:contracts` +- `flowchain:real-value-pilot:bridge` +- `flowchain:real-value-pilot:runtime` + diff --git a/docs/agent-runs/production-l1-ops/FINAL_E2E_REPORT_SCHEMA.md b/docs/agent-runs/production-l1-ops/FINAL_E2E_REPORT_SCHEMA.md new file mode 100644 index 00000000..eb87c4bf --- /dev/null +++ b/docs/agent-runs/production-l1-ops/FINAL_E2E_REPORT_SCHEMA.md @@ -0,0 +1,40 @@ +# Final E2E Report Schema + +Report path: + +```text +devnet/local/production-l1-e2e/flowchain-production-l1-e2e-report.json +``` + +Required top-level fields now written by `npm run flowchain:production-l1:e2e`: + +| Field | Meaning | +| --- | --- | +| `schema` | Report schema id. | +| `timestamp` | UTC report time. | +| `repoPath` | Local repository root. | +| `git.branch`, `git.commit` | Current branch and commit when available. | +| `os.platform`, `os.version`, `os.shell` | Local OS and shell metadata. | +| `toolVersions` | Git, Node, npm, Cargo, Rust, Foundry, Cast, and Python discovery. | +| `portsUsed` | Control-plane and dashboard port status. | +| `localUrls` | Dashboard and control-plane URLs. | +| `dataDirectory` | Local devnet data directory. | +| `chainId` | Local chain id. | +| `genesisHash` | First local block hash when available. | +| `latestHeight`, `latestHash`, `finalizedHeight`, `stateRoot` | Current local chain head and state root. | +| `walletE2EStatus`, `transferE2EStatus` | Wallet and local transfer status. | +| `tokenE2EStatus`, `dexE2EStatus`, `productE2EStatus` | Product/token/DEX status. | +| `bridgeMockStatus`, `bridgeLiveReadinessStatus` | Mock bridge and live-readiness status. | +| `rpcSmokeStatus` | Control-plane smoke status. | +| `dashboardBuildOrBrowserStatus` | Dashboard build or browser verification status. | +| `exportImportStatus`, `restartRecoveryStatus` | Backup/restore and restart recovery status. | +| `noSecretScanStatus`, `unsafeClaimScanStatus` | Security scan status. | +| `missingEnvNamesForLiveMode` | Env names only, never values. | +| `missingSubsystemCommands` | Owner, reason, log path, report path, and blocker class for missing strict live-pilot proof commands. | +| `failureBlockerDetails` | Failure/blocker rows with subsystem, owner, command, status, log path, report path, blocker class, and mock/live impact booleans. | +| `commandList` | Commands run or verified by the wrapper. | +| `subsystemSteps` | Per-step status, owner, command, log path, report path, and blocker class. | +| `localLogPaths`, `healthEndpoint`, `reportPaths` | Observability paths. | +| `restartCommands`, `emergencyCommands` | Operator commands for recovery and stop paths. | +| `evidencePaths` | Evidence bundle, report, and export bundle paths. | +| `passFailSummary` | Overall, mock path, live readiness, live broadcast, failures, and blockers. | diff --git a/docs/agent-runs/production-l1-ops/HANDOFF.md b/docs/agent-runs/production-l1-ops/HANDOFF.md new file mode 100644 index 00000000..65c87173 --- /dev/null +++ b/docs/agent-runs/production-l1-ops/HANDOFF.md @@ -0,0 +1,66 @@ +# Handoff + +Current state: + +- `npm run flowchain:production-l1:e2e` passes the mock-safe local path. +- Overall report status is `passed-with-live-blockers`. +- Strict live pilot is blocked by env names and missing subsystem proof commands. + +Final commands: + +```powershell +npm run flowchain:production-l1:e2e +npm run flowchain:bridge:live:check +npm run flowchain:emergency:stop-local +npm run flowchain:emergency:export-evidence +``` + +Reports and evidence: + +- Final JSON report: `devnet/local/production-l1-e2e/flowchain-production-l1-e2e-report.json` +- Readable summary: `devnet/local/production-l1-e2e/flowchain-production-l1-e2e-summary.md` +- Evidence bundle: `devnet/local/production-l1-e2e/evidence/flowchain-production-l1-evidence.zip` +- Export bundle: `devnet/local/export/flowchain-local-state.zip` +- Bridge readiness report: `devnet/local/production-l1-e2e/bridge-live-readiness-report.json` + +Local URLs: + +- Dashboard: `http://127.0.0.1:5173/` +- Control-plane health: `http://127.0.0.1:8787/health` + +Data directory: + +```text +E:\FlowMemory\flowmemory-prod-ops\devnet\local +``` + +Required live env names: + +- `FLOWCHAIN_PILOT_OPERATOR_ACK` +- `FLOWCHAIN_BASE8453_RPC_URL` +- `FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS` +- `FLOWCHAIN_BASE8453_FROM_BLOCK` +- `FLOWCHAIN_BASE8453_TO_BLOCK` +- `FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI` +- `FLOWCHAIN_PILOT_TOTAL_CAP_WEI` +- `FLOWCHAIN_BASE8453_CONFIRMATION_DEPTH` +- `FLOWCHAIN_BASE8453_TOKEN_MODE` +- `FLOWCHAIN_BASE8453_SUPPORTED_TOKEN` + +Emergency commands: + +```powershell +npm run flowchain:emergency:stop-local +npm run flowchain:bridge:emergency-stop +npm run flowchain:emergency:pause-bridge +npm run flowchain:emergency:export-evidence +npm run flowchain:emergency:print-recovery +``` + +Blocked reason for live pilot: + +- Owner live env values are not present in this shell. +- `flowchain:real-value-pilot:contracts` is missing; owner `contracts`, issue #133. +- `flowchain:real-value-pilot:bridge` is missing; owner `bridge-relayer`, issue #138. +- `flowchain:real-value-pilot:runtime` is missing; owner `chain-runtime`, issue #134. + diff --git a/docs/agent-runs/production-l1-ops/INSTALL_PROOF.md b/docs/agent-runs/production-l1-ops/INSTALL_PROOF.md new file mode 100644 index 00000000..5ac878c2 --- /dev/null +++ b/docs/agent-runs/production-l1-ops/INSTALL_PROOF.md @@ -0,0 +1,50 @@ +# Install Proof + +Windows prerequisite and install commands: + +```powershell +npm install +npm install --prefix apps/dashboard +npm install --prefix crypto +npm run flowchain:prereq +npm run flowchain:doctor +``` + +Authenticated private-repo path: + +```powershell +winget install --id Git.Git --exact --source winget --accept-package-agreements --accept-source-agreements +winget install --id GitHub.cli --exact --source winget --accept-package-agreements --accept-source-agreements +$env:Path = [Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [Environment]::GetEnvironmentVariable("Path","User") +gh auth login +gh repo clone FlowmemoryAI/FlowMemory "$env:USERPROFILE\FlowMemory\FlowMemory" +cd "$env:USERPROFILE\FlowMemory\FlowMemory" +npm install +npm install --prefix apps/dashboard +npm install --prefix crypto +npm run flowchain:production-l1:e2e +``` + +Offline bundle path: + +```powershell +npm run flowchain:second-computer:bundle +``` + +On the second computer: + +```powershell +Expand-Archive .\flowchain-second-computer-source-bundle.zip -DestinationPath "$env:USERPROFILE\FlowMemory" +cd "$env:USERPROFILE\FlowMemory\FlowMemory" +npm install +npm install --prefix apps/dashboard +npm install --prefix crypto +npm run flowchain:second-computer:verify +npm run flowchain:production-l1:e2e +``` + +Latest local prerequisite proof: + +- `npm run flowchain:prereq` passed inside the final root command. +- `npm run flowchain:doctor` passed with local live mode blocked on env names. + diff --git a/docs/agent-runs/production-l1-ops/NOTES.md b/docs/agent-runs/production-l1-ops/NOTES.md new file mode 100644 index 00000000..c7c7a853 --- /dev/null +++ b/docs/agent-runs/production-l1-ops/NOTES.md @@ -0,0 +1,8 @@ +# Private/Local Ops Wrapper Notes + +## Initial Findings + +- Existing docs mark the private/local FlowChain package as the current milestone. +- Existing root scripts cover prereq, init, bounded start/stop, demo, smoke, full smoke, product E2E, export/import, workbench, and real-value pilot ops. +- The requested final wrapper must be more explicit than `flowchain:full-smoke`: it must produce a `flowchain:production-l1:e2e` JSON report with subsystem status, URLs, logs, evidence paths, emergency commands, and live-pilot missing env names. +- The wrapper must not claim production readiness when a subsystem is missing or blocked on external live env. diff --git a/docs/agent-runs/production-l1-ops/OBSERVABILITY_PROOF.md b/docs/agent-runs/production-l1-ops/OBSERVABILITY_PROOF.md new file mode 100644 index 00000000..f837d211 --- /dev/null +++ b/docs/agent-runs/production-l1-ops/OBSERVABILITY_PROOF.md @@ -0,0 +1,34 @@ +# Observability Proof + +Local URLs: + +- Dashboard: `http://127.0.0.1:5173/` +- Control-plane health: `http://127.0.0.1:8787/health` +- Control-plane state: `http://127.0.0.1:8787/state` + +Status and log commands: + +```powershell +npm run flowchain:doctor +npm run flowchain:node:status +npm run flowchain:node:logs +npm run flowchain:control-plane:smoke +npm run flowchain:dashboard:build +``` + +Predictable paths: + +- Final reports/logs: `devnet/local/production-l1-e2e/` +- Node logs: `devnet/local/node/logs/` +- Node status: `devnet/local/node/node-status.json` +- Bridge mock output: `services/bridge-relayer/out/` +- Real-value pilot report: `devnet/local/real-value-pilot/flowchain-real-value-pilot-e2e-report.json` +- Evidence bundle: `devnet/local/production-l1-e2e/evidence/flowchain-production-l1-evidence.zip` + +Latest status: + +- Control-plane smoke: passed. +- Dashboard build: passed. +- Node status: passed. +- Bridge live readiness: blocked on env names, not crashed. + diff --git a/docs/agent-runs/production-l1-ops/ONE_COMMAND_LOCAL_PROOF.md b/docs/agent-runs/production-l1-ops/ONE_COMMAND_LOCAL_PROOF.md new file mode 100644 index 00000000..cf87da6a --- /dev/null +++ b/docs/agent-runs/production-l1-ops/ONE_COMMAND_LOCAL_PROOF.md @@ -0,0 +1,27 @@ +# One Command Local Proof + +Command: + +```powershell +npm run flowchain:production-l1:e2e +``` + +Latest result: + +- Mock-safe local path: passed. +- Prereq, init, node start, node status: passed. +- Wallet E2E and local transfer: passed. +- Product, token, and DEX: passed. +- Bridge mock pilot: passed. +- Control-plane smoke: passed. +- Dashboard build: passed. +- Export/import root comparison: passed. +- Restart recovery: passed. +- No-secret and unsafe-claim scans: passed. + +The command writes: + +- JSON report: `devnet/local/production-l1-e2e/flowchain-production-l1-e2e-report.json` +- Readable summary: `devnet/local/production-l1-e2e/flowchain-production-l1-e2e-summary.md` +- Evidence bundle: `devnet/local/production-l1-e2e/evidence/flowchain-production-l1-evidence.zip` + diff --git a/docs/agent-runs/production-l1-ops/PLAN.md b/docs/agent-runs/production-l1-ops/PLAN.md new file mode 100644 index 00000000..4f955dac --- /dev/null +++ b/docs/agent-runs/production-l1-ops/PLAN.md @@ -0,0 +1,35 @@ +# Private/Local Ops Wrapper Plan + +Status: in progress. + +## Scope + +Allowed folders: + +- `infra/scripts/` +- `docs/` +- `package.json` +- `README.md` +- `.github/` +- `docs/agent-runs/production-l1-ops/` + +Forbidden folders: + +- `crates/` +- `contracts/` +- `services/` +- `crypto/` +- `apps/dashboard/` +- `hardware/` +- committed local secrets + +## Plan + +1. Inventory package scripts, PowerShell wrappers, docs, and generated report paths. +2. Add or normalize root command aliases without breaking existing command names. +3. Implement a mock-safe `flowchain:production-l1:e2e` orchestrator that writes JSON and readable summaries without claiming production readiness. +4. Add Windows prereq, doctor, lifecycle, status, logs, export/import, restart, evidence, emergency, and live-readiness command wrappers where missing. +5. Fail closed for Base `8453` live-readiness checks and print only env names. +6. Add secret-safe evidence export and final no-secret/unsafe-claim checks. +7. Update second-computer, troubleshooting, operator, command matrix, and proof docs. +8. Run parser checks and required npm gates, then record the final result. diff --git a/docs/agent-runs/production-l1-ops/REAL_FUNDS_COMMAND_SEPARATION.md b/docs/agent-runs/production-l1-ops/REAL_FUNDS_COMMAND_SEPARATION.md new file mode 100644 index 00000000..f87bdb8d --- /dev/null +++ b/docs/agent-runs/production-l1-ops/REAL_FUNDS_COMMAND_SEPARATION.md @@ -0,0 +1,34 @@ +# Real Funds Command Separation + +Mock-safe commands: + +```powershell +npm run flowchain:production-l1:e2e +npm run flowchain:bridge:mock:e2e +npm run flowchain:real-value-pilot:e2e -- -AllowIncomplete -SkipBaseline +``` + +Readiness command, no broadcast: + +```powershell +npm run flowchain:bridge:live:check +``` + +Live owner actions are separated behind explicit acknowledgement and local env: + +```powershell +$env:FLOWCHAIN_PILOT_OPERATOR_ACK="I_UNDERSTAND_THIS_IS_CAPPED_BASE8453_OWNER_PILOT" +npm run flowchain:real-value-pilot -- --Mode Live --Action Observe +npm run flowchain:real-value-pilot -- --Mode Live --Action Credit +npm run flowchain:real-value-pilot -- --Mode Live --Action Withdraw +npm run flowchain:bridge:emergency-stop +``` + +Rules: + +- Mock commands do not require live RPC or keys. +- Readiness commands may read live env but do not broadcast. +- Broadcast actions require explicit acknowledgement and owner-supplied env. +- Live commands print env names and caps, never env values. +- Only tiny owner pilot funds should be used after strict proof commands pass. + diff --git a/docs/agent-runs/production-l1-ops/REPORT_QUALITY_PROOF.md b/docs/agent-runs/production-l1-ops/REPORT_QUALITY_PROOF.md new file mode 100644 index 00000000..095cb52b --- /dev/null +++ b/docs/agent-runs/production-l1-ops/REPORT_QUALITY_PROOF.md @@ -0,0 +1,26 @@ +# Report Quality Proof + +The final report includes per-subsystem entries with: + +- owner; +- command; +- status; +- log path; +- report path when available; +- blocker class: `mockPath`, `liveReadiness`, `liveBroadcast`, or `none`; +- reason on failure or block. + +Latest summary: + +- Overall: `passed-with-live-blockers`. +- Mock path: `passed`. +- Live readiness: `blocked`. +- Live broadcast: not run; requires explicit operator acknowledgement and owner env. + +Missing live env is represented as `blocked`, not a crash. + +Unsafe live config is represented as `failed` by `npm run flowchain:bridge:live:check`, with env names only. + +Missing strict live-pilot proof commands include owner, command, reason, log path, report path, and blocker class in `missingSubsystemCommands`. + +`failureBlockerDetails` repeats every non-pass/blocker row with subsystem, owner, command, status, log path, report path, blocker class, and explicit mock/live-readiness/live-broadcast impact booleans. diff --git a/docs/agent-runs/production-l1-ops/SECOND_COMPUTER_PROOF.md b/docs/agent-runs/production-l1-ops/SECOND_COMPUTER_PROOF.md new file mode 100644 index 00000000..f7e55066 --- /dev/null +++ b/docs/agent-runs/production-l1-ops/SECOND_COMPUTER_PROOF.md @@ -0,0 +1,40 @@ +# Second Computer Proof + +Fresh Windows command order: + +```powershell +winget install --id Git.Git --exact --source winget --accept-package-agreements --accept-source-agreements +winget install --id GitHub.cli --exact --source winget --accept-package-agreements --accept-source-agreements +$env:Path = [Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [Environment]::GetEnvironmentVariable("Path","User") +gh auth login +gh repo clone FlowmemoryAI/FlowMemory "$env:USERPROFILE\FlowMemory\FlowMemory" +cd "$env:USERPROFILE\FlowMemory\FlowMemory" +npm install +npm install --prefix apps/dashboard +npm install --prefix crypto +npm run flowchain:second-computer:verify +npm run flowchain:production-l1:e2e +``` + +Restore from export: + +```powershell +npm run flowchain:import -- --BundlePath devnet/local/export/flowchain-local-state.zip -Force +npm run flowchain:node:status +``` + +Start and inspect: + +```powershell +npm run flowchain:node:start +npm run flowchain:node:status +npm run control-plane:serve +npm run workbench:dev +``` + +Stop: + +```powershell +npm run flowchain:emergency:stop-local +``` + diff --git a/docs/agent-runs/production-l1-ops/SECRET_SCAN_PROOF.md b/docs/agent-runs/production-l1-ops/SECRET_SCAN_PROOF.md new file mode 100644 index 00000000..e5779fd0 --- /dev/null +++ b/docs/agent-runs/production-l1-ops/SECRET_SCAN_PROOF.md @@ -0,0 +1,41 @@ +# Secret Scan Proof + +Commands: + +```powershell +npm run flowchain:no-secret:scan +node infra/scripts/check-unsafe-claims.mjs +npm run flowchain:emergency:export-evidence +``` + +Latest status: + +- No-secret scan: passed. +- Unsafe-claim scan: passed. +- Evidence stage no-secret scan: passed. +- Evidence bundle manifest safety check: passed. + +Scanned surfaces include: + +- final command reports and logs; +- full-smoke and product reports; +- real-value pilot reports; +- dashboard fixtures; +- bridge mock evidence; +- wallet public metadata. + +Evidence export excludes: + +- `.git`; +- `node_modules`; +- Rust target directories; +- build outputs; +- env files; +- local vaults; +- private-key material; +- seed phrase and mnemonic files; +- RPC credentials; +- API keys; +- webhooks; +- nested archives. + diff --git a/infra/scripts/flowchain-bridge-live-check.ps1 b/infra/scripts/flowchain-bridge-live-check.ps1 new file mode 100644 index 00000000..8793d7a6 --- /dev/null +++ b/infra/scripts/flowchain-bridge-live-check.ps1 @@ -0,0 +1,243 @@ +param( + [string] $ReportPath = "devnet/local/production-l1-e2e/bridge-live-readiness-report.json", + [switch] $AllowBlocked +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +. "$PSScriptRoot\flowchain-common.ps1" + +$repoRoot = Set-FlowChainRepoRoot +$reportFullPath = Assert-FlowChainPathInsideRepo -RepoRoot $repoRoot -Path (Resolve-FlowChainPath -RepoRoot $repoRoot -Path $ReportPath) + +$Base8453ChainId = 8453 +$OperatorAckValue = "I_UNDERSTAND_THIS_IS_CAPPED_BASE8453_OWNER_PILOT" +$MaxSingleDepositWeiLimit = [UInt64] 100000000000000 +$TotalCapWeiLimit = [UInt64] 1000000000000000 +$MaxBlockRange = [UInt64] 5000 +$MinConfirmationDepth = [UInt64] 2 +$MaxConfirmationDepth = [UInt64] 256 + +$requiredEnv = @( + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_BASE8453_TO_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_BASE8453_CONFIRMATION_DEPTH" +) + +function Get-EnvValue { + param([Parameter(Mandatory = $true)][string] $Name) + return [Environment]::GetEnvironmentVariable($Name, "Process") +} + +function Add-Problem { + param( + [System.Collections.ArrayList] $Problems, + [string] $EnvName, + [string] $Reason, + [string] $Kind = "blocked" + ) + + [void] $Problems.Add([ordered]@{ + envName = $EnvName + reason = $Reason + kind = $Kind + }) +} + +function Convert-UInt64Env { + param( + [string] $Name, + [string] $Value, + [System.Collections.ArrayList] $Problems + ) + + if ([string]::IsNullOrWhiteSpace($Value)) { + Add-Problem -Problems $Problems -EnvName $Name -Reason "missing required env value" + return $null + } + if ($Value -notmatch '^[0-9]+$') { + Add-Problem -Problems $Problems -EnvName $Name -Reason "must be a non-negative decimal integer" -Kind "failed" + return $null + } + try { + return [UInt64]::Parse($Value, [System.Globalization.CultureInfo]::InvariantCulture) + } + catch { + Add-Problem -Problems $Problems -EnvName $Name -Reason "outside supported UInt64 range" -Kind "failed" + return $null + } +} + +function Invoke-SafeRpcChainId { + param([string] $RpcUrl) + + $body = ([ordered]@{ + jsonrpc = "2.0" + id = 1 + method = "eth_chainId" + params = @() + } | ConvertTo-Json -Depth 6 -Compress) + + try { + $response = Invoke-RestMethod -Uri $RpcUrl -Method Post -ContentType "application/json" -Body $body -TimeoutSec 20 + } + catch { + throw "Could not read eth_chainId from FLOWCHAIN_BASE8453_RPC_URL." + } + + if ($response.PSObject.Properties.Name -contains "error") { + throw "RPC eth_chainId returned an error." + } + if (-not ($response.PSObject.Properties.Name -contains "result")) { + throw "RPC eth_chainId did not return result." + } + + return $response.result +} + +$problems = New-Object System.Collections.ArrayList +$checks = [ordered]@{} +$missingEnv = New-Object System.Collections.ArrayList + +foreach ($name in $requiredEnv) { + if ([string]::IsNullOrWhiteSpace((Get-EnvValue -Name $name))) { + [void] $missingEnv.Add($name) + Add-Problem -Problems $problems -EnvName $name -Reason "missing required env value" + } +} + +$tokenMode = Get-EnvValue -Name "FLOWCHAIN_BASE8453_TOKEN_MODE" +if ([string]::IsNullOrWhiteSpace($tokenMode)) { + $tokenMode = "native" +} +$checks.tokenMode = $tokenMode +if ($tokenMode -in @("erc20", "token", "supported-token")) { + $supportedToken = Get-EnvValue -Name "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN" + if ([string]::IsNullOrWhiteSpace($supportedToken)) { + [void] $missingEnv.Add("FLOWCHAIN_BASE8453_SUPPORTED_TOKEN") + Add-Problem -Problems $problems -EnvName "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN" -Reason "missing for token mode" + } + elseif ($supportedToken -notmatch '^0x[0-9a-fA-F]{40}$') { + Add-Problem -Problems $problems -EnvName "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN" -Reason "must be a 20-byte hex address" -Kind "failed" + } +} + +$ack = Get-EnvValue -Name "FLOWCHAIN_PILOT_OPERATOR_ACK" +if (-not [string]::IsNullOrWhiteSpace($ack) -and $ack -ne $OperatorAckValue) { + Add-Problem -Problems $problems -EnvName "FLOWCHAIN_PILOT_OPERATOR_ACK" -Reason "acknowledgement value is not the required exact string" -Kind "failed" +} + +$rpcUrl = Get-EnvValue -Name "FLOWCHAIN_BASE8453_RPC_URL" +if (-not [string]::IsNullOrWhiteSpace($rpcUrl)) { + $uri = $null + if (-not [System.Uri]::TryCreate($rpcUrl, [System.UriKind]::Absolute, [ref] $uri) -or ($uri.Scheme -ne "http" -and $uri.Scheme -ne "https")) { + Add-Problem -Problems $problems -EnvName "FLOWCHAIN_BASE8453_RPC_URL" -Reason "must be an absolute HTTP(S) URL" -Kind "failed" + } + else { + try { + $chainHex = Invoke-SafeRpcChainId -RpcUrl $rpcUrl + if ($chainHex -notmatch '^0x[0-9a-fA-F]+$') { + Add-Problem -Problems $problems -EnvName "FLOWCHAIN_BASE8453_RPC_URL" -Reason "eth_chainId returned invalid hex" -Kind "failed" + } + else { + $actualChainId = [Convert]::ToInt64($chainHex.Substring(2), 16) + $checks.chainId = [ordered]@{ + expected = $Base8453ChainId + actual = $actualChainId + passed = ($actualChainId -eq $Base8453ChainId) + } + if ($actualChainId -ne $Base8453ChainId) { + Add-Problem -Problems $problems -EnvName "FLOWCHAIN_BASE8453_RPC_URL" -Reason "wrong chain id; expected Base 8453" -Kind "failed" + } + } + } + catch { + Add-Problem -Problems $problems -EnvName "FLOWCHAIN_BASE8453_RPC_URL" -Reason $_.Exception.Message -Kind "failed" + } + } +} + +$lockbox = Get-EnvValue -Name "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS" +if (-not [string]::IsNullOrWhiteSpace($lockbox) -and $lockbox -notmatch '^0x[0-9a-fA-F]{40}$') { + Add-Problem -Problems $problems -EnvName "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS" -Reason "must be a 20-byte hex address" -Kind "failed" +} + +$maxDeposit = Convert-UInt64Env -Name "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI" -Value (Get-EnvValue -Name "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI") -Problems $problems +$totalCap = Convert-UInt64Env -Name "FLOWCHAIN_PILOT_TOTAL_CAP_WEI" -Value (Get-EnvValue -Name "FLOWCHAIN_PILOT_TOTAL_CAP_WEI") -Problems $problems +if ($null -ne $maxDeposit) { + if ($maxDeposit -eq 0) { Add-Problem -Problems $problems -EnvName "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI" -Reason "must be nonzero" -Kind "failed" } + if ($maxDeposit -gt $MaxSingleDepositWeiLimit) { Add-Problem -Problems $problems -EnvName "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI" -Reason "above documented pilot cap" -Kind "failed" } +} +if ($null -ne $totalCap) { + if ($totalCap -eq 0) { Add-Problem -Problems $problems -EnvName "FLOWCHAIN_PILOT_TOTAL_CAP_WEI" -Reason "must be nonzero" -Kind "failed" } + if ($totalCap -gt $TotalCapWeiLimit) { Add-Problem -Problems $problems -EnvName "FLOWCHAIN_PILOT_TOTAL_CAP_WEI" -Reason "above documented pilot cap" -Kind "failed" } +} +if ($null -ne $maxDeposit -and $null -ne $totalCap -and $totalCap -lt $maxDeposit) { + Add-Problem -Problems $problems -EnvName "FLOWCHAIN_PILOT_TOTAL_CAP_WEI" -Reason "must be greater than or equal to max deposit" -Kind "failed" +} + +$confirmations = Convert-UInt64Env -Name "FLOWCHAIN_BASE8453_CONFIRMATION_DEPTH" -Value (Get-EnvValue -Name "FLOWCHAIN_BASE8453_CONFIRMATION_DEPTH") -Problems $problems +if ($null -ne $confirmations) { + if ($confirmations -lt $MinConfirmationDepth) { Add-Problem -Problems $problems -EnvName "FLOWCHAIN_BASE8453_CONFIRMATION_DEPTH" -Reason "confirmation depth is unsafe" -Kind "failed" } + if ($confirmations -gt $MaxConfirmationDepth) { Add-Problem -Problems $problems -EnvName "FLOWCHAIN_BASE8453_CONFIRMATION_DEPTH" -Reason "confirmation depth is unexpectedly high for the pilot" -Kind "failed" } +} + +$fromBlock = Convert-UInt64Env -Name "FLOWCHAIN_BASE8453_FROM_BLOCK" -Value (Get-EnvValue -Name "FLOWCHAIN_BASE8453_FROM_BLOCK") -Problems $problems +$toBlock = Convert-UInt64Env -Name "FLOWCHAIN_BASE8453_TO_BLOCK" -Value (Get-EnvValue -Name "FLOWCHAIN_BASE8453_TO_BLOCK") -Problems $problems +if ($null -ne $fromBlock -and $null -ne $toBlock) { + if ($fromBlock -gt $toBlock) { + Add-Problem -Problems $problems -EnvName "FLOWCHAIN_BASE8453_FROM_BLOCK" -Reason "from block must be <= to block" -Kind "failed" + } + elseif (($toBlock - $fromBlock) -gt $MaxBlockRange) { + Add-Problem -Problems $problems -EnvName "FLOWCHAIN_BASE8453_TO_BLOCK" -Reason "block range is too broad" -Kind "failed" + } +} + +$failed = @($problems | Where-Object { $_.kind -eq "failed" }) +$status = if ($failed.Count -gt 0) { + "failed" +} +elseif ($problems.Count -gt 0) { + "blocked" +} +else { + "passed" +} + +$report = [ordered]@{ + schema = "flowchain.bridge_live_readiness_report.v0" + generatedAt = (Get-Date).ToUniversalTime().ToString("o") + status = $status + owner = "bridge/ops" + baseChainId = $Base8453ChainId + command = "npm run flowchain:bridge:live:check" + missingEnvNames = @($missingEnv | Select-Object -Unique) + envNames = @($requiredEnv + @("FLOWCHAIN_BASE8453_TOKEN_MODE", "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN")) + checks = $checks + problems = @($problems) + capLimits = [ordered]@{ + maxSingleDepositWei = $MaxSingleDepositWeiLimit.ToString() + totalCapWei = $TotalCapWeiLimit.ToString() + maxBlockRange = $MaxBlockRange.ToString() + minConfirmationDepth = $MinConfirmationDepth.ToString() + } + broadcasts = $false + printsEnvValues = $false +} +Write-FlowChainJson -Path $reportFullPath -Value $report -Depth 16 + +Write-Host "FlowChain bridge live readiness status: $status" +Write-Host "Report: $reportFullPath" +if ($missingEnv.Count -gt 0) { + Write-Host "Missing env names: $((@($missingEnv | Select-Object -Unique)) -join ', ')" +} +if ($status -ne "passed" -and -not $AllowBlocked) { + throw "Bridge live readiness $status. See report for env names and reasons." +} + diff --git a/infra/scripts/flowchain-dex-e2e.ps1 b/infra/scripts/flowchain-dex-e2e.ps1 new file mode 100644 index 00000000..442a3ff7 --- /dev/null +++ b/infra/scripts/flowchain-dex-e2e.ps1 @@ -0,0 +1,75 @@ +param( + [string] $OutDir = "devnet/local/production-l1-e2e/dex", + [string] $StatePath = "devnet/local/production-l1-e2e/dex/state.json" +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +. "$PSScriptRoot\flowchain-common.ps1" + +$repoRoot = Set-FlowChainRepoRoot +Set-FlowChainCargoTargetDir -RepoRoot $repoRoot -Purpose "dex-e2e" | Out-Null +$outFullDir = Assert-FlowChainPathInsideRepo -RepoRoot $repoRoot -Path (Resolve-FlowChainPath -RepoRoot $repoRoot -Path $OutDir) +$stateFullPath = Assert-FlowChainPathInsideRepo -RepoRoot $repoRoot -Path (Resolve-FlowChainPath -RepoRoot $repoRoot -Path $StatePath) +$exportDir = Join-Path $outFullDir "export" +$reportPath = Join-Path $outFullDir "dex-e2e-report.json" + +if (Test-Path -LiteralPath $outFullDir) { + Remove-Item -LiteralPath $outFullDir -Recurse -Force +} +New-Item -ItemType Directory -Force -Path $outFullDir | Out-Null + +$previousErrorActionPreference = $ErrorActionPreference +$ErrorActionPreference = "Continue" +try { + $productOutput = (& cargo run --manifest-path crates/flowmemory-devnet/Cargo.toml -- --state $stateFullPath product-smoke --out-dir $exportDir 2>&1) | ForEach-Object { "$_" } + $productExitCode = $LASTEXITCODE +} +finally { + $ErrorActionPreference = $previousErrorActionPreference +} +if ($productExitCode -ne 0) { + throw "Runtime product-smoke failed: $($productOutput -join [Environment]::NewLine)" +} +$text = $productOutput -join [Environment]::NewLine +$jsonStart = $text.IndexOf("{") +$jsonEnd = $text.LastIndexOf("}") +if ($jsonStart -lt 0 -or $jsonEnd -lt $jsonStart) { + throw "Runtime product-smoke did not emit JSON output." +} +$productSmoke = $text.Substring($jsonStart, $jsonEnd - $jsonStart + 1) | ConvertFrom-Json + +$required = @( + "tokenLaunched", + "poolCreated", + "liquidityAdded", + "swapExecuted", + "liquidityRemoved", + "productReceiptsQueryable", + "noValueBoundary" +) +foreach ($name in $required) { + if (-not [bool] $productSmoke.checks.$name) { + throw "DEX/product check failed: $name" + } +} + +$report = [ordered]@{ + schema = "flowchain.dex_e2e_report.v0" + generatedAt = (Get-Date).ToUniversalTime().ToString("o") + status = "passed" + statePath = $stateFullPath + exportDir = $exportDir + tokenStatus = "passed" + dexStatus = "passed" + checks = $productSmoke.checks + stateRoot = $productSmoke.stateRoot + noValueBoundary = "local product testnet records only; no tokenomics or real value" +} +Write-FlowChainJson -Path $reportPath -Value $report -Depth 16 +Assert-FlowChainNoSecretFiles -Path $outFullDir + +Write-Host "FlowChain token/DEX E2E passed." +Write-Host "State root: $($productSmoke.stateRoot)" +Write-Host "Report: $reportPath" diff --git a/infra/scripts/flowchain-doctor.ps1 b/infra/scripts/flowchain-doctor.ps1 new file mode 100644 index 00000000..e355631e --- /dev/null +++ b/infra/scripts/flowchain-doctor.ps1 @@ -0,0 +1,122 @@ +param( + [string] $ReportPath = "devnet/local/doctor/flowchain-doctor-report.json" +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +. "$PSScriptRoot\flowchain-common.ps1" + +$repoRoot = Set-FlowChainRepoRoot +$reportFullPath = Assert-FlowChainPathInsideRepo -RepoRoot $repoRoot -Path (Resolve-FlowChainPath -RepoRoot $repoRoot -Path $ReportPath) +$checks = New-Object System.Collections.ArrayList + +function Add-DoctorCheck { + param([string] $Name, [string] $Status, [string] $Owner, [string] $Detail, [string] $NextCommand = "") + [void] $checks.Add([ordered]@{ + name = $Name + status = $Status + owner = $Owner + detail = $Detail + nextCommand = $NextCommand + }) +} + +foreach ($tool in @("git", "node", "npm", "cargo", "rustc", "forge", "python")) { + $found = Get-Command $tool -ErrorAction SilentlyContinue + Add-DoctorCheck ` + -Name "tool:$tool" ` + -Status $(if ($found) { "running" } else { "misconfigured" }) ` + -Owner "installer" ` + -Detail $(if ($found) { "$tool found at $($found.Source)" } else { "$tool missing from PATH" }) ` + -NextCommand "npm run flowchain:prereq" +} + +$packageJson = Get-Content -Raw -LiteralPath (Join-Path $repoRoot "package.json") | ConvertFrom-Json +$scripts = @($packageJson.scripts.PSObject.Properties.Name) +foreach ($scriptName in @( + "flowchain:prereq", + "flowchain:init", + "flowchain:node:start", + "flowchain:node:status", + "flowchain:node:stop", + "flowchain:wallet:e2e", + "flowchain:wallet:transfer:e2e", + "flowchain:bridge:mock:e2e", + "flowchain:bridge:live:check", + "flowchain:production-l1:e2e", + "flowchain:emergency:stop-local" + )) { + Add-DoctorCheck ` + -Name "script:$scriptName" ` + -Status $(if ($scripts -contains $scriptName) { "running" } else { "misconfigured" }) ` + -Owner "ops" ` + -Detail $(if ($scripts -contains $scriptName) { "root package script exists" } else { "root package script missing" }) +} + +$dataDir = Join-Path $repoRoot "devnet/local" +Add-DoctorCheck ` + -Name "data-dir" ` + -Status $(if (Test-Path -LiteralPath $dataDir) { "running" } else { "stopped" }) ` + -Owner "storage" ` + -Detail $dataDir ` + -NextCommand "npm run flowchain:init" + +$gitignore = Get-Content -Raw -LiteralPath (Join-Path $repoRoot ".gitignore") +foreach ($ignored in @("devnet/local/", ".env", ".env.*", "node_modules/", "crates/flowmemory-devnet/target/")) { + Add-DoctorCheck ` + -Name "ignored:$ignored" ` + -Status $(if ($gitignore.Contains($ignored)) { "running" } else { "misconfigured" }) ` + -Owner "security" ` + -Detail $(if ($gitignore.Contains($ignored)) { "ignored by .gitignore" } else { "missing from .gitignore" }) +} + +foreach ($port in @(8787, 5173)) { + $connections = @(Get-NetTCPConnection -LocalPort $port -ErrorAction SilentlyContinue) + $status = if ($connections.Count -gt 0) { "running" } else { "stopped" } + $owner = if ($port -eq 8787) { "control-plane" } else { "dashboard" } + $next = if ($port -eq 8787) { "npm run control-plane:serve" } else { "npm run workbench:dev" } + $pidSummary = (($connections | Select-Object -ExpandProperty OwningProcess -Unique) -join ", ") + Add-DoctorCheck -Name "port:$port" -Status $status -Owner $owner -Detail $(if ($pidSummary) { "owned by PID(s): $pidSummary" } else { "not listening" }) -NextCommand $next +} + +$liveEnvNames = @( + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_BASE8453_TO_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_BASE8453_CONFIRMATION_DEPTH" +) +$missingLive = @($liveEnvNames | Where-Object { [string]::IsNullOrWhiteSpace([Environment]::GetEnvironmentVariable($_, "Process")) }) +Add-DoctorCheck ` + -Name "base-live-env" ` + -Status $(if ($missingLive.Count -eq 0) { "running" } else { "blocked-on-env" }) ` + -Owner "bridge/ops" ` + -Detail $(if ($missingLive.Count -eq 0) { "live readiness env names are present" } else { "missing env names: $($missingLive -join ', ')" }) ` + -NextCommand "npm run flowchain:bridge:live:check" + +$failed = @($checks | Where-Object { $_.status -eq "misconfigured" }) +$blocked = @($checks | Where-Object { $_.status -eq "blocked-on-env" }) +$report = [ordered]@{ + schema = "flowchain.doctor_report.v0" + generatedAt = (Get-Date).ToUniversalTime().ToString("o") + status = if ($failed.Count -gt 0) { "failed" } elseif ($blocked.Count -gt 0) { "degraded" } else { "passed" } + repoRoot = $repoRoot + localUrls = [ordered]@{ + controlPlaneHealth = "http://127.0.0.1:8787/health" + dashboard = "http://127.0.0.1:5173/" + } + checks = @($checks) + missingLiveEnvNames = $missingLive +} +Write-FlowChainJson -Path $reportFullPath -Value $report -Depth 14 + +Write-Host "FlowChain doctor status: $($report.status)" +Write-Host "Report: $reportFullPath" +if ($failed.Count -gt 0) { + throw "FlowChain doctor found misconfigured checks." +} + diff --git a/infra/scripts/flowchain-emergency-print-recovery.ps1 b/infra/scripts/flowchain-emergency-print-recovery.ps1 new file mode 100644 index 00000000..005e33bf --- /dev/null +++ b/infra/scripts/flowchain-emergency-print-recovery.ps1 @@ -0,0 +1,43 @@ +param( + [string] $ReportPath = "devnet/local/emergency/flowchain-emergency-recovery-report.json" +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +. "$PSScriptRoot\flowchain-common.ps1" + +$repoRoot = Set-FlowChainRepoRoot +$reportFullPath = Assert-FlowChainPathInsideRepo -RepoRoot $repoRoot -Path (Resolve-FlowChainPath -RepoRoot $repoRoot -Path $ReportPath) + +$steps = @( + "npm run flowchain:emergency:export-evidence", + "npm run flowchain:doctor", + "npm run flowchain:init", + "npm run flowchain:node:start", + "npm run flowchain:node:status", + "npm run control-plane:serve", + "npm run workbench:dev", + "npm run flowchain:bridge:live:check" +) + +$report = [ordered]@{ + schema = "flowchain.emergency_recovery_report.v0" + generatedAt = (Get-Date).ToUniversalTime().ToString("o") + status = "printed" + steps = $steps + emergencyStopCommands = @( + "npm run flowchain:emergency:stop-local", + "npm run flowchain:bridge:emergency-stop", + "npm run flowchain:emergency:pause-bridge", + "npm run flowchain:emergency:export-evidence" + ) +} +Write-FlowChainJson -Path $reportFullPath -Value $report -Depth 10 + +Write-Host "FlowChain recovery steps:" +foreach ($step in $steps) { + Write-Host "- $step" +} +Write-Host "Report: $reportFullPath" + diff --git a/infra/scripts/flowchain-emergency-stop-local.ps1 b/infra/scripts/flowchain-emergency-stop-local.ps1 new file mode 100644 index 00000000..45a6d7e7 --- /dev/null +++ b/infra/scripts/flowchain-emergency-stop-local.ps1 @@ -0,0 +1,51 @@ +param( + [switch] $StopKnownPorts, + [string] $ReportPath = "devnet/local/emergency/flowchain-emergency-stop-local-report.json" +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +. "$PSScriptRoot\flowchain-common.ps1" + +$repoRoot = Set-FlowChainRepoRoot +$reportFullPath = Assert-FlowChainPathInsideRepo -RepoRoot $repoRoot -Path (Resolve-FlowChainPath -RepoRoot $repoRoot -Path $ReportPath) +$actions = New-Object System.Collections.ArrayList + +try { + & powershell -NoProfile -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "flowchain-node-stop.ps1") + [void] $actions.Add([ordered]@{ action = "node-stop"; status = "requested"; command = "npm run flowchain:node:stop" }) +} +catch { + [void] $actions.Add([ordered]@{ action = "node-stop"; status = "failed"; command = "npm run flowchain:node:stop"; reason = $_.Exception.Message }) +} + +foreach ($port in @(8787, 5173)) { + $connections = @(Get-NetTCPConnection -LocalPort $port -ErrorAction SilentlyContinue) + $pids = @($connections | Select-Object -ExpandProperty OwningProcess -Unique) + foreach ($processId in $pids) { + if ($StopKnownPorts) { + Stop-Process -Id $processId -Force -ErrorAction SilentlyContinue + [void] $actions.Add([ordered]@{ action = "stop-port-process"; status = "stopped"; port = $port; pid = $processId }) + } + else { + [void] $actions.Add([ordered]@{ action = "stop-port-process"; status = "manual"; port = $port; pid = $processId; command = "Stop-Process -Id $processId -Force" }) + } + } +} + +$report = [ordered]@{ + schema = "flowchain.emergency_stop_local_report.v0" + generatedAt = (Get-Date).ToUniversalTime().ToString("o") + status = "completed" + actions = @($actions) + bridgeRelayerStop = "Bridge relayer is normally one-shot in this repo. Stop any relayer PowerShell window or process using the PID shown by the relayer command." + evidenceCommand = "npm run flowchain:emergency:export-evidence" + recoveryCommand = "npm run flowchain:emergency:print-recovery" +} +Write-FlowChainJson -Path $reportFullPath -Value $report -Depth 12 + +Write-Host "FlowChain local emergency stop completed." +Write-Host "Report: $reportFullPath" +Write-Host "Evidence command: npm run flowchain:emergency:export-evidence" +Write-Host "Recovery command: npm run flowchain:emergency:print-recovery" diff --git a/infra/scripts/flowchain-evidence-export.ps1 b/infra/scripts/flowchain-evidence-export.ps1 new file mode 100644 index 00000000..f7d25c67 --- /dev/null +++ b/infra/scripts/flowchain-evidence-export.ps1 @@ -0,0 +1,185 @@ +param( + [string] $SourceDir = "devnet/local/production-l1-e2e", + [string] $BundlePath = "devnet/local/production-l1-e2e/evidence/flowchain-production-l1-evidence.zip", + [switch] $Force +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +. "$PSScriptRoot\flowchain-common.ps1" + +function Get-RelativePath { + param([string] $Root, [string] $Path) + $fullRoot = [System.IO.Path]::GetFullPath($Root).TrimEnd([System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar) + $fullPath = [System.IO.Path]::GetFullPath($Path) + if ($fullPath -eq $fullRoot) { return "" } + $prefix = $fullRoot + [System.IO.Path]::DirectorySeparatorChar + if (-not $fullPath.StartsWith($prefix, [System.StringComparison]::OrdinalIgnoreCase)) { + throw "Path is outside root: $fullPath" + } + return $fullPath.Substring($prefix.Length) +} + +function Get-EvidenceExclusionReason { + param([string] $RelativePath, [string] $Name, [bool] $IsDirectory) + $normalized = ($RelativePath -replace "\\", "/").Trim("/") + $lower = $normalized.ToLowerInvariant() + $lowerName = $Name.ToLowerInvariant() + if ($IsDirectory) { + if ($lowerName -in @(".git", "node_modules", "target", "dist", "cache", "out", "broadcast", "stage")) { return "repository metadata, dependency, or build output" } + if ($lowerName -like "*vault*") { return "local vault directory" } + if ($lowerName -in @("secret", "secrets", ".secret", ".secrets")) { return "secret directory" } + return "" + } + if ($lower -match '(^|/)(\.git|node_modules|target|dist|cache|out|broadcast)(/|$)') { return "repository metadata, dependency, or build output path" } + if ($lowerName -eq ".env" -or ($lowerName.StartsWith(".env.") -and $lowerName -ne ".env.example")) { return "local env file" } + if ($lowerName.EndsWith(".local.json")) { return "local-only JSON file" } + if ($lowerName -like "*vault*") { return "local vault file" } + if ($lowerName.EndsWith(".pem") -or $lowerName.EndsWith(".key") -or $lowerName.EndsWith(".pfx") -or $lowerName.EndsWith(".p12")) { return "private key material file" } + if ($lowerName -like "*private-key*" -or $lowerName -like "*private_key*" -or $lowerName -like "*mnemonic*" -or $lowerName -like "*seed-phrase*") { return "secret-named file" } + if ($lowerName.EndsWith(".zip")) { return "nested archive" } + return "" +} + +function Copy-EvidenceTree { + param([string] $Source, [string] $Destination, [string] $Root, [System.Collections.ArrayList] $Excluded) + if (-not (Test-Path -LiteralPath $Source)) { return } + New-Item -ItemType Directory -Force -Path $Destination | Out-Null + foreach ($item in Get-ChildItem -LiteralPath $Source -Force) { + $relative = Get-RelativePath -Root $Root -Path $item.FullName + $reason = Get-EvidenceExclusionReason -RelativePath $relative -Name $item.Name -IsDirectory $item.PSIsContainer + if (-not [string]::IsNullOrWhiteSpace($reason)) { + [void] $Excluded.Add([ordered]@{ path = $relative; reason = $reason }) + continue + } + $dest = Join-Path $Destination $item.Name + if ($item.PSIsContainer) { + Copy-EvidenceTree -Source $item.FullName -Destination $dest -Root $Root -Excluded $Excluded + } + else { + Copy-Item -LiteralPath $item.FullName -Destination $dest + } + } +} + +function Assert-ZipManifestSafe { + param([string] $Path) + Add-Type -AssemblyName System.IO.Compression.FileSystem + $zip = [System.IO.Compression.ZipFile]::OpenRead($Path) + try { + foreach ($entry in $zip.Entries) { + $entryPath = ($entry.FullName -replace "\\", "/").Trim("/") + if ([string]::IsNullOrWhiteSpace($entryPath)) { continue } + $entryName = [System.IO.Path]::GetFileName($entryPath.TrimEnd("/")) + $reason = Get-EvidenceExclusionReason -RelativePath $entryPath -Name $entryName -IsDirectory $entry.FullName.EndsWith("/") + if (-not [string]::IsNullOrWhiteSpace($reason)) { + throw "Evidence bundle contains excluded path '$entryPath' ($reason)." + } + } + } + finally { + $zip.Dispose() + } +} + +$repoRoot = Set-FlowChainRepoRoot +$sourceFullDir = Assert-FlowChainPathInsideRepo -RepoRoot $repoRoot -Path (Resolve-FlowChainPath -RepoRoot $repoRoot -Path $SourceDir) +$bundleFullPath = Assert-FlowChainPathInsideRepo -RepoRoot $repoRoot -Path (Resolve-FlowChainPath -RepoRoot $repoRoot -Path $BundlePath) +$exportRoot = Split-Path -Parent $bundleFullPath +$stageRoot = Join-Path $exportRoot "stage" +$stageEvidence = Join-Path $stageRoot "flowchain-production-l1-evidence" + +if ((Test-Path -LiteralPath $bundleFullPath) -and -not $Force) { + Remove-Item -LiteralPath $bundleFullPath -Force +} +if (Test-Path -LiteralPath $stageRoot) { + Remove-Item -LiteralPath $stageRoot -Recurse -Force +} +New-Item -ItemType Directory -Force -Path $stageEvidence | Out-Null + +$excluded = New-Object System.Collections.ArrayList +Copy-EvidenceTree -Source $sourceFullDir -Destination (Join-Path $stageEvidence "production-l1-e2e") -Root $sourceFullDir -Excluded $excluded + +foreach ($optional in @( + "devnet/local/smoke", + "devnet/local/full-smoke", + "devnet/local/product-e2e", + "devnet/local/real-value-pilot/ops-e2e", + "devnet/local/real-value-pilot/export", + "services/bridge-relayer/out" + )) { + $optionalFull = Resolve-FlowChainPath -RepoRoot $repoRoot -Path $optional + if (Test-Path -LiteralPath $optionalFull) { + $safeName = ($optional -replace '[:\\/]+', '-') + Copy-EvidenceTree -Source $optionalFull -Destination (Join-Path $stageEvidence $safeName) -Root $optionalFull -Excluded $excluded + } +} + +$schemas = Resolve-FlowChainPath -RepoRoot $repoRoot -Path "schemas/flowmemory" +if (Test-Path -LiteralPath $schemas) { + Copy-EvidenceTree -Source $schemas -Destination (Join-Path $stageEvidence "public-schemas-flowmemory") -Root $schemas -Excluded $excluded +} + +$safeConfigPath = Join-Path $stageEvidence "safe-config-summary.json" +$packageJson = Get-Content -Raw -LiteralPath (Join-Path $repoRoot "package.json") | ConvertFrom-Json +$safeConfig = [ordered]@{ + schema = "flowchain.production_l1.safe_config_summary.v0" + generatedAt = (Get-Date).ToUniversalTime().ToString("o") + packageScripts = @($packageJson.scripts.PSObject.Properties.Name | Sort-Object) + ignoredLocalPaths = @("devnet/local/", ".env", ".env.*", "node_modules/", "target/", "dist/", "cache/", "out/", "broadcast/") + localUrls = @("http://127.0.0.1:8787/health", "http://127.0.0.1:5173/") + printsSecrets = $false +} +Write-FlowChainJson -Path $safeConfigPath -Value $safeConfig -Depth 8 + +$manifestPath = Join-Path $stageEvidence "evidence-export-manifest.json" +$manifest = [ordered]@{ + schema = "flowchain.production_l1.evidence_export_manifest.v0" + exportedAt = (Get-Date).ToUniversalTime().ToString("o") + sourceDir = $sourceFullDir + excludes = @( + ".git", + "node_modules", + "Rust target directories", + "build outputs", + "env files", + "vaults", + "private keys", + "seed phrases", + "mnemonics", + "RPC credentials", + "API keys", + "webhooks", + "nested archives" + ) + excludedCount = $excluded.Count + excludedSamples = @($excluded | Select-Object -First 50) +} +Write-FlowChainJson -Path $manifestPath -Value $manifest -Depth 12 + +& powershell -NoProfile -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "flowchain-no-secret-scan.ps1") -Paths @($stageEvidence) -ReportPath (Join-Path $stageEvidence "no-secret-scan-report.json") +if ($LASTEXITCODE -ne 0) { + throw "Evidence stage no-secret scan failed." +} + +New-Item -ItemType Directory -Force -Path $exportRoot | Out-Null +Compress-Archive -Path $stageEvidence -DestinationPath $bundleFullPath -Force +Assert-ZipManifestSafe -Path $bundleFullPath + +$hash = Get-FileHash -Algorithm SHA256 -LiteralPath $bundleFullPath +$reportPath = Join-Path $exportRoot "flowchain-production-l1-evidence-export-report.json" +$report = [ordered]@{ + schema = "flowchain.production_l1.evidence_export_report.v0" + generatedAt = (Get-Date).ToUniversalTime().ToString("o") + status = "passed" + bundlePath = $bundleFullPath + bundleSha256 = $hash.Hash + manifestPath = $manifestPath + excludedCount = $excluded.Count +} +Write-FlowChainJson -Path $reportPath -Value $report -Depth 10 + +Write-Host "FlowChain production-l1 evidence export complete." +Write-Host "Bundle: $bundleFullPath" +Write-Host "Report: $reportPath" diff --git a/infra/scripts/flowchain-no-secret-scan.ps1 b/infra/scripts/flowchain-no-secret-scan.ps1 new file mode 100644 index 00000000..02e8eb88 --- /dev/null +++ b/infra/scripts/flowchain-no-secret-scan.ps1 @@ -0,0 +1,109 @@ +param( + [string[]] $Paths = @( + "devnet/local/production-l1-e2e", + "devnet/local/full-smoke", + "devnet/local/product-e2e", + "devnet/local/real-value-pilot", + "fixtures/dashboard", + "services/bridge-relayer/out" + ), + [string] $ReportPath = "devnet/local/production-l1-e2e/no-secret-scan-report.json" +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +. "$PSScriptRoot\flowchain-common.ps1" + +$repoRoot = Set-FlowChainRepoRoot +$reportFullPath = Assert-FlowChainPathInsideRepo -RepoRoot $repoRoot -Path (Resolve-FlowChainPath -RepoRoot $repoRoot -Path $ReportPath) +$findings = New-Object System.Collections.ArrayList +$scanned = New-Object System.Collections.ArrayList + +function Test-ExcludedSecretScanPath { + param([string] $Path) + + $normalized = ($Path -replace "\\", "/").ToLowerInvariant() + $name = [System.IO.Path]::GetFileName($normalized) + if ($normalized -match '(^|/)(\.git|node_modules|target|dist|cache|out/broadcast|broadcast)(/|$)') { return $true } + if ($name -eq ".env" -or ($name.StartsWith(".env.") -and $name -ne ".env.example")) { return $true } + if ($name.EndsWith(".local.json")) { return $true } + if ($name -like "*vault*") { return $true } + if ($name.EndsWith(".zip")) { return $true } + return $false +} + +function Add-Finding { + param([string] $Path, [string] $Reason) + [void] $findings.Add([ordered]@{ + path = $Path + reason = $Reason + }) +} + +foreach ($path in $Paths) { + $full = Resolve-FlowChainPath -RepoRoot $repoRoot -Path $path + if (-not (Test-Path -LiteralPath $full)) { + continue + } + $item = Get-Item -LiteralPath $full + $files = if ($item.PSIsContainer) { + Get-ChildItem -LiteralPath $full -Recurse -File | Where-Object { + $_.Extension -in @(".json", ".txt", ".md", ".log", ".csv", ".jsonl") -and + -not (Test-ExcludedSecretScanPath -Path $_.FullName) + } + } + else { + @($item) + } + + foreach ($file in $files) { + [void] $scanned.Add($file.FullName) + $text = Get-Content -Raw -LiteralPath $file.FullName + foreach ($pattern in @( + ("BEGIN RSA " + "PRIVATE KEY"), + ("BEGIN OPENSSH " + "PRIVATE KEY"), + ("BEGIN " + "PRIVATE KEY"), + "seedPhrase", + "mnemonicPhrase", + "privateKey", + "private_key", + "webhookUrl", + "webhook_url" + )) { + if ($text.IndexOf($pattern, [System.StringComparison]::OrdinalIgnoreCase) -ge 0) { + Add-Finding -Path $file.FullName -Reason "secret marker '$pattern'" + } + } + if ($text -match 'https?://[^\s"<>]*(alchemy|infura|apikey|api-key|token=|key=)[^\s"<>]*') { + Add-Finding -Path $file.FullName -Reason "credential-shaped RPC or API URL" + } + } +} + +$status = if ($findings.Count -eq 0) { "passed" } else { "failed" } +$report = [ordered]@{ + schema = "flowchain.no_secret_scan_report.v0" + generatedAt = (Get-Date).ToUniversalTime().ToString("o") + status = $status + scannedCount = $scanned.Count + scannedPaths = @($Paths) + findings = @($findings) + excluded = @( + ".git", + "node_modules", + "target", + "dist/cache/build outputs", + "env files", + "*.local.json", + "vault paths", + "zip files" + ) +} +Write-FlowChainJson -Path $reportFullPath -Value $report -Depth 12 + +Write-Host "FlowChain no-secret scan status: $status" +Write-Host "Report: $reportFullPath" +if ($status -ne "passed") { + throw "No-secret scan found secret-shaped output." +} diff --git a/infra/scripts/flowchain-node-logs.ps1 b/infra/scripts/flowchain-node-logs.ps1 new file mode 100644 index 00000000..f0cb0db0 --- /dev/null +++ b/infra/scripts/flowchain-node-logs.ps1 @@ -0,0 +1,30 @@ +param( + [string] $NodeDir = "devnet/local/node", + [int] $Tail = 80 +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +. "$PSScriptRoot\flowchain-common.ps1" + +$repoRoot = Set-FlowChainRepoRoot +$nodeFullDir = Assert-FlowChainPathInsideRepo -RepoRoot $repoRoot -Path (Resolve-FlowChainPath -RepoRoot $repoRoot -Path $NodeDir) +$logsDir = Join-Path $nodeFullDir "logs" +$stdoutPath = Join-Path $logsDir "node.stdout.jsonl" +$stderrPath = Join-Path $logsDir "node.stderr.log" +$statusPath = Join-Path $nodeFullDir "node-status.json" + +Write-Host "FlowChain node log paths:" +Write-Host "stdout: $stdoutPath" +Write-Host "stderr: $stderrPath" +Write-Host "status: $statusPath" + +foreach ($path in @($stdoutPath, $stderrPath, $statusPath)) { + if (Test-Path -LiteralPath $path) { + Write-Host "" + Write-Host "== Tail: $path ==" + Get-Content -LiteralPath $path -Tail $Tail + } +} + diff --git a/infra/scripts/flowchain-node-restart.ps1 b/infra/scripts/flowchain-node-restart.ps1 new file mode 100644 index 00000000..d7f9b025 --- /dev/null +++ b/infra/scripts/flowchain-node-restart.ps1 @@ -0,0 +1,65 @@ +param( + [string] $StatePath = "devnet/local/state.json", + [string] $NodeDir = "devnet/local/node", + [int] $BlockMs = 1000, + [int] $MaxBlocks = 0, + [switch] $Wait +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +. "$PSScriptRoot\flowchain-common.ps1" + +$repoRoot = Set-FlowChainRepoRoot +$reportPath = Assert-FlowChainPathInsideRepo -RepoRoot $repoRoot -Path (Resolve-FlowChainPath -RepoRoot $repoRoot -Path "devnet/local/node/flowchain-node-restart-report.json") +$startedAt = (Get-Date).ToUniversalTime().ToString("o") + +& powershell -NoProfile -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "flowchain-node-stop.ps1") -StatePath $StatePath -NodeDir $NodeDir +if ($LASTEXITCODE -ne 0) { + throw "Node stop failed during restart." +} + +$startArgs = @( + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + (Join-Path $PSScriptRoot "flowchain-node-start.ps1"), + "-StatePath", + $StatePath, + "-NodeDir", + $NodeDir, + "-BlockMs", + "$BlockMs" +) +if ($MaxBlocks -gt 0) { + $startArgs += @("-MaxBlocks", "$MaxBlocks") +} +if ($Wait) { + $startArgs += "-Wait" +} + +& powershell @startArgs +if ($LASTEXITCODE -ne 0) { + throw "Node start failed during restart." +} + +$report = [ordered]@{ + schema = "flowchain.private_testnet.node_restart_report.v0" + generatedAt = (Get-Date).ToUniversalTime().ToString("o") + startedAt = $startedAt + completedAt = (Get-Date).ToUniversalTime().ToString("o") + status = "passed" + statePath = $StatePath + nodeDir = $NodeDir + maxBlocks = $MaxBlocks + waited = [bool] $Wait + statusCommand = "npm run flowchain:node:status" + logsCommand = "npm run flowchain:node:logs" +} +Write-FlowChainJson -Path $reportPath -Value $report + +Write-Host "FlowChain node restart complete." +Write-Host "Report: $reportPath" + diff --git a/infra/scripts/flowchain-node-start.ps1 b/infra/scripts/flowchain-node-start.ps1 new file mode 100644 index 00000000..c5e98438 --- /dev/null +++ b/infra/scripts/flowchain-node-start.ps1 @@ -0,0 +1,163 @@ +param( + [string] $StatePath = "devnet/local/state.json", + [string] $NodeDir = "devnet/local/node", + [string] $NodeId = "node:local:alpha", + [int] $BlockMs = 1000, + [int] $MaxBlocks = 0, + [switch] $Wait +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +. "$PSScriptRoot\flowchain-common.ps1" + +$repoRoot = Set-FlowChainRepoRoot +Set-FlowChainCargoTargetDir -RepoRoot $repoRoot -Purpose "node-start" | Out-Null +$stateFullPath = Assert-FlowChainPathInsideRepo -RepoRoot $repoRoot -Path (Resolve-FlowChainPath -RepoRoot $repoRoot -Path $StatePath) +$nodeFullDir = Assert-FlowChainPathInsideRepo -RepoRoot $repoRoot -Path (Resolve-FlowChainPath -RepoRoot $repoRoot -Path $NodeDir) +$logsDir = Join-Path $nodeFullDir "logs" +$pidPath = Join-Path $nodeFullDir "flowchain-node.pid" +$reportPath = Join-Path $nodeFullDir "flowchain-node-start-report.json" +$stdoutPath = Join-Path $logsDir "node.stdout.jsonl" +$stderrPath = Join-Path $logsDir "node.stderr.log" +$stopPath = Join-Path $nodeFullDir "stop" + +New-Item -ItemType Directory -Force -Path $logsDir | Out-Null + +function Test-FlowChainNodePid { + param([int] $ProcessId) + + $process = Get-Process -Id $ProcessId -ErrorAction SilentlyContinue + if (-not $process) { + return $false + } + + try { + $commandLine = (Get-CimInstance Win32_Process -Filter "ProcessId=$ProcessId").CommandLine + return ($commandLine -like "*flowmemory-devnet*" -or $commandLine -like "*cargo*") + } + catch { + return $false + } +} + +if (-not (Test-Path -LiteralPath $stateFullPath)) { + & powershell -NoProfile -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "flowchain-init.ps1") -StatePath $stateFullPath + if ($LASTEXITCODE -ne 0) { + throw "flowchain-init failed before node start." + } +} + +if (Test-Path -LiteralPath $pidPath) { + $existingPid = (Get-Content -Raw -LiteralPath $pidPath).Trim() + if ($existingPid -match '^[0-9]+$') { + if (Test-FlowChainNodePid -Pid ([int] $existingPid)) { + $report = [ordered]@{ + schema = "flowchain.private_testnet.node_start_report.v0" + generatedAt = (Get-Date).ToUniversalTime().ToString("o") + status = "already-running" + pid = [int] $existingPid + statePath = $stateFullPath + nodeDir = $nodeFullDir + stdoutLog = $stdoutPath + stderrLog = $stderrPath + stopCommand = "npm run flowchain:node:stop" + statusCommand = "npm run flowchain:node:status" + } + Write-FlowChainJson -Path $reportPath -Value $report + Write-Host "FlowChain node is already running with PID $existingPid." + Write-Host "Status command: npm run flowchain:node:status" + return + } + } +} + +$arguments = @( + "run", + "--manifest-path", + "crates/flowmemory-devnet/Cargo.toml", + "--", + "--state", + $stateFullPath, + "--node-dir", + $nodeFullDir, + "node", + "--node-id", + $NodeId, + "--block-ms", + "$BlockMs" +) + +if ($MaxBlocks -gt 0) { + $arguments += @("--max-blocks", "$MaxBlocks") +} + +if (Test-Path -LiteralPath $stopPath) { + Remove-Item -LiteralPath $stopPath -Force +} + +$cargoPath = (Get-Command "cargo" -ErrorAction Stop).Source +$process = Start-Process -FilePath $cargoPath ` + -ArgumentList (Join-FlowChainProcessArguments -ArgumentList $arguments) ` + -WorkingDirectory $repoRoot ` + -PassThru ` + -WindowStyle Hidden ` + -RedirectStandardOutput $stdoutPath ` + -RedirectStandardError $stderrPath + +Set-Content -LiteralPath $pidPath -Value "$($process.Id)" +$status = "started" +$exitCode = $null + +if ($Wait) { + $timeoutMs = if ($MaxBlocks -gt 0) { [Math]::Max(30000, $MaxBlocks * $BlockMs * 10) } else { 30000 } + if (-not $process.WaitForExit($timeoutMs)) { + & cargo run --manifest-path crates/flowmemory-devnet/Cargo.toml -- --state $stateFullPath --node-dir $nodeFullDir node-stop | Out-Null + $process.Kill() + throw "FlowChain node did not exit within ${timeoutMs}ms." + } + $process.Refresh() + if ($process.HasExited) { + $exitCode = $process.ExitCode + } + elseif ($MaxBlocks -gt 0 -and (Test-Path -LiteralPath $stdoutPath) -and ((Get-Content -Raw -LiteralPath $stdoutPath) -like "*max blocks reached*")) { + $exitCode = 0 + } + if ($null -ne $exitCode -and $exitCode -ne 0) { + $status = "failed" + } + else { + $status = "completed" + } +} + +$report = [ordered]@{ + schema = "flowchain.private_testnet.node_start_report.v0" + generatedAt = (Get-Date).ToUniversalTime().ToString("o") + status = $status + pid = $process.Id + exitCode = $exitCode + statePath = $stateFullPath + nodeDir = $nodeFullDir + nodeId = $NodeId + blockMs = $BlockMs + maxBlocks = $MaxBlocks + waited = [bool] $Wait + stdoutLog = $stdoutPath + stderrLog = $stderrPath + pidPath = $pidPath + stopCommand = "npm run flowchain:node:stop" + statusCommand = "npm run flowchain:node:status" + logsCommand = "npm run flowchain:node:logs" +} +Write-FlowChainJson -Path $reportPath -Value $report -Depth 12 + +if ($status -eq "failed") { + throw "FlowChain node exited with code $exitCode. See $stderrPath" +} + +Write-Host "FlowChain node $status." +Write-Host "PID: $($process.Id)" +Write-Host "Logs: $logsDir" +Write-Host "Status command: npm run flowchain:node:status" diff --git a/infra/scripts/flowchain-production-l1-e2e.ps1 b/infra/scripts/flowchain-production-l1-e2e.ps1 new file mode 100644 index 00000000..621a0156 --- /dev/null +++ b/infra/scripts/flowchain-production-l1-e2e.ps1 @@ -0,0 +1,537 @@ +param( + [string] $ReportDir = "devnet/local/production-l1-e2e" +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +. "$PSScriptRoot\flowchain-common.ps1" + +$repoRoot = Set-FlowChainRepoRoot +Set-FlowChainCargoTargetDir -RepoRoot $repoRoot -Purpose "production-l1-e2e" | Out-Null +$reportFullDir = Assert-FlowChainPathInsideRepo -RepoRoot $repoRoot -Path (Resolve-FlowChainPath -RepoRoot $repoRoot -Path $ReportDir) +$logsDir = Join-Path $reportFullDir "logs" +$reportPath = Join-Path $reportFullDir "flowchain-production-l1-e2e-report.json" +$summaryPath = Join-Path $reportFullDir "flowchain-production-l1-e2e-summary.md" + +if (Test-Path -LiteralPath $reportFullDir) { + Remove-Item -LiteralPath $reportFullDir -Recurse -Force +} +New-Item -ItemType Directory -Force -Path $logsDir | Out-Null + +$steps = New-Object System.Collections.ArrayList +$commandsRun = New-Object System.Collections.ArrayList + +function Get-SafeStepName { + param([string] $Name) + return (($Name -replace '[^A-Za-z0-9_.-]', '-') -replace '-+', '-').Trim("-") +} + +function Invoke-ProductionStep { + param( + [Parameter(Mandatory = $true)] + [string] $Name, + + [Parameter(Mandatory = $true)] + [string] $Owner, + + [Parameter(Mandatory = $true)] + [string] $Command, + + [Parameter(Mandatory = $true)] + [string] $FilePath, + + [string[]] $ArgumentList = @(), + + [ValidateSet("mockPath", "liveReadiness", "liveBroadcast", "none")] + [string] $Blocks = "mockPath", + + [switch] $AllowFailure, + + [string] $ExpectedReportPath = "" + ) + + $safe = Get-SafeStepName -Name $Name + $logPath = Join-Path $logsDir "$safe.log" + $startedAt = (Get-Date).ToUniversalTime().ToString("o") + [void] $commandsRun.Add($Command) + + Write-Host "" + Write-Host "== $Name ==" + Write-Host $Command + + $previousErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = "Continue" + try { + $output = (& $FilePath @ArgumentList 2>&1) | ForEach-Object { "$_" } + $exitCode = $LASTEXITCODE + } + catch { + $output = @($_.Exception.Message) + $exitCode = 1 + } + finally { + $ErrorActionPreference = $previousErrorActionPreference + } + + $output | Set-Content -LiteralPath $logPath -Encoding utf8 + $endedAt = (Get-Date).ToUniversalTime().ToString("o") + $status = if ($exitCode -eq 0) { "passed" } elseif ($AllowFailure) { "blocked" } else { "failed" } + $reason = if ($exitCode -eq 0) { "" } else { (($output | Select-Object -Last 12) -join [Environment]::NewLine) } + + $step = [ordered]@{ + name = $Name + owner = $Owner + command = $Command + status = $status + exitCode = $exitCode + blocks = $Blocks + logPath = $logPath + reportPath = $ExpectedReportPath + startedAt = $startedAt + endedAt = $endedAt + reason = $reason + } + [void] $steps.Add($step) + + if ($status -eq "failed") { + Write-Host "FAILED: $Name" + } + elseif ($status -eq "blocked") { + Write-Host "BLOCKED: $Name" + } + else { + Write-Host "PASSED: $Name" + } + + return $step +} + +function Add-InternalStep { + param([string] $Name, [string] $Owner, [string] $Command, [string] $Status, [string] $Blocks, [string] $LogPath = "", [string] $ReportPath = "", [string] $Reason = "") + [void] $commandsRun.Add($Command) + [void] $steps.Add([ordered]@{ + name = $Name + owner = $Owner + command = $Command + status = $Status + exitCode = if ($Status -eq "passed") { 0 } else { 1 } + blocks = $Blocks + logPath = $LogPath + reportPath = $ReportPath + startedAt = (Get-Date).ToUniversalTime().ToString("o") + endedAt = (Get-Date).ToUniversalTime().ToString("o") + reason = $Reason + }) +} + +function Get-ToolVersion { + param([string] $Command, [string[]] $VersionArgs = @("--version")) + $found = Get-Command $Command -ErrorAction SilentlyContinue + if (-not $found) { + return [ordered]@{ found = $false; version = ""; path = "" } + } + try { + $version = (& $Command @VersionArgs 2>$null | Select-Object -First 1) + } + catch { + $version = "found" + } + return [ordered]@{ found = $true; version = "$version"; path = $found.Source } +} + +function Get-PortStatus { + param([int] $Port) + $connections = @(Get-NetTCPConnection -LocalPort $Port -ErrorAction SilentlyContinue) + return [ordered]@{ + port = $Port + status = if ($connections.Count -gt 0) { "running" } else { "stopped" } + owningProcesses = @($connections | Select-Object -ExpandProperty OwningProcess -Unique) + } +} + +function Get-StateFacts { + param([string] $StatePath) + $facts = [ordered]@{ + chainId = "unknown" + genesisHash = "unknown" + latestHeight = "unknown" + latestHash = "unknown" + finalizedHeight = "unknown" + stateRoot = "unknown" + } + if (-not (Test-Path -LiteralPath $StatePath)) { + return $facts + } + try { + $state = Get-Content -Raw -LiteralPath $StatePath | ConvertFrom-Json + if ($state.PSObject.Properties.Name -contains "chainId") { $facts.chainId = "$($state.chainId)" } + $blocks = @() + if ($state.PSObject.Properties.Name -contains "blocks") { $blocks = @($state.blocks) } + if ($blocks.Count -gt 0) { + $genesis = $blocks[0] + $latest = $blocks[$blocks.Count - 1] + foreach ($candidate in @("blockHash", "hash")) { + if ($genesis.PSObject.Properties.Name -contains $candidate) { $facts.genesisHash = "$($genesis.$candidate)" } + if ($latest.PSObject.Properties.Name -contains $candidate) { $facts.latestHash = "$($latest.$candidate)" } + } + foreach ($candidate in @("height", "blockNumber", "number")) { + if ($latest.PSObject.Properties.Name -contains $candidate) { $facts.latestHeight = "$($latest.$candidate)" } + } + } + if ($state.PSObject.Properties.Name -contains "finalizedHeight") { $facts.finalizedHeight = "$($state.finalizedHeight)" } + elseif ($facts.latestHeight -ne "unknown") { $facts.finalizedHeight = $facts.latestHeight } + if ($state.PSObject.Properties.Name -contains "stateRoot") { $facts.stateRoot = "$($state.stateRoot)" } + } + catch { + return $facts + } + + try { + $previousErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = "Continue" + try { + $summaryOutput = (& cargo run --manifest-path crates/flowmemory-devnet/Cargo.toml -- --state $StatePath inspect-state --summary 2>&1) | ForEach-Object { "$_" } + $summaryExitCode = $LASTEXITCODE + } + finally { + $ErrorActionPreference = $previousErrorActionPreference + } + if ($summaryExitCode -eq 0) { + $summaryText = $summaryOutput -join [Environment]::NewLine + $jsonStart = $summaryText.IndexOf("{") + $jsonEnd = $summaryText.LastIndexOf("}") + if ($jsonStart -lt 0 -or $jsonEnd -lt $jsonStart) { + return $facts + } + $summary = $summaryText.Substring($jsonStart, $jsonEnd - $jsonStart + 1) | ConvertFrom-Json + if ($summary.PSObject.Properties.Name -contains "stateRoot") { $facts.stateRoot = "$($summary.stateRoot)" } + if ($summary.PSObject.Properties.Name -contains "blocks") { $facts.latestHeight = "$($summary.blocks)" } + if ($summary.PSObject.Properties.Name -contains "chainId") { $facts.chainId = "$($summary.chainId)" } + } + } + catch { + return $facts + } + return $facts +} + +function Read-JsonIfExists { + param([string] $Path) + if (Test-Path -LiteralPath $Path) { + try { return Get-Content -Raw -LiteralPath $Path | ConvertFrom-Json } catch { return $null } + } + return $null +} + +Write-Host "FlowChain production-l1:e2e mock-safe gate starting." +Write-Host "Report directory: $reportFullDir" + +$parserErrors = New-Object System.Collections.ArrayList +$parserLogPath = Join-Path $logsDir "powershell-parser-check.log" +$changedScripts = @(Get-ChildItem -LiteralPath (Join-Path $repoRoot "infra/scripts") -Filter "*.ps1" -File) +foreach ($script in $changedScripts) { + $tokens = $null + $errors = $null + [System.Management.Automation.PSParser]::Tokenize((Get-Content -Raw -LiteralPath $script.FullName), [ref] $errors) | Out-Null + if ($errors -and $errors.Count -gt 0) { + foreach ($error in $errors) { + [void] $parserErrors.Add("$($script.FullName): $($error.Message) at line $($error.Token.StartLine)") + } + } +} +($parserErrors | ForEach-Object { "$_" }) | Set-Content -LiteralPath $parserLogPath -Encoding utf8 +if ($parserErrors.Count -eq 0) { + Add-InternalStep -Name "PowerShell parser checks" -Owner "ops" -Command "PowerShell parser checks for infra/scripts/*.ps1" -Status "passed" -Blocks "mockPath" -LogPath $parserLogPath +} +else { + Add-InternalStep -Name "PowerShell parser checks" -Owner "ops" -Command "PowerShell parser checks for infra/scripts/*.ps1" -Status "failed" -Blocks "mockPath" -LogPath $parserLogPath -Reason ($parserErrors -join [Environment]::NewLine) +} + +$packageJson = Get-Content -Raw -LiteralPath (Join-Path $repoRoot "package.json") | ConvertFrom-Json +$rootScripts = @($packageJson.scripts.PSObject.Properties.Name) +$realValuePilotReportPath = Resolve-FlowChainPath -RepoRoot $repoRoot -Path "devnet/local/real-value-pilot/flowchain-real-value-pilot-e2e-report.json" +$realValuePilotLogPath = Join-Path $logsDir "Real-value-pilot-coordination-E2E.log" +$missingSubsystemCommands = New-Object System.Collections.ArrayList +foreach ($entry in @( + [ordered]@{ command = "flowchain:real-value-pilot:contracts"; owner = "contracts"; subsystem = "contracts"; reason = "required for strict live real-value pilot proof; tracked by GitHub issue #133" }, + [ordered]@{ command = "flowchain:real-value-pilot:bridge"; owner = "bridge-relayer"; subsystem = "bridge-relayer"; reason = "required for strict live real-value pilot proof; tracked by GitHub issue #138" }, + [ordered]@{ command = "flowchain:real-value-pilot:runtime"; owner = "chain-runtime"; subsystem = "chain-runtime"; reason = "required for strict live real-value pilot proof; tracked by GitHub issue #134" } + )) { + if ($rootScripts -notcontains $entry.command) { + $entry["logPath"] = $realValuePilotLogPath + $entry["reportPath"] = $realValuePilotReportPath + $entry["blocks"] = "liveReadiness" + $entry["blocksMockPath"] = $false + $entry["blocksLiveReadiness"] = $true + $entry["blocksLiveBroadcast"] = $true + [void] $missingSubsystemCommands.Add($entry) + } +} + +Invoke-ProductionStep -Name "Prerequisite check" -Owner "installer" -Command "npm run flowchain:prereq" -FilePath "npm" -ArgumentList @("run", "flowchain:prereq") -ExpectedReportPath "" +Invoke-ProductionStep -Name "Doctor" -Owner "ops" -Command "npm run flowchain:doctor" -FilePath "npm" -ArgumentList @("run", "flowchain:doctor") -Blocks "none" -AllowFailure -ExpectedReportPath (Resolve-FlowChainPath -RepoRoot $repoRoot -Path "devnet/local/doctor/flowchain-doctor-report.json") +Invoke-ProductionStep -Name "Initialize local state" -Owner "runtime/storage" -Command "npm run flowchain:init" -FilePath "npm" -ArgumentList @("run", "flowchain:init") +Invoke-ProductionStep -Name "L1 baseline E2E" -Owner "integration" -Command "npm run flowchain:l1-e2e" -FilePath "npm" -ArgumentList @("run", "flowchain:l1-e2e") -ExpectedReportPath (Resolve-FlowChainPath -RepoRoot $repoRoot -Path "devnet/local/full-smoke/flowchain-full-smoke-report.json") +Invoke-ProductionStep -Name "Node start bounded" -Owner "runtime" -Command "npm run flowchain:node:start -- -MaxBlocks 3 -Wait" -FilePath "powershell" -ArgumentList @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", (Join-Path $PSScriptRoot "flowchain-node-start.ps1"), "-MaxBlocks", "3", "-Wait") -ExpectedReportPath (Resolve-FlowChainPath -RepoRoot $repoRoot -Path "devnet/local/node/flowchain-node-start-report.json") +Invoke-ProductionStep -Name "Node status" -Owner "runtime" -Command "npm run flowchain:node:status" -FilePath "npm" -ArgumentList @("run", "flowchain:node:status") +Invoke-ProductionStep -Name "Wallet E2E" -Owner "wallet/crypto" -Command "npm run flowchain:wallet:e2e" -FilePath "npm" -ArgumentList @("run", "flowchain:wallet:e2e") -ExpectedReportPath (Join-Path $reportFullDir "wallet-e2e-report.json") +Invoke-ProductionStep -Name "Wallet transfer E2E" -Owner "wallet/runtime" -Command "npm run flowchain:wallet:transfer:e2e" -FilePath "npm" -ArgumentList @("run", "flowchain:wallet:transfer:e2e") -ExpectedReportPath (Join-Path $reportFullDir "wallet-transfer/wallet-transfer-e2e-report.json") +Invoke-ProductionStep -Name "Product E2E" -Owner "runtime/product" -Command "npm run flowchain:product:e2e -- -SkipFullSmoke" -FilePath "powershell" -ArgumentList @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", (Join-Path $PSScriptRoot "flowchain-product-e2e.ps1"), "-SkipFullSmoke") -ExpectedReportPath (Resolve-FlowChainPath -RepoRoot $repoRoot -Path "devnet/local/product-e2e/flowchain-product-e2e-report.json") +Invoke-ProductionStep -Name "Token and DEX E2E" -Owner "runtime/token-dex" -Command "npm run flowchain:dex:e2e" -FilePath "npm" -ArgumentList @("run", "flowchain:dex:e2e") -ExpectedReportPath (Join-Path $reportFullDir "dex/dex-e2e-report.json") +Invoke-ProductionStep -Name "Bridge mock pilot E2E" -Owner "bridge-relayer" -Command "npm run flowchain:bridge:mock:e2e" -FilePath "npm" -ArgumentList @("run", "flowchain:bridge:mock:e2e") +Invoke-ProductionStep -Name "Real-value pilot coordination E2E" -Owner "hq/ops" -Command "npm run flowchain:real-value-pilot:e2e -- -AllowIncomplete -SkipBaseline" -FilePath "powershell" -ArgumentList @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", (Join-Path $PSScriptRoot "flowchain-real-value-pilot-e2e.ps1"), "-AllowIncomplete", "-SkipBaseline") -Blocks "liveReadiness" -AllowFailure -ExpectedReportPath $realValuePilotReportPath +Invoke-ProductionStep -Name "Bridge live readiness check" -Owner "bridge/ops" -Command "npm run flowchain:bridge:live:check" -FilePath "powershell" -ArgumentList @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", (Join-Path $PSScriptRoot "flowchain-bridge-live-check.ps1"), "-AllowBlocked") -Blocks "liveReadiness" -AllowFailure -ExpectedReportPath (Join-Path $reportFullDir "bridge-live-readiness-report.json") +Invoke-ProductionStep -Name "Control-plane smoke" -Owner "control-plane" -Command "npm run flowchain:control-plane:smoke" -FilePath "npm" -ArgumentList @("run", "flowchain:control-plane:smoke") +Invoke-ProductionStep -Name "Dashboard build" -Owner "dashboard" -Command "npm run flowchain:dashboard:build" -FilePath "npm" -ArgumentList @("run", "flowchain:dashboard:build") +Invoke-ProductionStep -Name "Export local state" -Owner "storage" -Command "npm run flowchain:export" -FilePath "npm" -ArgumentList @("run", "flowchain:export") + +$mainStatePath = Resolve-FlowChainPath -RepoRoot $repoRoot -Path "devnet/local/state.json" +$bundlePath = Resolve-FlowChainPath -RepoRoot $repoRoot -Path "devnet/local/export/flowchain-local-state.zip" +$importedStatePath = Join-Path $reportFullDir "imported-state.json" +$importDir = Join-Path $reportFullDir "imported" +Invoke-ProductionStep -Name "Import local state" -Owner "storage" -Command "npm run flowchain:import -- --BundlePath devnet/local/export/flowchain-local-state.zip -StatePath devnet/local/production-l1-e2e/imported-state.json -Force" -FilePath "powershell" -ArgumentList @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", (Join-Path $PSScriptRoot "flowchain-import.ps1"), "-BundlePath", $bundlePath, "-StatePath", $importedStatePath, "-ImportDir", $importDir, "-Force") + +$originalFacts = Get-StateFacts -StatePath $mainStatePath +$importedFacts = Get-StateFacts -StatePath $importedStatePath +$rootComparePath = Join-Path $reportFullDir "export-import-root-compare.json" +$rootCompareStatus = if ($originalFacts.stateRoot -ne "unknown" -and $originalFacts.stateRoot -eq $importedFacts.stateRoot) { "passed" } else { "failed" } +Write-FlowChainJson -Path $rootComparePath -Value ([ordered]@{ + schema = "flowchain.export_import_root_compare.v0" + generatedAt = (Get-Date).ToUniversalTime().ToString("o") + status = $rootCompareStatus + originalStateRoot = $originalFacts.stateRoot + importedStateRoot = $importedFacts.stateRoot + exportBundle = $bundlePath + importedStatePath = $importedStatePath + }) +Add-InternalStep -Name "Verify root after restore" -Owner "storage" -Command "compare original/imported state root" -Status $rootCompareStatus -Blocks "mockPath" -ReportPath $rootComparePath -Reason $(if ($rootCompareStatus -eq "passed") { "" } else { "state roots did not match" }) + +Invoke-ProductionStep -Name "Restart recovery" -Owner "runtime/storage" -Command "npm run flowchain:restart:verify" -FilePath "npm" -ArgumentList @("run", "flowchain:restart:verify") -ExpectedReportPath (Resolve-FlowChainPath -RepoRoot $repoRoot -Path "devnet/local/node-smoke/one-node-smoke-report.json") +Invoke-ProductionStep -Name "No-secret scan" -Owner "security" -Command "npm run flowchain:no-secret:scan" -FilePath "npm" -ArgumentList @("run", "flowchain:no-secret:scan") -ExpectedReportPath (Join-Path $reportFullDir "no-secret-scan-report.json") +Invoke-ProductionStep -Name "Unsafe-claim scan" -Owner "security" -Command "node infra/scripts/check-unsafe-claims.mjs" -FilePath "node" -ArgumentList @("infra/scripts/check-unsafe-claims.mjs") +Invoke-ProductionStep -Name "Patch whitespace check" -Owner "ops" -Command "git diff --check" -FilePath "git" -ArgumentList @("diff", "--check") +Invoke-ProductionStep -Name "Evidence export" -Owner "ops/security" -Command "npm run flowchain:emergency:export-evidence" -FilePath "npm" -ArgumentList @("run", "flowchain:emergency:export-evidence") -ExpectedReportPath (Join-Path $reportFullDir "evidence/flowchain-production-l1-evidence-export-report.json") + +$stateFacts = Get-StateFacts -StatePath $mainStatePath +$bridgeLiveReport = Read-JsonIfExists -Path (Join-Path $reportFullDir "bridge-live-readiness-report.json") +$productReport = Read-JsonIfExists -Path (Resolve-FlowChainPath -RepoRoot $repoRoot -Path "devnet/local/product-e2e/flowchain-product-e2e-report.json") +$walletTransferReport = Read-JsonIfExists -Path (Join-Path $reportFullDir "wallet-transfer/wallet-transfer-e2e-report.json") +$dexReport = Read-JsonIfExists -Path (Join-Path $reportFullDir "dex/dex-e2e-report.json") +$evidenceReport = Read-JsonIfExists -Path (Join-Path $reportFullDir "evidence/flowchain-production-l1-evidence-export-report.json") +$restartReport = Read-JsonIfExists -Path (Resolve-FlowChainPath -RepoRoot $repoRoot -Path "devnet/local/node-smoke/one-node-smoke-report.json") + +$mockFailures = @($steps | Where-Object { $_.blocks -eq "mockPath" -and $_.status -ne "passed" }) +$liveFailed = @($steps | Where-Object { $_.blocks -eq "liveReadiness" -and $_.status -eq "failed" }) +$liveBlocked = @($steps | Where-Object { $_.blocks -eq "liveReadiness" -and $_.status -ne "passed" }) +$overallStatus = if ($mockFailures.Count -gt 0) { + "failed" +} +elseif ($liveFailed.Count -gt 0) { + "failed" +} +elseif ($liveBlocked.Count -gt 0 -or $missingSubsystemCommands.Count -gt 0) { + "passed-with-live-blockers" +} +else { + "passed" +} + +$failureBlockerDetails = New-Object System.Collections.ArrayList +foreach ($step in @($steps | Where-Object { $_.status -ne "passed" })) { + [void] $failureBlockerDetails.Add([ordered]@{ + subsystem = $step.owner + owner = $step.owner + command = $step.command + status = $step.status + logPath = $step.logPath + reportPath = $step.reportPath + blocks = $step.blocks + blocksMockPath = ($step.blocks -eq "mockPath") + blocksLiveReadiness = ($step.blocks -eq "liveReadiness") + blocksLiveBroadcast = ($step.blocks -eq "liveBroadcast" -or $step.blocks -eq "liveReadiness") + reason = $step.reason + }) +} +foreach ($missingCommand in @($missingSubsystemCommands)) { + [void] $failureBlockerDetails.Add([ordered]@{ + subsystem = $missingCommand.subsystem + owner = $missingCommand.owner + command = $missingCommand.command + status = "blocked" + logPath = $missingCommand.logPath + reportPath = $missingCommand.reportPath + blocks = $missingCommand.blocks + blocksMockPath = $missingCommand.blocksMockPath + blocksLiveReadiness = $missingCommand.blocksLiveReadiness + blocksLiveBroadcast = $missingCommand.blocksLiveBroadcast + reason = $missingCommand.reason + }) +} +if ($bridgeLiveReport -and $bridgeLiveReport.status -ne "passed") { + $bridgeStep = @($steps | Where-Object { $_.name -eq "Bridge live readiness check" } | Select-Object -First 1) + foreach ($problem in @($bridgeLiveReport.problems)) { + [void] $failureBlockerDetails.Add([ordered]@{ + subsystem = "bridge/ops" + owner = "bridge/ops" + command = "npm run flowchain:bridge:live:check" + status = $bridgeLiveReport.status + envName = $problem.envName + logPath = if ($bridgeStep) { $bridgeStep.logPath } else { "" } + reportPath = (Join-Path $reportFullDir "bridge-live-readiness-report.json") + blocks = "liveReadiness" + blocksMockPath = $false + blocksLiveReadiness = $true + blocksLiveBroadcast = $true + reason = $problem.reason + }) + } +} + +$emergencyCommands = @( + "npm run flowchain:emergency:stop-local", + "npm run flowchain:bridge:emergency-stop", + "npm run flowchain:emergency:pause-bridge", + "npm run flowchain:emergency:export-evidence", + "npm run flowchain:emergency:print-recovery" +) +$restartCommands = @( + "npm run flowchain:node:restart", + "npm run flowchain:node:status", + "npm run control-plane:serve", + "npm run workbench:dev" +) + +$report = [ordered]@{ + schema = "flowchain.production_l1.e2e_report.v0" + timestamp = (Get-Date).ToUniversalTime().ToString("o") + repoPath = $repoRoot + git = [ordered]@{ + branch = (& git rev-parse --abbrev-ref HEAD).Trim() + commit = (& git rev-parse HEAD).Trim() + } + os = [ordered]@{ + platform = [System.Environment]::OSVersion.Platform.ToString() + version = [System.Environment]::OSVersion.VersionString + shell = "PowerShell" + } + toolVersions = [ordered]@{ + git = Get-ToolVersion -Command "git" + node = Get-ToolVersion -Command "node" + npm = Get-ToolVersion -Command "npm" + cargo = Get-ToolVersion -Command "cargo" + rustc = Get-ToolVersion -Command "rustc" + forge = Get-ToolVersion -Command "forge" + cast = Get-ToolVersion -Command "cast" + python = Get-ToolVersion -Command "python" -VersionArgs @("--version") + } + portsUsed = @( + Get-PortStatus -Port 8787 + Get-PortStatus -Port 5173 + ) + localUrls = [ordered]@{ + controlPlaneHealth = "http://127.0.0.1:8787/health" + controlPlaneState = "http://127.0.0.1:8787/state" + dashboard = "http://127.0.0.1:5173/" + } + dataDirectory = (Resolve-FlowChainPath -RepoRoot $repoRoot -Path "devnet/local") + chainId = $stateFacts.chainId + genesisHash = $stateFacts.genesisHash + latestHeight = $stateFacts.latestHeight + latestHash = $stateFacts.latestHash + finalizedHeight = $stateFacts.finalizedHeight + stateRoot = $stateFacts.stateRoot + walletE2EStatus = (@($steps | Where-Object { $_.name -eq "Wallet E2E" })[0]).status + transferE2EStatus = (@($steps | Where-Object { $_.name -eq "Wallet transfer E2E" })[0]).status + tokenE2EStatus = if ($dexReport) { $dexReport.tokenStatus } else { "unknown" } + dexE2EStatus = if ($dexReport) { $dexReport.dexStatus } else { "unknown" } + productE2EStatus = if ($productReport) { $productReport.status } else { (@($steps | Where-Object { $_.name -eq "Product E2E" })[0]).status } + bridgeMockStatus = (@($steps | Where-Object { $_.name -eq "Bridge mock pilot E2E" })[0]).status + bridgeLiveReadinessStatus = if ($bridgeLiveReport) { $bridgeLiveReport.status } else { (@($steps | Where-Object { $_.name -eq "Bridge live readiness check" })[0]).status } + rpcSmokeStatus = (@($steps | Where-Object { $_.name -eq "Control-plane smoke" })[0]).status + dashboardBuildOrBrowserStatus = (@($steps | Where-Object { $_.name -eq "Dashboard build" })[0]).status + exportImportStatus = $rootCompareStatus + restartRecoveryStatus = if ($restartReport) { "passed" } else { (@($steps | Where-Object { $_.name -eq "Restart recovery" })[0]).status } + noSecretScanStatus = (@($steps | Where-Object { $_.name -eq "No-secret scan" })[0]).status + unsafeClaimScanStatus = (@($steps | Where-Object { $_.name -eq "Unsafe-claim scan" })[0]).status + missingEnvNamesForLiveMode = if ($bridgeLiveReport) { @($bridgeLiveReport.missingEnvNames) } else { @() } + missingSubsystemCommands = @($missingSubsystemCommands) + failureBlockerDetails = @($failureBlockerDetails) + commandList = @($commandsRun) + subsystemSteps = @($steps) + localLogPaths = [ordered]@{ + productionE2ELogs = $logsDir + nodeLogs = (Resolve-FlowChainPath -RepoRoot $repoRoot -Path "devnet/local/node/logs") + bridgeLogs = (Resolve-FlowChainPath -RepoRoot $repoRoot -Path "services/bridge-relayer/out") + apiLogs = "Use the PowerShell window running npm run control-plane:serve; final smoke logs are under $logsDir" + dashboardBuildLog = Join-Path $logsDir "Dashboard-build.log" + } + healthEndpoint = "http://127.0.0.1:8787/health" + reportPaths = [ordered]@{ + production = $reportPath + summary = $summaryPath + l1Baseline = (Resolve-FlowChainPath -RepoRoot $repoRoot -Path "devnet/local/full-smoke/flowchain-full-smoke-report.json") + smoke = (Resolve-FlowChainPath -RepoRoot $repoRoot -Path "devnet/local/smoke/flowchain-smoke-report.json") + product = (Resolve-FlowChainPath -RepoRoot $repoRoot -Path "devnet/local/product-e2e/flowchain-product-e2e-report.json") + bridgeLiveReadiness = (Join-Path $reportFullDir "bridge-live-readiness-report.json") + exportImportRootCompare = $rootComparePath + restart = (Resolve-FlowChainPath -RepoRoot $repoRoot -Path "devnet/local/node-smoke/one-node-smoke-report.json") + evidence = if ($evidenceReport) { $evidenceReport.bundlePath } else { "" } + } + restartCommands = $restartCommands + emergencyCommands = $emergencyCommands + evidencePaths = [ordered]@{ + bundle = if ($evidenceReport) { $evidenceReport.bundlePath } else { "" } + report = (Join-Path $reportFullDir "evidence/flowchain-production-l1-evidence-export-report.json") + exportBundle = $bundlePath + } + passFailSummary = [ordered]@{ + overall = $overallStatus + mockPath = if ($mockFailures.Count -eq 0) { "passed" } else { "failed" } + liveReadiness = if ($liveBlocked.Count -eq 0 -and $missingSubsystemCommands.Count -eq 0) { "passed" } else { "blocked" } + liveBroadcast = "not-run; requires explicit operator acknowledgement and owner-supplied live env" + failedMockSteps = @($mockFailures | ForEach-Object { $_.name }) + blockedLiveSteps = @($liveBlocked | ForEach-Object { $_.name }) + } +} + +Write-FlowChainJson -Path $reportPath -Value $report -Depth 24 + +$summary = @( + "# FlowChain production-l1:e2e Summary", + "", + "- Status: $overallStatus", + "- Local dashboard URL: http://127.0.0.1:5173/", + "- Control-plane health URL: http://127.0.0.1:8787/health", + "- Data directory: $($report.dataDirectory)", + "- Final report: $reportPath", + "- Evidence bundle: $($report.evidencePaths.bundle)", + "- State root: $($report.stateRoot)", + "- Live readiness command: npm run flowchain:bridge:live:check", + "- Missing live env names: $((@($report.missingEnvNamesForLiveMode) | Select-Object -Unique) -join ', ')", + "", + "## Emergency Commands", + ($emergencyCommands | ForEach-Object { "- $_" }), + "", + "## Blockers", + $(if ($missingSubsystemCommands.Count -eq 0 -and $liveBlocked.Count -eq 0) { "- None for live readiness." } else { @($missingSubsystemCommands | ForEach-Object { "- $($_.command) [$($_.owner)]: $($_.reason)" }) + @($liveBlocked | ForEach-Object { "- $($_.name): $($_.status)" }) }) +) | ForEach-Object { + if ($_ -is [System.Array]) { $_ } else { "$_" } +} | Set-Content -LiteralPath $summaryPath -Encoding utf8 + +Write-Host "" +Write-Host "FlowChain production-l1:e2e status: $overallStatus" +Write-Host "Dashboard URL: http://127.0.0.1:5173/" +Write-Host "Data directory: $($report.dataDirectory)" +Write-Host "Final report: $reportPath" +Write-Host "Evidence bundle: $($report.evidencePaths.bundle)" +Write-Host "Bridge live readiness command: npm run flowchain:bridge:live:check" +Write-Host "Required live env names: $((@($report.missingEnvNamesForLiveMode) | Select-Object -Unique) -join ', ')" +Write-Host "Emergency stop commands:" +foreach ($command in $emergencyCommands) { + Write-Host "- $command" +} + +if ($overallStatus -eq "failed") { + throw "FlowChain production-l1:e2e failed. See $reportPath" +} diff --git a/infra/scripts/flowchain-second-computer-bundle.ps1 b/infra/scripts/flowchain-second-computer-bundle.ps1 new file mode 100644 index 00000000..48a86bce --- /dev/null +++ b/infra/scripts/flowchain-second-computer-bundle.ps1 @@ -0,0 +1,110 @@ +param( + [string] $BundlePath = "devnet/local/second-computer/flowchain-second-computer-source-bundle.zip", + [switch] $Force +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +. "$PSScriptRoot\flowchain-common.ps1" + +function Get-BundleExclusionReason { + param([string] $RelativePath, [string] $Name, [bool] $IsDirectory) + $normalized = ($RelativePath -replace "\\", "/").Trim("/") + $lower = $normalized.ToLowerInvariant() + $lowerName = $Name.ToLowerInvariant() + if ($IsDirectory) { + if ($lowerName -in @(".git", "node_modules", "target", "dist", "cache", "out", "broadcast")) { return "metadata, dependency, or build output directory" } + if ($lower -eq "devnet/local" -or $lower.StartsWith("devnet/local/")) { return "ignored local runtime output" } + if ($lowerName -like "*vault*") { return "local vault directory" } + return "" + } + if ($lower -match '(^|/)(\.git|node_modules|target|dist|cache|out|broadcast)(/|$)') { return "metadata, dependency, or build output path" } + if ($lower -eq "devnet/local" -or $lower.StartsWith("devnet/local/")) { return "ignored local runtime output" } + if ($lowerName -eq ".env" -or ($lowerName.StartsWith(".env.") -and $lowerName -ne ".env.example")) { return "local env file" } + if ($lowerName.EndsWith(".local.json")) { return "local-only JSON file" } + if ($lowerName -like "*vault*" -or $lowerName -like "*private-key*" -or $lowerName -like "*private_key*" -or $lowerName -like "*mnemonic*" -or $lowerName -like "*seed-phrase*") { return "secret-named file" } + if ($lowerName.EndsWith(".pem") -or $lowerName.EndsWith(".key") -or $lowerName.EndsWith(".pfx") -or $lowerName.EndsWith(".p12")) { return "private key material file" } + return "" +} + +$repoRoot = Set-FlowChainRepoRoot +$bundleFullPath = Assert-FlowChainPathInsideRepo -RepoRoot $repoRoot -Path (Resolve-FlowChainPath -RepoRoot $repoRoot -Path $BundlePath) +$bundleRoot = Split-Path -Parent $bundleFullPath +$stageRoot = Join-Path $bundleRoot "stage" +$stageRepo = Join-Path $stageRoot "FlowMemory" +if ((Test-Path -LiteralPath $bundleFullPath) -and -not $Force) { + Remove-Item -LiteralPath $bundleFullPath -Force +} +if (Test-Path -LiteralPath $stageRoot) { + Remove-Item -LiteralPath $stageRoot -Recurse -Force +} +New-Item -ItemType Directory -Force -Path $stageRepo | Out-Null + +$excluded = New-Object System.Collections.ArrayList +$repoPrefix = [System.IO.Path]::GetFullPath($repoRoot).TrimEnd([System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar) + [System.IO.Path]::DirectorySeparatorChar +foreach ($item in Get-ChildItem -LiteralPath $repoRoot -Force) { + $stack = New-Object System.Collections.Stack + $stack.Push($item) + while ($stack.Count -gt 0) { + $current = $stack.Pop() + $relative = [System.IO.Path]::GetFullPath($current.FullName).Substring($repoPrefix.Length) + $reason = Get-BundleExclusionReason -RelativePath $relative -Name $current.Name -IsDirectory $current.PSIsContainer + if (-not [string]::IsNullOrWhiteSpace($reason)) { + [void] $excluded.Add([ordered]@{ path = $relative; reason = $reason }) + continue + } + $dest = Join-Path $stageRepo $relative + if ($current.PSIsContainer) { + New-Item -ItemType Directory -Force -Path $dest | Out-Null + foreach ($child in Get-ChildItem -LiteralPath $current.FullName -Force) { + $stack.Push($child) + } + } + else { + New-Item -ItemType Directory -Force -Path (Split-Path -Parent $dest) | Out-Null + Copy-Item -LiteralPath $current.FullName -Destination $dest + } + } +} + +$manifestPath = Join-Path $stageRepo "SECOND_COMPUTER_BUNDLE_MANIFEST.json" +$manifest = [ordered]@{ + schema = "flowchain.second_computer.source_bundle_manifest.v0" + generatedAt = (Get-Date).ToUniversalTime().ToString("o") + sourceRepo = $repoRoot + excludes = @(".git", "node_modules", "devnet/local", "target", "dist/cache/out/broadcast", "env files", "vaults", "private keys") + excludedCount = $excluded.Count + nextCommands = @( + "npm install", + "npm install --prefix apps/dashboard", + "npm install --prefix crypto", + "npm run flowchain:second-computer:verify", + "npm run flowchain:production-l1:e2e" + ) +} +Write-FlowChainJson -Path $manifestPath -Value $manifest -Depth 10 + +& powershell -NoProfile -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "flowchain-no-secret-scan.ps1") -Paths @($stageRepo) -ReportPath (Join-Path $stageRepo "SECOND_COMPUTER_BUNDLE_NO_SECRET_SCAN.json") +if ($LASTEXITCODE -ne 0) { + throw "Second-computer bundle no-secret scan failed." +} + +New-Item -ItemType Directory -Force -Path $bundleRoot | Out-Null +Compress-Archive -Path $stageRepo -DestinationPath $bundleFullPath -Force +$hash = Get-FileHash -Algorithm SHA256 -LiteralPath $bundleFullPath +$reportPath = Join-Path $bundleRoot "flowchain-second-computer-bundle-report.json" +$report = [ordered]@{ + schema = "flowchain.second_computer.bundle_report.v0" + generatedAt = (Get-Date).ToUniversalTime().ToString("o") + status = "passed" + bundlePath = $bundleFullPath + bundleSha256 = $hash.Hash + manifestPath = $manifestPath +} +Write-FlowChainJson -Path $reportPath -Value $report -Depth 10 + +Write-Host "FlowChain second-computer offline source bundle created." +Write-Host "Bundle: $bundleFullPath" +Write-Host "Report: $reportPath" + diff --git a/infra/scripts/flowchain-second-computer-verify.ps1 b/infra/scripts/flowchain-second-computer-verify.ps1 new file mode 100644 index 00000000..3963fc28 --- /dev/null +++ b/infra/scripts/flowchain-second-computer-verify.ps1 @@ -0,0 +1,57 @@ +param( + [string] $ReportPath = "devnet/local/second-computer/flowchain-second-computer-verify-report.json" +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +. "$PSScriptRoot\flowchain-common.ps1" + +$repoRoot = Set-FlowChainRepoRoot +$reportFullPath = Assert-FlowChainPathInsideRepo -RepoRoot $repoRoot -Path (Resolve-FlowChainPath -RepoRoot $repoRoot -Path $ReportPath) + +$checks = New-Object System.Collections.ArrayList +function Add-VerifyCheck { + param([string] $Name, [bool] $Passed, [string] $NextCommand) + [void] $checks.Add([ordered]@{ + name = $Name + status = if ($Passed) { "passed" } else { "failed" } + nextCommand = $NextCommand + }) +} + +Add-VerifyCheck -Name "repo-root" -Passed (Test-Path -LiteralPath (Join-Path $repoRoot "package.json")) -NextCommand "cd " +Add-VerifyCheck -Name "root-node-modules" -Passed (Test-Path -LiteralPath (Join-Path $repoRoot "node_modules")) -NextCommand "npm install" +Add-VerifyCheck -Name "dashboard-node-modules" -Passed (Test-Path -LiteralPath (Join-Path $repoRoot "apps/dashboard/node_modules")) -NextCommand "npm install --prefix apps/dashboard" +Add-VerifyCheck -Name "crypto-node-modules" -Passed (Test-Path -LiteralPath (Join-Path $repoRoot "crypto/node_modules")) -NextCommand "npm install --prefix crypto" +Add-VerifyCheck -Name "local-ignore" -Passed ((Get-Content -Raw -LiteralPath (Join-Path $repoRoot ".gitignore")).Contains("devnet/local/")) -NextCommand "restore .gitignore from repo" + +$failed = @($checks | Where-Object { $_.status -ne "passed" }) +$report = [ordered]@{ + schema = "flowchain.second_computer.verify_report.v0" + generatedAt = (Get-Date).ToUniversalTime().ToString("o") + status = if ($failed.Count -eq 0) { "passed" } else { "failed" } + checks = @($checks) + authenticatedPrivateRepoPath = @( + "winget install --id Git.Git --exact --source winget --accept-package-agreements --accept-source-agreements", + "winget install --id GitHub.cli --exact --source winget --accept-package-agreements --accept-source-agreements", + "gh auth login", + "gh repo clone FlowmemoryAI/FlowMemory `$env:USERPROFILE\FlowMemory\FlowMemory" + ) + offlineBundlePath = @( + "Extract flowchain-second-computer-source-bundle.zip", + "cd FlowMemory", + "npm install", + "npm install --prefix apps/dashboard", + "npm install --prefix crypto", + "npm run flowchain:production-l1:e2e" + ) +} +Write-FlowChainJson -Path $reportFullPath -Value $report -Depth 12 + +Write-Host "FlowChain second-computer verify status: $($report.status)" +Write-Host "Report: $reportFullPath" +if ($failed.Count -gt 0) { + throw "Second-computer verification found missing install steps." +} + diff --git a/infra/scripts/flowchain-wallet-e2e.ps1 b/infra/scripts/flowchain-wallet-e2e.ps1 new file mode 100644 index 00000000..276ba70f --- /dev/null +++ b/infra/scripts/flowchain-wallet-e2e.ps1 @@ -0,0 +1,42 @@ +param( + [string] $ReportPath = "devnet/local/production-l1-e2e/wallet-e2e-report.json" +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +. "$PSScriptRoot\flowchain-common.ps1" + +$repoRoot = Set-FlowChainRepoRoot +$reportFullPath = Assert-FlowChainPathInsideRepo -RepoRoot $repoRoot -Path (Resolve-FlowChainPath -RepoRoot $repoRoot -Path $ReportPath) + +Invoke-FlowChainCommand -Label "Run wallet product transaction smoke" -FilePath "npm" -ArgumentList @( + "run", + "wallet:product-smoke", + "--prefix", + "crypto" +) + +$metadataPath = Join-Path (Split-Path -Parent $reportFullPath) "wallet-public-metadata.json" +$metadata = [ordered]@{ + schema = "flowchain.wallet.public_metadata.v0" + generatedAt = (Get-Date).ToUniversalTime().ToString("o") + walletEvidence = "crypto product transaction fixtures validated" + exportsSecretMaterial = $false + command = "npm run wallet:product-smoke --prefix crypto" +} +Write-FlowChainJson -Path $metadataPath -Value $metadata + +$report = [ordered]@{ + schema = "flowchain.wallet_e2e_report.v0" + generatedAt = (Get-Date).ToUniversalTime().ToString("o") + status = "passed" + owner = "wallet/crypto" + command = "npm run wallet:product-smoke --prefix crypto" + publicMetadataPath = $metadataPath + secretMaterialExported = $false +} +Write-FlowChainJson -Path $reportFullPath -Value $report + +Write-Host "FlowChain wallet E2E passed." +Write-Host "Report: $reportFullPath" diff --git a/infra/scripts/flowchain-wallet-transfer-e2e.ps1 b/infra/scripts/flowchain-wallet-transfer-e2e.ps1 new file mode 100644 index 00000000..26db44c4 --- /dev/null +++ b/infra/scripts/flowchain-wallet-transfer-e2e.ps1 @@ -0,0 +1,153 @@ +param( + [string] $OutDir = "devnet/local/production-l1-e2e/wallet-transfer", + [string] $StatePath = "devnet/local/production-l1-e2e/wallet-transfer/state.json" +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +. "$PSScriptRoot\flowchain-common.ps1" + +$repoRoot = Set-FlowChainRepoRoot +Set-FlowChainCargoTargetDir -RepoRoot $repoRoot -Purpose "wallet-transfer-e2e" | Out-Null +$outFullDir = Assert-FlowChainPathInsideRepo -RepoRoot $repoRoot -Path (Resolve-FlowChainPath -RepoRoot $repoRoot -Path $OutDir) +$stateFullPath = Assert-FlowChainPathInsideRepo -RepoRoot $repoRoot -Path (Resolve-FlowChainPath -RepoRoot $repoRoot -Path $StatePath) +$nodeFullDir = Join-Path $outFullDir "node" +$txPath = Join-Path $outFullDir "transfer-tx.json" +$reportPath = Join-Path $outFullDir "wallet-transfer-e2e-report.json" + +if (Test-Path -LiteralPath $outFullDir) { + Remove-Item -LiteralPath $outFullDir -Recurse -Force +} +New-Item -ItemType Directory -Force -Path $outFullDir | Out-Null + +Invoke-FlowChainCommand -Label "Initialize wallet transfer state" -FilePath "cargo" -ArgumentList @( + "run", + "--manifest-path", + "crates/flowmemory-devnet/Cargo.toml", + "--", + "--state", + $stateFullPath, + "init" +) + +Invoke-FlowChainCommand -Label "Fund sender account" -FilePath "cargo" -ArgumentList @( + "run", + "--manifest-path", + "crates/flowmemory-devnet/Cargo.toml", + "--", + "--state", + $stateFullPath, + "--node-dir", + $nodeFullDir, + "faucet", + "--account", + "local-account:transfer:alice", + "--amount", + "100", + "--reason", + "wallet-transfer-e2e", + "--authorized-by", + "operator:transfer:alice", + "--direct" +) + +Invoke-FlowChainCommand -Label "Create recipient account" -FilePath "cargo" -ArgumentList @( + "run", + "--manifest-path", + "crates/flowmemory-devnet/Cargo.toml", + "--", + "--state", + $stateFullPath, + "--node-dir", + $nodeFullDir, + "faucet", + "--account", + "local-account:transfer:bob", + "--amount", + "1", + "--reason", + "wallet-transfer-e2e-recipient", + "--authorized-by", + "operator:transfer:bob", + "--direct" +) + +$txFixture = [ordered]@{ + schema = "flowmemory.local_devnet.fixture.txs.v0" + txs = @( + [ordered]@{ + type = "TransferLocalTestUnits" + transferId = "transfer:wallet-e2e:001" + fromAccountId = "local-account:transfer:alice" + toAccountId = "local-account:transfer:bob" + amountUnits = 25 + memo = "wallet-transfer-e2e" + } + ) +} +Write-FlowChainJson -Path $txPath -Value $txFixture + +Invoke-FlowChainCommand -Label "Submit local wallet transfer" -FilePath "cargo" -ArgumentList @( + "run", + "--manifest-path", + "crates/flowmemory-devnet/Cargo.toml", + "--", + "--state", + $stateFullPath, + "--node-dir", + $nodeFullDir, + "submit-tx", + "--tx-file", + $txPath, + "--authorized-by", + "operator:transfer:alice", + "--direct" +) + +Invoke-FlowChainCommand -Label "Include wallet transfer in a block" -FilePath "cargo" -ArgumentList @( + "run", + "--manifest-path", + "crates/flowmemory-devnet/Cargo.toml", + "--", + "--state", + $stateFullPath, + "run", + "--blocks", + "1" +) + +$summary = & cargo run --manifest-path crates/flowmemory-devnet/Cargo.toml -- --state $stateFullPath inspect-state --summary | ConvertFrom-Json +if ($LASTEXITCODE -ne 0) { + throw "Failed to inspect wallet transfer state." +} +$transferCount = if ($summary.PSObject.Properties.Name -contains "balanceTransfers") { + [int] $summary.balanceTransfers +} +elseif ($summary.PSObject.Properties.Name -contains "counts" -and $summary.counts.PSObject.Properties.Name -contains "balanceTransfers") { + [int] $summary.counts.balanceTransfers +} +else { + 0 +} +if ($transferCount -lt 1) { + throw "Wallet transfer was not recorded in state." +} + +$report = [ordered]@{ + schema = "flowchain.wallet_transfer_e2e_report.v0" + generatedAt = (Get-Date).ToUniversalTime().ToString("o") + status = "passed" + statePath = $stateFullPath + transferFixture = $txPath + transferCount = $transferCount + latestHeight = $summary.blocks + stateRoot = $summary.stateRoot + noValueBoundary = "local test-unit transfer only; no tokenomics, fees, staking, or real value" +} +Write-FlowChainJson -Path $reportPath -Value $report -Depth 12 +Assert-FlowChainNoSecretFiles -Path $outFullDir + +Write-Host "FlowChain wallet transfer E2E passed." +Write-Host "State root: $($summary.stateRoot)" +Write-Host "Report: $reportPath" diff --git a/package.json b/package.json index 8e1a2449..b1af6c63 100644 --- a/package.json +++ b/package.json @@ -33,12 +33,18 @@ "launch:candidate": "npm run contracts:hardening && npm run launch:v0 && npm run validate:launch && npm run fixtures:check && node infra/scripts/check-unsafe-claims.mjs", "build:production": "npm run launch:candidate && npm run build --prefix apps/dashboard", "flowchain:prereq": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-check-prereqs.ps1", + "flowchain:doctor": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-doctor.ps1", "flowchain:init": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-init.ps1", + "flowchain:second-computer:bundle": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-second-computer-bundle.ps1", + "flowchain:second-computer:verify": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-second-computer-verify.ps1", "flowchain:start": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-start.ps1", "flowchain:stop": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-stop.ps1", "flowchain:node": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-node.ps1", + "flowchain:node:start": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-node-start.ps1", "flowchain:node:stop": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-node-stop.ps1", "flowchain:node:status": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-node-status.ps1", + "flowchain:node:restart": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-node-restart.ps1", + "flowchain:node:logs": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-node-logs.ps1", "flowchain:tx": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-tx.ps1", "flowchain:faucet": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-faucet.ps1", "flowchain:node:smoke": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-node-smoke.ps1", @@ -48,7 +54,13 @@ "flowchain:smoke": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-smoke.ps1", "flowchain:full-smoke": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-full-smoke.ps1", "flowchain:product-e2e": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-product-e2e.ps1", + "flowchain:product:e2e": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-product-e2e.ps1", + "flowchain:dex:e2e": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-dex-e2e.ps1", + "flowchain:wallet:e2e": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-wallet-e2e.ps1", + "flowchain:wallet:transfer:e2e": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-wallet-transfer-e2e.ps1", "flowchain:l1-e2e": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-full-smoke.ps1", + "flowchain:l1:e2e": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-full-smoke.ps1", + "flowchain:production-l1:e2e": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-production-l1-e2e.ps1", "flowchain:real-value-pilot:e2e": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-real-value-pilot-e2e.ps1", "flowchain:real-value-pilot": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-real-value-pilot.ps1", "flowchain:real-value-pilot:contracts": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-real-value-pilot-contracts-e2e.ps1", @@ -59,8 +71,21 @@ "flowchain:real-value-pilot:runtime": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-real-value-pilot-runtime.ps1", "flowchain:real-value-pilot:control-dashboard": "npm run real-value-pilot:e2e --prefix services/control-plane", "flowchain:real-value-pilot:wallet": "npm run wallet:pilot-e2e --prefix crypto", + "flowchain:bridge:mock:e2e": "npm run bridge:local-credit:smoke", + "flowchain:bridge:live:check": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-bridge-live-check.ps1", + "flowchain:bridge:evidence:export": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-evidence-export.ps1", + "flowchain:bridge:emergency-stop": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-real-value-pilot-emergency-stop.ps1", + "flowchain:control-plane:smoke": "npm run control-plane:smoke", + "flowchain:dashboard:build": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-workbench.ps1 -BuildOnly", + "flowchain:dashboard:verify": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-workbench.ps1 -BuildOnly", "flowchain:export": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-export.ps1", "flowchain:import": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-import.ps1", + "flowchain:restart:verify": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-node-smoke.ps1", + "flowchain:no-secret:scan": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-no-secret-scan.ps1", + "flowchain:emergency:stop-local": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-emergency-stop-local.ps1", + "flowchain:emergency:pause-bridge": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-real-value-pilot-emergency-stop.ps1", + "flowchain:emergency:export-evidence": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-evidence-export.ps1", + "flowchain:emergency:print-recovery": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-emergency-print-recovery.ps1", "workbench:dev": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-workbench.ps1", "e2e": "npm run index:fixtures && npm run verify:fixtures && npm run flowmemory:generate", "demo:indexer": "npm run demo --prefix services/indexer",