diff --git a/README.md b/README.md index 82fa3d95..06cc0fe4 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,15 @@ npm run flowchain:full-smoke npm run flowchain:product-e2e ``` +Run the capped owner pilot dry-run before any Base `8453` pilot action: + +```powershell +npm run flowchain:real-value-pilot:ops +``` + +Owner pilot coordination and go/no-go criteria live in +`docs/FLOWCHAIN_REAL_VALUE_PILOT.md`. + Run the existing dashboard as the local workbench: ```powershell diff --git a/docs/EASY_SECOND_COMPUTER_SETUP.md b/docs/EASY_SECOND_COMPUTER_SETUP.md index bc1b87ee..92c50936 100644 --- a/docs/EASY_SECOND_COMPUTER_SETUP.md +++ b/docs/EASY_SECOND_COMPUTER_SETUP.md @@ -55,6 +55,15 @@ npm run flowchain:product-e2e That gate must pass before the setup should be treated as ready for a local second-computer test. +Before any capped Base `8453` owner pilot action, run the ops dry-run proof: + +```powershell +npm run flowchain:real-value-pilot:ops +``` + +Then follow `docs/FLOWCHAIN_REAL_VALUE_PILOT.md` for owner go/no-go criteria, +emergency stop, evidence export, and restart recovery commands. + ## Already Cloned Setup If the repo is already cloned: diff --git a/docs/FLOWCHAIN_OPERATOR_CHECKLIST.md b/docs/FLOWCHAIN_OPERATOR_CHECKLIST.md index f1b7d69b..09b8cd25 100644 --- a/docs/FLOWCHAIN_OPERATOR_CHECKLIST.md +++ b/docs/FLOWCHAIN_OPERATOR_CHECKLIST.md @@ -44,6 +44,16 @@ npm run flowchain:export Run `npm run flowchain:full-smoke` when the machine has the full prerequisite set, including Foundry, Python, dashboard dependencies, and crypto dependencies. +Capped owner pilot preflight: + +```powershell +npm run flowchain:real-value-pilot:ops +``` + +Do not run live pilot actions until the owner has reviewed +`docs/FLOWCHAIN_REAL_VALUE_PILOT.md` and supplied the required env vars in a +local shell only. + ## Launch Demo Day Primary script: `docs/LAUNCH_DEMO_RUNBOOK.md`. diff --git a/docs/FLOWCHAIN_REAL_VALUE_PILOT.md b/docs/FLOWCHAIN_REAL_VALUE_PILOT.md index f31cbf2e..3fdc8d6f 100644 --- a/docs/FLOWCHAIN_REAL_VALUE_PILOT.md +++ b/docs/FLOWCHAIN_REAL_VALUE_PILOT.md @@ -19,8 +19,8 @@ approval. ## Current Baseline -Current `main` after PR #142 merged at -`c4959f8223c491f5a45c6b7d572707420457b743`: +Current `main` after PR #143 merged at +`a16fb9a7ce817b8c32d4641610c35e559a6c444c`: - `npm run flowchain:product-e2e` exists as the local product testnet gate. - `npm run flowchain:full-smoke` exists as the private/local L1 baseline gate. @@ -31,6 +31,8 @@ Current `main` after PR #142 merged at gate. It fails by default while required subsystem proof commands are missing. - `npm run flowchain:real-value-pilot:control-dashboard` exists on `main` after PR #142 merged. +- `npm run flowchain:real-value-pilot:wallet` exists on `main` after PR + #143 merged. GitHub source-of-truth state checked for this pass: @@ -41,8 +43,10 @@ GitHub source-of-truth state checked for this pass: policy while keeping `contracts:hardening:slither` as the explicit audit gate. - Issue #137 is closed; PR #142 merged the control-plane/dashboard pilot proof command. -- Issues #133, #138, #134, #136, and #135 remain the open subsystem proof - blockers for strict pilot-gate pass. +- Issue #136 is closed; PR #143 merged the wallet/operator pilot proof + command. +- Issues #133, #138, #134, and #135 remain the open subsystem proof blockers + for strict pilot-gate pass. ## Final Gate @@ -67,6 +71,45 @@ devnet/local/real-value-pilot/flowchain-real-value-pilot-e2e-report.json The report must show `status: "passed"` before the owner can mark the capped pilot go. Until then, missing proof rows are blockers, not warnings. +## Ops Command Surface + +The ops proof command is branch-local until issue #135 merges: + +```powershell +npm run flowchain:real-value-pilot:ops +``` + +It verifies that the owner-pilot scripts parse, dry-run mode needs no live RPC +URL or private key, live mode refuses missing acknowledgement/env values, +emergency stop prints the pause recovery command, and evidence export excludes +secret-shaped files. + +Live owner actions require explicit local shell env vars and are not run by the +proof command. The command surface is: + +```powershell +npm run flowchain:real-value-pilot -- --Mode Live --Action Deploy +npm run flowchain:real-value-pilot -- --Mode Live --Action Deploy -Execute +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:real-value-pilot:emergency-stop +npm run flowchain:real-value-pilot -- --Mode Live --Action Resume -Execute +npm run flowchain:real-value-pilot:export +npm run flowchain:real-value-pilot -- --Mode Live --Action Restart +``` + +Set live env vars only in a local shell or ignored env file. The minimum +operator acknowledgement is: + +```powershell +$env:FLOWCHAIN_PILOT_OPERATOR_ACK="I_UNDERSTAND_THIS_IS_CAPPED_BASE8453_OWNER_PILOT" +``` + +The ops wrapper also requires action-specific Base `8453` RPC, lockbox, +owner/release/submitter/recipient, block range, and tiny cap env values before +any live action proceeds. + ## Release-Gate Boundary This section is the issue #130 boundary for real-value pilot PRs. It does not @@ -93,18 +136,18 @@ the proof is branch-local or verified from `main`. | --- | --- | --- | --- | | Existing product testnet gate remains green. | HQ/Ops | `npm run flowchain:product-e2e` | Existing command; run before PR when practical. | | L1 baseline gate remains green. | HQ/Ops | `npm run flowchain:l1-e2e` | Exists on `main` as current alias to `flowchain:full-smoke`; latest local main-equivalent run passed. | -| Base chain ID `8453` is verified before any live observer or deployment action. | Contracts + Bridge + Ops | `npm run flowchain:real-value-pilot:contracts`; `npm run flowchain:real-value-pilot:bridge`; `npm run flowchain:real-value-pilot:ops` | Missing dedicated pilot commands. | -| Lockbox address is loaded from ignored local config or env, not hardcoded as a blanket endorsement. | Contracts + Ops | `npm run flowchain:real-value-pilot:contracts`; `npm run flowchain:real-value-pilot:ops` | Missing dedicated pilot commands. | +| Base chain ID `8453` is verified before any live observer or deployment action. | Contracts + Bridge + Ops | `npm run flowchain:real-value-pilot:contracts`; `npm run flowchain:real-value-pilot:bridge`; `npm run flowchain:real-value-pilot:ops` | Contracts and bridge commands are still missing; ops branch command added here pending PR merge. | +| Lockbox address is loaded from ignored local config or env, not hardcoded as a blanket endorsement. | Contracts + Ops | `npm run flowchain:real-value-pilot:contracts`; `npm run flowchain:real-value-pilot:ops` | Contracts command is still missing; ops branch command added here pending PR merge. | | Per-deposit cap, total pilot cap, supported-asset allowlist, pause, release, recovery, and replay protection are covered by tests and dry-run deployment evidence. | Contracts | `npm run flowchain:real-value-pilot:contracts` | Missing dedicated pilot command. | | Deposit observation writes deterministic observation, credit, and evidence files. | Bridge relayer | `npm run flowchain:real-value-pilot:bridge` | Missing dedicated pilot command. | | Duplicate Base event replay is rejected or idempotent with explicit evidence. | Bridge relayer + Chain runtime | `npm run flowchain:real-value-pilot:bridge`; `npm run flowchain:real-value-pilot:runtime` | Missing dedicated pilot commands. | | Local runtime applies each pilot bridge credit exactly once and preserves state across restart/export/import. | Chain runtime | `npm run flowchain:real-value-pilot:runtime` | Missing dedicated pilot command. | -| Operator wallet can sign pilot acknowledgements, withdrawal intents, release evidence, and emergency messages without committing secrets. | Wallet/operator | `npm run flowchain:real-value-pilot:wallet` | Branch command added here; local proof passes, pending PR merge. | -| Wallet verification rejects wrong chain ID, wrong contract, wrong operator, mutated payload, replay nonce, expired message, and missing cap fields. | Wallet/operator | `npm run flowchain:real-value-pilot:wallet` | Branch command added here; local proof passes, pending PR merge. | +| Operator wallet can sign pilot acknowledgements, withdrawal intents, release evidence, and emergency messages without committing secrets. | Wallet/operator | `npm run flowchain:real-value-pilot:wallet` | Merged on `main` by PR #143; latest local main-equivalent proof passed. | +| Wallet verification rejects wrong chain ID, wrong contract, wrong operator, mutated payload, replay nonce, expired message, and missing cap fields. | Wallet/operator | `npm run flowchain:real-value-pilot:wallet` | Merged on `main` by PR #143; latest local main-equivalent proof passed. | | API exposes pilot status, observations, credits, withdrawal intents, release evidence, cap status, pause status, retry state, and emergency state. | Control plane/dashboard | `npm run flowchain:real-value-pilot:control-dashboard` | Merged on `main` by PR #142; latest local main-equivalent proof passed. | | Dashboard labels the flow as capped owner testing and shows live/degraded/error state plus exact next operator commands. | Control plane/dashboard | `npm run flowchain:real-value-pilot:control-dashboard` | Merged on `main` by PR #142; latest local main-equivalent proof passed. | -| Browser stores no private keys or RPC credentials. | Control plane/dashboard + Wallet/operator | `npm run flowchain:real-value-pilot:control-dashboard`; `npm run flowchain:real-value-pilot:wallet` | Control-dashboard proof is merged; wallet branch proof passes, pending PR merge. | -| Ops path verifies required env, tiny caps, explicit owner ack, emergency stop, export evidence, restart recovery, and no-secret scans. | Ops/installer | `npm run flowchain:real-value-pilot:ops` | Missing dedicated pilot command. | +| Browser stores no private keys or RPC credentials. | Control plane/dashboard + Wallet/operator | `npm run flowchain:real-value-pilot:control-dashboard`; `npm run flowchain:real-value-pilot:wallet` | Control-dashboard and wallet proofs are merged. | +| Ops path verifies required env, tiny caps, explicit owner ack, emergency stop, export evidence, restart recovery, and no-secret scans. | Ops/installer | `npm run flowchain:real-value-pilot:ops` | Branch command added here; local proof passes, pending PR merge. | | Final pilot gate runs baseline commands plus every available dedicated proof command. | HQ/Ops | `npm run flowchain:real-value-pilot:e2e` | Exists on `main`; strict mode still fails until subsystem commands land. | ## In-Flight Implementation Status @@ -116,12 +159,12 @@ from `main`. | Area | In-flight branch state | Required next step | | --- | --- | --- | -| Contracts | `agent/real-value-pilot-contracts` checklist reports the contracts proof complete, including hardening, deploy dry-run, and product E2E. | Rebase onto `c4959f8`, expose `flowchain:real-value-pilot:contracts`, rerun evidence, and open a PR. | -| Bridge relayer | `agent/real-value-pilot-bridge` checklist reports the bridge proof complete; service-local `pilot:e2e` exists. | Rebase onto `c4959f8`, expose `flowchain:real-value-pilot:bridge`, rerun evidence, and open a PR. | -| Chain runtime | `agent/real-value-pilot-chain` checklist reports runtime credit/replay/restart/export proof complete through the direct wrapper; root package command is missing. | Rebase onto `c4959f8`, expose `flowchain:real-value-pilot:runtime`, rerun evidence, and open a PR. | -| Wallet/operator | `agent/real-value-pilot-wallet` is rebased onto `c4959f8`; checklist reports wallet/operator schemas, signing, validation, negative cases, scans, product evidence, and branch-local `flowchain:real-value-pilot:wallet` complete. | Open a PR for issue #136 so the proof command lands on `main`. | +| Contracts | `agent/real-value-pilot-contracts` checklist reports the contracts proof complete, including hardening, deploy dry-run, and product E2E. | Rebase onto `a16fb9a`, expose `flowchain:real-value-pilot:contracts`, rerun evidence, and open a PR. | +| Bridge relayer | `agent/real-value-pilot-bridge` checklist reports the bridge proof complete; service-local `pilot:e2e` exists. | Rebase onto `a16fb9a`, expose `flowchain:real-value-pilot:bridge`, rerun evidence, and open a PR. | +| Chain runtime | `agent/real-value-pilot-chain` checklist reports runtime credit/replay/restart/export proof complete through the direct wrapper; root package command is missing. | Rebase onto `a16fb9a`, expose `flowchain:real-value-pilot:runtime`, rerun evidence, and open a PR. | +| Wallet/operator | `flowchain:real-value-pilot:wallet` merged on `main` through PR #143 and closed issue #136. | No wallet/operator blocker remains for the final pilot gate. | | Control plane/dashboard | `flowchain:real-value-pilot:control-dashboard` merged on `main` through PR #142 and closed issue #137. | No control-dashboard blocker remains for the final pilot gate. | -| Ops/installer | `agent/real-value-pilot-ops` checklist reports ops proof complete; root lifecycle commands exist branch-locally, but `flowchain:real-value-pilot:ops` is missing. | Rebase onto `c4959f8`, expose `flowchain:real-value-pilot:ops`, rerun evidence, and open a PR. | +| Ops/installer | This branch adapts `agent/real-value-pilot-ops` work onto `a16fb9a` and exposes branch-local `flowchain:real-value-pilot:ops`. | Open a PR for issue #135 so the proof command lands on `main`. | ## Owner Go/No-Go Checklist @@ -151,9 +194,9 @@ in committed files, or if any document presents the pilot as public readiness. - Dedicated real-value contracts gate does not exist; tracked by issue #133. - Dedicated real-value bridge relayer gate does not exist; tracked by issue #138. - Dedicated real-value runtime gate does not exist; tracked by issue #134. -- Dedicated real-value wallet/operator gate exists branch-locally and passes; tracked by issue #136 until merged. +- Dedicated real-value wallet/operator gate is merged on `main`; issue #136 is closed by PR #143. - Dedicated real-value control-plane/dashboard gate is merged on `main`; issue #137 is closed by PR #142. -- Dedicated real-value ops/installer gate does not exist; tracked by issue #135. +- Dedicated real-value ops/installer gate exists branch-locally and passes; tracked by issue #135 until merged. - Issue #130 is closed by PR #132; the release-gate boundary is now on `main`. - Issue #131 is closed by PR #132; default `contracts:hardening` skips optional Slither unless the audit gate is explicitly requested. @@ -167,7 +210,7 @@ in committed files, or if any document presents the pilot as public readiness. | Contracts | #133 | `npm run flowchain:real-value-pilot:contracts` | | Bridge relayer | #138 | `npm run flowchain:real-value-pilot:bridge` | | Chain runtime | #134 | `npm run flowchain:real-value-pilot:runtime` | -| Wallet/operator | #136 | `npm run flowchain:real-value-pilot:wallet` | +| Wallet/operator | #136, closed by PR #143 | `npm run flowchain:real-value-pilot:wallet` | | Control plane/dashboard | #137, closed by PR #142 | `npm run flowchain:real-value-pilot:control-dashboard` | | Ops/installer | #135 | `npm run flowchain:real-value-pilot:ops` | | Release-gate boundary | #130, closed by PR #132 | `npm run flowchain:real-value-pilot:e2e -- -AllowIncomplete` until proofs land | diff --git a/docs/FLOWCHAIN_SECOND_COMPUTER_SETUP.md b/docs/FLOWCHAIN_SECOND_COMPUTER_SETUP.md index d02287cc..32279fbf 100644 --- a/docs/FLOWCHAIN_SECOND_COMPUTER_SETUP.md +++ b/docs/FLOWCHAIN_SECOND_COMPUTER_SETUP.md @@ -120,6 +120,15 @@ npm run flowchain:full-smoke npm run flowchain:product-e2e ``` +Run the capped owner pilot ops dry-run before any Base `8453` pilot action: + +```powershell +npm run flowchain:real-value-pilot:ops +``` + +The full owner pilot coordination checklist is +`docs/FLOWCHAIN_REAL_VALUE_PILOT.md`. + Run the local workbench in a separate PowerShell window: ```powershell @@ -156,6 +165,8 @@ Expected current result: local account funding, token launch, pool creation, liquidity, swap receipts, bridge-test credit visibility, control-plane query surfaces, workbench product surfaces, and no-secret response boundaries. +- `npm run flowchain:real-value-pilot:ops` is the dry-run ops proof for the + capped owner pilot. It uses no live RPC URL or private key. - `npm run workbench:dev` opens the existing dashboard as the local workbench. Current stop point: if a second computer needs production custody, production @@ -180,6 +191,7 @@ npm run flowchain:start npm run control-plane:serve npm run workbench:dev npm run flowchain:product-e2e +npm run flowchain:real-value-pilot:ops npm run flowchain:export ``` @@ -206,6 +218,9 @@ npm run flowchain:demo npm run flowchain:smoke npm run flowchain:full-smoke npm run flowchain:product-e2e +npm run flowchain:real-value-pilot:ops +npm run flowchain:real-value-pilot:emergency-stop +npm run flowchain:real-value-pilot:export npm run flowchain:export npm run workbench:dev ``` @@ -222,6 +237,9 @@ 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: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. | | `npm run flowchain:export` | Implemented | Writes ignored export directory and zip bundle. | | `npm run flowchain:import -- --BundlePath -Force` | Implemented script path | Restores local state from an exported bundle. | | `npm run workbench:dev` | Implemented | Wraps `npm run dev --prefix apps/dashboard`. | diff --git a/docs/FLOWCHAIN_TROUBLESHOOTING.md b/docs/FLOWCHAIN_TROUBLESHOOTING.md index cf5bb3b8..f180d714 100644 --- a/docs/FLOWCHAIN_TROUBLESHOOTING.md +++ b/docs/FLOWCHAIN_TROUBLESHOOTING.md @@ -61,6 +61,7 @@ or workbench commands. | Smoke fails during hardware fixture check | Python is missing or not on PATH. | Install Python 3 or run `npm run flowchain:smoke -- -SkipHardware` for a scoped local smoke. | | `flowchain:full-smoke` fails with missing command coverage | The full local/private L1 package is not complete yet. | Read `devnet/local/smoke/flowchain-full-smoke-report.json` and the owning issues #99-#108. Use `npm run flowchain:full-smoke -- -SkipMergedSmoke -AllowIncomplete` only to validate the temporary report wrapper. | | Cargo output looks like a different worktree | A shared `CARGO_TARGET_DIR` is reusing stale binaries. | Use the root wrapper scripts; they pin cargo output to `crates/flowmemory-devnet/target` for this checkout. | +| 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`. | @@ -115,6 +116,19 @@ Get-NetTCPConnection -LocalPort 8787 -ErrorAction SilentlyContinue Close the old PowerShell window that owns the service, or stop the process if you intentionally started it and no longer need it. +### Port Conflicts + +The control plane normally uses `http://127.0.0.1:8787/`, and the workbench +usually uses `http://127.0.0.1:5173/`. If either port is busy: + +```powershell +Get-NetTCPConnection -LocalPort 8787 -ErrorAction SilentlyContinue +Get-NetTCPConnection -LocalPort 5173 -ErrorAction SilentlyContinue +``` + +Close the stale terminal that owns the port. Vite may choose another workbench +port automatically; use the URL printed by `npm run workbench:dev`. + ### Workbench Dev Server Does Not Open Install dashboard dependencies and rerun the wrapper: @@ -154,6 +168,24 @@ npm run flowchain:demo This does not edit committed fixtures. +## Capped Owner Pilot Troubleshooting + +Start with the dry-run: + +```powershell +npm run flowchain:real-value-pilot:ops +``` + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| Live pilot says `FLOWCHAIN_PILOT_OPERATOR_ACK` is required | Missing explicit owner acknowledgement. | Set `$env:FLOWCHAIN_PILOT_OPERATOR_ACK="I_UNDERSTAND_THIS_IS_CAPPED_BASE8453_OWNER_PILOT"` in the local shell only. | +| Live pilot reports wrong chain | `FLOWCHAIN_BASE8453_RPC_URL` points to a non-Base endpoint or stale provider route. | Verify the endpoint with the provider, then rerun. The script must see chain id `8453` before live deploy or observer actions. | +| Live observer says the lockbox address is invalid | `FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS` is missing, malformed, or from a different deployment. | Use the exact deployed lockbox address for this pilot. The script accepts only a 20-byte hex address. | +| Live observer finds no events | Block range is wrong, the contract is wrong, or the deposit has not been indexed by the RPC provider yet. | Set `FLOWCHAIN_BASE8453_FROM_BLOCK` and `FLOWCHAIN_BASE8453_TO_BLOCK` around the known transaction block, keep the range at `5000` blocks or less, and rerun observe. | +| Replay or duplicate credit evidence appears | The same Base event was observed more than once. | Keep the generated `replayKey` evidence and do not manually credit twice. Rerun `npm run flowchain:real-value-pilot -- --Mode Live --Action Observe` to regenerate deterministic evidence. | +| 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`. | + ## Smoke Evidence After a successful smoke run, check: diff --git a/docs/agent-runs/real-value-pilot-ops/CHECKLIST.md b/docs/agent-runs/real-value-pilot-ops/CHECKLIST.md new file mode 100644 index 00000000..fb29aae1 --- /dev/null +++ b/docs/agent-runs/real-value-pilot-ops/CHECKLIST.md @@ -0,0 +1,30 @@ +# Real-Value Pilot Ops Checklist + +## Acceptance + +- [x] `npm run flowchain:real-value-pilot:ops` exists. +- [x] Merged HQ `npm run flowchain:real-value-pilot:e2e` remains the final + all-proof pilot gate. +- [x] Dry-run mode passes without live RPC or keys. +- [x] Live mode refuses missing required env vars and missing operator acknowledgement. +- [x] Base chain id `8453` is verified before live observer/deploy actions. +- [x] Cap env values are checked as tiny and nonzero. +- [x] Exact next commands print after deploy, observe, credit, withdraw, pause, resume, export evidence, and restart. +- [x] Emergency stop script exists. +- [x] Evidence export excludes `.git`, `node_modules`, build targets, local vaults, private keys, and env files. +- [x] Second-computer docs include a step-by-step owner pilot path. +- [x] Troubleshooting covers wrong chain, wrong contract, replay, stalled relayer, locked Cargo target, port conflict, and missing credentials. +- [x] `node infra/scripts/check-unsafe-claims.mjs` passes. +- [x] `git diff --check` passes. +- [x] `npm run flowchain:product-e2e` still passes. +- [x] `npm run flowchain:real-value-pilot:e2e -- -AllowIncomplete` reports + only contracts, bridge, and runtime missing after this branch. + +## Commands To Run + +- [x] PowerShell parser checks for changed scripts. +- [x] `npm run flowchain:real-value-pilot:ops` +- [x] `npm run flowchain:real-value-pilot:e2e -- -AllowIncomplete` +- [x] `node infra/scripts/check-unsafe-claims.mjs` +- [x] `git diff --check` +- [x] `npm run flowchain:product-e2e` diff --git a/docs/agent-runs/real-value-pilot-ops/EXPERIMENTS.md b/docs/agent-runs/real-value-pilot-ops/EXPERIMENTS.md new file mode 100644 index 00000000..74c3788d --- /dev/null +++ b/docs/agent-runs/real-value-pilot-ops/EXPERIMENTS.md @@ -0,0 +1,35 @@ +# Real-Value Pilot Ops Experiments + +## Dry-Run Pilot Ops Proof + +- Command: `npm run flowchain:real-value-pilot:ops` +- Result: passed; writes + `devnet/local/real-value-pilot/ops-e2e/flowchain-real-value-pilot-ops-e2e-report.json`. + +## Final Pilot Gate, Report-Only + +- Command: `npm run flowchain:real-value-pilot:e2e -- -AllowIncomplete` +- Result: passed in incomplete mode; after this branch the remaining missing + proof commands are contracts, bridge, and runtime. + +## Parser Checks + +- Command: PowerShell parser check over all `flowchain-real-value-pilot*.ps1` scripts. +- Result: passed. + +## Unsafe Claims Scan + +- Command: `node infra/scripts/check-unsafe-claims.mjs` +- Result: passed. + +## Product E2E + +- Command: `npm run flowchain:product-e2e` +- Result: passed on current branch; generated tracked fixture/output files from + the test run were restored because they are outside this task's intended + diff. + +## Diff Check + +- Command: `git diff --check` +- Result: passed. diff --git a/docs/agent-runs/real-value-pilot-ops/NOTES.md b/docs/agent-runs/real-value-pilot-ops/NOTES.md new file mode 100644 index 00000000..3e69bd28 --- /dev/null +++ b/docs/agent-runs/real-value-pilot-ops/NOTES.md @@ -0,0 +1,28 @@ +# Real-Value Pilot Ops Notes + +## Context Read + +- Required docs read: `START_HERE`, `FLOWMEMORY_HQ_CONTEXT`, `CURRENT_STATE`, `ROOTFLOW_V0`, `FLOW_MEMORY_V0`, and `V0_LAUNCH_ACCEPTANCE`. +- GitHub issue #135 is the active ops/installer proof tracker. +- The old `agent/real-value-pilot-ops` worktree had a useful dry-run E2E + wrapper, but it predated the merged HQ final pilot gate. +- Current product E2E is `infra/scripts/flowchain-product-e2e.ps1`. +- Current `flowchain:l1-e2e` is the merged alias to `flowchain:full-smoke`. + +## Implementation Notes + +- The ops wrapper will not deploy or modify contracts by itself during dry-run. +- Live observer mode can call the existing bridge observer after explicit env, chain, cap, contract, and block-range checks. +- Evidence export should stage only sanitized pilot artifacts and validate the zip contents. +- Reused the old ops dry-run E2E as + `infra/scripts/flowchain-real-value-pilot-ops-e2e.ps1` so it does not + replace `infra/scripts/flowchain-real-value-pilot-e2e.ps1`, which is now the + merged final HQ gate. +- Added package aliases for pilot action, ops proof, emergency stop, and + evidence export. +- Updated `docs/FLOWCHAIN_REAL_VALUE_PILOT.md`, second-computer docs, + checklist, troubleshooting, and README surfaces with the capped owner ops + command path. +- Product E2E passed on the current branch. Generated tracked fixture/output + files from the test run were restored because they are outside this task's + intended diff. diff --git a/docs/agent-runs/real-value-pilot-ops/PLAN.md b/docs/agent-runs/real-value-pilot-ops/PLAN.md new file mode 100644 index 00000000..a9bc0834 --- /dev/null +++ b/docs/agent-runs/real-value-pilot-ops/PLAN.md @@ -0,0 +1,30 @@ +# Real-Value Pilot Ops Plan + +Status: integrated on current `main` (`a16fb9a`) as the branch-local +`flowchain:real-value-pilot:ops` proof. + +## Scope + +- Add docs and wrapper scripts for the capped Base `8453` owner pilot. +- Keep changes inside `infra/scripts/`, `docs/`, `README.md`, and `package.json`. +- Do not edit protocol, service, crypto, dashboard, hardware, or contract code. + +## Plan + +1. Inspect current setup docs, installer scripts, product E2E, long-loop installer work, and GitHub prompt state. +2. Add a dry-run-safe pilot ops proof command. +3. Add live-mode preflight checks for explicit env vars, operator acknowledgement, Base chain id `8453`, tiny nonzero caps, and explicit contract/block ranges. +4. Add live observer wiring to the existing bridge observer path without committing RPC URLs or keys. +5. Add emergency stop and evidence export wrappers. +6. Update second-computer setup and troubleshooting docs with exact owner commands and failure recovery. +7. Preserve the merged HQ final pilot gate by keeping ops dry-run validation in + `infra/scripts/flowchain-real-value-pilot-ops-e2e.ps1`. +8. Run syntax checks, dry-run pilot ops proof, final pilot report-only gate, + unsafe-claims scan, `git diff --check`, and product E2E. + +## Guardrails + +- Dry-run must not require live RPC URLs or private keys. +- Live mode must fail closed without the required env values and explicit acknowledgement. +- Evidence bundles must exclude Git metadata, dependencies, build output, local vaults, private keys, and env files. +- The pilot remains a capped owner-controlled canary, not a production bridge or public release. diff --git a/infra/scripts/flowchain-real-value-pilot-emergency-stop.ps1 b/infra/scripts/flowchain-real-value-pilot-emergency-stop.ps1 new file mode 100644 index 00000000..4f920cc4 --- /dev/null +++ b/infra/scripts/flowchain-real-value-pilot-emergency-stop.ps1 @@ -0,0 +1,32 @@ +param( + [ValidateSet("DryRun", "Live")] + [string] $Mode = "Live", + + [switch] $PlanOnly +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +$args = @( + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + (Join-Path $PSScriptRoot "flowchain-real-value-pilot.ps1"), + "-Mode", + $Mode, + "-Action", + "Pause" +) + +if ($Mode -eq "Live" -and -not $PlanOnly) { + $args += "-Execute" +} + +Write-Host "FlowChain real-value pilot emergency stop." +Write-Host "This routes to the guarded Pause action. Live mode requires env acknowledgement, Base 8453 chain verification, lockbox address, caps, and the owner key in the local shell." +& powershell @args +if ($LASTEXITCODE -ne 0) { + throw "FlowChain real-value pilot emergency stop failed." +} diff --git a/infra/scripts/flowchain-real-value-pilot-export.ps1 b/infra/scripts/flowchain-real-value-pilot-export.ps1 new file mode 100644 index 00000000..97a10966 --- /dev/null +++ b/infra/scripts/flowchain-real-value-pilot-export.ps1 @@ -0,0 +1,280 @@ +param( + [string] $EvidenceDir = "devnet/local/real-value-pilot/evidence", + [string] $BundlePath = "devnet/local/real-value-pilot/export/flowchain-real-value-pilot-evidence.zip", + [switch] $DryRun, + [switch] $Force +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +. "$PSScriptRoot\flowchain-common.ps1" + +function Get-PilotRelativePath { + param( + [Parameter(Mandatory = $true)] + [string] $Root, + + [Parameter(Mandatory = $true)] + [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-PilotEvidenceExclusionReason { + param( + [Parameter(Mandatory = $true)] + [string] $RelativePath, + + [Parameter(Mandatory = $true)] + [string] $Name, + + [Parameter(Mandatory = $true)] + [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 "repository metadata, dependency, or build target directory" + } + 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 target path" + } + if ($lowerName -eq ".env" -or ($lowerName.StartsWith(".env.") -and $lowerName -ne ".env.example")) { + return "local environment 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") -or + $lowerName -like "*private-key*" -or + $lowerName -like "*private_key*" + ) { + return "private key material file" + } + if ( + $lowerName -in @("secret", "secrets", ".secret", ".secrets") -or + $lowerName -like "secret.*" -or + $lowerName -like "secrets.*" -or + $lowerName -like "*.secret" -or + $lowerName -like "*.secrets" -or + $lowerName -like "*.secret.*" -or + $lowerName -like "*.secrets.*" + ) { + return "secret-named file" + } + + return "" +} + +function Assert-PilotEvidenceTextSafe { + param( + [Parameter(Mandatory = $true)] + [string] $Path + ) + + $files = Get-ChildItem -LiteralPath $Path -Recurse -File | Where-Object { + $_.Extension -in @(".json", ".txt", ".md", ".log", ".csv") + } + foreach ($file in $files) { + $text = Get-Content -Raw -LiteralPath $file.FullName + foreach ($pattern in @( + ("BEGIN RSA " + "PRIVATE KEY"), + ("BEGIN OPENSSH " + "PRIVATE KEY"), + ("BEGIN " + "PRIVATE KEY"), + "seed phrase", + "mnemonic", + "apiKey", + "webhook" + )) { + if ($text.IndexOf($pattern, [System.StringComparison]::OrdinalIgnoreCase) -ge 0) { + throw "Potential secret marker '$pattern' found in evidence file $($file.FullName)." + } + } + } +} + +function Copy-PilotEvidenceTree { + param( + [Parameter(Mandatory = $true)] + [string] $SourceDir, + + [Parameter(Mandatory = $true)] + [string] $DestinationDir, + + [Parameter(Mandatory = $true)] + [string] $Root, + + [Parameter(Mandatory = $true)] + [AllowEmptyCollection()] + [System.Collections.ArrayList] $Excluded + ) + + New-Item -ItemType Directory -Force -Path $DestinationDir | Out-Null + + foreach ($item in Get-ChildItem -LiteralPath $SourceDir -Force) { + $relativePath = Get-PilotRelativePath -Root $Root -Path $item.FullName + $reason = Get-PilotEvidenceExclusionReason -RelativePath $relativePath -Name $item.Name -IsDirectory $item.PSIsContainer + if (-not [string]::IsNullOrWhiteSpace($reason)) { + [void] $Excluded.Add([ordered]@{ + path = $relativePath + reason = $reason + }) + continue + } + + $destinationPath = Join-Path $DestinationDir $item.Name + if ($item.PSIsContainer) { + Copy-PilotEvidenceTree -SourceDir $item.FullName -DestinationDir $destinationPath -Root $Root -Excluded $Excluded + } + else { + Copy-Item -LiteralPath $item.FullName -Destination $destinationPath + } + } +} + +function Assert-PilotEvidenceZipSafe { + param([Parameter(Mandatory = $true)][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-PilotEvidenceExclusionReason -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 +$evidenceFullDir = Assert-FlowChainPathInsideRepo -RepoRoot $repoRoot -Path (Resolve-FlowChainPath -RepoRoot $repoRoot -Path $EvidenceDir) +$bundleFullPath = Assert-FlowChainPathInsideRepo -RepoRoot $repoRoot -Path (Resolve-FlowChainPath -RepoRoot $repoRoot -Path $BundlePath) +$exportRoot = Split-Path -Parent $bundleFullPath +$stageRoot = Join-Path $exportRoot "stage" +$stageEvidenceRoot = Join-Path $stageRoot "flowchain-real-value-pilot-evidence" + +New-Item -ItemType Directory -Force -Path $evidenceFullDir | Out-Null +if ($DryRun) { + Write-FlowChainJson -Path (Join-Path $evidenceFullDir "dry-run-evidence.json") -Value ([ordered]@{ + schema = "flowchain.real_value_pilot.dry_run_evidence.v0" + generatedAt = (Get-Date).ToUniversalTime().ToString("o") + dryRun = $true + containsSecrets = $false + }) +} + +if ((Test-Path -LiteralPath $bundleFullPath) -and -not $Force) { + Remove-Item -LiteralPath $bundleFullPath -Force +} + +if (Test-Path -LiteralPath $stageRoot) { + $verifiedStageRoot = Assert-FlowChainPathInsideRepo -RepoRoot $repoRoot -Path $stageRoot + Remove-Item -LiteralPath $verifiedStageRoot -Recurse -Force +} +New-Item -ItemType Directory -Force -Path $stageEvidenceRoot | Out-Null + +$excluded = New-Object System.Collections.ArrayList +Copy-PilotEvidenceTree -SourceDir $evidenceFullDir -DestinationDir $stageEvidenceRoot -Root $evidenceFullDir -Excluded $excluded + +$manifestPath = Join-Path $stageEvidenceRoot "evidence-export-manifest.json" +$manifest = [ordered]@{ + schema = "flowchain.real_value_pilot.evidence_export_manifest.v0" + exportedAt = (Get-Date).ToUniversalTime().ToString("o") + sourceEvidenceDir = $evidenceFullDir + dryRun = [bool] $DryRun + excludes = @( + ".git", + "node_modules", + "target", + "dist", + "cache", + "out", + "broadcast", + ".env files except .env.example", + "*.local.json", + "local vault paths", + "private key material files", + "secret-named paths" + ) + excludedCount = $excluded.Count + excludedSamples = @($excluded | Select-Object -First 40) +} +Write-FlowChainJson -Path $manifestPath -Value $manifest -Depth 10 + +Assert-PilotEvidenceTextSafe -Path $stageEvidenceRoot + +New-Item -ItemType Directory -Force -Path $exportRoot | Out-Null +Compress-Archive -Path $stageEvidenceRoot -DestinationPath $bundleFullPath -Force +Assert-PilotEvidenceZipSafe -Path $bundleFullPath + +$hash = Get-FileHash -Algorithm SHA256 -LiteralPath $bundleFullPath +$reportPath = Join-Path $exportRoot "flowchain-real-value-pilot-evidence-export-report.json" +$report = [ordered]@{ + schema = "flowchain.real_value_pilot.evidence_export_report.v0" + generatedAt = (Get-Date).ToUniversalTime().ToString("o") + bundlePath = $bundleFullPath + bundleSha256 = $hash.Hash + dryRun = [bool] $DryRun + verifiedExclusions = @( + ".git", + "node_modules", + "build targets", + "local vaults", + "private keys", + "env files" + ) + manifestPath = $manifestPath +} +Write-FlowChainJson -Path $reportPath -Value $report -Depth 10 + +Write-Host "" +Write-Host "FlowChain real-value pilot evidence export complete." +Write-Host "Bundle: $bundleFullPath" +Write-Host "SHA256: $($hash.Hash)" +Write-Host "Report: $reportPath" +Write-Host "After export evidence, next command: npm run flowchain:real-value-pilot -- --Mode Live --Action Restart" diff --git a/infra/scripts/flowchain-real-value-pilot-ops-e2e.ps1 b/infra/scripts/flowchain-real-value-pilot-ops-e2e.ps1 new file mode 100644 index 00000000..c8f56eba --- /dev/null +++ b/infra/scripts/flowchain-real-value-pilot-ops-e2e.ps1 @@ -0,0 +1,184 @@ +param() + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +. "$PSScriptRoot\flowchain-common.ps1" + +function Invoke-PilotParserCheck { + param([Parameter(Mandatory = $true)][string] $Path) + + $tokens = $null + $errors = $null + [System.Management.Automation.PSParser]::Tokenize((Get-Content -Raw -LiteralPath $Path), [ref] $errors) | Out-Null + if ($errors -and $errors.Count -gt 0) { + $messages = $errors | ForEach-Object { "$($_.Message) at line $($_.Token.StartLine)" } + throw "PowerShell parser errors in ${Path}: $($messages -join '; ')" + } +} + +function Invoke-CapturedPowerShell { + param( + [Parameter(Mandatory = $true)] + [string[]] $Arguments, + + [switch] $ExpectFailure + ) + + $previousErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = "Continue" + try { + $output = (& powershell @Arguments 2>&1) | ForEach-Object { "$_" } + $exitCode = $LASTEXITCODE + } + finally { + $ErrorActionPreference = $previousErrorActionPreference + } + if ($ExpectFailure) { + if ($exitCode -eq 0) { + throw "Expected command to fail, but it passed: powershell $($Arguments -join ' ')" + } + } + elseif ($exitCode -ne 0) { + throw "Command failed with exit code ${exitCode}: powershell $($Arguments -join ' ')`n$($output -join [Environment]::NewLine)" + } + + return ($output -join [Environment]::NewLine) +} + +$repoRoot = Set-FlowChainRepoRoot +$reportDir = Assert-FlowChainPathInsideRepo -RepoRoot $repoRoot -Path (Resolve-FlowChainPath -RepoRoot $repoRoot -Path "devnet/local/real-value-pilot/ops-e2e") +if (Test-Path -LiteralPath $reportDir) { + Remove-Item -LiteralPath $reportDir -Recurse -Force +} +New-Item -ItemType Directory -Force -Path $reportDir | Out-Null + +$scriptPaths = @( + (Join-Path $PSScriptRoot "flowchain-real-value-pilot.ps1"), + (Join-Path $PSScriptRoot "flowchain-real-value-pilot-export.ps1"), + (Join-Path $PSScriptRoot "flowchain-real-value-pilot-emergency-stop.ps1"), + (Join-Path $PSScriptRoot "flowchain-real-value-pilot-ops-e2e.ps1") +) + +foreach ($scriptPath in $scriptPaths) { + Invoke-PilotParserCheck -Path $scriptPath +} + +$pilotEnvNames = @( + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_DEPLOYER_PRIVATE_KEY", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_BASE8453_TO_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_OWNER_ADDRESS", + "FLOWCHAIN_PILOT_RELEASE_AUTHORITY_ADDRESS", + "FLOWCHAIN_PILOT_SETTLEMENT_SUBMITTER_ADDRESS", + "FLOWCHAIN_PILOT_WITHDRAWAL_RECIPIENT", + "FLOWCHAIN_PILOT_MAX_USD" +) + +$savedEnv = @{} +foreach ($name in $pilotEnvNames) { + $savedEnv[$name] = [Environment]::GetEnvironmentVariable($name, "Process") + [Environment]::SetEnvironmentVariable($name, $null, "Process") +} + +try { + $dryRunOutput = Invoke-CapturedPowerShell -Arguments @( + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + (Join-Path $PSScriptRoot "flowchain-real-value-pilot.ps1"), + "-Mode", + "DryRun", + "-Action", + "All" + ) + + foreach ($expected in @( + "After deploy, next command:", + "After observe, next command:", + "After credit, next command:", + "After withdraw, next command:", + "After pause, next command:", + "After resume, next command:", + "After export evidence, next command:", + "After restart, next command:" + )) { + if ($dryRunOutput -notlike "*$expected*") { + throw "Dry-run output did not include expected next-command line: $expected" + } + } + + $emergencyDryRunOutput = Invoke-CapturedPowerShell -Arguments @( + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + (Join-Path $PSScriptRoot "flowchain-real-value-pilot-emergency-stop.ps1"), + "-Mode", + "DryRun", + "-PlanOnly" + ) + + if ($emergencyDryRunOutput -notlike "*After pause, next command:*") { + throw "Emergency stop dry-run did not print the pause next command." + } + + $liveFailureOutput = Invoke-CapturedPowerShell -ExpectFailure -Arguments @( + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + (Join-Path $PSScriptRoot "flowchain-real-value-pilot.ps1"), + "-Mode", + "Live", + "-Action", + "Observe" + ) +} +finally { + foreach ($name in $pilotEnvNames) { + [Environment]::SetEnvironmentVariable($name, $savedEnv[$name], "Process") + } +} + +if ($liveFailureOutput -notlike "*FLOWCHAIN_PILOT_OPERATOR_ACK*") { + throw "Live missing-env refusal did not name FLOWCHAIN_PILOT_OPERATOR_ACK." +} + +$exportReport = Resolve-FlowChainPath -RepoRoot $repoRoot -Path "devnet/local/real-value-pilot/export/flowchain-real-value-pilot-evidence-export-report.json" +if (-not (Test-Path -LiteralPath $exportReport)) { + throw "Expected evidence export report was not written: $exportReport" +} + +$reportPath = Join-Path $reportDir "flowchain-real-value-pilot-ops-e2e-report.json" +$report = [ordered]@{ + schema = "flowchain.real_value_pilot.ops_e2e_report.v0" + generatedAt = (Get-Date).ToUniversalTime().ToString("o") + status = "passed" + parserChecks = "passed" + dryRunPilot = "passed" + dryRunEmergencyStop = "passed" + liveMissingEnvRefusal = "passed" + evidenceExportReport = $exportReport + checkedNextCommands = @( + "deploy", + "observe", + "credit", + "withdraw", + "pause", + "resume", + "export evidence", + "restart" + ) +} +Write-FlowChainJson -Path $reportPath -Value $report -Depth 12 + +Write-Host "" +Write-Host "FlowChain real-value pilot ops dry-run E2E passed." +Write-Host "Report: $reportPath" diff --git a/infra/scripts/flowchain-real-value-pilot.ps1 b/infra/scripts/flowchain-real-value-pilot.ps1 new file mode 100644 index 00000000..80e67315 --- /dev/null +++ b/infra/scripts/flowchain-real-value-pilot.ps1 @@ -0,0 +1,654 @@ +param( + [ValidateSet("DryRun", "Live")] + [string] $Mode = "DryRun", + + [ValidateSet("All", "Deploy", "Observe", "Credit", "Withdraw", "Pause", "Resume", "ExportEvidence", "Restart")] + [string] $Action = "All", + + [switch] $Execute, + + [string] $EvidenceDir = "devnet/local/real-value-pilot/evidence", + + [string] $ReportPath = "devnet/local/real-value-pilot/flowchain-real-value-pilot-report.json" +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +. "$PSScriptRoot\flowchain-common.ps1" + +$Base8453ChainId = 8453 +$OperatorAckValue = "I_UNDERSTAND_THIS_IS_CAPPED_BASE8453_OWNER_PILOT" +$MaxSingleDepositWeiLimit = [UInt64] 100000000000000 +$TotalCapWeiLimit = [UInt64] 1000000000000000 +$MaxBlockRange = [UInt64] 5000 +$ZeroAddress = "0x0000000000000000000000000000000000000000" + +function Get-PilotEnv { + param([Parameter(Mandatory = $true)][string] $Name) + + return [Environment]::GetEnvironmentVariable($Name, "Process") +} + +function Require-PilotEnv { + param([Parameter(Mandatory = $true)][string] $Name) + + $value = Get-PilotEnv -Name $Name + if ([string]::IsNullOrWhiteSpace($value)) { + throw "$Name is required for live real-value pilot action '$Action'." + } + return $value +} + +function Require-PilotAck { + $ack = Require-PilotEnv -Name "FLOWCHAIN_PILOT_OPERATOR_ACK" + if ($ack -ne $OperatorAckValue) { + throw "FLOWCHAIN_PILOT_OPERATOR_ACK must exactly equal '$OperatorAckValue'." + } +} + +function Assert-PilotAddress { + param( + [Parameter(Mandatory = $true)] + [string] $Name, + + [Parameter(Mandatory = $true)] + [string] $Value + ) + + if ($Value -notmatch '^0x[0-9a-fA-F]{40}$') { + throw "$Name must be a 20-byte hex address." + } +} + +function Require-PilotAddressEnv { + param([Parameter(Mandatory = $true)][string] $Name) + + $value = Require-PilotEnv -Name $Name + Assert-PilotAddress -Name $Name -Value $value + return $value +} + +function Require-PilotPrivateKeyEnv { + param([Parameter(Mandatory = $true)][string] $Name) + + $value = Require-PilotEnv -Name $Name + if ($value -notmatch '^0x[0-9a-fA-F]{64}$') { + throw "$Name must be a 32-byte hex private key. The value must stay in the local shell only." + } + return $value +} + +function Convert-PilotUInt64 { + param( + [Parameter(Mandatory = $true)] + [string] $Name, + + [Parameter(Mandatory = $true)] + [string] $Value + ) + + if ($Value -notmatch '^[0-9]+$') { + throw "$Name must be a decimal integer." + } + + try { + return [UInt64]::Parse($Value, [System.Globalization.CultureInfo]::InvariantCulture) + } + catch { + throw "$Name is outside the supported UInt64 range." + } +} + +function Assert-PilotCaps { + $maxDeposit = Convert-PilotUInt64 -Name "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI" -Value (Require-PilotEnv -Name "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI") + $totalCap = Convert-PilotUInt64 -Name "FLOWCHAIN_PILOT_TOTAL_CAP_WEI" -Value (Require-PilotEnv -Name "FLOWCHAIN_PILOT_TOTAL_CAP_WEI") + + if ($maxDeposit -eq 0) { + throw "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI must be nonzero." + } + if ($totalCap -eq 0) { + throw "FLOWCHAIN_PILOT_TOTAL_CAP_WEI must be nonzero." + } + if ($maxDeposit -gt $MaxSingleDepositWeiLimit) { + throw "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI must be <= $MaxSingleDepositWeiLimit for this owner pilot." + } + if ($totalCap -gt $TotalCapWeiLimit) { + throw "FLOWCHAIN_PILOT_TOTAL_CAP_WEI must be <= $TotalCapWeiLimit for this owner pilot." + } + if ($totalCap -lt $maxDeposit) { + throw "FLOWCHAIN_PILOT_TOTAL_CAP_WEI must be greater than or equal to FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI." + } + + return [ordered]@{ + maxDepositWei = $maxDeposit.ToString() + totalCapWei = $totalCap.ToString() + maxSingleDepositLimitWei = $MaxSingleDepositWeiLimit.ToString() + totalCapLimitWei = $TotalCapWeiLimit.ToString() + } +} + +function Require-PilotRpcUrl { + $rpcUrl = Require-PilotEnv -Name "FLOWCHAIN_BASE8453_RPC_URL" + $uri = $null + if (-not [System.Uri]::TryCreate($rpcUrl, [System.UriKind]::Absolute, [ref] $uri)) { + throw "FLOWCHAIN_BASE8453_RPC_URL must be an absolute HTTP(S) URL." + } + if ($uri.Scheme -ne "http" -and $uri.Scheme -ne "https") { + throw "FLOWCHAIN_BASE8453_RPC_URL must use http or https." + } + return $rpcUrl +} + +function Invoke-PilotRpc { + param( + [Parameter(Mandatory = $true)] + [string] $RpcUrl, + + [Parameter(Mandatory = $true)] + [string] $Method, + + [object[]] $Params = @() + ) + + $body = ([ordered]@{ + jsonrpc = "2.0" + id = 1 + method = $Method + params = $Params + } | ConvertTo-Json -Depth 8 -Compress) + + try { + $response = Invoke-RestMethod -Uri $RpcUrl -Method Post -ContentType "application/json" -Body $body -TimeoutSec 20 + } + catch { + throw "Could not read $Method from FLOWCHAIN_BASE8453_RPC_URL. Check the endpoint without committing or printing it." + } + + if ($response.PSObject.Properties.Name -contains "error") { + throw "RPC $Method returned an error. Check the endpoint and credentials locally." + } + if (-not ($response.PSObject.Properties.Name -contains "result")) { + throw "RPC $Method did not return a result." + } + + return $response.result +} + +function Assert-Base8453Chain { + param([Parameter(Mandatory = $true)][string] $RpcUrl) + + $chainHex = Invoke-PilotRpc -RpcUrl $RpcUrl -Method "eth_chainId" + if ($chainHex -notmatch '^0x[0-9a-fA-F]+$') { + throw "eth_chainId returned an invalid hex value." + } + $actual = [Convert]::ToInt64($chainHex.Substring(2), 16) + if ($actual -ne $Base8453ChainId) { + throw "Wrong chain id: expected $Base8453ChainId for Base, got $actual." + } + + Write-Host "Verified Base chain id: $Base8453ChainId" + return $actual +} + +function Assert-PilotBlockRange { + $fromBlock = Convert-PilotUInt64 -Name "FLOWCHAIN_BASE8453_FROM_BLOCK" -Value (Require-PilotEnv -Name "FLOWCHAIN_BASE8453_FROM_BLOCK") + $toBlock = Convert-PilotUInt64 -Name "FLOWCHAIN_BASE8453_TO_BLOCK" -Value (Require-PilotEnv -Name "FLOWCHAIN_BASE8453_TO_BLOCK") + if ($fromBlock -gt $toBlock) { + throw "FLOWCHAIN_BASE8453_FROM_BLOCK must be <= FLOWCHAIN_BASE8453_TO_BLOCK." + } + if (($toBlock - $fromBlock) -gt $MaxBlockRange) { + throw "Base 8453 observer range is too wide. Max range is $MaxBlockRange blocks." + } + + return [ordered]@{ + fromBlock = $fromBlock.ToString() + toBlock = $toBlock.ToString() + } +} + +function Get-PilotMaxUsd { + $value = Get-PilotEnv -Name "FLOWCHAIN_PILOT_MAX_USD" + if ([string]::IsNullOrWhiteSpace($value)) { + return 1.0 + } + $parsed = 0.0 + if (-not [double]::TryParse($value, [System.Globalization.NumberStyles]::Float, [System.Globalization.CultureInfo]::InvariantCulture, [ref] $parsed)) { + throw "FLOWCHAIN_PILOT_MAX_USD must be a decimal number." + } + if ($parsed -le 0 -or $parsed -gt 25) { + throw "FLOWCHAIN_PILOT_MAX_USD must be greater than 0 and <= 25." + } + return $parsed +} + +function Get-PilotActionList { + if ($Action -eq "All") { + return @("Deploy", "Observe", "Credit", "Withdraw", "Pause", "Resume", "ExportEvidence", "Restart") + } + return @($Action) +} + +function Get-PilotActionLabel { + param([Parameter(Mandatory = $true)][string] $Name) + + switch ($Name) { + "ExportEvidence" { return "export evidence" } + default { return $Name.ToLowerInvariant() } + } +} + +function Get-PilotNextCommand { + param([Parameter(Mandatory = $true)][string] $Name) + + switch ($Name) { + "Deploy" { return "npm run flowchain:real-value-pilot -- --Mode Live --Action Observe" } + "Observe" { return "npm run flowchain:real-value-pilot -- --Mode Live --Action Credit" } + "Credit" { return "npm run flowchain:real-value-pilot -- --Mode Live --Action Withdraw" } + "Withdraw" { return "npm run flowchain:real-value-pilot:export" } + "Pause" { return "npm run flowchain:real-value-pilot -- --Mode Live --Action Resume -Execute" } + "Resume" { return "npm run flowchain:real-value-pilot -- --Mode Live --Action Observe" } + "ExportEvidence" { return "npm run flowchain:real-value-pilot -- --Mode Live --Action Restart" } + "Restart" { return "npm run flowchain:real-value-pilot -- --Mode Live --Action Observe" } + default { throw "Unknown action for next command: $Name" } + } +} + +function Write-PilotNextCommand { + param([Parameter(Mandatory = $true)][string] $Name) + + $label = Get-PilotActionLabel -Name $Name + $next = Get-PilotNextCommand -Name $Name + Write-Host "After $label, next command: $next" + return $next +} + +function Get-PilotCommit { + $commit = (& git rev-parse HEAD 2>$null | Select-Object -First 1) + if ($LASTEXITCODE -eq 0 -and -not [string]::IsNullOrWhiteSpace($commit)) { + return ($commit -as [string]).Trim() + } + return "unknown" +} + +function Invoke-PilotBridgeObserver { + param( + [Parameter(Mandatory = $true)] + [string] $RepoRoot, + + [Parameter(Mandatory = $true)] + [string] $RpcUrl, + + [Parameter(Mandatory = $true)] + [string] $LockboxAddress, + + [Parameter(Mandatory = $true)] + [string] $FromBlock, + + [Parameter(Mandatory = $true)] + [string] $ToBlock, + + [Parameter(Mandatory = $true)] + [string] $OutPath, + + [Parameter(Mandatory = $true)] + [string] $CreditOutPath, + + [Parameter(Mandatory = $true)] + [string] $HandoffOutPath, + + [string] $WithdrawalOutPath = "", + + [switch] $ApplyCredit, + + [switch] $WithdrawalIntent + ) + + $args = @( + "run", + "bridge:observe", + "--", + "--mode", + "base-mainnet-canary", + "--rpc-url", + $RpcUrl, + "--lockbox-address", + $LockboxAddress, + "--from-block", + $FromBlock, + "--to-block", + $ToBlock, + "--expected-chain-id", + "$Base8453ChainId", + "--acknowledge-real-funds", + "--max-usd", + ((Get-PilotMaxUsd).ToString([System.Globalization.CultureInfo]::InvariantCulture)), + "--out", + $OutPath, + "--credit-out", + $CreditOutPath, + "--handoff-out", + $HandoffOutPath + ) + + if ($ApplyCredit) { + $args += "--apply-credit" + } + if ($WithdrawalIntent) { + if ([string]::IsNullOrWhiteSpace($WithdrawalOutPath)) { + throw "WithdrawalOutPath is required when WithdrawalIntent is set." + } + $recipient = Require-PilotAddressEnv -Name "FLOWCHAIN_PILOT_WITHDRAWAL_RECIPIENT" + $args += @("--withdrawal-intent", "--withdrawal-base-recipient", $recipient, "--withdrawal-out", $WithdrawalOutPath) + } + + Write-Host "Running Base 8453 bridge observer. RPC URL is supplied from env and is not printed." + & npm @args + if ($LASTEXITCODE -ne 0) { + throw "Base 8453 bridge observer failed." + } +} + +function Invoke-PilotDeploy { + param( + [Parameter(Mandatory = $true)] + [string] $RpcUrl, + + [Parameter(Mandatory = $true)] + [string] $PrivateKey, + + [Parameter(Mandatory = $true)] + [hashtable] $DeployEnv + ) + + if (-not $Execute) { + Write-Host "Deploy preflight passed. Plan only; no transaction was broadcast." + Write-Host 'Exact deploy command when ready: npm run flowchain:real-value-pilot -- --Mode Live --Action Deploy -Execute' + return "planned" + } + + if (-not (Get-Command forge -ErrorAction SilentlyContinue)) { + throw "forge is required for deploy execution." + } + + $previous = @{} + foreach ($name in $DeployEnv.Keys) { + $previous[$name] = [Environment]::GetEnvironmentVariable($name, "Process") + [Environment]::SetEnvironmentVariable($name, [string] $DeployEnv[$name], "Process") + } + + try { + Write-Host "Broadcasting deploy with private key from FLOWCHAIN_BASE8453_DEPLOYER_PRIVATE_KEY. The key is not logged or stored." + & forge @( + "script", + "script/DeployBridgeSpine.s.sol:DeployBridgeSpine", + "--rpc-url", + $RpcUrl, + "--private-key", + $PrivateKey, + "--broadcast" + ) + if ($LASTEXITCODE -ne 0) { + throw "forge deploy failed." + } + } + finally { + foreach ($name in $DeployEnv.Keys) { + [Environment]::SetEnvironmentVariable($name, $previous[$name], "Process") + } + } + + return "executed" +} + +function Invoke-PilotSetPaused { + param( + [Parameter(Mandatory = $true)] + [string] $RpcUrl, + + [Parameter(Mandatory = $true)] + [string] $PrivateKey, + + [Parameter(Mandatory = $true)] + [string] $LockboxAddress, + + [Parameter(Mandatory = $true)] + [bool] $Paused + ) + + $pausedText = if ($Paused) { "true" } else { "false" } + if (-not $Execute) { + Write-Host "Pause/resume preflight passed. Plan only; no transaction was broadcast." + Write-Host "Exact cast command when ready: cast send $LockboxAddress `"setPaused(bool)`" $pausedText --rpc-url `$env:FLOWCHAIN_BASE8453_RPC_URL --private-key `$env:FLOWCHAIN_BASE8453_DEPLOYER_PRIVATE_KEY" + return "planned" + } + + if (-not (Get-Command cast -ErrorAction SilentlyContinue)) { + throw "cast is required for pause/resume execution." + } + + Write-Host "Broadcasting setPaused($pausedText) with private key from FLOWCHAIN_BASE8453_DEPLOYER_PRIVATE_KEY. The key is not logged or stored." + & cast @( + "send", + $LockboxAddress, + "setPaused(bool)", + $pausedText, + "--rpc-url", + $RpcUrl, + "--private-key", + $PrivateKey + ) + if ($LASTEXITCODE -ne 0) { + throw "cast setPaused($pausedText) failed." + } + + return "executed" +} + +function Invoke-PilotAction { + param( + [Parameter(Mandatory = $true)] + [string] $Name, + + [Parameter(Mandatory = $true)] + [string] $RepoRoot, + + [Parameter(Mandatory = $true)] + [string] $EvidenceFullDir + ) + + Write-Host "" + Write-Host "== Real-value pilot action: $Name ($Mode) ==" + + $result = [ordered]@{ + action = $Name + mode = $Mode + status = "planned" + execute = [bool] $Execute + outputs = [ordered]@{} + checks = [ordered]@{} + } + + if ($Mode -eq "DryRun") { + $result.status = "dry-run-passed" + $result.checks.requiresNoRpcOrKeys = $true + $result.checks.base8453ChainCheck = "skipped-dry-run" + $result.checks.capCheck = "skipped-dry-run" + if ($Name -eq "ExportEvidence") { + & powershell -NoProfile -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "flowchain-real-value-pilot-export.ps1") -DryRun + if ($LASTEXITCODE -ne 0) { + throw "Dry-run evidence export failed." + } + } + $result.nextCommand = Write-PilotNextCommand -Name $Name + return $result + } + + Require-PilotAck + $result.checks.operatorAck = "present" + $result.checks.caps = Assert-PilotCaps + + switch ($Name) { + "Deploy" { + $rpcUrl = Require-PilotRpcUrl + Assert-Base8453Chain -RpcUrl $rpcUrl | Out-Null + $result.checks.chainId = "$Base8453ChainId" + $privateKey = Require-PilotPrivateKeyEnv -Name "FLOWCHAIN_BASE8453_DEPLOYER_PRIVATE_KEY" + $owner = Require-PilotAddressEnv -Name "FLOWCHAIN_PILOT_OWNER_ADDRESS" + $releaseAuthority = Require-PilotAddressEnv -Name "FLOWCHAIN_PILOT_RELEASE_AUTHORITY_ADDRESS" + $settlementSubmitter = Require-PilotAddressEnv -Name "FLOWCHAIN_PILOT_SETTLEMENT_SUBMITTER_ADDRESS" + $caps = $result.checks.caps + $deployEnv = @{ + FLOWCHAIN_BRIDGE_OWNER = $owner + FLOWCHAIN_BRIDGE_RELEASE_AUTHORITY = $releaseAuthority + FLOWCHAIN_SETTLEMENT_SUBMITTER = $settlementSubmitter + FLOWCHAIN_BRIDGE_ALLOW_NATIVE = "true" + FLOWCHAIN_BRIDGE_NATIVE_PER_DEPOSIT_CAP = $caps.maxDepositWei + FLOWCHAIN_BRIDGE_NATIVE_TOTAL_CAP = $caps.totalCapWei + FLOWCHAIN_BRIDGE_ALLOW_ERC20 = "false" + FLOWCHAIN_BRIDGE_ERC20_TOKEN = $ZeroAddress + FLOWCHAIN_BRIDGE_ERC20_PER_DEPOSIT_CAP = "0" + FLOWCHAIN_BRIDGE_ERC20_TOTAL_CAP = "0" + } + $result.status = Invoke-PilotDeploy -RpcUrl $rpcUrl -PrivateKey $privateKey -DeployEnv $deployEnv + } + "Observe" { + $rpcUrl = Require-PilotRpcUrl + Assert-Base8453Chain -RpcUrl $rpcUrl | Out-Null + $lockbox = Require-PilotAddressEnv -Name "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS" + $range = Assert-PilotBlockRange + $result.checks.chainId = "$Base8453ChainId" + $result.checks.lockboxAddress = "valid" + $result.checks.blockRange = $range + $out = Join-Path $EvidenceFullDir "base8453-observation.json" + $creditOut = Join-Path $EvidenceFullDir "base8453-credit-pending.json" + $handoffOut = Join-Path $EvidenceFullDir "base8453-handoff-pending.json" + Invoke-PilotBridgeObserver -RepoRoot $RepoRoot -RpcUrl $rpcUrl -LockboxAddress $lockbox -FromBlock $range.fromBlock -ToBlock $range.toBlock -OutPath $out -CreditOutPath $creditOut -HandoffOutPath $handoffOut + $result.status = "executed" + $result.outputs.observation = $out + $result.outputs.credit = $creditOut + $result.outputs.handoff = $handoffOut + } + "Credit" { + $rpcUrl = Require-PilotRpcUrl + Assert-Base8453Chain -RpcUrl $rpcUrl | Out-Null + $lockbox = Require-PilotAddressEnv -Name "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS" + $range = Assert-PilotBlockRange + $out = Join-Path $EvidenceFullDir "base8453-observation-for-credit.json" + $creditOut = Join-Path $EvidenceFullDir "base8453-credit-applied.json" + $handoffOut = Join-Path $EvidenceFullDir "base8453-handoff-applied.json" + Invoke-PilotBridgeObserver -RepoRoot $RepoRoot -RpcUrl $rpcUrl -LockboxAddress $lockbox -FromBlock $range.fromBlock -ToBlock $range.toBlock -OutPath $out -CreditOutPath $creditOut -HandoffOutPath $handoffOut -ApplyCredit + $result.status = "executed" + $result.outputs.observation = $out + $result.outputs.credit = $creditOut + $result.outputs.handoff = $handoffOut + } + "Withdraw" { + $rpcUrl = Require-PilotRpcUrl + Assert-Base8453Chain -RpcUrl $rpcUrl | Out-Null + $lockbox = Require-PilotAddressEnv -Name "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS" + $range = Assert-PilotBlockRange + $out = Join-Path $EvidenceFullDir "base8453-observation-for-withdrawal.json" + $creditOut = Join-Path $EvidenceFullDir "base8453-credit-for-withdrawal.json" + $handoffOut = Join-Path $EvidenceFullDir "base8453-handoff-with-withdrawal.json" + $withdrawalOut = Join-Path $EvidenceFullDir "base8453-withdrawal-intent.json" + Invoke-PilotBridgeObserver -RepoRoot $RepoRoot -RpcUrl $rpcUrl -LockboxAddress $lockbox -FromBlock $range.fromBlock -ToBlock $range.toBlock -OutPath $out -CreditOutPath $creditOut -HandoffOutPath $handoffOut -WithdrawalOutPath $withdrawalOut -ApplyCredit -WithdrawalIntent + $result.status = "executed" + $result.outputs.withdrawalIntent = $withdrawalOut + } + "Pause" { + $rpcUrl = Require-PilotRpcUrl + Assert-Base8453Chain -RpcUrl $rpcUrl | Out-Null + $privateKey = Require-PilotPrivateKeyEnv -Name "FLOWCHAIN_BASE8453_DEPLOYER_PRIVATE_KEY" + $lockbox = Require-PilotAddressEnv -Name "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS" + $result.status = Invoke-PilotSetPaused -RpcUrl $rpcUrl -PrivateKey $privateKey -LockboxAddress $lockbox -Paused $true + } + "Resume" { + $rpcUrl = Require-PilotRpcUrl + Assert-Base8453Chain -RpcUrl $rpcUrl | Out-Null + $privateKey = Require-PilotPrivateKeyEnv -Name "FLOWCHAIN_BASE8453_DEPLOYER_PRIVATE_KEY" + $lockbox = Require-PilotAddressEnv -Name "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS" + $result.status = Invoke-PilotSetPaused -RpcUrl $rpcUrl -PrivateKey $privateKey -LockboxAddress $lockbox -Paused $false + } + "ExportEvidence" { + & powershell -NoProfile -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "flowchain-real-value-pilot-export.ps1") + if ($LASTEXITCODE -ne 0) { + throw "Evidence export failed." + } + $result.status = "executed" + } + "Restart" { + Write-Host "Restart recovery commands:" + Write-Host "npm run flowchain:start" + Write-Host "npm run control-plane:serve" + Write-Host "npm run workbench:dev" + Write-Host "npm run flowchain:real-value-pilot -- --Mode Live --Action Observe" + $result.status = "restart-commands-printed" + } + default { + throw "Unsupported action: $Name" + } + } + + $result.nextCommand = Write-PilotNextCommand -Name $Name + return $result +} + +$repoRoot = Set-FlowChainRepoRoot +if ($Mode -eq "Live" -and $Action -eq "All") { + throw "Live mode must run one action at a time. Use -Action Deploy, Observe, Credit, Withdraw, Pause, Resume, ExportEvidence, or Restart." +} + +$evidenceFullDir = Assert-FlowChainPathInsideRepo -RepoRoot $repoRoot -Path (Resolve-FlowChainPath -RepoRoot $repoRoot -Path $EvidenceDir) +$reportFullPath = Assert-FlowChainPathInsideRepo -RepoRoot $repoRoot -Path (Resolve-FlowChainPath -RepoRoot $repoRoot -Path $ReportPath) +New-Item -ItemType Directory -Force -Path $evidenceFullDir | Out-Null + +$actionResults = @() +foreach ($name in Get-PilotActionList) { + $actionResults += Invoke-PilotAction -Name $name -RepoRoot $repoRoot -EvidenceFullDir $evidenceFullDir +} + +$report = [ordered]@{ + schema = "flowchain.real_value_pilot.ops_report.v0" + generatedAt = (Get-Date).ToUniversalTime().ToString("o") + commit = Get-PilotCommit + mode = $Mode + action = $Action + status = "passed" + baseChainId = $Base8453ChainId + operatorAckRequiredValue = $OperatorAckValue + capLimits = [ordered]@{ + maxDepositWeiLimit = $MaxSingleDepositWeiLimit.ToString() + totalCapWeiLimit = $TotalCapWeiLimit.ToString() + } + evidenceDir = $evidenceFullDir + actions = $actionResults + envVarNames = @( + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_DEPLOYER_PRIVATE_KEY", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_BASE8453_TO_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_OWNER_ADDRESS", + "FLOWCHAIN_PILOT_RELEASE_AUTHORITY_ADDRESS", + "FLOWCHAIN_PILOT_SETTLEMENT_SUBMITTER_ADDRESS", + "FLOWCHAIN_PILOT_WITHDRAWAL_RECIPIENT", + "FLOWCHAIN_PILOT_MAX_USD" + ) + boundaries = @( + "capped owner pilot only", + "Base public network chain id 8453 is checked before live observer/deploy actions", + "dry-run uses no RPC URL or private key", + "private keys and RPC URLs are read from the local shell only and are not written to reports" + ) +} + +Write-FlowChainJson -Path $reportFullPath -Value $report -Depth 18 + +Write-Host "" +Write-Host "FlowChain real-value pilot ops check passed." +Write-Host "Report: $reportFullPath" +Write-Host "Evidence directory: $evidenceFullDir" diff --git a/package.json b/package.json index 4dcd3fb6..d79d409c 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,10 @@ "flowchain:product-e2e": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-product-e2e.ps1", "flowchain:l1-e2e": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-full-smoke.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:ops": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-real-value-pilot-ops-e2e.ps1", + "flowchain:real-value-pilot:emergency-stop": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-real-value-pilot-emergency-stop.ps1", + "flowchain:real-value-pilot:export": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-real-value-pilot-export.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:export": "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-export.ps1",