diff --git a/.gitignore b/.gitignore index 6a9e295e..d399f2c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ node_modules/ +__pycache__/ +*.pyc dist/ cache/ out/ diff --git a/apps/dashboard/e2e/flowchain-ui-readiness.spec.ts b/apps/dashboard/e2e/flowchain-ui-readiness.spec.ts index d6504529..c2424932 100644 --- a/apps/dashboard/e2e/flowchain-ui-readiness.spec.ts +++ b/apps/dashboard/e2e/flowchain-ui-readiness.spec.ts @@ -8,6 +8,7 @@ type BrowserState = { created: boolean; funded: boolean; sent: boolean; + unhandledRequests: string[]; }; async function fulfillJson(route: Route, payload: unknown, status = 200) { @@ -47,6 +48,17 @@ async function installControlPlaneMocks(page: Page, state: BrowserState) { return; } + if (pathname === "/rpc" && method === "POST") { + const payload = request.postDataJSON() as Array<{ id?: string | number }> | { id?: string | number }; + const requests = Array.isArray(payload) ? payload : [payload]; + await fulfillJson(route, requests.map((entry) => ({ + jsonrpc: "2.0", + id: entry.id ?? null, + result: null, + }))); + return; + } + if (pathname === "/state") { await fulfillJson(route, { state: { @@ -234,7 +246,8 @@ async function installControlPlaneMocks(page: Page, state: BrowserState) { return; } - await fulfillJson(route, { schema: "flowmemory.control_plane.not_found.v0", noSecrets: true }, 404); + state.unhandledRequests.push(`${method} ${pathname}`); + await fulfillJson(route, { schema: "flowmemory.control_plane.unhandled_mock.v0", noSecrets: true }); }); } @@ -261,7 +274,7 @@ async function expectNoHorizontalOverflow(page: Page) { test.describe("FlowChain wallet, faucet, and explorer browser readiness", () => { test("completes the tester wallet funding loop and keeps the explorer inspectable", async ({ page }) => { const consoleErrors: string[] = []; - const state: BrowserState = { created: false, funded: false, sent: false }; + const state: BrowserState = { created: false, funded: false, sent: false, unhandledRequests: [] }; page.on("console", (message) => { if (message.type() === "error") { @@ -276,21 +289,33 @@ test.describe("FlowChain wallet, faucet, and explorer browser readiness", () => await page.goto("/wallet?panel=tester"); await expect(page.getByText("Tester gateway configured")).toBeVisible(); + await expect(page.getByLabel("Tester launch path")).toContainText("Create, fund, send, inspect"); + await expect(page.getByLabel("Tester launch path")).toContainText("needs passphrase"); await page.getByLabel("Tester bearer token").fill(TESTER_TOKEN); await page.getByLabel("Tester wallet label").fill("friend-browser-a"); await page.getByLabel("Tester wallet passphrase").fill("browser-test-passphrase"); await page.getByRole("button", { name: /Create tester wallet/ }).click(); await expect(page.getByRole("status")).toContainText("Tester wallet created"); + await expect(page.getByLabel("Tester launch path")).toContainText("wallet ready"); + await expect(page.getByLabel("Fund account")).toHaveValue(TESTER_ACCOUNT_A); await page.getByRole("button", { name: "Tester", exact: true }).click(); await page.getByLabel("Fund account").fill(TESTER_ACCOUNT_A); + await page.getByLabel("Faucet units").fill("3"); + await expect(page.getByLabel("Faucet units")).toHaveAttribute("aria-invalid", "true"); + await expect(page.getByRole("button", { name: /Request tester faucet/ })).toBeDisabled(); await page.getByLabel("Faucet units").fill("2"); await page.getByRole("button", { name: /Request tester faucet/ }).click(); await expect(page.getByRole("status")).toContainText("Tester faucet accepted"); + await expect(page.getByLabel("Tester launch path")).toContainText("funding proof"); + await expect(page.getByLabel("Sender account")).toHaveValue(TESTER_ACCOUNT_A); await page.getByRole("button", { name: "Tester", exact: true }).click(); await page.getByLabel("Sender account").fill(TESTER_ACCOUNT_A); await page.getByLabel("Recipient account").fill(TESTER_ACCOUNT_B); + await page.getByLabel("Amount units").fill("3"); + await expect(page.getByLabel("Amount units")).toHaveAttribute("aria-invalid", "true"); + await expect(page.getByRole("button", { name: /Send tester units/ })).toBeDisabled(); await page.getByLabel("Amount units").fill("1"); await page.getByRole("button", { name: /Send tester units/ }).click(); await expect(page.getByRole("status")).toContainText("Tester send accepted"); @@ -311,8 +336,90 @@ test.describe("FlowChain wallet, faucet, and explorer browser readiness", () => await page.getByRole("button", { name: /Transactions/ }).first().click(); await expect(page.getByLabel("Explorer records")).toContainText(/transaction|transfer/i); + await page.goto("/tester"); + await expect(page.getByRole("heading", { name: "Friends-and-family launch" })).toBeVisible(); + await expect(page.getByLabel("Tester launch status")).toContainText("Live infra"); + await expect(page.getByLabel("Tester launch status")).toContainText("Missing inputs"); + await expect(page.getByLabel("Tester launch status")).toContainText("RPC command matrix"); + await expect(page.getByText("RPC headers", { exact: true }).first()).toBeVisible(); + await expect(page.getByText("HSTS, no-sniff, no-store, CSP")).toBeVisible(); + await expect(page.getByText("RPC matrix", { exact: true }).first()).toBeVisible(); + await expect(page.getByText("RPC launch matrix", { exact: true }).first()).toBeVisible(); + await expect(page.getByText("npm run flowchain:public-rpc:command-matrix").first()).toBeVisible(); + + await page.goto("/activation"); + await expect(page.getByRole("heading", { name: "L1 activation" })).toBeVisible(); + await expect(page.getByLabel("L1 activation status")).toContainText("Needed now"); + await expect(page.getByLabel("L1 activation status")).toContainText("Release"); + await expect(page.getByRole("heading", { name: "Needed now" })).toBeVisible(); + await expect(page.getByLabel("Owner setup groups")).toContainText("Public RPC edge"); + await expect(page.getByLabel("Owner setup groups")).toContainText("Pick the public RPC URL"); + await expect(page.getByLabel("Public RPC edge validation commands")).toContainText("flowchain:public-rpc:synthetic-canary"); + await expect(page.getByLabel("Owner setup groups")).toContainText("Backup storage"); + await expect(page.getByLabel("Owner setup groups")).toContainText("Base 8453 bridge"); + await expect(page.getByLabel("Ready setup groups")).toContainText("Tester write gateway"); + await expect(page.getByRole("heading", { name: "Host apply sequence" })).toBeVisible(); + await expect(page.getByLabel("Owner host apply proof")).toContainText("owner-host-apply.sh plan"); + await expect(page.getByLabel("Owner host apply proof")).toContainText("owner-host-apply.sh apply"); + await expect(page.getByLabel("Owner host apply proof")).toContainText("owner-host-apply.ps1 -Action Plan"); + await expect(page.getByLabel("Owner host apply proof")).toContainText("owner-host-apply.ps1 -Action Apply"); + await expect(page.getByLabel("Owner host rollback commands")).toContainText("owner-host-apply.sh rollback"); + await expect(page.getByLabel("Owner host rollback commands")).toContainText("owner-host-apply.ps1 -Action Rollback"); + await expect(page.getByLabel("Go-live launch sequence")).toContainText("Apply owner-host public RPC edge"); + await expect(page.getByLabel("Next owner inputs")).toContainText("FLOWCHAIN_RPC_PUBLIC_URL"); + await expect(page.getByText("Expose repo-owned FlowChain RPC", { exact: false })).toBeVisible(); + await expect(page.getByLabel("Missing owner inputs")).toContainText("FLOWCHAIN_RPC_PUBLIC_URL"); + await expect(page.getByLabel("Owner env field guide")).toContainText("Guide rows"); + await expect(page.getByLabel("Owner env field guide")).toContainText("FLOWCHAIN_RPC_PUBLIC_URL"); + await expect(page.getByLabel("Owner env field guide")).toContainText("absolute non-local HTTPS endpoint"); + await expect(page.getByLabel("Owner env field guide")).toContainText("owner DNS, tunnel, or reverse proxy hostname"); + + await page.goto("/bridge"); + await expect(page.getByRole("heading", { name: "Bridge funds into Flowchain" })).toBeVisible(); + const bridgeRuntimeProof = page.getByLabel("Bridge runtime proof"); + await expect(bridgeRuntimeProof).toContainText("Bridge command matrix"); + await expect(bridgeRuntimeProof).toContainText("flowchain:bridge:command-matrix"); + await expect(bridgeRuntimeProof).toContainText("No-secret audit"); + await expect(bridgeRuntimeProof).toContainText("flowchain:bridge:no-secret-audit"); + await expect(bridgeRuntimeProof).toContainText("Pilot aggregate"); + await expect(bridgeRuntimeProof).toContainText("Runtime credit"); + await expect(bridgeRuntimeProof).toContainText("Transfer settlement"); + await expect(bridgeRuntimeProof).toContainText("Relayer guardrail"); + await expect(bridgeRuntimeProof).toContainText("Relayer loop"); + await expect(bridgeRuntimeProof).toContainText("Reconciliation schedule"); + await expect(bridgeRuntimeProof).toContainText("flowchain:bridge:reconciliation:schedule:validate"); + await expect(bridgeRuntimeProof).toContainText(/\d+ proof commands/); + await expect(bridgeRuntimeProof).toContainText(/\d+(\.\d+)?s to spendable credit/); + await expect(bridgeRuntimeProof).toContainText(/\d+(\.\d+)?s/); + await expectNoHorizontalOverflow(page); + + await page.goto("/ops"); + await expect(page.getByRole("heading", { name: "Ops center" })).toBeVisible(); + await expect(page.getByLabel("Bridge relayer check contract")).toContainText("Relayer check contract"); + await expect(page.getByLabel("Bridge relayer check contract")).toContainText("bridge-relayer-check-contract-failed"); + await expect(page.getByLabel("Service and deployment automation proof")).toContainText("Autorecovery drill"); + await expect(page.getByLabel("Service and deployment automation proof")).toContainText("Public RPC automation"); + await expect(page.getByLabel("Service and deployment automation proof")).toContainText("Systemd service plan"); + await expect(page.getByLabel("Service and deployment automation proof")).toContainText("Bridge evidence audit"); + await expect(page.getByLabel("Service and deployment automation proof")).toContainText("Ops install proof"); + await expect(page.getByText("Active rules", { exact: true })).toBeVisible(); + await expect(page.getByText("Escalation dry run", { exact: true }).first()).toBeVisible(); + + await page.goto("/alerts"); + await expect(page.getByRole("heading", { name: "Alerts" })).toBeVisible(); + await expect(page.getByText("Verifier failed", { exact: true })).toBeVisible(); + await expect(page.getByText("UPSTREAM_LOSS", { exact: true })).toBeVisible(); + await expect(page.getByText("next action").first()).toBeVisible(); + + await page.goto("/"); + await expect(page.getByLabel("Public L1 launch readiness")).toContainText("Bridge runtime credit"); + await expect(page.getByLabel("Public L1 launch readiness")).toContainText("flowchain:bridge:runtime-credit:validate"); + await expect(page.getByLabel("Public L1 launch readiness")).toContainText("Bridge release evidence"); + await expect(page.getByLabel("Public L1 launch readiness")).toContainText("flowchain:bridge:release:evidence:validate"); + await expectNoUiLeakage(page); await expectNoHorizontalOverflow(page); + expect(state.unhandledRequests).toEqual([]); expect(consoleErrors).toEqual([]); }); }); diff --git a/apps/dashboard/public/data/flowchain-live-readiness-report.json b/apps/dashboard/public/data/flowchain-live-readiness-report.json index d5a93c9b..74882dd5 100644 --- a/apps/dashboard/public/data/flowchain-live-readiness-report.json +++ b/apps/dashboard/public/data/flowchain-live-readiness-report.json @@ -1,6 +1,6 @@ { "schema": "flowchain.live_readiness_dashboard_report.v0", - "generatedAt": "2026-05-18T15:18:49.2356414Z", + "generatedAt": "2026-05-21T13:56:36.8835297Z", "status": "blocked", "deploymentReady": false, "packetShareable": false, @@ -8,41 +8,146 @@ "summary": "Public launch is still blocked by owner-provided RPC edge, backup, Base 8453 bridge, or tester packet inputs.", "privateRpcUrl": "http://127.0.0.1:8787", "metrics": { - "latestHeight": "73360", - "finalizedHeight": "73360", + "latestHeight": "113229", + "finalizedHeight": "113229", "monitorHeightAdvanced": true, + "serviceSupervisorValidationStatus": "passed", + "serviceSupervisorRestartAttempts": "1", + "serviceSupervisorNodeRestartAttempts": "1", + "serviceSupervisorRelayerRestartAttempts": "1", + "serviceInstallValidationStatus": "passed", + "serviceInstallPlanDidNotMutate": true, + "serviceInstallStatusDidNotMutate": true, + "serviceInstallRelayerOptInStartsLoop": true, + "systemdServiceInstallValidationStatus": "passed", + "systemdInstallPlanUsesRenderedUnits": true, + "systemdBridgeRelayerDefaultOff": true, + "systemdBridgeRelayerOptInStartsLoop": true, "bridgeRelayerStatus": "blocked", "bridgeQueuedTransactions": "0", "bridgeRelayerChildTimeoutSeconds": "300", "bridgeRelayerStepCount": 1, "bridgeRelayerTimedOutStepCount": 0, "bridgeRelayerNoChildTimeouts": true, + "bridgeRelayerCheckContractReady": true, + "bridgeRelayerFailedChecks": "0", + "bridgeRelayerMissingChecks": "0", + "bridgeCommandMatrixStatus": "passed", + "bridgeCommandMatrixReady": true, + "bridgeCommandMatrixCommands": "20", + "bridgeCommandMatrixPhases": "9", + "bridgeCommandMatrixLiveBroadcastCommands": "4", + "bridgeCommandMatrixCommittedEvidencePaths": "13", + "bridgeCommandMatrixFailedChecks": "0", + "bridgeCommandMatrixBroadcastAckGaps": "0", + "bridgeCommandMatrixNoSecrets": true, + "bridgeCommandMatrixNoBroadcasts": true, + "bridgeNoSecretAuditStatus": "passed", + "bridgeNoSecretAuditReady": true, + "bridgeNoSecretAuditScannedFiles": "32", + "bridgeNoSecretAuditFindings": "0", + "bridgeNoSecretAuditSecretFindings": "0", + "bridgeNoSecretAuditFailedChecks": "0", + "bridgeNoSecretAuditNoSecrets": true, + "bridgeNoSecretAuditNoBroadcasts": true, "bridgeRelayerGuardrailStatus": "passed", "bridgeRelayerLoopValidationStatus": "passed", + "bridgeReconciliationScheduleStatus": "passed", + "bridgeReconciliationScheduleReady": true, + "bridgeReconciliationScheduleIntervalMinutes": "5", + "bridgeReconciliationScheduleNoMutation": true, + "bridgeReconciliationScheduleNoExternalDelivery": true, + "bridgeRuntimeCreditValidationStatus": "passed", + "bridgeRuntimeCreditReady": true, + "bridgeRuntimeCreditLatencySeconds": "0.693", + "bridgeRuntimeCreditTransferSeconds": "0.641", + "bridgeRuntimeCreditFailedChecks": "0", + "bridgeReleaseEvidenceStatus": "passed", + "bridgeReleaseEvidenceReady": true, + "bridgeReleaseEvidenceCaseCount": "12", + "bridgeReleaseEvidenceFailedChecks": "0", + "realValuePilotAggregateStatus": "passed", + "realValuePilotAggregateReady": true, + "realValuePilotAggregateCommandsRun": "6", + "realValuePilotAggregateTimedOutCommands": "0", + "realValuePilotAggregateFailedCommands": "0", + "realValuePilotAggregateMissingProofs": "0", + "realValuePilotAggregateOwnerGoNoGo": true, "backupOwnerPathDryRunStatus": "passed", "publicRpcDeploymentBundleStatus": "passed", "publicRpcOwnerRenderValidationStatus": "passed", "publicRpcDeploymentAutomationStatus": "passed", "publicRpcDeploymentAutomationAction": "Validate", + "publicRpcSecurityHeaders": true, + "publicRpcSecurityHeaderPreflight": true, + "publicRpcRenderedSecurityHeaders": true, + "publicRpcRenderedSecurityHeaderPreflight": true, + "publicRpcCommandMatrixStatus": "passed", + "publicRpcCommandMatrixReady": true, + "publicRpcCommandMatrixCommands": "21", + "publicRpcCommandMatrixOwnerHostCommands": "6", + "publicRpcCommandMatrixMutatingOwnerHostCommands": "4", + "publicRpcCommandMatrixCommittedEvidencePaths": "21", + "publicRpcCommandMatrixFailedChecks": "0", + "publicRpcCommandMatrixMissingScripts": "0", + "publicRpcCommandMatrixNoSecrets": true, + "publicRpcCommandMatrixNoBroadcasts": true, "externalTesterPacketStatus": "blocked", "externalTesterConnectPackStatus": "blocked", + "publicTesterGatewayStatus": "passed", + "publicTesterGatewayAccountCount": "2", + "publicTesterGatewayFailedChecks": "0", + "publicTesterGatewayNoSecrets": true, + "publicTesterGatewayNoBroadcasts": true, "ownerInputReady": false, + "ownerEnvTemplateStatus": "passed", + "ownerEnvTemplateFieldGuideCount": "19", + "ownerEnvTemplateRequiredEnvNameCount": "17", + "ownerEnvTemplateNoSecrets": true, + "ownerEnvTemplateEnvValuesPrinted": false, + "ownerActivationStatus": "passed", + "ownerActivationReady": false, + "ownerActivationStageCount": "8", + "ownerActivationReadyStageCount": "1", + "ownerActivationMissingCount": 14, + "ownerGoLiveHandoffStatus": "passed", + "ownerGoLiveHandoffReady": true, + "ownerGoLiveReleaseReady": false, + "ownerGoLiveNextInputCount": 14, + "ownerGoLiveStageCount": "8", + "ownerGoLiveLaunchSequenceCount": "10", + "ownerGoLiveLaunchSequenceCommandCount": "44", + "ownerGoLiveExpectedReportPathCount": "45", + "ownerGoLiveRollbackCommandCount": "9", + "ownerNeedsNowStatus": "passed", + "ownerNeedsNowGroupCount": "4", + "ownerNeedsNowNeededGroupCount": "3", + "ownerNeedsNowReadyGroupCount": "1", + "ownerHostApplyPlanCovered": true, + "ownerHostApplyExecutionCovered": true, + "ownerHostApplyRollbackCovered": true, + "windowsOwnerHostApplyPlanCovered": true, + "windowsOwnerHostApplyExecutionCovered": true, + "windowsOwnerHostApplyRollbackCovered": true, "noSecretStatus": "passed", "opsSnapshotStatus": "blocked", "opsAlertState": "blocked", "opsCriticalCount": "0", "opsBlockedCount": "6", "opsActiveRuleCount": 6, - "opsRuleCount": "18", - "opsCriticalRuleCount": "11", + "opsRuleCount": "44", + "opsCriticalRuleCount": "37", "opsBlockedRuleCount": "7", "opsUnmappedCurrentFindingCount": 0, + "opsMetricCount": "334", + "opsRequiredMetricsPresent": true, + "opsPublicRpcSecurityHeaderMetricsPresent": true, "incidentDrillStatus": "passed", "alertInstallValidationStatus": "passed", "opsEscalationDryRunStatus": "passed", "opsEscalationDryRunEvents": "6", "statusCounts": { - "passed": 7, + "passed": 11, "blocked": 5 } }, @@ -54,12 +159,86 @@ "alertInstallValidationStatus": "passed", "escalationDryRunStatus": "passed", "escalationDryRunEvents": "6", + "serviceSupervisorValidationStatus": "passed", + "serviceSupervisorRestartAttempts": "1", + "serviceSupervisorNodeRestartAttempts": "1", + "serviceSupervisorRelayerRestartAttempts": "1", + "serviceInstallValidationStatus": "passed", + "serviceInstallPlanDidNotMutate": true, + "serviceInstallStatusDidNotMutate": true, + "serviceInstallRelayerOptInStartsLoop": true, + "systemdServiceInstallValidationStatus": "passed", + "systemdInstallPlanUsesRenderedUnits": true, + "systemdBridgeRelayerDefaultOff": true, + "systemdBridgeRelayerOptInStartsLoop": true, + "publicRpcDeploymentBundleStatus": "passed", + "publicRpcDeploymentAutomationStatus": "passed", + "publicRpcDeploymentAutomationAction": "Validate", + "publicRpcSecurityHeaders": true, + "publicRpcRenderedSecurityHeaders": true, + "publicRpcCommandMatrixStatus": "passed", + "publicRpcCommandMatrixReady": true, + "publicRpcCommandMatrixCommands": "21", + "publicRpcCommandMatrixOwnerHostCommands": "6", + "publicRpcCommandMatrixMutatingOwnerHostCommands": "4", + "publicRpcCommandMatrixCommittedEvidencePaths": "21", + "publicRpcCommandMatrixFailedChecks": "0", + "publicRpcCommandMatrixNoSecrets": true, + "publicRpcCommandMatrixNoBroadcasts": true, + "opsMetricCount": "334", + "opsRequiredMetricsPresent": true, + "publicTesterGatewayStatus": "passed", + "publicTesterGatewayAccountCount": "2", + "publicTesterGatewayFailedChecks": "0", + "publicTesterGatewayTransferApplied": true, + "publicTesterGatewayCapRejected": true, + "publicTesterGatewayNoSecrets": true, + "publicTesterGatewayNoBroadcasts": true, + "bridgeRelayerCheckContractReady": true, + "bridgeRelayerFailedChecks": "0", + "bridgeRelayerMissingChecks": "0", + "bridgeCommandMatrixStatus": "passed", + "bridgeCommandMatrixReady": true, + "bridgeCommandMatrixCommands": "20", + "bridgeCommandMatrixLiveBroadcastCommands": "4", + "bridgeCommandMatrixFailedChecks": "0", + "bridgeCommandMatrixBroadcastAckGaps": "0", + "bridgeCommandMatrixNoSecrets": true, + "bridgeCommandMatrixNoBroadcasts": true, + "bridgeNoSecretAuditStatus": "passed", + "bridgeNoSecretAuditReady": true, + "bridgeNoSecretAuditScannedFiles": "32", + "bridgeNoSecretAuditFindings": "0", + "bridgeNoSecretAuditSecretFindings": "0", + "bridgeNoSecretAuditFailedChecks": "0", + "bridgeNoSecretAuditNoSecrets": true, + "bridgeNoSecretAuditNoBroadcasts": true, "bridgeRelayerGuardrailStatus": "passed", "bridgeRelayerGuardrailReady": true, + "bridgeReconciliationScheduleStatus": "passed", + "bridgeReconciliationScheduleReady": true, + "bridgeReconciliationScheduleIntervalMinutes": "5", + "bridgeReconciliationScheduleNoMutation": true, + "bridgeReconciliationScheduleNoExternalDelivery": true, + "bridgeRuntimeCreditStatus": "passed", + "bridgeRuntimeCreditReady": true, + "bridgeRuntimeCreditLatencySeconds": "0.845", + "bridgeRuntimeTransferLatencySeconds": "0.695", + "bridgeReleaseEvidenceStatus": "passed", + "bridgeReleaseEvidenceReady": true, + "bridgeReleaseEvidenceCaseCount": "12", + "bridgeReleaseEvidenceFailedChecks": "0", + "realValuePilotAggregateStatus": "passed", + "realValuePilotAggregateReady": true, + "realValuePilotAggregateCommandsRun": "6", + "realValuePilotAggregateTimedOutCommands": "0", + "realValuePilotAggregateFailedCommands": "0", + "realValuePilotAggregateMissingProofs": "0", + "realValuePilotAggregateOwnerGoNoGo": true, "criticalCount": "0", "blockedCount": "6", - "latestHeight": "73289", - "finalizedHeight": "73289", + "latestHeight": "112092", + "finalizedHeight": "112092", "monitorStatus": "passed", "monitorHeightAdvanced": true, "findings": [ @@ -105,10 +284,12 @@ { "severity": "blocked", "code": "external-tester-not-shareable", - "message": "External tester packet must remain not-shareable.", + "message": "External tester launch is not shareable; local rehearsal, public tester gateway, faucet route, external sharing, and live infra readiness must all pass first.", "commands": [ - "npm run flowchain:tester:readiness", - "npm run flowchain:external-tester:packet" + "npm run flowchain:wallet:live-tester:e2e", + "npm run flowchain:tester:gateway:e2e", + "npm run flowchain:tester:readiness -- -AllowBlocked", + "npm run flowchain:external-tester:packet -- -AllowBlocked" ] }, { @@ -135,13 +316,39 @@ "chain-height-unreadable", "height-not-advancing", "state-stale", + "transaction-intake-invalid-rows", "no-secret-scan-not-passed", + "backup-retention-unsafe", + "backup-restore-validation-failed", + "backup-owner-path-dry-run-failed", + "bridge-deploy-control-validation-failed", + "bridge-command-matrix-failed", + "bridge-no-secret-audit-failed", + "bridge-relayer-check-contract-failed", "bridge-relayer-latency-failed", "bridge-relayer-cursor-unsafe", "bridge-relayer-guardrail-failed", + "bridge-direct-observe-cursor-unsafe", + "bridge-runtime-credit-validation-failed", + "bridge-reconciliation-failed", + "real-value-pilot-aggregate-failed", "bridge-relayer-loop-unhealthy", + "supervisor-relayer-recovery-failed", + "supervisor-node-recovery-validation-failed", + "service-install-validation-failed", "deployment-refresh-aborted", + "truth-table-stale-or-failed", "external-tester-evidence-unsafe", + "public-tester-gateway-e2e-failed", + "dashboard-ui-readiness-failed", + "second-computer-readiness-failed", + "developer-dev-pack-readiness-failed", + "owner-inputs-validation-failed", + "owner-go-live-handoff-failed", + "owner-needs-now-failed", + "public-rpc-edge-hardening-failed", + "public-rpc-command-matrix-failed", + "public-rpc-synthetic-canary-failed", "public-rpc-not-ready", "backup-not-ready", "bridge-not-ready", @@ -198,10 +405,12 @@ "id": "external-tester-not-shareable", "severity": "blocked", "signal": "External tester packet is not shareable.", - "threshold": "tester readiness status is not passed", + "threshold": "tester readiness status is not passed, local tester wallet rehearsal is not ready/fresh, public tester gateway or faucet route is not validated, external sharing is false, or live infra readiness is blocked", "commands": [ - "npm run flowchain:tester:readiness", - "npm run flowchain:external-tester:packet" + "npm run flowchain:wallet:live-tester:e2e", + "npm run flowchain:tester:gateway:e2e", + "npm run flowchain:tester:readiness -- -AllowBlocked", + "npm run flowchain:external-tester:packet -- -AllowBlocked" ] }, { @@ -214,8 +423,8 @@ ] } ], - "ruleCount": "18", - "criticalRuleCount": "11", + "ruleCount": "44", + "criticalRuleCount": "37", "blockedRuleCount": "7", "unmappedCurrentFindingCodes": [], "incidentCommands": { @@ -235,9 +444,22 @@ ], "publicExposure": [ "npm run flowchain:public-rpc:check", + "npm run flowchain:public-rpc:synthetic-canary -- -AllowBlocked", "npm run flowchain:public-rpc:abuse-test", + "npm run flowchain:public-rpc:deployment-bundle", + "npm run flowchain:public-rpc:deployment:automation", + "npm run flowchain:external-tester:packet" + ], + "productSurface": [ + "npm run flowchain:dashboard:ui:readiness", + "npm run flowchain:tester:evidence:validate", "npm run flowchain:external-tester:packet" ], + "ownerInputs": [ + "npm run flowchain:owner-inputs:validate", + "npm run flowchain:owner-inputs", + "npm run flowchain:owner-env:readiness" + ], "drills": [ "npm run flowchain:ops:incident-drill", "npm run flowchain:ops:snapshot -- -AllowBlocked -NoRefresh" @@ -247,10 +469,22 @@ "npm run flowchain:bridge:emergency-stop", "npm run flowchain:emergency:export-evidence" ], + "bridgePilot": [ + "npm run flowchain:bridge:command-matrix", + "npm run flowchain:bridge:deploy:control:validate", + "npm run flowchain:bridge:relayer:guardrail:validate", + "npm run flowchain:bridge:reconciliation" + ], "bridgeRelayerLoop": [ "npm run flowchain:service:status", + "npm run flowchain:service:supervisor -- -Once -StartBridgeRelayerLoop", "npm run flowchain:bridge:relayer:loop:validate", "npm run flowchain:service:restart -- -LiveProfile -StartBridgeRelayerLoop" + ], + "serviceInstall": [ + "npm run flowchain:service:install:validate", + "npm run flowchain:service:install:systemd:validate", + "npm run flowchain:service:status" ] }, "dryRunEvents": [ @@ -303,8 +537,10 @@ "ruleId": "external-tester-not-shareable", "signal": "External tester packet is not shareable.", "commands": [ - "npm run flowchain:tester:readiness", - "npm run flowchain:external-tester:packet" + "npm run flowchain:wallet:live-tester:e2e", + "npm run flowchain:tester:gateway:e2e", + "npm run flowchain:tester:readiness -- -AllowBlocked", + "npm run flowchain:external-tester:packet -- -AllowBlocked" ] }, { @@ -320,6 +556,2072 @@ "sendsNetworkNotifications": false, "storesSecrets": false }, + "ownerActivation": { + "status": "passed", + "activationReady": false, + "stageCount": "8", + "readyStageCount": "1", + "blockedStageCount": "7", + "stagesNeedingOwnerInputCount": "7", + "stagesNeedingValidationCount": "0", + "missingEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED", + "FLOWCHAIN_RPC_STATE_BACKUP_PATH", + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "invalidEnvNames": [], + "nextOwnerInputNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED", + "FLOWCHAIN_RPC_STATE_BACKUP_PATH", + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "requiredOwnerEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED", + "FLOWCHAIN_RPC_STATE_BACKUP_PATH", + "FLOWCHAIN_TESTER_WRITE_ENABLED", + "FLOWCHAIN_TESTER_WRITE_TOKEN_SHA256", + "FLOWCHAIN_TESTER_MAX_SEND_UNITS", + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "optionalOwnerEnvNames": [ + "FLOWCHAIN_BASE8453_CURSOR_STATE", + "FLOWCHAIN_BASE8453_TO_BLOCK" + ], + "nextCommands": [ + "npm run flowchain:owner-env:template", + "npm run flowchain:owner-env:readiness -- -AllowBlocked", + "npm run flowchain:owner-inputs -- -AllowBlocked", + "npm run flowchain:public-rpc:check -- -AllowBlocked", + "npm run flowchain:bridge:live:check -- -AllowBlocked", + "npm run flowchain:wallet:live-tester:e2e", + "npm run flowchain:live:cutover:rehearsal -- -AllowBlocked", + "npm run flowchain:completion:audit -- -AllowBlocked" + ], + "forbiddenItems": [ + "Host login password", + "SSH private key", + "Owner env file contents", + "Provider URLs that carry account tokens", + "Registrar password", + "tunnel token", + "TLS private key", + "Storage account secret", + "cloud backup credentials", + "Raw tester bearer token", + "token hash together with the raw token", + "Wallet private key", + "wallet recovery words", + "provider dashboard password", + "Raw tester token in GitHub or chat", + "owner env file contents", + "Any secret-bearing provider URL", + "wallet recovery material" + ], + "stages": [ + { + "id": "always-on-service-host", + "title": "Keep the chain and private RPC running", + "status": "ready", + "ready": true, + "requiredEnvNames": [], + "optionalEnvNames": [], + "missingEnvNames": [], + "invalidEnvNames": [], + "upstreamMissingEnvNames": [], + "upstreamInvalidEnvNames": [], + "blockingEnvNames": [], + "blockedByReportNames": [], + "externalAccountsOrResources": [ + "Always-on Windows host, Linux host, or VPS" + ], + "ownerMustDo": [ + "Choose the host that will stay online and keep the FlowChain node/control-plane running." + ], + "ownerMustNotSend": [ + "Host login password", + "SSH private key" + ], + "validationCommands": [ + "npm run flowchain:service:status -- -AllowBlocked", + "npm run flowchain:service:monitor" + ], + "sourceReports": [ + { + "name": "serviceStatus", + "status": "passed", + "path": "\\docs\\agent-runs\\live-product-infra-rpc\\service-status-report.json" + } + ] + }, + { + "id": "owner-env-file", + "title": "Fill the ignored local owner env file", + "status": "needs-owner-input", + "ready": false, + "requiredEnvNames": [ + "FLOWCHAIN_OWNER_ENV_FILE" + ], + "optionalEnvNames": [], + "missingEnvNames": [], + "invalidEnvNames": [], + "upstreamMissingEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED", + "FLOWCHAIN_RPC_STATE_BACKUP_PATH", + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "upstreamInvalidEnvNames": [], + "blockingEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED", + "FLOWCHAIN_RPC_STATE_BACKUP_PATH", + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "blockedByReportNames": [ + "ownerEnvReadiness" + ], + "externalAccountsOrResources": [ + "Local ignored env file or service environment" + ], + "ownerMustDo": [ + "Run the template command, fill real values only on the launch host, and point FLOWCHAIN_OWNER_ENV_FILE at that file." + ], + "ownerMustNotSend": [ + "Owner env file contents", + "Provider URLs that carry account tokens" + ], + "validationCommands": [ + "npm run flowchain:owner-env:template", + "npm run flowchain:owner-env:readiness:validate", + "npm run flowchain:owner-env:readiness -- -AllowBlocked" + ], + "sourceReports": [ + { + "name": "ownerEnvReadiness", + "status": "blocked", + "path": "\\docs\\agent-runs\\live-product-infra-rpc\\owner-env-readiness-report.json" + } + ] + }, + { + "id": "public-rpc-edge", + "title": "Expose repo-owned FlowChain RPC through a public HTTPS edge", + "status": "needs-owner-input", + "ready": false, + "requiredEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED" + ], + "optionalEnvNames": [], + "missingEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED" + ], + "invalidEnvNames": [], + "upstreamMissingEnvNames": [ + "FLOWCHAIN_RPC_STATE_BACKUP_PATH" + ], + "upstreamInvalidEnvNames": [], + "blockingEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED", + "FLOWCHAIN_RPC_STATE_BACKUP_PATH" + ], + "blockedByReportNames": [ + "publicRpc", + "publicDeploymentContract" + ], + "externalAccountsOrResources": [ + "DNS provider or existing domain", + "TLS edge, reverse proxy, or tunnel" + ], + "ownerMustDo": [ + "Create DNS or a tunnel hostname for the FlowChain RPC edge.", + "Terminate TLS at the edge.", + "Set exact allowed browser origins and a positive per-minute rate limit." + ], + "ownerMustNotSend": [ + "Registrar password", + "tunnel token", + "TLS private key" + ], + "validationCommands": [ + "npm run flowchain:public-rpc:check -- -AllowBlocked", + "npm run flowchain:public-rpc:validate", + "npm run flowchain:public-rpc:abuse-test" + ], + "sourceReports": [ + { + "name": "publicRpc", + "status": "blocked", + "path": "\\docs\\agent-runs\\live-product-infra-rpc\\public-rpc-readiness-report.json" + }, + { + "name": "publicDeploymentContract", + "status": "blocked", + "path": "\\docs\\agent-runs\\live-product-infra-rpc\\public-deployment-contract-report.json" + } + ] + }, + { + "id": "state-backup-storage", + "title": "Provision durable state backup storage", + "status": "needs-owner-input", + "ready": false, + "requiredEnvNames": [ + "FLOWCHAIN_RPC_STATE_BACKUP_PATH" + ], + "optionalEnvNames": [], + "missingEnvNames": [ + "FLOWCHAIN_RPC_STATE_BACKUP_PATH" + ], + "invalidEnvNames": [], + "upstreamMissingEnvNames": [], + "upstreamInvalidEnvNames": [], + "blockingEnvNames": [ + "FLOWCHAIN_RPC_STATE_BACKUP_PATH" + ], + "blockedByReportNames": [ + "backup" + ], + "externalAccountsOrResources": [ + "Persistent local disk, mounted volume, or owner-managed backup directory" + ], + "ownerMustDo": [ + "Create a writable persistent directory available to the FlowChain service process.", + "Keep the path local to the launch host or mounted as durable storage." + ], + "ownerMustNotSend": [ + "Storage account secret", + "cloud backup credentials" + ], + "validationCommands": [ + "npm run flowchain:backup:check -- -AllowBlocked", + "npm run flowchain:backup:restore:validate", + "npm run flowchain:backup:owner-path:dry-run" + ], + "sourceReports": [ + { + "name": "backup", + "status": "blocked", + "path": "\\docs\\agent-runs\\live-product-infra-rpc\\backup-readiness-report.json" + } + ] + }, + { + "id": "tester-write-gateway", + "title": "Enable capped friends-and-family tester writes", + "status": "needs-owner-input", + "ready": false, + "requiredEnvNames": [ + "FLOWCHAIN_TESTER_WRITE_ENABLED", + "FLOWCHAIN_TESTER_WRITE_TOKEN_SHA256", + "FLOWCHAIN_TESTER_MAX_SEND_UNITS" + ], + "optionalEnvNames": [], + "missingEnvNames": [], + "invalidEnvNames": [], + "upstreamMissingEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED", + "FLOWCHAIN_RPC_STATE_BACKUP_PATH", + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "upstreamInvalidEnvNames": [], + "blockingEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED", + "FLOWCHAIN_RPC_STATE_BACKUP_PATH", + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "blockedByReportNames": [ + "externalTester", + "externalTesterPacket", + "publicDeploymentContract" + ], + "externalAccountsOrResources": [ + "Owner password manager or secret store" + ], + "ownerMustDo": [ + "Run the tester token setup command to create or preserve the raw bearer token in ignored local storage.", + "Store only its SHA-256 digest in the owner env file.", + "Choose a small positive per-send test-unit cap." + ], + "ownerMustNotSend": [ + "Raw tester bearer token", + "token hash together with the raw token" + ], + "validationCommands": [ + "npm run flowchain:tester:token:setup", + "npm run flowchain:tester:gateway:e2e", + "npm run flowchain:external-tester:packet -- -AllowBlocked", + "npm run flowchain:external-tester:packet:validate" + ], + "sourceReports": [ + { + "name": "externalTester", + "status": "blocked", + "path": "\\docs\\agent-runs\\live-product-infra-rpc\\external-tester-readiness-report.json" + }, + { + "name": "externalTesterPacket", + "status": "blocked", + "path": "\\docs\\agent-runs\\live-product-infra-rpc\\external-tester-packet-report.json" + }, + { + "name": "publicDeploymentContract", + "status": "blocked", + "path": "\\docs\\agent-runs\\live-product-infra-rpc\\public-deployment-contract-report.json" + } + ] + }, + { + "id": "base8453-bridge-pilot", + "title": "Configure capped Base 8453 bridge pilot observation", + "status": "needs-owner-input", + "ready": false, + "requiredEnvNames": [ + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "optionalEnvNames": [ + "FLOWCHAIN_BASE8453_CURSOR_STATE", + "FLOWCHAIN_BASE8453_TO_BLOCK" + ], + "missingEnvNames": [ + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "invalidEnvNames": [], + "upstreamMissingEnvNames": [], + "upstreamInvalidEnvNames": [], + "blockingEnvNames": [ + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "blockedByReportNames": [ + "bridgeLive", + "bridgeInfra", + "publicDeploymentContract" + ], + "externalAccountsOrResources": [ + "Base RPC provider or owner-operated Base node", + "Deployed pilot bridge contract details" + ], + "ownerMustDo": [ + "Provide a Base chain 8453 HTTPS endpoint.", + "Provide deployed lockbox and supported-token addresses.", + "Choose the bootstrap from-block, confirmations, max deposit, total cap, and explicit capped-pilot acknowledgement." + ], + "ownerMustNotSend": [ + "Wallet private key", + "wallet recovery words", + "provider dashboard password" + ], + "validationCommands": [ + "npm run flowchain:bridge:live:check -- -AllowBlocked", + "npm run flowchain:bridge:infra:check -- -AllowBlocked", + "npm run flowchain:bridge:relayer:guardrail:validate", + "npm run flowchain:bridge:relayer:loop:validate" + ], + "sourceReports": [ + { + "name": "bridgeLive", + "status": "blocked", + "path": "\\docs\\agent-runs\\live-product-infra-rpc\\bridge-live-readiness-report.json" + }, + { + "name": "bridgeInfra", + "status": "blocked", + "path": "\\docs\\agent-runs\\live-product-infra-rpc\\bridge-infra-readiness-report.json" + }, + { + "name": "publicDeploymentContract", + "status": "blocked", + "path": "\\docs\\agent-runs\\live-product-infra-rpc\\public-deployment-contract-report.json" + } + ] + }, + { + "id": "friends-and-family-launch", + "title": "Release the external tester packet only after public gates pass", + "status": "needs-owner-input", + "ready": false, + "requiredEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED", + "FLOWCHAIN_RPC_STATE_BACKUP_PATH", + "FLOWCHAIN_TESTER_WRITE_ENABLED", + "FLOWCHAIN_TESTER_WRITE_TOKEN_SHA256", + "FLOWCHAIN_TESTER_MAX_SEND_UNITS", + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "optionalEnvNames": [], + "missingEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED", + "FLOWCHAIN_RPC_STATE_BACKUP_PATH", + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "invalidEnvNames": [], + "upstreamMissingEnvNames": [], + "upstreamInvalidEnvNames": [], + "blockingEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED", + "FLOWCHAIN_RPC_STATE_BACKUP_PATH", + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "blockedByReportNames": [ + "externalTester", + "externalTesterPacket", + "publicDeploymentContract" + ], + "externalAccountsOrResources": [ + "Friends-and-family tester list" + ], + "ownerMustDo": [ + "Share wallet/tester instructions only after the packet report marks external sharing ready.", + "Keep per-send caps low for the first pilot." + ], + "ownerMustNotSend": [ + "Raw tester token in GitHub or chat", + "owner env file contents" + ], + "validationCommands": [ + "npm run flowchain:wallet:live-tester:e2e", + "npm run flowchain:external-tester:packet -- -AllowBlocked", + "npm run flowchain:external-tester:packet:validate", + "npm run flowchain:dashboard:ui:readiness" + ], + "sourceReports": [ + { + "name": "externalTester", + "status": "blocked", + "path": "\\docs\\agent-runs\\live-product-infra-rpc\\external-tester-readiness-report.json" + }, + { + "name": "externalTesterPacket", + "status": "blocked", + "path": "\\docs\\agent-runs\\live-product-infra-rpc\\external-tester-packet-report.json" + }, + { + "name": "publicDeploymentContract", + "status": "blocked", + "path": "\\docs\\agent-runs\\live-product-infra-rpc\\public-deployment-contract-report.json" + } + ] + }, + { + "id": "final-go-live-audit", + "title": "Run final no-secret production audit before public use", + "status": "needs-owner-input", + "ready": false, + "requiredEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED", + "FLOWCHAIN_RPC_STATE_BACKUP_PATH", + "FLOWCHAIN_TESTER_WRITE_ENABLED", + "FLOWCHAIN_TESTER_WRITE_TOKEN_SHA256", + "FLOWCHAIN_TESTER_MAX_SEND_UNITS", + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "optionalEnvNames": [], + "missingEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED", + "FLOWCHAIN_RPC_STATE_BACKUP_PATH", + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "invalidEnvNames": [], + "upstreamMissingEnvNames": [], + "upstreamInvalidEnvNames": [], + "blockingEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED", + "FLOWCHAIN_RPC_STATE_BACKUP_PATH", + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "blockedByReportNames": [ + "completionAudit", + "truthTable" + ], + "externalAccountsOrResources": [ + "None beyond the configured launch resources" + ], + "ownerMustDo": [ + "Run the aggregate gates after all owner values are configured.", + "Do not announce public readiness until completionReady is true and the truth table has no owner blockers." + ], + "ownerMustNotSend": [ + "Any secret-bearing provider URL", + "wallet recovery material" + ], + "validationCommands": [ + "npm run flowchain:live:cutover:rehearsal -- -AllowBlocked", + "npm run flowchain:completion:audit -- -AllowBlocked", + "npm run flowchain:truth-table -- -AllowBlocked", + "npm run flowchain:no-secret:scan" + ], + "sourceReports": [ + { + "name": "completionAudit", + "status": "blocked", + "path": "\\docs\\agent-runs\\live-product-infra-rpc\\flowchain-completion-audit-report.json" + }, + { + "name": "truthTable", + "status": "blocked-owner-input", + "path": "\\docs\\agent-runs\\live-product-infra-rpc\\production-truth-table-report.json" + } + ] + } + ], + "checks": { + "stageCountMinimumMet": true, + "requiredEnvCoverageComplete": true, + "knownMissingEnvNamesOnly": true, + "invalidEnvNamesEmpty": true, + "knownInvalidEnvNamesOnly": true, + "validationCommandsPresent": true, + "ownerMustNotSendPresent": true, + "externalResourceMappingPresent": true, + "serviceStagePresent": true, + "publicRpcStagePresent": true, + "backupStagePresent": true, + "testerStagePresent": true, + "bridgeStagePresent": true, + "finalAuditStagePresent": true, + "nonReadyStagesExplainBlockers": true, + "nextOwnerInputNamesPresentWhenBlocked": true, + "envValuesPrintedFalse": true, + "noSecrets": true, + "broadcastsFalse": true, + "secretMarkerFindingsEmpty": true + }, + "failedChecks": [], + "noSecrets": true, + "broadcasts": false, + "envValuesPrinted": false + }, + "ownerEnvTemplate": { + "status": "passed", + "requiredEnvNameCount": "17", + "optionalEnvNameCount": "2", + "fieldGuideCount": "19", + "fieldGuide": [ + { + "name": "FLOWCHAIN_RPC_PUBLIC_URL", + "group": "public-rpc", + "required": true, + "purpose": "Public HTTPS URL testers and wallets use for FlowChain RPC.", + "validation": "absolute non-local HTTPS endpoint", + "source": "owner DNS, tunnel, or reverse proxy hostname", + "doNotSend": "provider login password, tunnel token, or TLS private key" + }, + { + "name": "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "group": "public-rpc", + "required": true, + "purpose": "Comma-separated HTTPS browser origins allowed to call the public RPC edge.", + "validation": "one or more explicit HTTPS origins; wildcard is rejected", + "source": "dashboard/tester site origin list", + "doNotSend": "wildcard origin or private browser session data" + }, + { + "name": "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "group": "public-rpc", + "required": true, + "purpose": "Per-origin or per-client public RPC request limit.", + "validation": "positive decimal integer", + "source": "owner public edge rate-limit policy", + "doNotSend": "provider account credentials" + }, + { + "name": "FLOWCHAIN_RPC_TLS_TERMINATED", + "group": "public-rpc", + "required": true, + "purpose": "Acknowledgement that HTTPS termination is configured at the public edge.", + "validation": "must equal true", + "source": "owner TLS edge configuration", + "doNotSend": "TLS private key or certificate account credentials" + }, + { + "name": "FLOWCHAIN_RPC_STATE_BACKUP_PATH", + "group": "backup", + "required": true, + "purpose": "Existing writable directory for live state backup and restore proof.", + "validation": "existing writable directory", + "source": "owner host durable disk or mounted backup volume", + "doNotSend": "cloud storage secret or host login password" + }, + { + "name": "FLOWCHAIN_TESTER_WRITE_ENABLED", + "group": "tester-write", + "required": true, + "purpose": "Enables authenticated capped tester write routes.", + "validation": "must equal true", + "source": "owner launch decision after public gates are ready", + "doNotSend": "raw tester token" + }, + { + "name": "FLOWCHAIN_TESTER_WRITE_TOKEN_SHA256", + "group": "tester-write", + "required": true, + "purpose": "Digest of the out-of-band tester bearer token.", + "validation": "64-character SHA-256 hex digest", + "source": "npm run flowchain:tester:token:setup", + "doNotSend": "raw tester token or token hash together with the raw token" + }, + { + "name": "FLOWCHAIN_TESTER_MAX_SEND_UNITS", + "group": "tester-write", + "required": true, + "purpose": "Maximum units a tester can send per capped write request.", + "validation": "positive decimal integer", + "source": "owner tester pilot cap", + "doNotSend": "uncapped launch policy" + }, + { + "name": "FLOWCHAIN_PILOT_OPERATOR_ACK", + "group": "base8453-bridge", + "required": true, + "purpose": "Explicit acknowledgement for the capped Base 8453 bridge pilot.", + "validation": "must equal I_UNDERSTAND_THIS_IS_CAPPED_BASE8453_OWNER_PILOT", + "source": "owner go-live decision", + "doNotSend": "wallet recovery words or private key" + }, + { + "name": "FLOWCHAIN_BASE8453_RPC_URL", + "group": "base8453-bridge", + "required": true, + "purpose": "Base chain endpoint used by the bridge observer.", + "validation": "absolute HTTP(S) endpoint", + "source": "Base RPC provider or owner-operated Base node", + "doNotSend": "provider URLs that embed account tokens" + }, + { + "name": "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "group": "base8453-bridge", + "required": true, + "purpose": "Deployed Base 8453 lockbox contract address.", + "validation": "20-byte hex address", + "source": "bridge deployment artifact or verified owner contract", + "doNotSend": "deployer private key" + }, + { + "name": "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "group": "base8453-bridge", + "required": true, + "purpose": "Base 8453 token address accepted by the capped pilot.", + "validation": "20-byte hex address", + "source": "owner-approved bridge token contract", + "doNotSend": "wallet private key" + }, + { + "name": "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "group": "base8453-bridge", + "required": true, + "purpose": "Decimals for the supported Base 8453 asset.", + "validation": "integer from 0 through 255", + "source": "token metadata or deployment checklist", + "doNotSend": "provider account credentials" + }, + { + "name": "FLOWCHAIN_BASE8453_FROM_BLOCK", + "group": "base8453-bridge", + "required": true, + "purpose": "First Base 8453 block the bridge observer scans.", + "validation": "non-negative decimal block number", + "source": "lockbox deployment block or chosen pilot start block", + "doNotSend": "provider account credentials" + }, + { + "name": "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "group": "base8453-bridge", + "required": true, + "purpose": "Maximum single deposit credited during the capped pilot.", + "validation": "positive decimal integer", + "source": "owner pilot risk cap", + "doNotSend": "uncapped value" + }, + { + "name": "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "group": "base8453-bridge", + "required": true, + "purpose": "Total bridge credit cap for the capped pilot.", + "validation": "positive decimal integer", + "source": "owner pilot risk cap", + "doNotSend": "uncapped value" + }, + { + "name": "FLOWCHAIN_PILOT_CONFIRMATIONS", + "group": "base8453-bridge", + "required": true, + "purpose": "Base confirmations required before observer credit.", + "validation": "positive decimal integer", + "source": "owner bridge finality policy", + "doNotSend": "provider account credentials" + }, + { + "name": "FLOWCHAIN_BASE8453_CURSOR_STATE", + "group": "base8453-bridge", + "required": false, + "purpose": "Optional local cursor state path for Base scan progress.", + "validation": "local path controlled by the owner host", + "source": "default relayer state path unless overridden", + "doNotSend": "cursor file contents if they include local paths you want private" + }, + { + "name": "FLOWCHAIN_BASE8453_TO_BLOCK", + "group": "base8453-bridge", + "required": false, + "purpose": "Optional upper Base 8453 block for bounded observer scans.", + "validation": "non-negative decimal block number", + "source": "owner bounded scan plan", + "doNotSend": "provider account credentials" + } + ], + "checks": { + "pathIsGitIgnored": true, + "createdOrPreservedLocalFile": true, + "templateIncludesAllRequiredEnvNames": true, + "requiredEnvNameCountExpected": true, + "optionalEnvNameCountExpected": true, + "fieldGuideCoversAllRequiredEnvNames": true, + "fieldGuideCoversAllOptionalEnvNames": true, + "fieldGuideHasValidationForEveryName": true, + "fieldGuideHasDoNotSendForEveryName": true, + "valuesPrintedFalse": true, + "envValuesPrintedFalse": true, + "noSecrets": true, + "broadcastsFalse": true, + "secretMarkerFindingsEmpty": true + }, + "failedChecks": [], + "noSecrets": true, + "broadcasts": false, + "envValuesPrinted": false + }, + "ownerGoLiveHandoff": { + "status": "passed", + "releaseReady": false, + "activationReady": false, + "deploymentReady": false, + "packetShareable": false, + "completionReady": false, + "truthTableClear": false, + "blockedOnlyOnKnownOwnerInputs": true, + "stageCount": "8", + "readyStageCount": "1", + "blockedStageCount": "7", + "nextCommandCount": "63", + "launchSequenceCount": "10", + "launchSequenceCommandCount": "44", + "launchSequenceExpectedReportPathCount": "45", + "rollbackCommandCount": "9", + "mustNotSendCount": "18", + "missingEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED", + "FLOWCHAIN_RPC_STATE_BACKUP_PATH", + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS", + "FLOWCHAIN_BASE8453_TO_BLOCK" + ], + "invalidEnvNames": [], + "nextOwnerInputNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED", + "FLOWCHAIN_RPC_STATE_BACKUP_PATH", + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "requiredOwnerEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED", + "FLOWCHAIN_RPC_STATE_BACKUP_PATH", + "FLOWCHAIN_TESTER_WRITE_ENABLED", + "FLOWCHAIN_TESTER_WRITE_TOKEN_SHA256", + "FLOWCHAIN_TESTER_MAX_SEND_UNITS", + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "optionalOwnerEnvNames": [ + "FLOWCHAIN_BASE8453_CURSOR_STATE", + "FLOWCHAIN_BASE8453_TO_BLOCK" + ], + "nextCommands": [ + "npm run flowchain:owner-env:template", + "npm run flowchain:owner-env:readiness -- -AllowBlocked", + "npm run flowchain:owner-inputs -- -AllowBlocked", + "npm run flowchain:public-rpc:check -- -AllowBlocked", + "npm run flowchain:bridge:live:check -- -AllowBlocked", + "npm run flowchain:wallet:live-tester:e2e", + "npm run flowchain:live:cutover:rehearsal -- -AllowBlocked", + "npm run flowchain:completion:audit -- -AllowBlocked", + "npm run flowchain:owner-inputs:validate", + "npm run flowchain:owner:onboarding" + ], + "forbiddenItems": [ + "Host login password", + "SSH private key", + "Owner env file contents", + "Provider URLs that carry account tokens", + "Registrar password", + "tunnel token", + "TLS private key", + "Storage account secret", + "cloud backup credentials", + "Raw tester bearer token", + "token hash together with the raw token", + "Wallet private key", + "wallet recovery words", + "provider dashboard password", + "Raw tester token in GitHub or chat", + "owner env file contents", + "Any secret-bearing provider URL", + "wallet recovery material" + ], + "externalResources": [ + "Always-on Windows host, Linux host, or VPS", + "Local ignored env file or service environment", + "DNS provider or existing domain", + "TLS edge, reverse proxy, or tunnel", + "Persistent local disk, mounted volume, or owner-managed backup directory", + "Owner password manager or secret store", + "Base RPC provider or owner-operated Base node", + "Deployed pilot bridge contract details", + "Friends-and-family tester list", + "None beyond the configured launch resources" + ], + "stages": [ + { + "id": "always-on-service-host", + "title": "Keep the chain and private RPC running", + "status": "ready", + "ready": true, + "requiredEnvNames": [], + "optionalEnvNames": [], + "missingEnvNames": [], + "invalidEnvNames": [], + "upstreamMissingEnvNames": [], + "upstreamInvalidEnvNames": [], + "blockingEnvNames": [], + "blockedByReportNames": [], + "externalAccountsOrResources": [ + "Always-on Windows host, Linux host, or VPS" + ], + "ownerMustDo": [ + "Choose the host that will stay online and keep the FlowChain node/control-plane running." + ], + "ownerMustNotSend": [ + "Host login password", + "SSH private key" + ], + "validationCommands": [ + "npm run flowchain:service:status -- -AllowBlocked", + "npm run flowchain:service:monitor" + ], + "sourceReports": [] + }, + { + "id": "owner-env-file", + "title": "Fill the ignored local owner env file", + "status": "needs-owner-input", + "ready": false, + "requiredEnvNames": [ + "FLOWCHAIN_OWNER_ENV_FILE" + ], + "optionalEnvNames": [], + "missingEnvNames": [], + "invalidEnvNames": [], + "upstreamMissingEnvNames": [], + "upstreamInvalidEnvNames": [], + "blockingEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED", + "FLOWCHAIN_RPC_STATE_BACKUP_PATH", + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "blockedByReportNames": [ + "ownerEnvReadiness" + ], + "externalAccountsOrResources": [ + "Local ignored env file or service environment" + ], + "ownerMustDo": [ + "Run the template command, fill real values only on the launch host, and point FLOWCHAIN_OWNER_ENV_FILE at that file." + ], + "ownerMustNotSend": [ + "Owner env file contents", + "Provider URLs that carry account tokens" + ], + "validationCommands": [ + "npm run flowchain:owner-env:template", + "npm run flowchain:owner-env:readiness:validate", + "npm run flowchain:owner-env:readiness -- -AllowBlocked" + ], + "sourceReports": [] + }, + { + "id": "public-rpc-edge", + "title": "Expose repo-owned FlowChain RPC through a public HTTPS edge", + "status": "needs-owner-input", + "ready": false, + "requiredEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED" + ], + "optionalEnvNames": [], + "missingEnvNames": [], + "invalidEnvNames": [], + "upstreamMissingEnvNames": [], + "upstreamInvalidEnvNames": [], + "blockingEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED", + "FLOWCHAIN_RPC_STATE_BACKUP_PATH" + ], + "blockedByReportNames": [ + "publicRpc", + "publicDeploymentContract" + ], + "externalAccountsOrResources": [ + "DNS provider or existing domain", + "TLS edge, reverse proxy, or tunnel" + ], + "ownerMustDo": [ + "Create DNS or a tunnel hostname for the FlowChain RPC edge.", + "Terminate TLS at the edge.", + "Set exact allowed browser origins and a positive per-minute rate limit." + ], + "ownerMustNotSend": [ + "Registrar password", + "tunnel token", + "TLS private key" + ], + "validationCommands": [ + "npm run flowchain:public-rpc:check -- -AllowBlocked", + "npm run flowchain:public-rpc:validate", + "npm run flowchain:public-rpc:abuse-test" + ], + "sourceReports": [] + }, + { + "id": "state-backup-storage", + "title": "Provision durable state backup storage", + "status": "needs-owner-input", + "ready": false, + "requiredEnvNames": [ + "FLOWCHAIN_RPC_STATE_BACKUP_PATH" + ], + "optionalEnvNames": [], + "missingEnvNames": [], + "invalidEnvNames": [], + "upstreamMissingEnvNames": [], + "upstreamInvalidEnvNames": [], + "blockingEnvNames": [ + "FLOWCHAIN_RPC_STATE_BACKUP_PATH" + ], + "blockedByReportNames": [ + "backup" + ], + "externalAccountsOrResources": [ + "Persistent local disk, mounted volume, or owner-managed backup directory" + ], + "ownerMustDo": [ + "Create a writable persistent directory available to the FlowChain service process.", + "Keep the path local to the launch host or mounted as durable storage." + ], + "ownerMustNotSend": [ + "Storage account secret", + "cloud backup credentials" + ], + "validationCommands": [ + "npm run flowchain:backup:check -- -AllowBlocked", + "npm run flowchain:backup:restore:validate", + "npm run flowchain:backup:owner-path:dry-run" + ], + "sourceReports": [] + }, + { + "id": "tester-write-gateway", + "title": "Enable capped friends-and-family tester writes", + "status": "needs-owner-input", + "ready": false, + "requiredEnvNames": [ + "FLOWCHAIN_TESTER_WRITE_ENABLED", + "FLOWCHAIN_TESTER_WRITE_TOKEN_SHA256", + "FLOWCHAIN_TESTER_MAX_SEND_UNITS" + ], + "optionalEnvNames": [], + "missingEnvNames": [], + "invalidEnvNames": [], + "upstreamMissingEnvNames": [], + "upstreamInvalidEnvNames": [], + "blockingEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED", + "FLOWCHAIN_RPC_STATE_BACKUP_PATH", + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "blockedByReportNames": [ + "externalTester", + "externalTesterPacket", + "publicDeploymentContract" + ], + "externalAccountsOrResources": [ + "Owner password manager or secret store" + ], + "ownerMustDo": [ + "Run the tester token setup command to create or preserve the raw bearer token in ignored local storage.", + "Store only its SHA-256 digest in the owner env file.", + "Choose a small positive per-send test-unit cap." + ], + "ownerMustNotSend": [ + "Raw tester bearer token", + "token hash together with the raw token" + ], + "validationCommands": [ + "npm run flowchain:tester:token:setup", + "npm run flowchain:tester:gateway:e2e", + "npm run flowchain:external-tester:packet -- -AllowBlocked", + "npm run flowchain:external-tester:packet:validate" + ], + "sourceReports": [] + }, + { + "id": "base8453-bridge-pilot", + "title": "Configure capped Base 8453 bridge pilot observation", + "status": "needs-owner-input", + "ready": false, + "requiredEnvNames": [ + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "optionalEnvNames": [ + "FLOWCHAIN_BASE8453_CURSOR_STATE", + "FLOWCHAIN_BASE8453_TO_BLOCK" + ], + "missingEnvNames": [], + "invalidEnvNames": [], + "upstreamMissingEnvNames": [], + "upstreamInvalidEnvNames": [], + "blockingEnvNames": [ + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "blockedByReportNames": [ + "bridgeLive", + "bridgeInfra", + "publicDeploymentContract" + ], + "externalAccountsOrResources": [ + "Base RPC provider or owner-operated Base node", + "Deployed pilot bridge contract details" + ], + "ownerMustDo": [ + "Provide a Base chain 8453 HTTPS endpoint.", + "Provide deployed lockbox and supported-token addresses.", + "Choose the bootstrap from-block, confirmations, max deposit, total cap, and explicit capped-pilot acknowledgement." + ], + "ownerMustNotSend": [ + "Wallet private key", + "wallet recovery words", + "provider dashboard password" + ], + "validationCommands": [ + "npm run flowchain:bridge:live:check -- -AllowBlocked", + "npm run flowchain:bridge:infra:check -- -AllowBlocked", + "npm run flowchain:bridge:relayer:guardrail:validate", + "npm run flowchain:bridge:relayer:loop:validate" + ], + "sourceReports": [] + }, + { + "id": "friends-and-family-launch", + "title": "Release the external tester packet only after public gates pass", + "status": "needs-owner-input", + "ready": false, + "requiredEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED", + "FLOWCHAIN_RPC_STATE_BACKUP_PATH", + "FLOWCHAIN_TESTER_WRITE_ENABLED", + "FLOWCHAIN_TESTER_WRITE_TOKEN_SHA256", + "FLOWCHAIN_TESTER_MAX_SEND_UNITS", + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "optionalEnvNames": [], + "missingEnvNames": [], + "invalidEnvNames": [], + "upstreamMissingEnvNames": [], + "upstreamInvalidEnvNames": [], + "blockingEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED", + "FLOWCHAIN_RPC_STATE_BACKUP_PATH", + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "blockedByReportNames": [ + "externalTester", + "externalTesterPacket", + "publicDeploymentContract" + ], + "externalAccountsOrResources": [ + "Friends-and-family tester list" + ], + "ownerMustDo": [ + "Share wallet/tester instructions only after the packet report marks external sharing ready.", + "Keep per-send caps low for the first pilot." + ], + "ownerMustNotSend": [ + "Raw tester token in GitHub or chat", + "owner env file contents" + ], + "validationCommands": [ + "npm run flowchain:wallet:live-tester:e2e", + "npm run flowchain:external-tester:packet -- -AllowBlocked", + "npm run flowchain:external-tester:packet:validate", + "npm run flowchain:dashboard:ui:readiness" + ], + "sourceReports": [] + }, + { + "id": "final-go-live-audit", + "title": "Run final no-secret production audit before public use", + "status": "needs-owner-input", + "ready": false, + "requiredEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED", + "FLOWCHAIN_RPC_STATE_BACKUP_PATH", + "FLOWCHAIN_TESTER_WRITE_ENABLED", + "FLOWCHAIN_TESTER_WRITE_TOKEN_SHA256", + "FLOWCHAIN_TESTER_MAX_SEND_UNITS", + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "optionalEnvNames": [], + "missingEnvNames": [], + "invalidEnvNames": [], + "upstreamMissingEnvNames": [], + "upstreamInvalidEnvNames": [], + "blockingEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED", + "FLOWCHAIN_RPC_STATE_BACKUP_PATH", + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "blockedByReportNames": [ + "completionAudit", + "truthTable" + ], + "externalAccountsOrResources": [ + "None beyond the configured launch resources" + ], + "ownerMustDo": [ + "Run the aggregate gates after all owner values are configured.", + "Do not announce public readiness until completionReady is true and the truth table has no owner blockers." + ], + "ownerMustNotSend": [ + "Any secret-bearing provider URL", + "wallet recovery material" + ], + "validationCommands": [ + "npm run flowchain:live:cutover:rehearsal -- -AllowBlocked", + "npm run flowchain:completion:audit -- -AllowBlocked", + "npm run flowchain:truth-table -- -AllowBlocked", + "npm run flowchain:no-secret:scan" + ], + "sourceReports": [] + } + ], + "launchSequence": [ + { + "id": "owner-inputs", + "order": 1, + "title": "Validate ignored owner inputs", + "status": "not recorded", + "commands": [ + "npm run flowchain:owner-env:readiness -- -AllowBlocked", + "npm run flowchain:owner-inputs -- -AllowBlocked", + "npm run flowchain:owner-inputs:validate" + ], + "expectedReportPaths": [ + "docs/agent-runs/live-product-infra-rpc/owner-env-readiness-report.json", + "docs/agent-runs/live-product-infra-rpc/owner-inputs-report.json", + "docs/agent-runs/live-product-infra-rpc/owner-inputs-validation-report.json" + ], + "stopOnFailure": true + }, + { + "id": "render-public-rpc", + "order": 2, + "title": "Render public RPC edge artifacts", + "status": "not recorded", + "commands": [ + "npm run flowchain:public-rpc:deployment-bundle", + "npm run flowchain:public-rpc:deployment:automation", + "npm run flowchain:public-rpc:command-matrix", + "powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-public-rpc-deployment-automation.ps1 -Action Render -RenderDir -OwnerEnvFile -TlsCertificatePath -TlsCertificateKeyPath -NginxExe ", + "bash /owner-host-apply.sh plan", + "powershell -NoProfile -ExecutionPolicy Bypass -File /owner-host-apply.ps1 -Action Plan" + ], + "expectedReportPaths": [ + "docs/agent-runs/live-product-infra-rpc/public-rpc-deployment-bundle-report.json", + "docs/agent-runs/live-product-infra-rpc/public-rpc-deployment-automation-report.json", + "docs/agent-runs/live-product-infra-rpc/public-rpc-command-matrix-report.json" + ], + "stopOnFailure": true + }, + { + "id": "service-install", + "order": 3, + "title": "Plan reboot-persistent services", + "status": "not recorded", + "commands": [ + "npm run flowchain:service:install:systemd:validate", + "npm run flowchain:service:install:systemd -- -Action Plan -RenderDir ", + "npm run flowchain:service:install:systemd -- -Action Plan -RenderDir -StartBridgeRelayerLoop", + "bash /owner-host-apply.sh plan", + "powershell -NoProfile -ExecutionPolicy Bypass -File /owner-host-apply.ps1 -Action Plan" + ], + "expectedReportPaths": [ + "docs/agent-runs/live-product-infra-rpc/systemd-service-install-validation-report.json" + ], + "stopOnFailure": true + }, + { + "id": "owner-host-apply", + "order": 4, + "title": "Apply owner-host public RPC edge", + "status": "not recorded", + "commands": [ + "bash /owner-host-apply.sh apply", + "powershell -NoProfile -ExecutionPolicy Bypass -File /owner-host-apply.ps1 -Action Apply" + ], + "expectedReportPaths": [ + "docs/agent-runs/live-product-infra-rpc/systemd-service-install-report.json", + "docs/agent-runs/live-product-infra-rpc/service-status-report.json", + "docs/agent-runs/live-product-infra-rpc/public-rpc-validation-report.json", + "docs/agent-runs/live-product-infra-rpc/public-rpc-synthetic-canary-report.json", + "docs/agent-runs/live-product-infra-rpc/public-rpc-abuse-test-report.json", + "docs/agent-runs/live-product-infra-rpc/public-tester-gateway-e2e-report.json", + "docs/agent-runs/live-product-infra-rpc/live-service-tester-network-e2e-report.json", + "docs/agent-runs/live-product-infra-rpc/live-cutover-rehearsal-report.json", + "docs/agent-runs/live-product-infra-rpc/production-truth-table-report.json", + "docs/agent-runs/live-product-infra-rpc/no-secret-scan-report.json" + ], + "stopOnFailure": true + }, + { + "id": "local-service", + "order": 5, + "title": "Prove live service health", + "status": "not recorded", + "commands": [ + "npm run flowchain:service:status", + "npm run flowchain:service:monitor -- -DurationSeconds 300 -PollSeconds 30" + ], + "expectedReportPaths": [ + "docs/agent-runs/live-product-infra-rpc/service-status-report.json", + "docs/agent-runs/live-product-infra-rpc/service-monitor-report.json" + ], + "stopOnFailure": true + }, + { + "id": "public-rpc", + "order": 6, + "title": "Validate public RPC exposure", + "status": "not recorded", + "commands": [ + "npm run flowchain:public-rpc:check -- -AllowBlocked", + "npm run flowchain:public-rpc:validate", + "npm run flowchain:public-rpc:synthetic-canary -- -AllowBlocked", + "npm run flowchain:public-rpc:abuse-test" + ], + "expectedReportPaths": [ + "docs/agent-runs/live-product-infra-rpc/public-rpc-readiness-report.json", + "docs/agent-runs/live-product-infra-rpc/public-rpc-validation-report.json", + "docs/agent-runs/live-product-infra-rpc/public-rpc-synthetic-canary-report.json", + "docs/agent-runs/live-product-infra-rpc/public-rpc-abuse-test-report.json" + ], + "stopOnFailure": true + }, + { + "id": "backup", + "order": 7, + "title": "Prove backup and restore", + "status": "not recorded", + "commands": [ + "npm run flowchain:backup:check -- -AllowBlocked", + "npm run flowchain:backup:restore:validate", + "npm run flowchain:backup:owner-path:dry-run" + ], + "expectedReportPaths": [ + "docs/agent-runs/live-product-infra-rpc/backup-readiness-report.json", + "docs/agent-runs/live-product-infra-rpc/backup-restore-validation-report.json", + "docs/agent-runs/live-product-infra-rpc/backup-owner-path-dry-run-report.json" + ], + "stopOnFailure": true + }, + { + "id": "bridge", + "order": 8, + "title": "Harden bridge relayer pilot", + "status": "not recorded", + "commands": [ + "npm run flowchain:bridge:live:check -- -AllowBlocked", + "npm run flowchain:bridge:infra:check -- -AllowBlocked", + "npm run flowchain:bridge:command-matrix", + "npm run flowchain:bridge:relayer:guardrail:validate", + "npm run flowchain:bridge:relayer:loop:validate", + "npm run flowchain:bridge:relayer:once -- -AllowBlocked", + "npm run flowchain:bridge:reconciliation" + ], + "expectedReportPaths": [ + "docs/agent-runs/live-product-infra-rpc/bridge-live-readiness-report.json", + "docs/agent-runs/live-product-infra-rpc/bridge-infra-readiness-report.json", + "docs/agent-runs/live-product-infra-rpc/bridge-command-matrix-report.json", + "docs/agent-runs/live-product-infra-rpc/bridge-relayer-guardrail-validation-report.json", + "docs/agent-runs/live-product-infra-rpc/bridge-relayer-loop-validation-report.json", + "docs/agent-runs/live-product-infra-rpc/bridge-relayer-once-report.json", + "docs/agent-runs/live-product-infra-rpc/bridge-reconciliation-report.json" + ], + "stopOnFailure": true + }, + { + "id": "testers", + "order": 9, + "title": "Validate external tester launch", + "status": "not recorded", + "commands": [ + "npm run flowchain:tester:token:setup", + "npm run flowchain:tester:gateway:e2e", + "npm run flowchain:wallet:live-tester:e2e", + "npm run flowchain:external-tester:packet -- -AllowBlocked", + "npm run flowchain:external-tester:packet:validate", + "npm run flowchain:external-tester:client:validate", + "npm run flowchain:tester:evidence:validate" + ], + "expectedReportPaths": [ + "docs/agent-runs/live-product-infra-rpc/tester-write-token-setup-report.json", + "docs/agent-runs/live-product-infra-rpc/public-tester-gateway-e2e-report.json", + "docs/agent-runs/live-product-infra-rpc/live-service-tester-network-e2e-report.json", + "docs/agent-runs/live-product-infra-rpc/external-tester-packet-report.json", + "docs/agent-runs/live-product-infra-rpc/external-tester-packet-validation-report.json", + "docs/agent-runs/live-product-infra-rpc/external-tester-client-validation-report.json", + "docs/agent-runs/live-product-infra-rpc/external-tester-evidence-validation-report.json" + ], + "stopOnFailure": true + }, + { + "id": "final-audit", + "order": 10, + "title": "Run release gates", + "status": "not recorded", + "commands": [ + "npm run flowchain:public-deployment:contract -- -AllowBlocked", + "npm run flowchain:live:cutover:rehearsal -- -AllowBlocked", + "npm run flowchain:completion:audit -- -AllowBlocked", + "npm run flowchain:truth-table -- -AllowBlocked", + "npm run flowchain:no-secret:scan" + ], + "expectedReportPaths": [ + "docs/agent-runs/live-product-infra-rpc/public-deployment-contract-report.json", + "docs/agent-runs/live-product-infra-rpc/live-cutover-rehearsal-report.json", + "docs/agent-runs/live-product-infra-rpc/flowchain-completion-audit-report.json", + "docs/agent-runs/live-product-infra-rpc/production-truth-table-report.json", + "docs/agent-runs/live-product-infra-rpc/no-secret-scan-report.json" + ], + "stopOnFailure": true + } + ], + "rollbackCommands": [ + "npm run flowchain:ops:snapshot -- -AllowBlocked", + "npm run flowchain:service:status", + "npm run flowchain:service:restart -- -LiveProfile", + "npm run flowchain:service:stop", + "bash /owner-host-apply.sh rollback", + "powershell -NoProfile -ExecutionPolicy Bypass -File /owner-host-apply.ps1 -Action Rollback", + "npm run flowchain:emergency:stop-local", + "npm run flowchain:bridge:emergency-stop", + "npm run flowchain:public-deployment:contract -- -AllowBlocked" + ], + "checks": { + "packageScriptPresent": true, + "activationPlanLoaded": true, + "activationPlanPassed": true, + "signupChecklistLoaded": true, + "signupChecklistPassed": true, + "ownerInputsLoaded": true, + "truthTableLoaded": true, + "stageDeckPresent": true, + "stageCountMinimumMet": true, + "everyStageHasValidationCommand": true, + "everyStageHasOwnerMustNotSend": true, + "nonReadyStagesExplainBlockers": true, + "requiredEnvCoverageComplete": true, + "requiredAndOptionalOwnerInputsSeparated": true, + "neededNowExcludesOptionalOwnerInputs": true, + "knownOwnerInputBlockersOnly": true, + "nextOwnerInputsPresentWhenBlocked": true, + "nextCommandsPresent": true, + "launchSequencePresent": true, + "launchSequenceEveryStepHasCommands": true, + "launchSequenceEveryStepHasExpectedStatuses": true, + "launchSequenceEveryStepHasExpectedReportPath": true, + "launchSequenceExpectedReportPathsScoped": true, + "launchSequenceEveryStepStopsOnFailure": true, + "launchSequenceCoversOwnerEnvReadiness": true, + "launchSequenceCoversPublicRpcRender": true, + "launchSequenceCoversOwnerHostApplyPlan": true, + "launchSequenceCoversOwnerHostApplyExecution": true, + "launchSequenceCoversWindowsOwnerHostApplyPlan": true, + "launchSequenceCoversWindowsOwnerHostApplyExecution": true, + "launchSequenceCoversSystemdInstallPlan": true, + "launchSequenceCoversServiceMonitor": true, + "launchSequenceCoversPublicRpcCanary": true, + "launchSequenceCoversBackupRestore": true, + "launchSequenceCoversBridgeRelayer": true, + "launchSequenceCoversTesterPacket": true, + "launchSequenceCoversCutoverAudit": true, + "launchSequenceCoversTruthAndNoSecret": true, + "launchSequenceCommandsAvoidInlineEnvAssignment": true, + "launchSequenceCommandsAvoidUrls": true, + "launchSequencePackageScriptsPresent": true, + "rollbackCommandsPresent": true, + "rollbackCoversLocalStop": true, + "rollbackCoversBridgeEmergencyStop": true, + "rollbackCoversOpsSnapshot": true, + "rollbackCoversOwnerHostApplyRollback": true, + "rollbackCoversWindowsOwnerHostApplyRollback": true, + "rollbackPackageScriptsPresent": true, + "releaseClaimBlockedUntilTruthPassed": true, + "packetShareBlockedUntilReady": true, + "envValuesPrintedFalse": true, + "noSecrets": true, + "broadcastsFalse": true, + "secretMarkerFindingsEmpty": true + }, + "failedChecks": [], + "noSecrets": true, + "broadcasts": false, + "envValuesPrinted": false + }, + "ownerNeedsNow": { + "status": "passed", + "launchReadinessStatus": "blocked-owner-input", + "releaseReady": false, + "deploymentReady": false, + "packetShareable": false, + "completionReady": false, + "latestHeight": "112761", + "finalizedHeight": "112761", + "groupCount": "4", + "neededNowGroupCount": "3", + "readyGroupCount": "1", + "nextOwnerInputNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED", + "FLOWCHAIN_RPC_STATE_BACKUP_PATH", + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "missingRequiredEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED", + "FLOWCHAIN_RPC_STATE_BACKUP_PATH", + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "missingOptionalEnvNames": [ + "FLOWCHAIN_BASE8453_TO_BLOCK" + ], + "invalidEnvNames": [], + "groups": [ + { + "id": "public-rpc-edge", + "title": "Public RPC edge", + "status": "needs-owner-input", + "ready": false, + "whyNeeded": "Friends and family need a public HTTPS RPC endpoint that proxies to the private FlowChain origin without exposing local-only paths.", + "ownerAction": "Pick the public RPC URL, configure TLS termination, set exact HTTPS browser origins, and choose a positive per-minute rate limit.", + "envNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED" + ], + "missingEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED" + ], + "invalidEnvNames": [], + "unknownEnvNames": [], + "validationCommands": [ + "npm run flowchain:public-rpc:check -- -AllowBlocked", + "npm run flowchain:public-rpc:synthetic-canary -- -AllowBlocked", + "npm run flowchain:public-deployment:contract -- -AllowBlocked" + ], + "doNotSend": [ + "provider dashboard password", + "TLS private key", + "tunnel token", + "origin wildcard" + ] + }, + { + "id": "backup-storage", + "title": "Backup storage", + "status": "needs-owner-input", + "ready": false, + "whyNeeded": "The public launch must prove the live state can be snapshotted, restored, and tamper-checked from persistent storage.", + "ownerAction": "Create an existing writable backup directory on the always-on host and point the owner env file at it.", + "envNames": [ + "FLOWCHAIN_RPC_STATE_BACKUP_PATH" + ], + "missingEnvNames": [ + "FLOWCHAIN_RPC_STATE_BACKUP_PATH" + ], + "invalidEnvNames": [], + "unknownEnvNames": [], + "validationCommands": [ + "npm run flowchain:backup:owner-path:dry-run", + "npm run flowchain:backup:restore:validate", + "npm run flowchain:backup:check -- -AllowBlocked" + ], + "doNotSend": [ + "cloud storage secret", + "backup provider account password", + "private backup credentials" + ] + }, + { + "id": "tester-write-gateway", + "title": "Tester write gateway", + "status": "ready", + "ready": true, + "whyNeeded": "External testers need capped wallet create/faucet/send access without putting the raw bearer token in GitHub or chat.", + "ownerAction": "Keep tester writes enabled, store only the SHA-256 token digest in the ignored owner env file, and maintain a small per-send cap.", + "envNames": [ + "FLOWCHAIN_TESTER_WRITE_ENABLED", + "FLOWCHAIN_TESTER_WRITE_TOKEN_SHA256", + "FLOWCHAIN_TESTER_MAX_SEND_UNITS" + ], + "missingEnvNames": [], + "invalidEnvNames": [], + "unknownEnvNames": [], + "validationCommands": [ + "npm run flowchain:tester:token:setup", + "npm run flowchain:tester:gateway:e2e", + "npm run flowchain:external-tester:packet -- -AllowBlocked" + ], + "doNotSend": [ + "raw tester bearer token", + "owner env file contents", + "token hash together with the raw token" + ] + }, + { + "id": "base8453-bridge", + "title": "Base 8453 bridge", + "status": "needs-owner-input", + "ready": false, + "whyNeeded": "Bridge-funded testing stays closed until the Base 8453 observer, lockbox, asset, block range, pilot caps, and confirmations are configured.", + "ownerAction": "Provide the Base RPC endpoint, lockbox and supported token addresses, asset decimals, start block, capped pilot acknowledgement, deposit cap, total cap, and confirmation depth.", + "envNames": [ + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "missingEnvNames": [ + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "invalidEnvNames": [], + "unknownEnvNames": [], + "validationCommands": [ + "npm run flowchain:bridge:live:check -- -AllowBlocked", + "npm run flowchain:bridge:infra:check -- -AllowBlocked", + "npm run flowchain:bridge:relayer:once -- -AllowBlocked", + "npm run flowchain:bridge:release:evidence:validate", + "npm run flowchain:bridge:reconciliation" + ], + "doNotSend": [ + "wallet seed words", + "deployer private key", + "provider API secret pasted in chat", + "unbounded pilot caps" + ] + } + ], + "neededNowGroups": [ + { + "id": "public-rpc-edge", + "title": "Public RPC edge", + "status": "needs-owner-input", + "ready": false, + "whyNeeded": "Friends and family need a public HTTPS RPC endpoint that proxies to the private FlowChain origin without exposing local-only paths.", + "ownerAction": "Pick the public RPC URL, configure TLS termination, set exact HTTPS browser origins, and choose a positive per-minute rate limit.", + "envNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED" + ], + "missingEnvNames": [ + "FLOWCHAIN_RPC_PUBLIC_URL", + "FLOWCHAIN_RPC_ALLOWED_ORIGINS", + "FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE", + "FLOWCHAIN_RPC_TLS_TERMINATED" + ], + "invalidEnvNames": [], + "unknownEnvNames": [], + "validationCommands": [ + "npm run flowchain:public-rpc:check -- -AllowBlocked", + "npm run flowchain:public-rpc:synthetic-canary -- -AllowBlocked", + "npm run flowchain:public-deployment:contract -- -AllowBlocked" + ], + "doNotSend": [ + "provider dashboard password", + "TLS private key", + "tunnel token", + "origin wildcard" + ] + }, + { + "id": "backup-storage", + "title": "Backup storage", + "status": "needs-owner-input", + "ready": false, + "whyNeeded": "The public launch must prove the live state can be snapshotted, restored, and tamper-checked from persistent storage.", + "ownerAction": "Create an existing writable backup directory on the always-on host and point the owner env file at it.", + "envNames": [ + "FLOWCHAIN_RPC_STATE_BACKUP_PATH" + ], + "missingEnvNames": [ + "FLOWCHAIN_RPC_STATE_BACKUP_PATH" + ], + "invalidEnvNames": [], + "unknownEnvNames": [], + "validationCommands": [ + "npm run flowchain:backup:owner-path:dry-run", + "npm run flowchain:backup:restore:validate", + "npm run flowchain:backup:check -- -AllowBlocked" + ], + "doNotSend": [ + "cloud storage secret", + "backup provider account password", + "private backup credentials" + ] + }, + { + "id": "base8453-bridge", + "title": "Base 8453 bridge", + "status": "needs-owner-input", + "ready": false, + "whyNeeded": "Bridge-funded testing stays closed until the Base 8453 observer, lockbox, asset, block range, pilot caps, and confirmations are configured.", + "ownerAction": "Provide the Base RPC endpoint, lockbox and supported token addresses, asset decimals, start block, capped pilot acknowledgement, deposit cap, total cap, and confirmation depth.", + "envNames": [ + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "missingEnvNames": [ + "FLOWCHAIN_PILOT_OPERATOR_ACK", + "FLOWCHAIN_BASE8453_RPC_URL", + "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", + "FLOWCHAIN_BASE8453_SUPPORTED_TOKEN", + "FLOWCHAIN_BASE8453_ASSET_DECIMALS", + "FLOWCHAIN_BASE8453_FROM_BLOCK", + "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI", + "FLOWCHAIN_PILOT_TOTAL_CAP_WEI", + "FLOWCHAIN_PILOT_CONFIRMATIONS" + ], + "invalidEnvNames": [], + "unknownEnvNames": [], + "validationCommands": [ + "npm run flowchain:bridge:live:check -- -AllowBlocked", + "npm run flowchain:bridge:infra:check -- -AllowBlocked", + "npm run flowchain:bridge:relayer:once -- -AllowBlocked", + "npm run flowchain:bridge:release:evidence:validate", + "npm run flowchain:bridge:reconciliation" + ], + "doNotSend": [ + "wallet seed words", + "deployer private key", + "provider API secret pasted in chat", + "unbounded pilot caps" + ] + } + ], + "readyGroups": [ + { + "id": "tester-write-gateway", + "title": "Tester write gateway", + "status": "ready", + "ready": true, + "whyNeeded": "External testers need capped wallet create/faucet/send access without putting the raw bearer token in GitHub or chat.", + "ownerAction": "Keep tester writes enabled, store only the SHA-256 token digest in the ignored owner env file, and maintain a small per-send cap.", + "envNames": [ + "FLOWCHAIN_TESTER_WRITE_ENABLED", + "FLOWCHAIN_TESTER_WRITE_TOKEN_SHA256", + "FLOWCHAIN_TESTER_MAX_SEND_UNITS" + ], + "missingEnvNames": [], + "invalidEnvNames": [], + "unknownEnvNames": [], + "validationCommands": [ + "npm run flowchain:tester:token:setup", + "npm run flowchain:tester:gateway:e2e", + "npm run flowchain:external-tester:packet -- -AllowBlocked" + ], + "doNotSend": [ + "raw tester bearer token", + "owner env file contents", + "token hash together with the raw token" + ] + } + ], + "checks": { + "packageScriptPresent": true, + "ownerInputsLoaded": true, + "ownerGoLiveHandoffLoaded": true, + "activationPlanLoaded": true, + "truthTableLoaded": true, + "publicDeploymentContractLoaded": true, + "reportStatusDeckPresent": true, + "groupCountMinimumMet": true, + "requiredEnvCoverageComplete": true, + "groupCommandsPresent": true, + "groupDoNotSendPresent": true, + "deploymentGateSummaryPresent": true, + "releaseEvidenceGateCaptured": true, + "externalTesterGateCapturesReleaseEvidence": true, + "baseBridgeValidationIncludesReleaseEvidence": true, + "knownNeededNowOwnerInputsOnly": true, + "optionalOwnerInputsExcludedFromNeededNow": true, + "nextOwnerInputsPresentWhenBlocked": true, + "neededNowGroupsPresentWhenBlocked": true, + "readyTesterGatewayCaptured": true, + "noReleaseReadyClaimWhileBlocked": true, + "publicSharingBlockedUntilReady": true, + "envValuesPrintedFalse": true, + "noSecrets": true, + "broadcastsFalse": true, + "secretMarkerFindingsEmpty": true + }, + "failedChecks": [], + "noSecrets": true, + "broadcasts": false, + "envValuesPrinted": false + }, "testerLaunch": { "status": "blocked", "readinessStatus": "blocked", @@ -337,8 +2639,15 @@ }, "externalSharingReady": false, "localTesterRehearsalReady": true, + "liveInfraReady": false, + "serviceReady": true, + "chainProducing": true, + "missingOwnerInputCount": "14", + "testerCount": "4", "publicTesterGatewayReady": true, + "publicTesterGatewayFresh": true, "gatewayConfigured": true, + "testerWalletNetworkReady": true, "testerNetworkFresh": true, "faucetRouteValidated": true, "packetExecutableSmokeValidated": true, @@ -421,6 +2730,9 @@ ], "explorer": [ "npm run flowchain:public-deployment:contract -- -AllowBlocked" + ], + "publicRpc": [ + "npm run flowchain:public-rpc:command-matrix" ] }, "envValuesPrinted": false, @@ -510,7 +2822,7 @@ "label": "Private L1 origin", "status": "passed", "summary": "The public deployment origin service is running privately in live profile before any owner TLS edge is considered shareable.", - "evidence": "serviceStatus=passed, privateBind=True, latestHeight=73312, finalizedHeight=73312", + "evidence": "serviceStatus=passed, privateBind=True, latestHeight=113073, finalizedHeight=113073", "commands": [ "npm run flowchain:service:status" ], @@ -531,8 +2843,8 @@ "id": "service-autorecovery", "label": "Service autorecovery", "status": "passed", - "summary": "The owner service has an autorecovery supervisor and an isolated recovery drill proving control-plane restart without touching live state.", - "evidence": "supervisorValidation=passed, restartAttempts=1", + "summary": "The owner service has an autorecovery supervisor and an isolated recovery drill proving node, control-plane, and bridge-relayer-loop restart without touching live state.", + "evidence": "supervisorValidation=passed, restartAttempts=1, nodeRestartAttempts=1, relayerRestartAttempts=1, nodeRecovered=True, relayerRecovered=True", "commands": [ "npm run flowchain:service:supervisor:validate", "npm run flowchain:service:supervisor -- -IntervalSeconds 30 -MaxRestartAttempts 3" @@ -557,12 +2869,13 @@ "id": "public-rpc-edge", "label": "Public RPC edge", "status": "blocked", - "summary": "The owner TLS edge must pass endpoint, CORS, rate-limit, readiness, and response-hygiene checks before sharing.", - "evidence": "publicRpcStatus=blocked, publicRpcReady=False, validationStatus=passed, validationPassed=True, abuseStatus=passed, abusePassed=True", + "summary": "The owner TLS edge must pass endpoint, CORS, live security-header, rate-limit, readiness, and response-hygiene checks before sharing.", + "evidence": "publicRpcStatus=blocked, publicRpcReady=False, canaryStatus=blocked, canaryReady=False, canaryScheduleReady=True, validationStatus=passed, validationPassed=True, abuseStatus=passed, abusePassed=True", "commands": [ "npm run flowchain:public-rpc:validate", - "npm run flowchain:public-rpc:abuse-test", - "npm run flowchain:public-rpc:check" + "npm run flowchain:public-rpc:synthetic-canary -- -AllowBlocked", + "npm run flowchain:public-rpc:canary:schedule:validate", + "npm run flowchain:public-rpc:abuse-test" ], "blockers": [ "FLOWCHAIN_RPC_PUBLIC_URL", @@ -571,6 +2884,18 @@ "FLOWCHAIN_RPC_TLS_TERMINATED" ] }, + { + "id": "public-rpc-canary-schedule-automation", + "label": "RPC canary schedule", + "status": "passed", + "summary": "The owner host has no-secret Windows Scheduled Task and Linux systemd timer plans for recurring read-only public RPC synthetic canary checks without host mutation or external delivery credentials.", + "evidence": "canaryScheduleValidation=passed, windowsPlan=True, systemdTimer=True, ownerEnv=True, noMutation=True, noExternalDelivery=True", + "commands": [ + "npm run flowchain:public-rpc:canary:schedule:validate", + "npm run flowchain:public-rpc:synthetic-canary -- -AllowBlocked" + ], + "blockers": [] + }, { "id": "state-backup", "label": "State backup proof", @@ -591,13 +2916,25 @@ "id": "state-backup-owner-path-dry-run", "label": "Backup dry run", "status": "passed", - "summary": "Backup readiness has an owner-path dry run that injects an ignored local backup path into the production backup gate and proves snapshot plus restore evidence without using the owner's real directory.", - "evidence": "dryRun=passed, failedChecks=0, readiness=passed, snapshotProof=True, restoreProof=True", + "summary": "Backup readiness has an owner-path dry run that injects an ignored local backup path into the production backup gate and proves snapshot, retention, and restore evidence without using the owner's real directory.", + "evidence": "dryRun=passed, failedChecks=0, readiness=passed, snapshotProof=True, retention=True, restoreProof=True", "commands": [ "npm run flowchain:backup:owner-path:dry-run" ], "blockers": [] }, + { + "id": "base8453-bridge-reconciliation-schedule-automation", + "label": "Bridge reconciliation schedule", + "status": "passed", + "summary": "The owner host has no-secret Windows Scheduled Task and Linux systemd timer plans for recurring bridge reconciliation checks without host mutation or external delivery credentials.", + "evidence": "reconciliationSchedule=passed, windowsPlan=True, systemdTimer=True, ownerEnv=True, noMutation=True, noExternalDelivery=True", + "commands": [ + "npm run flowchain:bridge:reconciliation:schedule:validate", + "npm run flowchain:bridge:reconciliation" + ], + "blockers": [] + }, { "id": "base8453-bridge-edge", "label": "Base 8453 bridge edge", @@ -624,8 +2961,8 @@ "id": "base8453-bridge-relayer-queue", "label": "Bridge relayer queue", "status": "blocked", - "summary": "The bridge relayer has a no-broadcast one-shot path plus an isolated loop validation that checks owner guardrails, proves fresh no-secret/no-broadcast loop health, observes Base 8453 deposits with a staged cursor, filters replays, queues new credits into the running L1, waits for main-state credit evidence, records handoff-to-spendable latency, only commits the Base cursor after safe proof, and proves missing-owner-input runs leave cursor state untouched.", - "evidence": "relayer=blocked, guardrail=passed, loopValidation=passed, loopFailedChecks=0, loopReportHealthy=True, observed=0, new=0, queued=0, applied=0, latencyGate=not-run, cursorCommitRequired=True, cursorCommitted=False, cursorReason=not-run, handoffToSpendableSeconds=", + "summary": "The bridge relayer has a no-broadcast one-shot path plus an isolated loop validation that checks owner guardrails, proves fresh no-secret/no-broadcast loop health, observes Base 8453 deposits with a staged cursor, filters replays, queues new credits into the running L1, waits for main-state credit evidence, records handoff-to-spendable latency, only commits the Base cursor after safe proof, and proves missing-owner-input runs plus standalone observation leave final cursor state untouched.", + "evidence": "relayer=blocked, onceChecksReady=True, onceFailedChecks=0, guardrail=passed, directObserveStagedDefault=True, directObserveCursorNotFinal=True, loopValidation=passed, loopFailedChecks=0, loopReportHealthy=True, observed=0, new=0, queued=0, applied=0, latencyGate=not-run, cursorCommitRequired=True, cursorCommitted=False, cursorReason=not-run, handoffToSpendableSeconds=", "commands": [ "npm run flowchain:bridge:relayer:once", "npm run flowchain:bridge:relayer:guardrail:validate", @@ -643,12 +2980,34 @@ "FLOWCHAIN_PILOT_CONFIRMATIONS" ] }, + { + "id": "base8453-bridge-runtime-credit-proof", + "label": "Bridge runtime credit", + "status": "passed", + "summary": "The deployment has a local production-shaped proof that a Base 8453 bridge handoff becomes spendable on L1 within the settlement target, can be spent by the credited wallet, rejects replay, and survives restart/export/import without live broadcasts.", + "evidence": "runtimeCredit=passed, failedChecks=0, missingRuntimeChecks=0, falseRuntimeChecks=0, latencyGate=passed, queueToSpendableSeconds=0.693, transferSeconds=0.641", + "commands": [ + "npm run flowchain:bridge:runtime-credit:validate" + ], + "blockers": [] + }, + { + "id": "base8453-bridge-release-evidence-validation", + "label": "Bridge release evidence", + "status": "passed", + "summary": "Bridge withdrawal/release evidence must prove the Base 8453 release method, amount, token, recipient, source chain, destination asset, production-ready flag, local-only boundary, and no-broadcast/no-secret constraints before bridge-funded tester launch.", + "evidence": "releaseEvidence=passed, cases=12, failedChecks=0, missingChecks=0, failedCases=0, missingCases=0, secretFindings=0, broadcasts=False, noSecrets=True", + "commands": [ + "npm run flowchain:bridge:release:evidence:validate" + ], + "blockers": [] + }, { "id": "external-tester-sharing", "label": "External tester packet", "status": "blocked", "summary": "External tester packet and machine-readable connection pack must remain not-shareable until owner public RPC, backup, and bridge gates pass, and they must rely on fresh tester-wallet evidence plus authenticated tester faucet/send gateway smoke.", - "evidence": "externalTester=blocked, localTesterRehearsalReady=True, testerNetworkFresh=True, publicTesterGatewayReady=True, faucetRoute=True, packetSmoke=True, testerFaucet=True, capRejected=True, connectPackReady=True, externalSharingReady=False, packet=blocked, packetShareable=False", + "evidence": "externalTester=blocked, localTesterRehearsalReady=True, testerNetworkFresh=True, publicTesterGatewayReady=True, faucetRoute=True, packetSmoke=True, testerFaucet=True, capRejected=True, connectPackReady=True, bridgeReleaseEvidenceReady=True, externalSharingReady=False, packet=blocked, packetShareable=False", "commands": [ "npm run flowchain:tester:readiness", "npm run flowchain:external-tester:packet" @@ -699,26 +3058,26 @@ "npm run flowchain:service:monitor -- -DurationSeconds 300 -PollSeconds 30", "npm run flowchain:service:install:validate", "npm run flowchain:service:install:windows -- -Action Plan", + "npm run flowchain:service:install:systemd:validate", + "npm run flowchain:service:install:systemd -- -Action Plan -RenderDir ", + "npm run flowchain:service:install:systemd -- -Action Plan -RenderDir -StartBridgeRelayerLoop", "npm run flowchain:ops:snapshot -- -AllowBlocked", "npm run flowchain:ops:alerts -- -AllowBlocked", + "npm run flowchain:ops:metrics:export -- -AllowBlocked", "npm run flowchain:ops:alerts:install:validate", - "npm run flowchain:ops:escalation:dry-run -- -AllowBlocked", - "npm run flowchain:ops:alerts:install:windows -- -Action Plan", - "npm run flowchain:owner:onboarding", - "npm run flowchain:owner-env:template", - "npm run flowchain:owner-inputs" + "npm run flowchain:ops:metrics:install:validate" ], "rollback": [ "npm run flowchain:service:status", "npm run flowchain:service:install:windows -- -Action Status", "npm run flowchain:service:install:windows -- -Action Uninstall", + "npm run flowchain:service:install:systemd -- -Action Status", + "npm run flowchain:service:install:systemd -- -Action Uninstall", "npm run flowchain:backup:install:windows -- -Action Status", "npm run flowchain:backup:install:windows -- -Action Uninstall", - "npm run flowchain:ops:alerts:install:windows -- -Action Status", - "npm run flowchain:ops:alerts:install:windows -- -Action Uninstall", - "npm run flowchain:service:stop", - "npm run flowchain:service:restart -- -LiveProfile", - "npm run flowchain:emergency:stop-local" + "npm run flowchain:backup:install:systemd -- -Action Status", + "npm run flowchain:backup:install:systemd -- -Action Uninstall", + "npm run flowchain:ops:alerts:install:windows -- -Action Status" ] }, "sourceReports": [ @@ -726,139 +3085,235 @@ "fileName": "public-deployment-contract-report.json", "schema": "flowchain.public_deployment_contract_report.v0", "status": "blocked", - "generatedAt": "2026-05-18T15:18:49.2356414Z" + "generatedAt": "2026-05-21T13:56:36.8835297Z" }, { "fileName": "flowchain-live-infra-check-report.json", "schema": "flowchain.live_infra_check_report.v0", "status": "blocked", - "generatedAt": "2026-05-18T15:21:26.2767425Z" + "generatedAt": "2026-05-21T13:33:55.4910084Z" }, { "fileName": "service-status-report.json", "schema": "flowchain.service_status_report.v0", "status": "passed", - "generatedAt": "2026-05-18T15:22:05.2861073Z" + "generatedAt": "2026-05-21T14:21:15.9641750Z" }, { "fileName": "service-monitor-report.json", "schema": "flowchain.service_monitor_report.v0", "status": "passed", - "generatedAt": "2026-05-18T15:16:04.8496922Z" + "generatedAt": "2026-05-21T10:47:26.8728380Z" + }, + { + "fileName": "service-supervisor-validation-report.json", + "schema": "flowchain.service_supervisor_validation_report.v0", + "status": "passed", + "generatedAt": "2026-05-21T13:55:24.1478552Z" + }, + { + "fileName": "service-install-validation-report.json", + "schema": "flowchain.service_install_validation_report.v0", + "status": "passed", + "generatedAt": "2026-05-21T13:58:33.1880095Z" + }, + { + "fileName": "systemd-service-install-validation-report.json", + "schema": "flowchain.systemd_service_install_validation_report.v0", + "status": "passed", + "generatedAt": "2026-05-21T13:58:41.5038701Z" }, { "fileName": "public-rpc-deployment-bundle-report.json", "schema": "flowchain.public_rpc_deployment_bundle_report.v3", "status": "passed", - "generatedAt": "2026-05-18T06:51:17.2713954Z" + "generatedAt": "2026-05-21T12:55:17.9614147Z" }, { "fileName": "public-rpc-deployment-automation-report.json", "schema": "flowchain.public_rpc_deployment_automation_report.v1", "status": "passed", - "generatedAt": "2026-05-18T06:51:20.1880423Z" + "generatedAt": "2026-05-21T12:55:28.3476019Z" }, { "fileName": "public-rpc-readiness-report.json", "schema": "flowchain.public_rpc_readiness_report.v0", "status": "blocked", - "generatedAt": "2026-05-18T15:20:06.8347091Z" + "generatedAt": "2026-05-21T13:32:42.5274082Z" + }, + { + "fileName": "public-rpc-command-matrix-report.json", + "schema": "flowchain.public_rpc_command_matrix_report.v0", + "status": "passed", + "generatedAt": "2026-05-21T06:00:23.2217487Z" + }, + { + "fileName": "public-rpc-canary-schedule-validation-report.json", + "schema": "flowchain.public_rpc_canary_schedule_validation_report.v0", + "status": "passed", + "generatedAt": "2026-05-21T11:01:44.4164111Z" }, { "fileName": "backup-readiness-report.json", "schema": "flowchain.backup_readiness_report.v1", "status": "blocked", - "generatedAt": "2026-05-18T15:21:18.9644465Z" + "generatedAt": "2026-05-21T13:33:08.3732188Z" }, { "fileName": "backup-owner-path-dry-run-report.json", "schema": "flowchain.backup_owner_path_dry_run_report.v1", "status": "passed", - "generatedAt": "2026-05-18T04:44:23.9884243Z" + "generatedAt": "2026-05-21T11:04:31.7512485Z" + }, + { + "fileName": "bridge-command-matrix-report.json", + "schema": "flowchain.bridge_command_matrix_report.v0", + "status": "passed", + "generatedAt": "2026-05-21T09:38:03.0901118Z" + }, + { + "fileName": "bridge-no-secret-audit-report.json", + "schema": "flowchain.bridge_no_secret_audit_report.v0", + "status": "passed", + "generatedAt": "2026-05-21T09:38:02.2054096Z" }, { "fileName": "bridge-relayer-once-report.json", "schema": "flowchain.bridge_relayer_once_report.v0", "status": "blocked", - "generatedAt": "2026-05-18T15:21:24.2522874Z" + "generatedAt": "2026-05-21T13:33:42.0253650Z" }, { "fileName": "bridge-relayer-guardrail-validation-report.json", "schema": "flowchain.bridge_relayer_guardrail_validation_report.v1", "status": "passed", - "generatedAt": "2026-05-18T04:31:12.2070538Z" + "generatedAt": "2026-05-21T11:05:15.0146500Z" }, { "fileName": "bridge-relayer-loop-validation-report.json", "schema": "flowchain.bridge_relayer_loop_validation_report.v1", "status": "passed", - "generatedAt": "2026-05-18T04:36:28.6180686Z" + "generatedAt": "2026-05-21T13:43:07.5387180Z" + }, + { + "fileName": "bridge-runtime-credit-validation-report.json", + "schema": "flowchain.bridge_runtime_credit_validation_report.v1", + "status": "passed", + "generatedAt": "2026-05-21T11:07:57.3233364Z" + }, + { + "fileName": "bridge-release-evidence-validation-report.json", + "schema": "flowchain.bridge_release_evidence_validation_report.v0", + "status": "passed", + "generatedAt": "2026-05-21T12:13:13.1139764Z" + }, + { + "fileName": "bridge-reconciliation-schedule-validation-report.json", + "schema": "flowchain.bridge_reconciliation_schedule_validation_report.v0", + "status": "passed", + "generatedAt": "2026-05-21T11:07:58.8465436Z" + }, + { + "fileName": "real-value-pilot-aggregate-report.json", + "schema": "flowchain.real_value_pilot.e2e_report.v0", + "status": "passed", + "generatedAt": "2026-05-21T01:10:10.7303969Z" }, { "fileName": "external-tester-packet-report.json", "schema": "flowchain.external_tester_packet_report.v0", "status": "blocked", - "generatedAt": "2026-05-18T15:23:34.1357530Z" + "generatedAt": "2026-05-21T13:34:02.7730218Z" }, { "fileName": "external-tester-connect-pack.json", "schema": "flowchain.external_tester_connect_pack.v0", "status": "blocked", - "generatedAt": "2026-05-18T15:23:34.1357530Z" + "generatedAt": "2026-05-21T13:34:02.7730218Z" }, { "fileName": "external-tester-readiness-report.json", "schema": "flowchain.external_tester_readiness_report.v0", "status": "blocked", - "generatedAt": "2026-05-18T15:23:31.9122762Z" + "generatedAt": "2026-05-21T13:33:57.9391914Z" }, { "fileName": "public-tester-gateway-e2e-report.json", "schema": "flowchain.public_tester_gateway_e2e_report.v0", "status": "passed", - "generatedAt": "2026-05-18T14:55:36.9733820Z" + "generatedAt": "2026-05-21T13:09:17.6556003Z" }, { "fileName": "ops-snapshot-report.json", "schema": "flowchain.ops_snapshot_report.v1", "status": "blocked", - "generatedAt": "2026-05-18T15:16:05.4387092Z" + "generatedAt": "2026-05-21T10:55:37.7392322Z" }, { "fileName": "ops-alert-rules-report.json", "schema": "flowchain.ops_alert_rules_report.v0", "status": "passed", - "generatedAt": "2026-05-18T15:16:06.7011437Z" + "generatedAt": "2026-05-21T13:24:05.8900607Z" + }, + { + "fileName": "ops-metrics-export-report.json", + "schema": "flowchain.ops_metrics_export_report.v0", + "status": "passed", + "generatedAt": "2026-05-21T13:24:18.7364249Z" }, { "fileName": "incident-drill-report.json", "schema": "flowchain.incident_drill_report.v0", "status": "passed", - "generatedAt": "2026-05-18T15:14:02.2188427Z" + "generatedAt": "2026-05-21T14:28:29.7640209Z" }, { "fileName": "alert-install-validation-report.json", "schema": "flowchain.alert_install_validation_report.v0", "status": "passed", - "generatedAt": "2026-05-18T06:51:03.8641137Z" + "generatedAt": "2026-05-21T10:56:05.1308999Z" }, { "fileName": "ops-escalation-dry-run-report.json", "schema": "flowchain.ops_escalation_dry_run_report.v0", "status": "passed", - "generatedAt": "2026-05-18T06:51:05.9119884Z" + "generatedAt": "2026-05-21T10:56:28.2106051Z" }, { "fileName": "owner-inputs-report.json", "schema": "flowchain.owner_inputs_report.v0", "status": "blocked", - "generatedAt": "2026-05-18T15:23:33.2844595Z" + "generatedAt": "2026-05-21T13:34:00.7758952Z" + }, + { + "fileName": "owner-env-template-report.json", + "schema": "flowchain.owner_env_template_report.v0", + "status": "passed", + "generatedAt": "2026-05-21T11:14:19.9280392Z" + }, + { + "fileName": "owner-activation-plan-report.json", + "schema": "flowchain.owner_activation_plan_report.v0", + "status": "passed", + "generatedAt": "2026-05-20T23:05:40.5458704+00:00" + }, + { + "fileName": "owner-go-live-handoff-report.json", + "schema": "flowchain.owner_go_live_handoff_report.v0", + "status": "passed", + "generatedAt": "2026-05-21T06:00:32.9112705Z" + }, + { + "fileName": "owner-needs-now-report.json", + "schema": "flowchain.owner_needs_now_report.v0", + "status": "passed", + "generatedAt": "2026-05-21T12:39:12.9550784Z" }, { "fileName": "no-secret-scan-report.json", "schema": "flowchain.no_secret_scan_report.v0", "status": "passed", - "generatedAt": "2026-05-18T17:48:56.2016268Z" + "generatedAt": "2026-05-21T14:19:03.7533090Z" } ], "envValuesPrinted": false, diff --git a/apps/dashboard/scripts/sync-fixtures.mjs b/apps/dashboard/scripts/sync-fixtures.mjs index 43e3e30e..4203ee3f 100644 --- a/apps/dashboard/scripts/sync-fixtures.mjs +++ b/apps/dashboard/scripts/sync-fixtures.mjs @@ -44,24 +44,40 @@ const liveReadinessReportCopies = [ "flowchain-live-infra-check-report.json", "service-status-report.json", "service-monitor-report.json", + "service-supervisor-validation-report.json", + "service-install-validation-report.json", + "systemd-service-install-validation-report.json", "public-rpc-deployment-bundle-report.json", "public-rpc-deployment-automation-report.json", "public-rpc-readiness-report.json", + "public-rpc-command-matrix-report.json", + "public-rpc-canary-schedule-validation-report.json", "backup-readiness-report.json", "backup-owner-path-dry-run-report.json", + "bridge-command-matrix-report.json", + "bridge-no-secret-audit-report.json", "bridge-relayer-once-report.json", "bridge-relayer-guardrail-validation-report.json", "bridge-relayer-loop-validation-report.json", + "bridge-runtime-credit-validation-report.json", + "bridge-release-evidence-validation-report.json", + "bridge-reconciliation-schedule-validation-report.json", + "real-value-pilot-aggregate-report.json", "external-tester-packet-report.json", "external-tester-connect-pack.json", "external-tester-readiness-report.json", "public-tester-gateway-e2e-report.json", "ops-snapshot-report.json", "ops-alert-rules-report.json", + "ops-metrics-export-report.json", "incident-drill-report.json", "alert-install-validation-report.json", "ops-escalation-dry-run-report.json", "owner-inputs-report.json", + "owner-env-template-report.json", + "owner-activation-plan-report.json", + "owner-go-live-handoff-report.json", + "owner-needs-now-report.json", "no-secret-scan-report.json", ]; @@ -71,10 +87,14 @@ const liveReadinessGateLabels = new Map([ ["service-autorecovery", "Service autorecovery"], ["service-install-automation", "Windows service install"], ["public-rpc-edge", "Public RPC edge"], + ["public-rpc-canary-schedule-automation", "RPC canary schedule"], ["state-backup", "State backup proof"], ["state-backup-owner-path-dry-run", "Backup dry run"], + ["base8453-bridge-reconciliation-schedule-automation", "Bridge reconciliation schedule"], ["base8453-bridge-edge", "Base 8453 bridge edge"], ["base8453-bridge-relayer-queue", "Bridge relayer queue"], + ["base8453-bridge-runtime-credit-proof", "Bridge runtime credit"], + ["base8453-bridge-release-evidence-validation", "Bridge release evidence"], ["external-tester-sharing", "External tester packet"], ["public-tester-write-gateway", "Tester write gateway"], ["no-secret-no-broadcast", "No secrets or broadcasts"], @@ -132,6 +152,10 @@ function recordEntries(value) { return value && typeof value === "object" && !Array.isArray(value) ? Object.entries(value) : []; } +function uniqueTexts(values) { + return [...new Set(asArray(values).map((value) => sanitizeText(value)).filter((value) => value !== "not recorded"))]; +} + function gateFromContractItem(contract, id) { const item = contractItemById(contract, id); const label = liveReadinessGateLabels.get(id) ?? id; @@ -193,25 +217,48 @@ function writeLiveReadinessSummary() { const contract = reports["public-deployment-contract-report.json"]; const serviceStatus = reports["service-status-report.json"]; const monitor = reports["service-monitor-report.json"]; + const serviceSupervisorValidation = reports["service-supervisor-validation-report.json"]; + const serviceInstallValidation = reports["service-install-validation-report.json"]; + const systemdServiceInstallValidation = reports["systemd-service-install-validation-report.json"]; const bridgeRelayer = reports["bridge-relayer-once-report.json"]; const bridgeRelayerGuardrail = reports["bridge-relayer-guardrail-validation-report.json"]; const bridgeRelayerLoopValidation = reports["bridge-relayer-loop-validation-report.json"]; + const bridgeRuntimeCreditValidation = reports["bridge-runtime-credit-validation-report.json"]; + const bridgeReleaseEvidenceValidation = reports["bridge-release-evidence-validation-report.json"]; + const bridgeReconciliationScheduleValidation = reports["bridge-reconciliation-schedule-validation-report.json"]; + const realValuePilotAggregate = reports["real-value-pilot-aggregate-report.json"]; + const bridgeCommandMatrix = reports["bridge-command-matrix-report.json"]; + const bridgeNoSecretAudit = reports["bridge-no-secret-audit-report.json"]; const backupOwnerPathDryRun = reports["backup-owner-path-dry-run-report.json"]; const publicRpcDeploymentBundle = reports["public-rpc-deployment-bundle-report.json"]; const publicRpcDeploymentAutomation = reports["public-rpc-deployment-automation-report.json"]; + const publicRpcCommandMatrix = reports["public-rpc-command-matrix-report.json"]; const externalTesterPacket = reports["external-tester-packet-report.json"]; const externalTesterConnectPack = reports["external-tester-connect-pack.json"]; const externalTesterReadiness = reports["external-tester-readiness-report.json"]; const publicTesterGateway = reports["public-tester-gateway-e2e-report.json"]; const opsSnapshot = reports["ops-snapshot-report.json"]; const opsAlertRules = reports["ops-alert-rules-report.json"]; + const opsMetricsExport = reports["ops-metrics-export-report.json"]; const incidentDrill = reports["incident-drill-report.json"]; const alertInstallValidation = reports["alert-install-validation-report.json"]; const opsEscalationDryRun = reports["ops-escalation-dry-run-report.json"]; const ownerInputs = reports["owner-inputs-report.json"]; + const ownerEnvTemplate = reports["owner-env-template-report.json"]; + const ownerActivationPlan = reports["owner-activation-plan-report.json"]; + const ownerGoLiveHandoff = reports["owner-go-live-handoff-report.json"]; + const ownerNeedsNow = reports["owner-needs-now-report.json"]; const noSecretScan = reports["no-secret-scan-report.json"]; const gates = [...liveReadinessGateLabels.keys()].map((id) => gateFromContractItem(contract, id)); const activeRuleIds = asArray(opsAlertRules?.activeRuleIds).map((id) => sanitizeText(id)); + const opsRequiredMetricNames = asArray(opsMetricsExport?.requiredMetricNames).map((name) => sanitizeText(name)); + const publicRpcSecurityHeaderMetricNames = [ + "flowchain_public_rpc_security_headers", + "flowchain_public_rpc_security_header_preflight", + "flowchain_public_rpc_rendered_security_headers", + "flowchain_public_rpc_rendered_security_header_preflight", + ]; + const publicRpcSecurityHeaderMetricsPresent = publicRpcSecurityHeaderMetricNames.every((name) => opsRequiredMetricNames.includes(name)); const alertRules = asArray(opsAlertRules?.rules).map((rule) => ({ id: sanitizeText(rule?.id), severity: sanitizeText(rule?.severity), @@ -230,6 +277,71 @@ function writeLiveReadinessSummary() { const connectPackCheckValues = Object.values(externalTesterPacket?.connectPackChecks ?? {}); const bridgeRelayerSteps = asArray(bridgeRelayer?.steps); const bridgeRelayerTimedOutSteps = timedOutSteps(bridgeRelayerSteps); + const toActivationStage = (stage) => ({ + id: sanitizeText(stage?.id), + title: sanitizeText(stage?.title), + status: sanitizeText(stage?.status), + ready: stage?.ready === true, + requiredEnvNames: uniqueTexts(stage?.requiredEnvNames), + optionalEnvNames: uniqueTexts(stage?.optionalEnvNames), + missingEnvNames: uniqueTexts(stage?.missingEnvNames), + invalidEnvNames: uniqueTexts(stage?.invalidEnvNames), + upstreamMissingEnvNames: uniqueTexts(stage?.upstreamMissingEnvNames), + upstreamInvalidEnvNames: uniqueTexts(stage?.upstreamInvalidEnvNames), + blockingEnvNames: uniqueTexts(stage?.blockingEnvNames), + blockedByReportNames: uniqueTexts(stage?.blockedByReportNames), + externalAccountsOrResources: uniqueTexts(stage?.externalAccountsOrResources), + ownerMustDo: uniqueTexts(stage?.ownerMustDo), + ownerMustNotSend: uniqueTexts(stage?.ownerMustNotSend), + validationCommands: commandList(stage?.validationCommands, 8), + sourceReports: asArray(stage?.sourceReports).map((sourceReport) => ({ + name: sanitizeText(sourceReport?.name), + status: sanitizeText(sourceReport?.status), + path: sanitizeText(sourceReport?.path), + })), + }); + const toLaunchStep = (step, index) => ({ + id: sanitizeText(step?.id, `launch-step-${index + 1}`), + order: Number.isFinite(Number(step?.order)) ? Number(step.order) : index + 1, + title: sanitizeText(step?.title), + status: sanitizeText(step?.status, "not-run"), + commands: commandList(step?.commands, 10), + expectedReportPaths: uniqueTexts(step?.expectedReportPaths).slice(0, 14), + stopOnFailure: step?.stopOnFailure === true, + }); + const toOwnerNeedGroup = (group) => ({ + id: sanitizeText(group?.id), + title: sanitizeText(group?.title), + status: sanitizeText(group?.status), + ready: group?.ready === true, + whyNeeded: sanitizeText(group?.whyNeeded), + ownerAction: sanitizeText(group?.ownerAction), + envNames: uniqueTexts(group?.envNames), + missingEnvNames: uniqueTexts(group?.missingEnvNames), + invalidEnvNames: uniqueTexts(group?.invalidEnvNames), + unknownEnvNames: uniqueTexts(group?.unknownEnvNames), + validationCommands: commandList(group?.validationCommands, 6), + doNotSend: uniqueTexts(group?.doNotSend), + }); + const toOwnerEnvFieldGuide = (item) => ({ + name: sanitizeText(item?.name), + group: sanitizeText(item?.group), + required: item?.required === true, + purpose: sanitizeText(item?.purpose), + validation: sanitizeText(item?.validation), + source: sanitizeText(item?.source), + doNotSend: sanitizeText(item?.doNotSend), + }); + const activationStages = asArray(ownerActivationPlan?.stages).map(toActivationStage); + const goLiveStages = asArray(ownerGoLiveHandoff?.stages).map(toActivationStage); + const goLiveLaunchSequence = asArray(ownerGoLiveHandoff?.launchSequence).map(toLaunchStep); + const goLiveRollbackCommands = commandList(ownerGoLiveHandoff?.rollbackCommands, 12); + const ownerNeedGroups = asArray(ownerNeedsNow?.groups).map(toOwnerNeedGroup); + const ownerNeededNowGroups = asArray(ownerNeedsNow?.neededNowGroups).map(toOwnerNeedGroup); + const ownerReadyGroups = asArray(ownerNeedsNow?.readyGroups).map(toOwnerNeedGroup); + const ownerEnvFieldGuide = asArray(ownerEnvTemplate?.fieldGuide).map(toOwnerEnvFieldGuide); + const activationForbiddenItems = uniqueTexts(activationStages.flatMap((stage) => stage.ownerMustNotSend)); + const goLiveForbiddenItems = uniqueTexts(ownerGoLiveHandoff?.mustNotSend ?? goLiveStages.flatMap((stage) => stage.ownerMustNotSend)); const latestHeight = asText(serviceStatus?.chain?.latestHeight, "not recorded"); const finalizedHeight = asText(serviceStatus?.chain?.finalizedHeight, "not recorded"); const privateRpcUrl = serviceStatus?.bind @@ -257,22 +369,124 @@ function writeLiveReadinessSummary() { latestHeight, finalizedHeight, monitorHeightAdvanced: monitor?.heightAdvanced === true, + serviceSupervisorValidationStatus: asText(serviceSupervisorValidation?.status, "not recorded"), + serviceSupervisorRestartAttempts: asText(serviceSupervisorValidation?.restartAttempts, "0"), + serviceSupervisorNodeRestartAttempts: asText(serviceSupervisorValidation?.nodeRecovery?.restartAttempts, "0"), + serviceSupervisorRelayerRestartAttempts: asText(serviceSupervisorValidation?.relayerLoopRecovery?.restartAttempts, "0"), + serviceInstallValidationStatus: asText(serviceInstallValidation?.status, "not recorded"), + serviceInstallPlanDidNotMutate: serviceInstallValidation?.checks?.planDidNotMutate === true, + serviceInstallStatusDidNotMutate: serviceInstallValidation?.checks?.statusDidNotMutate === true, + serviceInstallRelayerOptInStartsLoop: serviceInstallValidation?.checks?.bridgeRelayerOptInStartsLoop === true, + systemdServiceInstallValidationStatus: asText(systemdServiceInstallValidation?.status, "not recorded"), + systemdInstallPlanUsesRenderedUnits: systemdServiceInstallValidation?.checks?.installPlanUsesRenderedUnits === true, + systemdBridgeRelayerDefaultOff: systemdServiceInstallValidation?.checks?.bridgeRelayerDefaultOff === true, + systemdBridgeRelayerOptInStartsLoop: systemdServiceInstallValidation?.checks?.bridgeRelayerOptInStartsLoop === true, bridgeRelayerStatus: asText(bridgeRelayer?.status, "not recorded"), bridgeQueuedTransactions: asText(bridgeRelayer?.counts?.queuedTransactions, "0"), bridgeRelayerChildTimeoutSeconds: asText(bridgeRelayer?.childTimeoutSeconds, "not recorded"), bridgeRelayerStepCount: bridgeRelayerSteps.length, bridgeRelayerTimedOutStepCount: bridgeRelayerTimedOutSteps.length, bridgeRelayerNoChildTimeouts: bridgeRelayerSteps.length > 0 && bridgeRelayerTimedOutSteps.length === 0, + bridgeRelayerCheckContractReady: opsSnapshot?.reportStatuses?.bridgeRelayerCheckContractReady === true, + bridgeRelayerFailedChecks: asText(opsSnapshot?.reportStatuses?.bridgeRelayerFailedChecks, "0"), + bridgeRelayerMissingChecks: asText(opsSnapshot?.reportStatuses?.bridgeRelayerMissingChecks, "0"), + bridgeCommandMatrixStatus: asText(opsSnapshot?.reportStatuses?.bridgeCommandMatrix ?? bridgeCommandMatrix?.status, "not recorded"), + bridgeCommandMatrixReady: opsSnapshot?.reportStatuses?.bridgeCommandMatrixReady === true || bridgeCommandMatrix?.status === "passed", + bridgeCommandMatrixCommands: asText(opsSnapshot?.reportStatuses?.bridgeCommandMatrixCommands ?? bridgeCommandMatrix?.commandCount, "0"), + bridgeCommandMatrixPhases: asText(opsSnapshot?.reportStatuses?.bridgeCommandMatrixPhases ?? bridgeCommandMatrix?.phaseCount, "0"), + bridgeCommandMatrixLiveBroadcastCommands: asText(opsSnapshot?.reportStatuses?.bridgeCommandMatrixLiveBroadcastCommands ?? bridgeCommandMatrix?.liveBroadcastCapableCommandCount, "0"), + bridgeCommandMatrixCommittedEvidencePaths: asText(opsSnapshot?.reportStatuses?.bridgeCommandMatrixCommittedEvidencePaths ?? bridgeCommandMatrix?.committedEvidencePathCount, "0"), + bridgeCommandMatrixFailedChecks: asText(opsSnapshot?.reportStatuses?.bridgeCommandMatrixFailedChecks ?? asArray(bridgeCommandMatrix?.failedChecks).length, "0"), + bridgeCommandMatrixBroadcastAckGaps: asText(opsSnapshot?.reportStatuses?.bridgeCommandMatrixBroadcastAckGaps ?? asArray(bridgeCommandMatrix?.liveBroadcastRowsWithoutAck).length, "0"), + bridgeCommandMatrixNoSecrets: opsSnapshot?.reportStatuses?.bridgeCommandMatrixNoSecrets === true || bridgeCommandMatrix?.noSecrets === true, + bridgeCommandMatrixNoBroadcasts: opsSnapshot?.reportStatuses?.bridgeCommandMatrixNoBroadcasts === true || bridgeCommandMatrix?.broadcasts === false, + bridgeNoSecretAuditStatus: asText(opsSnapshot?.reportStatuses?.bridgeNoSecretAudit ?? bridgeNoSecretAudit?.status, "not recorded"), + bridgeNoSecretAuditReady: opsSnapshot?.reportStatuses?.bridgeNoSecretAuditReady === true || bridgeNoSecretAudit?.status === "passed", + bridgeNoSecretAuditScannedFiles: asText(opsSnapshot?.reportStatuses?.bridgeNoSecretAuditScannedFiles ?? bridgeNoSecretAudit?.scannedFileCount, "0"), + bridgeNoSecretAuditFindings: asText(opsSnapshot?.reportStatuses?.bridgeNoSecretAuditFindings ?? asArray(bridgeNoSecretAudit?.findings).length, "0"), + bridgeNoSecretAuditSecretFindings: asText(opsSnapshot?.reportStatuses?.bridgeNoSecretAuditSecretFindings ?? asArray(bridgeNoSecretAudit?.secretMarkerFindings).length, "0"), + bridgeNoSecretAuditFailedChecks: asText(opsSnapshot?.reportStatuses?.bridgeNoSecretAuditFailedChecks ?? asArray(bridgeNoSecretAudit?.failedChecks).length, "0"), + bridgeNoSecretAuditNoSecrets: opsSnapshot?.reportStatuses?.bridgeNoSecretAuditReady === true || bridgeNoSecretAudit?.noSecrets === true, + bridgeNoSecretAuditNoBroadcasts: bridgeNoSecretAudit?.broadcasts === false, bridgeRelayerGuardrailStatus: asText(bridgeRelayerGuardrail?.status, "not recorded"), bridgeRelayerLoopValidationStatus: asText(bridgeRelayerLoopValidation?.status, "not recorded"), + bridgeReconciliationScheduleStatus: asText(bridgeReconciliationScheduleValidation?.status, "not recorded"), + bridgeReconciliationScheduleReady: bridgeReconciliationScheduleValidation?.status === "passed", + bridgeReconciliationScheduleIntervalMinutes: asText(bridgeReconciliationScheduleValidation?.intervalMinutes, "not recorded"), + bridgeReconciliationScheduleNoMutation: bridgeReconciliationScheduleValidation?.hostMutationPerformed === false, + bridgeReconciliationScheduleNoExternalDelivery: bridgeReconciliationScheduleValidation?.checks?.noExternalDelivery === true, + bridgeRuntimeCreditValidationStatus: asText(bridgeRuntimeCreditValidation?.status, "not recorded"), + bridgeRuntimeCreditReady: bridgeRuntimeCreditValidation?.status === "passed", + bridgeRuntimeCreditLatencySeconds: asText(bridgeRuntimeCreditValidation?.timing?.queueToSpendableSeconds, "not recorded"), + bridgeRuntimeCreditTransferSeconds: asText(bridgeRuntimeCreditValidation?.timing?.transferSettlementSeconds, "not recorded"), + bridgeRuntimeCreditFailedChecks: asText(asArray(bridgeRuntimeCreditValidation?.failedChecks).length, "0"), + bridgeReleaseEvidenceStatus: asText(bridgeReleaseEvidenceValidation?.status, "not recorded"), + bridgeReleaseEvidenceReady: bridgeReleaseEvidenceValidation?.status === "passed", + bridgeReleaseEvidenceCaseCount: asText(bridgeReleaseEvidenceValidation?.caseCount, "0"), + bridgeReleaseEvidenceFailedChecks: asText(asArray(bridgeReleaseEvidenceValidation?.failedChecks).length, "0"), + realValuePilotAggregateStatus: asText(opsSnapshot?.reportStatuses?.realValuePilotAggregate ?? realValuePilotAggregate?.status, "not recorded"), + realValuePilotAggregateReady: opsSnapshot?.reportStatuses?.realValuePilotAggregateReady === true || (realValuePilotAggregate?.status === "passed" && realValuePilotAggregate?.ownerGoNoGo?.go === true), + realValuePilotAggregateCommandsRun: asText(opsSnapshot?.reportStatuses?.realValuePilotAggregateCommandsRun ?? asArray(realValuePilotAggregate?.commandsRun).length, "0"), + realValuePilotAggregateTimedOutCommands: asText(opsSnapshot?.reportStatuses?.realValuePilotAggregateTimedOutCommands ?? asArray(realValuePilotAggregate?.timedOutCommands).length, "0"), + realValuePilotAggregateFailedCommands: asText(opsSnapshot?.reportStatuses?.realValuePilotAggregateFailedCommands ?? asArray(realValuePilotAggregate?.failedCommands).length, "0"), + realValuePilotAggregateMissingProofs: asText(opsSnapshot?.reportStatuses?.realValuePilotAggregateMissingProofs ?? asArray(realValuePilotAggregate?.missingProofs).length, "0"), + realValuePilotAggregateOwnerGoNoGo: opsSnapshot?.reportStatuses?.realValuePilotAggregateOwnerGoNoGo === true || realValuePilotAggregate?.ownerGoNoGo?.go === true, backupOwnerPathDryRunStatus: asText(backupOwnerPathDryRun?.status, "not recorded"), publicRpcDeploymentBundleStatus: asText(publicRpcDeploymentBundle?.status, "not recorded"), publicRpcOwnerRenderValidationStatus: asText(publicRpcDeploymentBundle?.renderValidation?.status, "not recorded"), publicRpcDeploymentAutomationStatus: asText(publicRpcDeploymentAutomation?.status, "not recorded"), publicRpcDeploymentAutomationAction: asText(publicRpcDeploymentAutomation?.action, "not recorded"), + publicRpcSecurityHeaders: publicRpcDeploymentBundle?.checks?.includesSecurityHeaders === true, + publicRpcSecurityHeaderPreflight: publicRpcDeploymentBundle?.checks?.preflightsCheckSecurityHeaders === true, + publicRpcRenderedSecurityHeaders: publicRpcDeploymentAutomation?.checks?.renderedNginxHasSecurityHeaders === true, + publicRpcRenderedSecurityHeaderPreflight: publicRpcDeploymentAutomation?.checks?.renderedPreflightChecksSecurityHeaders === true, + publicRpcCommandMatrixStatus: asText(opsSnapshot?.reportStatuses?.publicRpcCommandMatrix ?? publicRpcCommandMatrix?.status, "not recorded"), + publicRpcCommandMatrixReady: opsSnapshot?.reportStatuses?.publicRpcCommandMatrixReady === true || publicRpcCommandMatrix?.status === "passed", + publicRpcCommandMatrixCommands: asText(opsSnapshot?.reportStatuses?.publicRpcCommandMatrixCommands ?? publicRpcCommandMatrix?.commandCount, "0"), + publicRpcCommandMatrixOwnerHostCommands: asText(opsSnapshot?.reportStatuses?.publicRpcCommandMatrixOwnerHostCommands ?? publicRpcCommandMatrix?.ownerHostCommandCount, "0"), + publicRpcCommandMatrixMutatingOwnerHostCommands: asText(opsSnapshot?.reportStatuses?.publicRpcCommandMatrixMutatingOwnerHostCommands ?? publicRpcCommandMatrix?.mutatingOwnerHostCommandCount, "0"), + publicRpcCommandMatrixCommittedEvidencePaths: asText(opsSnapshot?.reportStatuses?.publicRpcCommandMatrixCommittedEvidencePaths ?? publicRpcCommandMatrix?.committedEvidencePathCount, "0"), + publicRpcCommandMatrixFailedChecks: asText(opsSnapshot?.reportStatuses?.publicRpcCommandMatrixFailedChecks ?? asArray(publicRpcCommandMatrix?.failedChecks).length, "0"), + publicRpcCommandMatrixMissingScripts: asText(opsSnapshot?.reportStatuses?.publicRpcCommandMatrixMissingScripts ?? asArray(publicRpcCommandMatrix?.missingPackageScripts).length, "0"), + publicRpcCommandMatrixNoSecrets: opsSnapshot?.reportStatuses?.publicRpcCommandMatrixNoSecrets === true || publicRpcCommandMatrix?.noSecrets === true, + publicRpcCommandMatrixNoBroadcasts: opsSnapshot?.reportStatuses?.publicRpcCommandMatrixNoBroadcasts === true || publicRpcCommandMatrix?.broadcasts === false, externalTesterPacketStatus: asText(externalTesterPacket?.status, "not recorded"), externalTesterConnectPackStatus: asText(externalTesterConnectPack?.status, "not recorded"), + publicTesterGatewayStatus: asText(publicTesterGateway?.status, "not recorded"), + publicTesterGatewayAccountCount: asText(opsSnapshot?.reportStatuses?.publicTesterGatewayAccountCount ?? publicTesterGateway?.accountCount, "0"), + publicTesterGatewayFailedChecks: asText(opsSnapshot?.reportStatuses?.publicTesterGatewayFailedChecks ?? asArray(publicTesterGateway?.failedChecks).length, "0"), + publicTesterGatewayNoSecrets: opsSnapshot?.reportStatuses?.publicTesterGatewayNoSecrets === true || publicTesterGateway?.noSecrets === true, + publicTesterGatewayNoBroadcasts: opsSnapshot?.reportStatuses?.publicTesterGatewayNoBroadcasts === true || publicTesterGateway?.broadcasts === false, ownerInputReady: ownerInputs?.ownerInputReady === true, + ownerEnvTemplateStatus: asText(ownerEnvTemplate?.status, "not recorded"), + ownerEnvTemplateFieldGuideCount: asText(ownerEnvTemplate?.fieldGuideCount, String(ownerEnvFieldGuide.length)), + ownerEnvTemplateRequiredEnvNameCount: asText(ownerEnvTemplate?.requiredEnvNameCount, "0"), + ownerEnvTemplateNoSecrets: ownerEnvTemplate?.noSecrets === true, + ownerEnvTemplateEnvValuesPrinted: ownerEnvTemplate?.envValuesPrinted === true, + ownerActivationStatus: asText(ownerActivationPlan?.status, "not recorded"), + ownerActivationReady: ownerActivationPlan?.activationReady === true, + ownerActivationStageCount: asText(ownerActivationPlan?.stageCount, String(activationStages.length)), + ownerActivationReadyStageCount: asText(ownerActivationPlan?.readyStageCount, "0"), + ownerActivationMissingCount: asArray(ownerActivationPlan?.missingEnvNames).length, + ownerGoLiveHandoffStatus: asText(ownerGoLiveHandoff?.status, "not recorded"), + ownerGoLiveHandoffReady: ownerGoLiveHandoff?.status === "passed" && asArray(ownerGoLiveHandoff?.failedChecks).length === 0, + ownerGoLiveReleaseReady: ownerGoLiveHandoff?.releaseReady === true, + ownerGoLiveNextInputCount: asArray(ownerGoLiveHandoff?.nextOwnerInputNames).length, + ownerGoLiveStageCount: asText(ownerGoLiveHandoff?.stageCount, String(goLiveStages.length)), + ownerGoLiveLaunchSequenceCount: asText(ownerGoLiveHandoff?.launchSequenceCount, String(goLiveLaunchSequence.length)), + ownerGoLiveLaunchSequenceCommandCount: asText(ownerGoLiveHandoff?.launchSequenceCommandCount, "0"), + ownerGoLiveExpectedReportPathCount: asText(ownerGoLiveHandoff?.launchSequenceExpectedReportPathCount, "0"), + ownerGoLiveRollbackCommandCount: asText(ownerGoLiveHandoff?.rollbackCommandCount, String(goLiveRollbackCommands.length)), + ownerNeedsNowStatus: asText(ownerNeedsNow?.status, "not recorded"), + ownerNeedsNowGroupCount: asText(ownerNeedsNow?.groupCount, String(ownerNeedGroups.length)), + ownerNeedsNowNeededGroupCount: asText(ownerNeedsNow?.neededNowGroupCount, String(ownerNeededNowGroups.length)), + ownerNeedsNowReadyGroupCount: asText(ownerNeedsNow?.readyGroupCount, String(ownerReadyGroups.length)), + ownerHostApplyPlanCovered: ownerGoLiveHandoff?.checks?.launchSequenceCoversOwnerHostApplyPlan === true, + ownerHostApplyExecutionCovered: ownerGoLiveHandoff?.checks?.launchSequenceCoversOwnerHostApplyExecution === true, + ownerHostApplyRollbackCovered: ownerGoLiveHandoff?.checks?.rollbackCoversOwnerHostApplyRollback === true, + windowsOwnerHostApplyPlanCovered: ownerGoLiveHandoff?.checks?.launchSequenceCoversWindowsOwnerHostApplyPlan === true, + windowsOwnerHostApplyExecutionCovered: ownerGoLiveHandoff?.checks?.launchSequenceCoversWindowsOwnerHostApplyExecution === true, + windowsOwnerHostApplyRollbackCovered: ownerGoLiveHandoff?.checks?.rollbackCoversWindowsOwnerHostApplyRollback === true, noSecretStatus: asText(noSecretScan?.status, "not recorded"), opsSnapshotStatus: asText(opsSnapshot?.status, "not recorded"), opsAlertState: asText(opsAlertRules?.currentAlertState, "not recorded"), @@ -283,6 +497,9 @@ function writeLiveReadinessSummary() { opsCriticalRuleCount: asText(opsAlertRules?.criticalRuleCount, "0"), opsBlockedRuleCount: asText(opsAlertRules?.blockedRuleCount, "0"), opsUnmappedCurrentFindingCount: asArray(opsAlertRules?.unmappedCurrentFindingCodes).length, + opsMetricCount: asText(opsMetricsExport?.metricCount, "0"), + opsRequiredMetricsPresent: opsMetricsExport?.checks?.requiredMetricsPresent === true, + opsPublicRpcSecurityHeaderMetricsPresent: publicRpcSecurityHeaderMetricsPresent, incidentDrillStatus: asText(incidentDrill?.status, "not recorded"), alertInstallValidationStatus: asText(alertInstallValidation?.status, "not recorded"), opsEscalationDryRunStatus: asText(opsEscalationDryRun?.status, "not recorded"), @@ -297,8 +514,82 @@ function writeLiveReadinessSummary() { alertInstallValidationStatus: asText(alertInstallValidation?.status, "not recorded"), escalationDryRunStatus: asText(opsEscalationDryRun?.status, "not recorded"), escalationDryRunEvents: asText(opsEscalationDryRun?.dryRunEventCount, "0"), + serviceSupervisorValidationStatus: asText(serviceSupervisorValidation?.status, "not recorded"), + serviceSupervisorRestartAttempts: asText(serviceSupervisorValidation?.restartAttempts, "0"), + serviceSupervisorNodeRestartAttempts: asText(serviceSupervisorValidation?.nodeRecovery?.restartAttempts, "0"), + serviceSupervisorRelayerRestartAttempts: asText(serviceSupervisorValidation?.relayerLoopRecovery?.restartAttempts, "0"), + serviceInstallValidationStatus: asText(serviceInstallValidation?.status, "not recorded"), + serviceInstallPlanDidNotMutate: serviceInstallValidation?.checks?.planDidNotMutate === true, + serviceInstallStatusDidNotMutate: serviceInstallValidation?.checks?.statusDidNotMutate === true, + serviceInstallRelayerOptInStartsLoop: serviceInstallValidation?.checks?.bridgeRelayerOptInStartsLoop === true, + systemdServiceInstallValidationStatus: asText(systemdServiceInstallValidation?.status, "not recorded"), + systemdInstallPlanUsesRenderedUnits: systemdServiceInstallValidation?.checks?.installPlanUsesRenderedUnits === true, + systemdBridgeRelayerDefaultOff: systemdServiceInstallValidation?.checks?.bridgeRelayerDefaultOff === true, + systemdBridgeRelayerOptInStartsLoop: systemdServiceInstallValidation?.checks?.bridgeRelayerOptInStartsLoop === true, + publicRpcDeploymentBundleStatus: asText(publicRpcDeploymentBundle?.status, "not recorded"), + publicRpcDeploymentAutomationStatus: asText(publicRpcDeploymentAutomation?.status, "not recorded"), + publicRpcDeploymentAutomationAction: asText(publicRpcDeploymentAutomation?.action, "not recorded"), + publicRpcSecurityHeaders: publicRpcDeploymentBundle?.checks?.includesSecurityHeaders === true, + publicRpcRenderedSecurityHeaders: publicRpcDeploymentAutomation?.checks?.renderedNginxHasSecurityHeaders === true, + publicRpcCommandMatrixStatus: asText(opsSnapshot?.reportStatuses?.publicRpcCommandMatrix ?? publicRpcCommandMatrix?.status, "not recorded"), + publicRpcCommandMatrixReady: opsSnapshot?.reportStatuses?.publicRpcCommandMatrixReady === true || publicRpcCommandMatrix?.status === "passed", + publicRpcCommandMatrixCommands: asText(opsSnapshot?.reportStatuses?.publicRpcCommandMatrixCommands ?? publicRpcCommandMatrix?.commandCount, "0"), + publicRpcCommandMatrixOwnerHostCommands: asText(opsSnapshot?.reportStatuses?.publicRpcCommandMatrixOwnerHostCommands ?? publicRpcCommandMatrix?.ownerHostCommandCount, "0"), + publicRpcCommandMatrixMutatingOwnerHostCommands: asText(opsSnapshot?.reportStatuses?.publicRpcCommandMatrixMutatingOwnerHostCommands ?? publicRpcCommandMatrix?.mutatingOwnerHostCommandCount, "0"), + publicRpcCommandMatrixCommittedEvidencePaths: asText(opsSnapshot?.reportStatuses?.publicRpcCommandMatrixCommittedEvidencePaths ?? publicRpcCommandMatrix?.committedEvidencePathCount, "0"), + publicRpcCommandMatrixFailedChecks: asText(opsSnapshot?.reportStatuses?.publicRpcCommandMatrixFailedChecks ?? asArray(publicRpcCommandMatrix?.failedChecks).length, "0"), + publicRpcCommandMatrixNoSecrets: opsSnapshot?.reportStatuses?.publicRpcCommandMatrixNoSecrets === true || publicRpcCommandMatrix?.noSecrets === true, + publicRpcCommandMatrixNoBroadcasts: opsSnapshot?.reportStatuses?.publicRpcCommandMatrixNoBroadcasts === true || publicRpcCommandMatrix?.broadcasts === false, + opsMetricCount: asText(opsMetricsExport?.metricCount, "0"), + opsRequiredMetricsPresent: opsMetricsExport?.checks?.requiredMetricsPresent === true, + publicTesterGatewayStatus: asText(publicTesterGateway?.status, "not recorded"), + publicTesterGatewayAccountCount: asText(opsSnapshot?.reportStatuses?.publicTesterGatewayAccountCount ?? publicTesterGateway?.accountCount, "0"), + publicTesterGatewayFailedChecks: asText(opsSnapshot?.reportStatuses?.publicTesterGatewayFailedChecks ?? asArray(publicTesterGateway?.failedChecks).length, "0"), + publicTesterGatewayTransferApplied: opsSnapshot?.reportStatuses?.publicTesterGatewayTransferApplied === true || publicTesterGateway?.checks?.transferAppliedLocalRuntime === true, + publicTesterGatewayCapRejected: opsSnapshot?.reportStatuses?.publicTesterGatewayCapRejected === true || publicTesterGateway?.capRejected === true, + publicTesterGatewayNoSecrets: opsSnapshot?.reportStatuses?.publicTesterGatewayNoSecrets === true || publicTesterGateway?.noSecrets === true, + publicTesterGatewayNoBroadcasts: opsSnapshot?.reportStatuses?.publicTesterGatewayNoBroadcasts === true || publicTesterGateway?.broadcasts === false, + bridgeRelayerCheckContractReady: opsSnapshot?.reportStatuses?.bridgeRelayerCheckContractReady === true, + bridgeRelayerFailedChecks: asText(opsSnapshot?.reportStatuses?.bridgeRelayerFailedChecks, "0"), + bridgeRelayerMissingChecks: asText(opsSnapshot?.reportStatuses?.bridgeRelayerMissingChecks, "0"), + bridgeCommandMatrixStatus: asText(opsSnapshot?.reportStatuses?.bridgeCommandMatrix ?? bridgeCommandMatrix?.status, "not recorded"), + bridgeCommandMatrixReady: opsSnapshot?.reportStatuses?.bridgeCommandMatrixReady === true || bridgeCommandMatrix?.status === "passed", + bridgeCommandMatrixCommands: asText(opsSnapshot?.reportStatuses?.bridgeCommandMatrixCommands ?? bridgeCommandMatrix?.commandCount, "0"), + bridgeCommandMatrixLiveBroadcastCommands: asText(opsSnapshot?.reportStatuses?.bridgeCommandMatrixLiveBroadcastCommands ?? bridgeCommandMatrix?.liveBroadcastCapableCommandCount, "0"), + bridgeCommandMatrixFailedChecks: asText(opsSnapshot?.reportStatuses?.bridgeCommandMatrixFailedChecks ?? asArray(bridgeCommandMatrix?.failedChecks).length, "0"), + bridgeCommandMatrixBroadcastAckGaps: asText(opsSnapshot?.reportStatuses?.bridgeCommandMatrixBroadcastAckGaps ?? asArray(bridgeCommandMatrix?.liveBroadcastRowsWithoutAck).length, "0"), + bridgeCommandMatrixNoSecrets: opsSnapshot?.reportStatuses?.bridgeCommandMatrixNoSecrets === true || bridgeCommandMatrix?.noSecrets === true, + bridgeCommandMatrixNoBroadcasts: opsSnapshot?.reportStatuses?.bridgeCommandMatrixNoBroadcasts === true || bridgeCommandMatrix?.broadcasts === false, + bridgeNoSecretAuditStatus: asText(opsSnapshot?.reportStatuses?.bridgeNoSecretAudit ?? bridgeNoSecretAudit?.status, "not recorded"), + bridgeNoSecretAuditReady: opsSnapshot?.reportStatuses?.bridgeNoSecretAuditReady === true || bridgeNoSecretAudit?.status === "passed", + bridgeNoSecretAuditScannedFiles: asText(opsSnapshot?.reportStatuses?.bridgeNoSecretAuditScannedFiles ?? bridgeNoSecretAudit?.scannedFileCount, "0"), + bridgeNoSecretAuditFindings: asText(opsSnapshot?.reportStatuses?.bridgeNoSecretAuditFindings ?? asArray(bridgeNoSecretAudit?.findings).length, "0"), + bridgeNoSecretAuditSecretFindings: asText(opsSnapshot?.reportStatuses?.bridgeNoSecretAuditSecretFindings ?? asArray(bridgeNoSecretAudit?.secretMarkerFindings).length, "0"), + bridgeNoSecretAuditFailedChecks: asText(opsSnapshot?.reportStatuses?.bridgeNoSecretAuditFailedChecks ?? asArray(bridgeNoSecretAudit?.failedChecks).length, "0"), + bridgeNoSecretAuditNoSecrets: opsSnapshot?.reportStatuses?.bridgeNoSecretAuditReady === true || bridgeNoSecretAudit?.noSecrets === true, + bridgeNoSecretAuditNoBroadcasts: bridgeNoSecretAudit?.broadcasts === false, bridgeRelayerGuardrailStatus: asText(opsSnapshot?.reportStatuses?.bridgeRelayerGuardrail ?? bridgeRelayerGuardrail?.status, "not recorded"), bridgeRelayerGuardrailReady: opsSnapshot?.reportStatuses?.bridgeRelayerGuardrailReady === true, + bridgeReconciliationScheduleStatus: asText(bridgeReconciliationScheduleValidation?.status, "not recorded"), + bridgeReconciliationScheduleReady: bridgeReconciliationScheduleValidation?.status === "passed", + bridgeReconciliationScheduleIntervalMinutes: asText(bridgeReconciliationScheduleValidation?.intervalMinutes, "not recorded"), + bridgeReconciliationScheduleNoMutation: bridgeReconciliationScheduleValidation?.hostMutationPerformed === false, + bridgeReconciliationScheduleNoExternalDelivery: bridgeReconciliationScheduleValidation?.checks?.noExternalDelivery === true, + bridgeRuntimeCreditStatus: asText(opsSnapshot?.reportStatuses?.bridgeRuntimeCredit ?? bridgeRuntimeCreditValidation?.status, "not recorded"), + bridgeRuntimeCreditReady: opsSnapshot?.reportStatuses?.bridgeRuntimeCreditReady === true || bridgeRuntimeCreditValidation?.status === "passed", + bridgeRuntimeCreditLatencySeconds: asText(opsSnapshot?.reportStatuses?.bridgeRuntimeCreditLatencySeconds ?? bridgeRuntimeCreditValidation?.timing?.queueToSpendableSeconds, "not recorded"), + bridgeRuntimeTransferLatencySeconds: asText(opsSnapshot?.reportStatuses?.bridgeRuntimeTransferLatencySeconds ?? bridgeRuntimeCreditValidation?.timing?.transferSettlementSeconds, "not recorded"), + bridgeReleaseEvidenceStatus: asText(bridgeReleaseEvidenceValidation?.status, "not recorded"), + bridgeReleaseEvidenceReady: bridgeReleaseEvidenceValidation?.status === "passed", + bridgeReleaseEvidenceCaseCount: asText(bridgeReleaseEvidenceValidation?.caseCount, "0"), + bridgeReleaseEvidenceFailedChecks: asText(asArray(bridgeReleaseEvidenceValidation?.failedChecks).length, "0"), + realValuePilotAggregateStatus: asText(opsSnapshot?.reportStatuses?.realValuePilotAggregate ?? realValuePilotAggregate?.status, "not recorded"), + realValuePilotAggregateReady: opsSnapshot?.reportStatuses?.realValuePilotAggregateReady === true || (realValuePilotAggregate?.status === "passed" && realValuePilotAggregate?.ownerGoNoGo?.go === true), + realValuePilotAggregateCommandsRun: asText(opsSnapshot?.reportStatuses?.realValuePilotAggregateCommandsRun ?? asArray(realValuePilotAggregate?.commandsRun).length, "0"), + realValuePilotAggregateTimedOutCommands: asText(opsSnapshot?.reportStatuses?.realValuePilotAggregateTimedOutCommands ?? asArray(realValuePilotAggregate?.timedOutCommands).length, "0"), + realValuePilotAggregateFailedCommands: asText(opsSnapshot?.reportStatuses?.realValuePilotAggregateFailedCommands ?? asArray(realValuePilotAggregate?.failedCommands).length, "0"), + realValuePilotAggregateMissingProofs: asText(opsSnapshot?.reportStatuses?.realValuePilotAggregateMissingProofs ?? asArray(realValuePilotAggregate?.missingProofs).length, "0"), + realValuePilotAggregateOwnerGoNoGo: opsSnapshot?.reportStatuses?.realValuePilotAggregateOwnerGoNoGo === true || realValuePilotAggregate?.ownerGoNoGo?.go === true, criticalCount: asText(opsSnapshot?.criticalCount, "0"), blockedCount: asText(opsSnapshot?.blockedCount, "0"), latestHeight: asText(opsSnapshot?.chain?.latestHeight, latestHeight), @@ -331,6 +622,100 @@ function writeLiveReadinessSummary() { sendsNetworkNotifications: opsAlertRules?.notificationPlan?.sendsNetworkNotifications === true, storesSecrets: opsAlertRules?.notificationPlan?.storesSecrets === true, }, + ownerActivation: { + status: asText(ownerActivationPlan?.status, "not recorded"), + activationReady: ownerActivationPlan?.activationReady === true, + stageCount: asText(ownerActivationPlan?.stageCount, String(activationStages.length)), + readyStageCount: asText(ownerActivationPlan?.readyStageCount, "0"), + blockedStageCount: asText(ownerActivationPlan?.blockedStageCount, "0"), + stagesNeedingOwnerInputCount: asText(ownerActivationPlan?.stagesNeedingOwnerInputCount, "0"), + stagesNeedingValidationCount: asText(ownerActivationPlan?.stagesNeedingValidationCount, "0"), + missingEnvNames: uniqueTexts(ownerActivationPlan?.missingEnvNames), + invalidEnvNames: uniqueTexts(ownerActivationPlan?.invalidEnvNames), + nextOwnerInputNames: uniqueTexts(ownerActivationPlan?.nextOwnerInputNames), + requiredOwnerEnvNames: uniqueTexts(ownerActivationPlan?.requiredOwnerEnvNames), + optionalOwnerEnvNames: uniqueTexts(ownerActivationPlan?.optionalOwnerEnvNames), + nextCommands: commandList(ownerActivationPlan?.nextCommands, 10), + forbiddenItems: activationForbiddenItems, + stages: activationStages, + checks: ownerActivationPlan?.checks ?? {}, + failedChecks: uniqueTexts(ownerActivationPlan?.failedChecks), + noSecrets: ownerActivationPlan?.noSecrets === true, + broadcasts: ownerActivationPlan?.broadcasts === true, + envValuesPrinted: ownerActivationPlan?.envValuesPrinted === true, + }, + ownerEnvTemplate: { + status: asText(ownerEnvTemplate?.status, "not recorded"), + requiredEnvNameCount: asText(ownerEnvTemplate?.requiredEnvNameCount, "0"), + optionalEnvNameCount: asText(asArray(ownerEnvTemplate?.optionalEnvNames).length, "0"), + fieldGuideCount: asText(ownerEnvTemplate?.fieldGuideCount, String(ownerEnvFieldGuide.length)), + fieldGuide: ownerEnvFieldGuide, + checks: ownerEnvTemplate?.checks ?? {}, + failedChecks: uniqueTexts(ownerEnvTemplate?.failedChecks), + noSecrets: ownerEnvTemplate?.noSecrets === true, + broadcasts: ownerEnvTemplate?.broadcasts === true, + envValuesPrinted: ownerEnvTemplate?.envValuesPrinted === true, + }, + ownerGoLiveHandoff: { + status: asText(ownerGoLiveHandoff?.status, "not recorded"), + releaseReady: ownerGoLiveHandoff?.releaseReady === true, + activationReady: ownerGoLiveHandoff?.activationReady === true, + deploymentReady: ownerGoLiveHandoff?.deploymentReady === true, + packetShareable: ownerGoLiveHandoff?.packetShareable === true, + completionReady: ownerGoLiveHandoff?.completionReady === true, + truthTableClear: ownerGoLiveHandoff?.truthTableClear === true, + blockedOnlyOnKnownOwnerInputs: ownerGoLiveHandoff?.blockedOnlyOnKnownOwnerInputs === true, + stageCount: asText(ownerGoLiveHandoff?.stageCount, String(goLiveStages.length)), + readyStageCount: asText(ownerGoLiveHandoff?.readyStageCount, "0"), + blockedStageCount: asText(ownerGoLiveHandoff?.blockedStageCount, "0"), + nextCommandCount: asText(ownerGoLiveHandoff?.nextCommandCount, "0"), + launchSequenceCount: asText(ownerGoLiveHandoff?.launchSequenceCount, String(goLiveLaunchSequence.length)), + launchSequenceCommandCount: asText(ownerGoLiveHandoff?.launchSequenceCommandCount, "0"), + launchSequenceExpectedReportPathCount: asText(ownerGoLiveHandoff?.launchSequenceExpectedReportPathCount, "0"), + rollbackCommandCount: asText(ownerGoLiveHandoff?.rollbackCommandCount, String(goLiveRollbackCommands.length)), + mustNotSendCount: asText(ownerGoLiveHandoff?.mustNotSendCount, "0"), + missingEnvNames: uniqueTexts(ownerGoLiveHandoff?.missingEnvNames), + invalidEnvNames: uniqueTexts(ownerGoLiveHandoff?.invalidEnvNames), + nextOwnerInputNames: uniqueTexts(ownerGoLiveHandoff?.nextOwnerInputNames), + requiredOwnerEnvNames: uniqueTexts(ownerGoLiveHandoff?.requiredOwnerEnvNames), + optionalOwnerEnvNames: uniqueTexts(ownerGoLiveHandoff?.optionalOwnerEnvNames), + nextCommands: commandList(ownerGoLiveHandoff?.nextCommands, 10), + forbiddenItems: goLiveForbiddenItems, + externalResources: uniqueTexts(ownerGoLiveHandoff?.externalResources), + stages: goLiveStages, + launchSequence: goLiveLaunchSequence, + rollbackCommands: goLiveRollbackCommands, + checks: ownerGoLiveHandoff?.checks ?? {}, + failedChecks: uniqueTexts(ownerGoLiveHandoff?.failedChecks), + noSecrets: ownerGoLiveHandoff?.noSecrets === true, + broadcasts: ownerGoLiveHandoff?.broadcasts === true, + envValuesPrinted: ownerGoLiveHandoff?.envValuesPrinted === true, + }, + ownerNeedsNow: { + status: asText(ownerNeedsNow?.status, "not recorded"), + launchReadinessStatus: asText(ownerNeedsNow?.launchReadinessStatus, "not recorded"), + releaseReady: ownerNeedsNow?.releaseReady === true, + deploymentReady: ownerNeedsNow?.deploymentReady === true, + packetShareable: ownerNeedsNow?.packetShareable === true, + completionReady: ownerNeedsNow?.completionReady === true, + latestHeight: asText(ownerNeedsNow?.latestHeight, latestHeight), + finalizedHeight: asText(ownerNeedsNow?.finalizedHeight, finalizedHeight), + groupCount: asText(ownerNeedsNow?.groupCount, String(ownerNeedGroups.length)), + neededNowGroupCount: asText(ownerNeedsNow?.neededNowGroupCount, String(ownerNeededNowGroups.length)), + readyGroupCount: asText(ownerNeedsNow?.readyGroupCount, String(ownerReadyGroups.length)), + nextOwnerInputNames: uniqueTexts(ownerNeedsNow?.nextOwnerInputNames), + missingRequiredEnvNames: uniqueTexts(ownerNeedsNow?.missingRequiredEnvNames), + missingOptionalEnvNames: uniqueTexts(ownerNeedsNow?.missingOptionalEnvNames), + invalidEnvNames: uniqueTexts(ownerNeedsNow?.invalidEnvNames), + groups: ownerNeedGroups, + neededNowGroups: ownerNeededNowGroups, + readyGroups: ownerReadyGroups, + checks: ownerNeedsNow?.checks ?? {}, + failedChecks: uniqueTexts(ownerNeedsNow?.failedChecks), + noSecrets: ownerNeedsNow?.noSecrets === true, + broadcasts: ownerNeedsNow?.broadcasts === true, + envValuesPrinted: ownerNeedsNow?.envValuesPrinted === true, + }, testerLaunch: { status: asText(externalTesterPacket?.status ?? externalTesterReadiness?.status, "not recorded"), readinessStatus: asText(externalTesterReadiness?.status, "not recorded"), @@ -348,8 +733,15 @@ function writeLiveReadinessSummary() { }, externalSharingReady: externalTesterReadiness?.externalSharingReady === true || externalTesterPacket?.externalSharingReady === true, localTesterRehearsalReady: externalTesterReadiness?.localTesterRehearsalReady === true, + liveInfraReady: externalTesterReadiness?.checks?.liveInfraReady === true || opsSnapshot?.reportStatuses?.externalTesterLiveInfraReady === true, + serviceReady: externalTesterReadiness?.checks?.serviceReady === true || opsSnapshot?.reportStatuses?.externalTesterServiceReady === true, + chainProducing: externalTesterReadiness?.checks?.chainProducing === true || opsSnapshot?.reportStatuses?.externalTesterChainProducing === true, + missingOwnerInputCount: asText(opsSnapshot?.reportStatuses?.externalTesterMissingEnvCount ?? asArray(externalTesterReadiness?.missingEnvNames).length, "0"), + testerCount: asText(opsSnapshot?.reportStatuses?.externalTesterTesterCount ?? externalTesterReadiness?.testerNetwork?.testerCount, "0"), publicTesterGatewayReady: externalTesterReadiness?.checks?.publicTesterGatewayReady === true, + publicTesterGatewayFresh: externalTesterReadiness?.checks?.publicTesterGatewayFresh === true, gatewayConfigured: publicTesterGateway?.testerGatewayConfigured === true, + testerWalletNetworkReady: externalTesterReadiness?.checks?.testerWalletNetworkReady === true, testerNetworkFresh: externalTesterReadiness?.checks?.testerWalletNetworkFresh === true, faucetRouteValidated: externalTesterReadiness?.checks?.publicTesterGatewayFaucetRouteValidated === true, packetExecutableSmokeValidated: externalTesterPacket?.packetExecutableSmokeValidated === true || externalTesterReadiness?.checks?.packetExecutableSmokeValidated === true, @@ -373,6 +765,7 @@ function writeLiveReadinessSummary() { gateway: ["npm run flowchain:tester:gateway:e2e"], wallet: ["npm run flowchain:wallet:live-tester:e2e"], explorer: ["npm run flowchain:public-deployment:contract -- -AllowBlocked"], + publicRpc: ["npm run flowchain:public-rpc:command-matrix"], }, envValuesPrinted: false, noSecrets: publicTesterGateway?.noSecrets === true, diff --git a/apps/dashboard/src/App.tsx b/apps/dashboard/src/App.tsx index 9a5e5358..b62e1567 100644 --- a/apps/dashboard/src/App.tsx +++ b/apps/dashboard/src/App.tsx @@ -15,6 +15,7 @@ import { FlowMemoryView } from "./views/FlowMemoryView"; import { FlowPulseStreamView } from "./views/FlowPulseStreamView"; import { HardwareNodesView } from "./views/HardwareNodesView"; import { OpsView } from "./views/OpsView"; +import { OwnerActivationView } from "./views/OwnerActivationView"; import { OverviewView } from "./views/OverviewView"; import { RawJsonInspectorView } from "./views/RawJsonInspectorView"; import { RootfieldsView } from "./views/RootfieldsView"; @@ -122,6 +123,7 @@ export default function App() { setVersion((current) => current + 1)} />} /> + } /> } /> } /> } /> diff --git a/apps/dashboard/src/components/AppShell.tsx b/apps/dashboard/src/components/AppShell.tsx index 3f2abb24..78db5869 100644 --- a/apps/dashboard/src/components/AppShell.tsx +++ b/apps/dashboard/src/components/AppShell.tsx @@ -8,6 +8,7 @@ import { BrainCircuit, Boxes, ClipboardCheck, + ClipboardList, ArrowRightLeft, Compass, GitBranch, @@ -34,6 +35,7 @@ interface AppShellProps { const NAV_ITEMS = [ { to: "/", label: "Workbench", icon: Monitor }, + { to: "/activation", label: "Activation", icon: ClipboardList }, { to: "/wallet", label: "Wallet", icon: Wallet }, { to: "/tester", label: "Tester launch", icon: UserPlus }, { to: "/bridge", label: "Bridge pilot", icon: ArrowRightLeft }, diff --git a/apps/dashboard/src/data/workbench.ts b/apps/dashboard/src/data/workbench.ts index 698be46b..391cb805 100644 --- a/apps/dashboard/src/data/workbench.ts +++ b/apps/dashboard/src/data/workbench.ts @@ -2834,11 +2834,46 @@ function buildLiveReadinessRecords(liveReadinessReport: unknown | null): Workben { label: "finalized height", value: text(metrics.finalizedHeight) }, { label: "height advanced", value: text(metrics.monitorHeightAdvanced, "false") }, { label: "owner input ready", value: text(metrics.ownerInputReady, "false") }, + { label: "owner env template", value: text(metrics.ownerEnvTemplateStatus) }, + { label: "owner env field guide", value: text(metrics.ownerEnvTemplateFieldGuideCount, "0") }, + { label: "owner env template no secrets", value: text(metrics.ownerEnvTemplateNoSecrets, "false") }, { label: "bridge relayer", value: text(metrics.bridgeRelayerStatus) }, { label: "relayer child timeout", value: text(metrics.bridgeRelayerChildTimeoutSeconds) }, { label: "relayer timed out steps", value: text(metrics.bridgeRelayerTimedOutStepCount, "0") }, + { label: "bridge command matrix", value: text(metrics.bridgeCommandMatrixStatus) }, + { label: "bridge command matrix ready", value: text(metrics.bridgeCommandMatrixReady, "false") }, + { label: "bridge command matrix commands", value: text(metrics.bridgeCommandMatrixCommands, "0") }, + { label: "bridge command matrix live-broadcast commands", value: text(metrics.bridgeCommandMatrixLiveBroadcastCommands, "0") }, + { label: "bridge command matrix ack gaps", value: text(metrics.bridgeCommandMatrixBroadcastAckGaps, "0") }, + { label: "bridge no-secret audit", value: text(metrics.bridgeNoSecretAuditStatus) }, + { label: "bridge no-secret scanned files", value: text(metrics.bridgeNoSecretAuditScannedFiles, "0") }, + { label: "bridge no-secret findings", value: text(metrics.bridgeNoSecretAuditFindings, "0") }, + { label: "bridge no-secret failed checks", value: text(metrics.bridgeNoSecretAuditFailedChecks, "0") }, + { label: "bridge runtime credit", value: text(metrics.bridgeRuntimeCreditValidationStatus) }, + { label: "bridge reconciliation schedule", value: text(metrics.bridgeReconciliationScheduleStatus) }, + { label: "bridge reconciliation interval minutes", value: text(metrics.bridgeReconciliationScheduleIntervalMinutes) }, + { label: "bridge reconciliation no mutation", value: text(metrics.bridgeReconciliationScheduleNoMutation, "false") }, + { label: "credit latency seconds", value: text(metrics.bridgeRuntimeCreditLatencySeconds) }, + { label: "transfer latency seconds", value: text(metrics.bridgeRuntimeCreditTransferSeconds) }, + { label: "pilot aggregate", value: text(metrics.realValuePilotAggregateStatus) }, + { label: "pilot aggregate ready", value: text(metrics.realValuePilotAggregateReady, "false") }, + { label: "pilot proof commands", value: text(metrics.realValuePilotAggregateCommandsRun, "0") }, { label: "tester packet", value: text(metrics.externalTesterPacketStatus) }, + { label: "RPC live header probe", value: text(metrics.publicRpcLiveSecurityHeaderProbe, "false") }, + { label: "RPC live headers", value: text(metrics.publicRpcLiveSecurityHeaders, "false") }, + { label: "RPC header policy", value: text(metrics.publicRpcSecurityHeaderPolicyReady, "false") }, + { label: "RPC headers", value: text(metrics.publicRpcSecurityHeaders, "false") }, + { label: "RPC header preflight", value: text(metrics.publicRpcSecurityHeaderPreflight, "false") }, + { label: "RPC rendered headers", value: text(metrics.publicRpcRenderedSecurityHeaders, "false") }, + { label: "RPC header metrics", value: text(metrics.opsPublicRpcSecurityHeaderMetricsPresent, "false") }, + { label: "RPC command matrix", value: text(metrics.publicRpcCommandMatrixStatus) }, + { label: "RPC command matrix ready", value: text(metrics.publicRpcCommandMatrixReady, "false") }, + { label: "RPC command matrix commands", value: text(metrics.publicRpcCommandMatrixCommands, "0") }, + { label: "RPC owner-host commands", value: text(metrics.publicRpcCommandMatrixOwnerHostCommands, "0") }, + { label: "RPC mutating owner-host commands", value: text(metrics.publicRpcCommandMatrixMutatingOwnerHostCommands, "0") }, + { label: "RPC command matrix failed checks", value: text(metrics.publicRpcCommandMatrixFailedChecks, "0") }, { label: "alert rules", value: text(metrics.opsRuleCount, text(metrics.opsActiveRuleCount, "0")) }, + { label: "ops metrics", value: text(metrics.opsMetricCount, "0") }, { label: "unmapped findings", value: text(metrics.opsUnmappedCurrentFindingCount, "0") }, { label: "no-secret scan", value: text(metrics.noSecretStatus) }, { label: "status counts", value: statusCounts }, diff --git a/apps/dashboard/src/styles.css b/apps/dashboard/src/styles.css index dce60d83..b1316fbe 100644 --- a/apps/dashboard/src/styles.css +++ b/apps/dashboard/src/styles.css @@ -892,11 +892,193 @@ input { gap: 14px; } +.wallet-signed-panel { + display: grid; + gap: 16px; +} + +.wallet-signed-readiness, +.wallet-signed-result { + display: grid; + gap: 12px; + padding: 14px; + border: 1px solid rgba(10, 33, 71, 0.1); + border-radius: 13px; + background: rgba(255, 255, 255, 0.34); +} + +.wallet-signed-readiness strong, +.wallet-signed-readiness span, +.wallet-signed-result strong { + display: block; + overflow-wrap: anywhere; +} + +.wallet-signed-readiness strong, +.wallet-signed-result strong { + color: #071d44; +} + +.wallet-signed-readiness span { + margin-top: 5px; + color: #61718e; + line-height: 1.42; +} + +.wallet-signed-readiness dl, +.wallet-signed-result dl { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; + margin: 0; +} + +.wallet-signed-readiness dl div, +.wallet-signed-result dl div { + min-width: 0; + padding: 9px; + border: 1px solid rgba(10, 33, 71, 0.1); + border-radius: 10px; + background: rgba(255, 255, 255, 0.38); +} + +.wallet-signed-readiness dt, +.wallet-signed-result dt { + color: #65728a; + font-size: 0.7rem; + font-weight: 800; + text-transform: uppercase; +} + +.wallet-signed-readiness dd, +.wallet-signed-result dd { + margin: 4px 0 0; + overflow-wrap: anywhere; + color: #071d44; + font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; + font-size: 0.82rem; +} + +.wallet-signed-result a { + display: inline-flex; + gap: 8px; + align-items: center; + justify-content: center; + min-height: 40px; + padding: 0 12px; + color: #071d44; + border: 1px solid rgba(10, 33, 71, 0.12); + border-radius: 10px; + background: rgba(255, 255, 255, 0.42); + font-weight: 780; + text-decoration: none; +} + .wallet-tester-panel { display: grid; gap: 16px; } +.wallet-tester-flow { + display: grid; + gap: 12px; + padding: 14px; + border: 1px solid rgba(10, 33, 71, 0.1); + border-radius: 13px; + background: rgba(255, 255, 255, 0.34); +} + +.wallet-tester-flow-head { + display: flex; + gap: 12px; + align-items: center; + justify-content: space-between; +} + +.wallet-tester-flow-head div { + display: grid; + gap: 4px; + min-width: 0; +} + +.wallet-tester-flow-head strong { + color: #071d44; +} + +.wallet-tester-flow-head span { + overflow: hidden; + color: #61718e; + font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; + font-size: 0.76rem; + text-overflow: ellipsis; + white-space: nowrap; +} + +.wallet-tester-flow-head a { + display: inline-flex; + flex: 0 0 auto; + gap: 8px; + align-items: center; + justify-content: center; + min-height: 34px; + padding: 0 10px; + color: #071d44; + border: 1px solid rgba(10, 33, 71, 0.12); + border-radius: 9px; + background: rgba(255, 255, 255, 0.5); + font-weight: 760; + text-decoration: none; +} + +.wallet-tester-flow-steps { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 8px; +} + +.wallet-tester-flow-steps button, +.wallet-tester-flow-steps a { + display: grid; + grid-template-rows: 22px auto auto; + gap: 5px; + min-width: 0; + min-height: 86px; + padding: 10px; + color: #071d44; + border: 1px solid rgba(10, 33, 71, 0.1); + border-radius: 11px; + background: rgba(255, 255, 255, 0.4); + text-align: left; + text-decoration: none; +} + +.wallet-tester-flow-steps button.complete, +.wallet-tester-flow-steps a.complete { + border-color: rgba(10, 168, 115, 0.28); + background: rgba(10, 168, 115, 0.08); +} + +.wallet-tester-flow-steps svg { + color: #0f5fff; +} + +.wallet-tester-flow-steps strong, +.wallet-tester-flow-steps small { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.wallet-tester-flow-steps strong { + font-size: 0.86rem; +} + +.wallet-tester-flow-steps small { + color: #61718e; + font-size: 0.72rem; +} + .wallet-tester-readiness { display: grid; gap: 12px; @@ -994,7 +1176,9 @@ input { font-weight: 700; } -.wallet-panel-form input:not([type="checkbox"]) { +.wallet-panel-form input:not([type="checkbox"]), +.wallet-panel-form select, +.wallet-panel-form textarea { min-height: 46px; padding: 0 13px; border: 1px solid rgba(10, 33, 71, 0.14); @@ -1002,6 +1186,38 @@ input { background: rgba(255, 255, 255, 0.46); } +.wallet-panel-form textarea { + min-height: 174px; + padding: 12px 13px; + resize: vertical; + color: #071d44; + font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; + font-size: 0.82rem; + line-height: 1.45; +} + +.wallet-panel-form select { + color: #071d44; + font-weight: 730; +} + +.wallet-panel-form input[aria-invalid="true"], +.wallet-panel-form textarea[aria-invalid="true"] { + border-color: rgba(170, 63, 63, 0.48); + background: rgba(255, 246, 244, 0.72); +} + +.wallet-field-help { + color: #61718e; + font-size: 0.73rem; + font-weight: 650; + line-height: 1.35; +} + +.wallet-field-help.invalid { + color: #943333; +} + .wallet-panel-form button, .wallet-receive-panel button { display: inline-flex; @@ -1146,6 +1362,7 @@ input { box-shadow: 0 18px 42px rgba(50, 37, 18, 0.12); transform: translateX(-50%); backdrop-filter: blur(18px); + pointer-events: none; } @media (max-width: 1220px) { @@ -1264,7 +1481,9 @@ input { width: auto; } - .wallet-tester-readiness dl { + .wallet-tester-readiness dl, + .wallet-signed-readiness dl, + .wallet-signed-result dl { grid-template-columns: 1fr; } } @@ -4025,140 +4244,719 @@ code { text-decoration: none; } -.ops-command-panel { +.activation-command-panel { display: grid; - grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); + grid-template-columns: 1.1fr 0.8fr 0.8fr 0.9fr 1.25fr; gap: 1px; overflow: hidden; - border: 1px solid var(--line); + border: 1px solid #b8c7c0; border-radius: 8px; - background: var(--line); + background: #b8c7c0; box-shadow: var(--shadow); } -.ops-command-panel div { +.activation-command-panel div { display: grid; gap: 6px; min-width: 0; padding: 14px; - background: var(--surface); + background: #f7faf5; } -.ops-command-panel span, -.ops-finding-head span, -.ops-rule-list span { +.activation-command-panel span, +.activation-stage-facts span, +.activation-host-apply-proof span, +.activation-rollback-strip span, +.activation-inputs small, +.activation-boundary span { color: var(--muted); font-size: 0.7rem; font-weight: 800; text-transform: uppercase; } -.ops-command-panel strong { +.activation-command-panel strong, +.activation-stage-facts strong, +.activation-host-apply-proof strong { overflow-wrap: anywhere; color: var(--ink); font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; - font-size: 1.28rem; + font-size: 1.05rem; } -.ops-command-panel small, -.ops-side small { +.activation-command-panel small { color: var(--muted); line-height: 1.35; } -.ops-layout { +.activation-layout { display: grid; - grid-template-columns: minmax(0, 1fr) 340px; + grid-template-columns: minmax(0, 1fr) 330px; gap: 14px; align-items: start; } -.ops-main, -.ops-side { +.activation-main, +.activation-side, +.activation-stage-body { min-width: 0; } -.ops-findings-panel { +.activation-stage-list, +.activation-main, +.activation-side, +.activation-launch-sequence, +.activation-launch-step-list, +.activation-need-groups, +.activation-commands div, +.activation-boundary div { display: grid; - gap: 12px; + gap: 10px; } -.ops-heading-status { - display: flex; - flex-wrap: wrap; - gap: 8px; - align-items: center; - justify-content: flex-end; - min-width: 0; +.activation-side { + position: sticky; + top: 18px; } -.ops-heading-status span { - display: inline-flex; - gap: 5px; - align-items: center; - min-height: 25px; - padding: 4px 8px; +.activation-stage { + display: grid; + grid-template-columns: 42px minmax(0, 1fr); + gap: 12px; + min-width: 0; + padding: 13px; border: 1px solid var(--line); - border-radius: 7px; - color: var(--muted); - font-size: 0.72rem; - font-weight: 800; - text-transform: uppercase; + border-radius: 8px; + background: #fbfcf8; } -.ops-finding-list { - display: grid; - gap: 10px; +.activation-stage-ready { + border-color: #a9c4b5; + background: #f3faf4; } -.ops-finding { +.activation-stage-invalid-owner-input, +.activation-stage-failed { + border-color: #e2b0a6; + background: #fff8f6; +} + +.activation-stage-icon { display: grid; - gap: 9px; - padding: 13px; - border: 1px solid var(--line); - border-left: 4px solid #9baaa0; + place-items: center; + width: 38px; + height: 38px; + color: var(--accent-strong); + border: 1px solid #b7cac1; border-radius: 8px; - background: #fbfcf9; + background: #e7f1ec; } -.ops-finding-critical, -.ops-finding-failed { - border-left-color: #a65353; +.activation-stage-body, +.activation-stage-title, +.activation-stage-facts, +.activation-chip-list, +.activation-report-list, +.activation-command-list, +.activation-input-grid, +.activation-inputs details div { + min-width: 0; } -.ops-finding-blocked, -.ops-finding-pending { - border-left-color: #9a7b2f; +.activation-stage-body { + display: grid; + gap: 10px; } -.ops-finding-head { +.activation-stage-title { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; + justify-content: space-between; } -.ops-finding h3 { +.activation-stage-title h3 { margin: 0; - overflow-wrap: anywhere; + color: var(--ink); font-size: 1rem; } -.ops-finding p { - margin: 0; - color: #3f483f; - line-height: 1.45; -} - -.ops-command-list, -.ops-incident-groups details div { +.activation-stage-facts { display: grid; - gap: 6px; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 8px; } -.ops-command-list code, -.ops-rule-list code, -.ops-incident-groups code { +.activation-stage-facts div { + min-width: 0; + padding: 9px; + border: 1px solid var(--line); + border-radius: 7px; + background: #f8f9f4; +} + +.activation-chip-list, +.activation-report-list, +.activation-command-list, +.activation-input-grid, +.activation-need-group div:last-child, +.activation-inputs details div { + display: flex; + flex-wrap: wrap; + gap: 7px; +} + +.activation-chip-list code, +.activation-report-list code, +.activation-command-list code, +.activation-input-grid code, +.activation-need-group code, +.activation-inputs details code, +.activation-commands code { + max-width: 100%; + overflow: hidden; + padding: 6px 7px; + color: var(--ink); + font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; + font-size: 0.74rem; + text-overflow: ellipsis; + white-space: nowrap; + border: 1px solid var(--line); + border-radius: 7px; + background: #ffffff; +} + +.activation-report-list { + align-items: center; + padding-top: 8px; + border-top: 1px solid var(--line); +} + +.activation-report-list span { + color: var(--muted); + font-size: 0.7rem; + font-weight: 800; + text-transform: uppercase; +} + +.activation-host-apply-proof { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 8px; +} + +.activation-host-apply-proof div { + display: grid; + gap: 7px; + min-width: 0; + padding: 10px; + border: 1px solid #bfd0c8; + border-radius: 8px; + background: #f5fbf7; +} + +.activation-host-apply-proof code, +.activation-launch-step code, +.activation-rollback-strip code { + max-width: 100%; + overflow: hidden; + padding: 6px 7px; + color: var(--ink); + font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; + font-size: 0.74rem; + text-overflow: ellipsis; + white-space: nowrap; + border: 1px solid var(--line); + border-radius: 7px; + background: #ffffff; +} + +.activation-launch-step { + display: grid; + gap: 9px; + min-width: 0; + padding: 11px; + border: 1px solid var(--line); + border-radius: 8px; + background: #fbfcf8; +} + +.activation-launch-step > div:first-child { + display: grid; + grid-template-columns: 28px minmax(0, 1fr) auto; + gap: 10px; + align-items: center; +} + +.activation-launch-step > div:first-child > span { + display: grid; + place-items: center; + width: 26px; + height: 26px; + color: var(--accent-strong); + border: 1px solid #bfd0c8; + border-radius: 7px; + background: #e8f3ed; + font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; + font-size: 0.72rem; + font-weight: 800; +} + +.activation-launch-step strong, +.activation-launch-step small { + display: block; + min-width: 0; + overflow-wrap: anywhere; +} + +.activation-launch-step strong { + color: var(--ink); +} + +.activation-launch-step small { + margin-top: 3px; + color: var(--muted); + font-size: 0.78rem; +} + +.activation-launch-step > div:last-child, +.activation-rollback-strip div { + display: flex; + flex-wrap: wrap; + gap: 7px; + min-width: 0; +} + +.activation-rollback-strip { + display: grid; + gap: 8px; + padding-top: 10px; + border-top: 1px solid var(--line); +} + +.activation-rollback-strip > strong { + color: var(--ink); +} + +.activation-commands code { + display: block; +} + +.activation-inputs, +.activation-needed, +.activation-commands, +.activation-boundary { + min-width: 0; +} + +.activation-need-groups { + padding-top: 10px; + border-top: 1px solid var(--line); +} + +.activation-need-group { + display: grid; + gap: 8px; + min-width: 0; + padding: 11px; + border: 1px solid #bfd0c8; + border-radius: 8px; + background: #f8fbf6; +} + +.activation-need-group > div:first-child { + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: baseline; + justify-content: space-between; + min-width: 0; +} + +.activation-need-group strong { + color: var(--ink); +} + +.activation-need-group span, +.activation-need-group p, +.activation-need-group small, +.activation-ready-groups span { + color: var(--muted); + font-size: 0.78rem; +} + +.activation-need-group p, +.activation-need-group small { + margin: 0; + line-height: 1.4; +} + +.activation-ready-groups { + display: flex; + flex-wrap: wrap; + gap: 7px; + align-items: center; + min-width: 0; + padding-top: 10px; + border-top: 1px solid var(--line); +} + +.activation-ready-groups strong { + color: var(--ink); + font-size: 0.82rem; +} + +.activation-ready-groups span { + padding: 5px 7px; + border: 1px solid #b9cec4; + border-radius: 7px; + background: #f1faf5; +} + +.activation-inputs details { + display: grid; + gap: 8px; + min-width: 0; + padding-top: 10px; + border-top: 1px solid var(--line); +} + +.activation-inputs summary { + cursor: pointer; + color: var(--ink); + font-weight: 800; +} + +.activation-boundary span { + display: block; + padding: 7px 0; + border-bottom: 1px solid var(--line); + text-transform: none; +} + +.activation-field-guide-panel { + display: grid; + gap: 12px; +} + +.activation-field-guide-summary { + display: grid; + grid-template-columns: 0.6fr 0.9fr 0.9fr; + gap: 1px; + overflow: hidden; + border: 1px solid #b8c7c0; + border-radius: 8px; + background: #b8c7c0; +} + +.activation-field-guide-summary div { + display: grid; + gap: 6px; + min-width: 0; + padding: 12px; + background: #f7faf5; +} + +.activation-field-guide-summary span, +.activation-field-guide-item dt { + color: var(--muted); + font-size: 0.7rem; + font-weight: 800; + text-transform: uppercase; +} + +.activation-field-guide-summary strong { + overflow-wrap: anywhere; + color: var(--ink); + font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; +} + +.activation-field-guide-summary small { + color: var(--muted); + line-height: 1.35; +} + +.activation-field-guide-list { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 10px; +} + +.activation-field-guide-item { + display: grid; + gap: 9px; + min-width: 0; + padding: 12px; + border: 1px solid #bfd0c8; + border-radius: 8px; + background: #fbfcf8; +} + +.activation-field-guide-item > div:first-child { + display: grid; + gap: 6px; + min-width: 0; +} + +.activation-field-guide-item code { + width: fit-content; + max-width: 100%; + overflow: hidden; + padding: 6px 7px; + color: var(--ink); + font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; + font-size: 0.74rem; + text-overflow: ellipsis; + white-space: nowrap; + border: 1px solid var(--line); + border-radius: 7px; + background: #ffffff; +} + +.activation-field-guide-item span, +.activation-field-guide-item p, +.activation-field-guide-item dd { + color: var(--muted); + font-size: 0.78rem; + line-height: 1.4; +} + +.activation-field-guide-item p { + margin: 0; + color: #43534d; +} + +.activation-field-guide-item dl { + display: grid; + gap: 7px; + margin: 0; +} + +.activation-field-guide-item dl div { + display: grid; + gap: 2px; + min-width: 0; +} + +.activation-field-guide-item dt, +.activation-field-guide-item dd { + margin: 0; + overflow-wrap: anywhere; +} + +.ops-command-panel { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); + gap: 1px; + overflow: hidden; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--line); + box-shadow: var(--shadow); +} + +.ops-command-panel div { + display: grid; + gap: 6px; + min-width: 0; + padding: 14px; + background: var(--surface); +} + +.ops-command-panel span, +.ops-finding-head span, +.ops-rule-list span { + color: var(--muted); + font-size: 0.7rem; + font-weight: 800; + text-transform: uppercase; +} + +.ops-command-panel strong { + overflow-wrap: anywhere; + color: var(--ink); + font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; + font-size: 1.28rem; +} + +.ops-command-panel small, +.ops-side small { + color: var(--muted); + line-height: 1.35; +} + +.ops-layout { + display: grid; + grid-template-columns: minmax(0, 1fr) 340px; + gap: 14px; + align-items: start; +} + +.ops-main, +.ops-side { + min-width: 0; +} + +.ops-main { + display: grid; + gap: 14px; +} + +.ops-relayer-contract { + display: grid; + grid-template-columns: 1.15fr repeat(3, minmax(0, 1fr)); + gap: 1px; + overflow: hidden; + border: 1px solid #b8c7c0; + border-radius: 8px; + background: #b8c7c0; + box-shadow: var(--shadow); +} + +.ops-automation-proof { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); + gap: 1px; + overflow: hidden; + border: 1px solid #c4c6b8; + border-radius: 8px; + background: #c4c6b8; + box-shadow: var(--shadow); +} + +.ops-relayer-contract div { + display: grid; + gap: 6px; + min-width: 0; + padding: 12px; + background: #f7faf5; +} + +.ops-automation-proof div { + display: grid; + gap: 6px; + min-width: 0; + padding: 12px; + background: #faf9f2; +} + +.ops-relayer-contract span { + color: var(--muted); + font-size: 0.7rem; + font-weight: 800; + text-transform: uppercase; +} + +.ops-automation-proof span { + color: var(--muted); + font-size: 0.7rem; + font-weight: 800; + text-transform: uppercase; +} + +.ops-relayer-contract strong, +.ops-relayer-contract small, +.ops-automation-proof strong, +.ops-automation-proof small { + display: block; + overflow-wrap: anywhere; +} + +.ops-relayer-contract strong, +.ops-automation-proof strong { + color: var(--ink); + font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; + font-size: 1.02rem; +} + +.ops-relayer-contract small, +.ops-automation-proof small { + color: var(--muted); + line-height: 1.35; +} + +.ops-findings-panel { + display: grid; + gap: 12px; +} + +.ops-heading-status { + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; + justify-content: flex-end; + min-width: 0; +} + +.ops-heading-status span { + display: inline-flex; + gap: 5px; + align-items: center; + min-height: 25px; + padding: 4px 8px; + border: 1px solid var(--line); + border-radius: 7px; + color: var(--muted); + font-size: 0.72rem; + font-weight: 800; + text-transform: uppercase; +} + +.ops-finding-list { + display: grid; + gap: 10px; +} + +.ops-finding { + display: grid; + gap: 9px; + padding: 13px; + border: 1px solid var(--line); + border-left: 4px solid #9baaa0; + border-radius: 8px; + background: #fbfcf9; +} + +.ops-finding-critical, +.ops-finding-failed { + border-left-color: #a65353; +} + +.ops-finding-blocked, +.ops-finding-pending { + border-left-color: #9a7b2f; +} + +.ops-finding-head { + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; +} + +.ops-finding h3 { + margin: 0; + overflow-wrap: anywhere; + font-size: 1rem; +} + +.ops-finding p { + margin: 0; + color: #3f483f; + line-height: 1.45; +} + +.ops-command-list, +.ops-incident-groups details div { + display: grid; + gap: 6px; +} + +.ops-command-list code, +.ops-rule-list code, +.ops-incident-groups code { display: block; min-width: 0; padding: 7px 8px; @@ -4724,7 +5522,11 @@ code { .explorer-settlement-trace, .explorer-launch-boundary, .explorer-layout, + .activation-command-panel, + .activation-layout, .ops-command-panel, + .ops-relayer-contract, + .ops-automation-proof, .ops-layout { grid-template-columns: 1fr; } @@ -4770,6 +5572,10 @@ code { position: static; } + .activation-side { + position: static; + } + .tester-launch-side { position: static; } @@ -4829,8 +5635,11 @@ code { .explorer-facts, .explorer-launch-grid, .explorer-trace-steps, + .wallet-tester-flow-steps, .tester-launch-card-grid, - .tester-launch-profile-grid { + .tester-launch-profile-grid, + .activation-host-apply-proof, + .activation-stage-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); } } @@ -4851,11 +5660,16 @@ code { .tester-launch-gateway-routes div, .tester-launch-route-pair, .tester-launch-route-pair > div > div, + .activation-command-panel, + .activation-field-guide-summary, + .activation-host-apply-proof, + .activation-stage-facts, .workbench-switcher, .explorer-category-strip, .explorer-facts, .explorer-launch-grid, .explorer-trace-steps, + .wallet-tester-flow-steps, .wallet-tester-links { grid-template-columns: 1fr; } @@ -5148,6 +5962,97 @@ code { width: 100%; } +.bridge-proof-rail { + display: grid; + grid-template-columns: minmax(175px, 0.95fr) repeat(4, minmax(118px, 1fr)); + overflow: hidden; + margin-bottom: 14px; + border: 1px solid rgba(72, 50, 27, 0.16); + border-radius: 8px; + background: rgba(250, 241, 229, 0.76); + box-shadow: 0 18px 48px rgba(46, 31, 15, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.58); + backdrop-filter: blur(14px); +} + +.bridge-proof-intro, +.bridge-proof-item { + min-width: 0; + padding: 13px 15px; +} + +.bridge-proof-intro { + display: grid; + align-content: center; + gap: 4px; + border-right: 1px solid rgba(83, 61, 37, 0.12); + background: rgba(255, 250, 243, 0.4); +} + +.bridge-proof-intro span { + color: rgba(42, 32, 22, 0.68); + font-size: 0.7rem; + font-weight: 800; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.bridge-proof-intro strong, +.bridge-proof-item strong { + display: block; + overflow: hidden; + color: #111923; + font-size: 0.88rem; + font-weight: 780; + text-overflow: ellipsis; + white-space: nowrap; +} + +.bridge-proof-intro small, +.bridge-proof-item small { + color: rgba(33, 28, 23, 0.62); + font-size: 0.78rem; + line-height: 1.35; +} + +.bridge-proof-item { + display: grid; + gap: 6px; + border-right: 1px solid rgba(83, 61, 37, 0.12); +} + +.bridge-proof-item:last-child { + border-right: 0; +} + +.bridge-proof-item-heading { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.bridge-proof-item-heading > span { + display: grid; + place-items: center; + width: 28px; + height: 28px; + flex: 0 0 auto; + color: #101820; + border-radius: 50%; + background: rgba(57, 41, 23, 0.1); +} + +.bridge-proof-item p { + overflow: hidden; + margin: 0; + color: #0b62df; + font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; + font-size: 0.92rem; + font-weight: 760; + text-overflow: ellipsis; + white-space: nowrap; +} + .bridge-console { width: 100%; min-width: 0; @@ -5590,6 +6495,21 @@ code { padding: 18px; } + .bridge-proof-rail { + grid-template-columns: 1fr; + } + + .bridge-proof-intro, + .bridge-proof-item, + .bridge-proof-item:last-child { + border-right: 0; + border-bottom: 1px solid rgba(83, 61, 37, 0.12); + } + + .bridge-proof-item:last-child { + border-bottom: 0; + } + .bridge-route-row, .bridge-token-amount-row, .bridge-estimate-grid, diff --git a/apps/dashboard/src/test/dashboardData.test.ts b/apps/dashboard/src/test/dashboardData.test.ts index 423f3d29..2292efc6 100644 --- a/apps/dashboard/src/test/dashboardData.test.ts +++ b/apps/dashboard/src/test/dashboardData.test.ts @@ -27,7 +27,10 @@ import { import { WalletView } from "../views/WalletView"; import { ExternalTesterLaunchView } from "../views/ExternalTesterLaunchView"; import { ExplorerView } from "../views/ExplorerView"; +import { BridgePilotView } from "../views/BridgePilotView"; import { OpsView } from "../views/OpsView"; +import { AlertsView } from "../views/AlertsView"; +import { OwnerActivationView } from "../views/OwnerActivationView"; import { UniswapHooksView } from "../views/UniswapHooksView"; import { WorkbenchView } from "../views/WorkbenchView"; @@ -163,6 +166,19 @@ describe("dashboard fixture", () => { expect(workbench.sections.liveReadiness.length).toBeGreaterThan(0); expect(workbench.sections.liveReadiness[0].facts.find((fact) => fact.label === "deployment ready")?.value).toBe("false"); expect(workbench.sections.liveReadiness.some((record) => record.id === "public-rpc-edge")).toBe(true); + expect(workbench.sections.liveReadiness.some((record) => record.id === "base8453-bridge-runtime-credit-proof")).toBe(true); + expect(workbench.sections.liveReadiness[0].facts.find((fact) => fact.label === "bridge runtime credit")?.value).toBe("passed"); + expect(workbench.sections.liveReadiness[0].facts.find((fact) => fact.label === "bridge reconciliation schedule")?.value).toBe("passed"); + expect(workbench.sections.liveReadiness[0].facts.find((fact) => fact.label === "bridge reconciliation no mutation")?.value).toBe("true"); + expect(workbench.sections.liveReadiness[0].facts.find((fact) => fact.label === "pilot aggregate")?.value).toBe("passed"); + expect(workbench.sections.liveReadiness[0].facts.find((fact) => fact.label === "bridge command matrix")?.value).toBe("passed"); + expect(workbench.sections.liveReadiness[0].facts.find((fact) => fact.label === "bridge command matrix commands")?.value).toBe("20"); + expect(workbench.sections.liveReadiness[0].facts.find((fact) => fact.label === "bridge no-secret audit")?.value).toBe("passed"); + expect(workbench.sections.liveReadiness[0].facts.find((fact) => fact.label === "bridge no-secret findings")?.value).toBe("0"); + expect(workbench.sections.liveReadiness[0].facts.find((fact) => fact.label === "owner env template")?.value).toBe("passed"); + expect(workbench.sections.liveReadiness[0].facts.find((fact) => fact.label === "owner env field guide")?.value).toBe("19"); + expect(workbench.sections.liveReadiness[0].facts.find((fact) => fact.label === "RPC command matrix")?.value).toBe("passed"); + expect(workbench.sections.liveReadiness[0].facts.find((fact) => fact.label === "RPC command matrix commands")?.value).toBe("21"); expect(workbench.sections.realValuePilot.some((record) => record.facts.some((fact) => fact.label === "scope" && fact.value === "capped owner testing"))).toBe(true); expect(workbench.sections.realValuePilot.some((record) => record.facts.some((fact) => fact.label === "source chain ID" && fact.value === "8453"))).toBe(true); expect(workbench.sections.explorerRecords.length).toBeGreaterThan(0); @@ -424,6 +440,9 @@ describe("dashboard fixture", () => { expect(html).toContain("State backup proof"); expect(html).toContain("Backup dry run"); expect(html).toContain("Bridge relayer queue"); + expect(html).toContain("Bridge runtime credit"); + expect(html).toContain("Bridge release evidence"); + expect(html).toContain("flowchain:bridge:release:evidence:validate"); expect(html).toContain("External tester packet"); expect(html).toContain("FLOWCHAIN_RPC_PUBLIC_URL"); expect(html).toContain("flowchain:public-deployment:contract"); @@ -447,6 +466,25 @@ describe("dashboard fixture", () => { expect(html).not.toContain("local-tester-write-token"); }); + it("renders signed transaction intake controls without exposing write RPC", () => { + const workbench = buildWorkbenchSnapshot(data, { + devnetState, + devnetDashboardState, + bridgeTestDeposit, + liveReadinessReport, + }); + const html = renderToStaticMarkup(createElement(MemoryRouter, { initialEntries: ["/wallet?panel=signed"] }, createElement(WalletView, { workbench }))); + + expect(html).toContain("Signed transaction intake"); + expect(html).toContain("/transactions/submit"); + expect(html).toContain("Submit signed envelope"); + expect(html).toContain("Intake only"); + expect(html).toContain("Runtime direct"); + expect(html).not.toContain("transaction_submit"); + expect(html).not.toContain("privateKey"); + expect(html).not.toContain("seedPhrase"); + }); + it("renders the dedicated friends-and-family launch flow without secrets", () => { const workbench = buildWorkbenchSnapshot(data, { devnetState, @@ -458,8 +496,14 @@ describe("dashboard fixture", () => { expect(html).toContain("Friends-and-family launch"); expect(html).toContain("Shareable"); + expect(html).toContain("Live infra"); + expect(html).toContain("Missing inputs"); expect(html).toContain("Packet smoke"); expect(html).toContain("Gateway proof"); + expect(html).toContain("RPC command matrix"); + expect(html).toContain("RPC launch matrix"); + expect(html).toContain("RPC matrix"); + expect(html).toContain("21 commands; 6 owner-host"); expect(html).toContain("Relayer timeout"); expect(html).toContain("Alert rules"); expect(html).toContain("Tester workflow"); @@ -480,10 +524,60 @@ describe("dashboard fixture", () => { expect(html).toContain("/tester/faucet"); expect(html).toContain("/tester/wallets/send"); expect(html).toContain("/explorer/summary"); + expect(html).toContain("npm run flowchain:public-rpc:command-matrix"); expect(html).toContain("npm run flowchain:external-tester:packet"); expect(html).not.toContain("local-tester-write-token"); }); + it("renders the L1 activation cockpit without owner values", () => { + const workbench = buildWorkbenchSnapshot(data, { + devnetState, + devnetDashboardState, + bridgeTestDeposit, + liveReadinessReport, + }); + const html = renderToStaticMarkup(createElement(MemoryRouter, { initialEntries: ["/activation"] }, createElement(OwnerActivationView, { workbench }))); + + expect(html).toContain("L1 activation"); + expect(html).toContain("Release"); + expect(html).toContain("handoff passed"); + expect(html).toContain("Activation stages"); + expect(html).toContain("Host apply sequence"); + expect(html).toContain("owner-host-apply.sh plan"); + expect(html).toContain("owner-host-apply.sh apply"); + expect(html).toContain("owner-host-apply.sh rollback"); + expect(html).toContain("owner-host-apply.ps1 -Action Plan"); + expect(html).toContain("owner-host-apply.ps1 -Action Apply"); + expect(html).toContain("owner-host-apply.ps1 -Action Rollback"); + expect(html).toContain("Apply owner-host public RPC edge"); + expect(html).toContain("Expose repo-owned FlowChain RPC"); + expect(html).toContain("Provision durable state backup storage"); + expect(html).toContain("Configure capped Base 8453 bridge pilot observation"); + expect(html).toContain("Needed now"); + expect(html).toContain("Owner setup groups"); + expect(html).toContain("Public RPC edge"); + expect(html).toContain("Pick the public RPC URL"); + expect(html).toContain("Friends and family need a public HTTPS RPC endpoint"); + expect(html).toContain("npm run flowchain:public-rpc:synthetic-canary -- -AllowBlocked"); + expect(html).toContain("Backup storage"); + expect(html).toContain("Base 8453 bridge"); + expect(html).toContain("Ready groups"); + expect(html).toContain("Tester write gateway"); + expect(html).toContain("blocked by"); + expect(html).toContain("Owner inputs"); + expect(html).toContain("Next commands"); + expect(html).toContain("Do not send"); + expect(html).toContain("Env field guide"); + expect(html).toContain("Guide rows"); + expect(html).toContain("absolute non-local HTTPS endpoint"); + expect(html).toContain("owner DNS, tunnel, or reverse proxy hostname"); + expect(html).toContain("Where to get it"); + expect(html).toContain("FLOWCHAIN_RPC_PUBLIC_URL"); + expect(html).toContain("FLOWCHAIN_BASE8453_RPC_URL"); + expect(html).toContain("npm run flowchain:owner-env:readiness"); + expect(html).not.toContain("local-tester-write-token"); + }); + it("renders the explorer route for tester-visible chain activity without secrets", () => { const workbench = buildWorkbenchSnapshot(data, { devnetState, @@ -499,6 +593,8 @@ describe("dashboard fixture", () => { expect(html).toContain("Private chain live"); expect(html).toContain("Public RPC"); expect(html).toContain("Tester sharing"); + expect(html).toContain("Bridge audit"); + expect(html).toContain(`${liveReadinessReport.metrics.bridgeNoSecretAuditStatus}; findings ${liveReadinessReport.metrics.bridgeNoSecretAuditFindings}`); expect(html).toContain("Alert coverage"); expect(html).toContain("timeout 300s"); expect(html).toContain("Open readiness"); @@ -511,6 +607,37 @@ describe("dashboard fixture", () => { expect(html).not.toContain("local-tester-write-token"); }); + it("renders bridge pilot runtime proof from live readiness evidence", () => { + const workbench = buildWorkbenchSnapshot(data, { + devnetState, + devnetDashboardState, + bridgeTestDeposit, + liveReadinessReport, + }); + const html = renderToStaticMarkup(createElement(MemoryRouter, { initialEntries: ["/bridge"] }, createElement(BridgePilotView, { workbench }))); + + expect(html).toContain("Bridge runtime proof"); + expect(html).toContain("Relayer and credit checks"); + expect(html).toContain("Bridge command matrix"); + expect(html).toContain(`${liveReadinessReport.metrics.bridgeCommandMatrixCommands} commands`); + expect(html).toContain(`${liveReadinessReport.metrics.bridgeCommandMatrixLiveBroadcastCommands} live-broadcast gated`); + expect(html).toContain("npm run flowchain:bridge:command-matrix"); + expect(html).toContain("No-secret audit"); + expect(html).toContain(`${liveReadinessReport.metrics.bridgeNoSecretAuditScannedFiles} files scanned`); + expect(html).toContain("npm run flowchain:bridge:no-secret-audit"); + expect(html).toContain("Pilot aggregate"); + expect(html).toContain("Runtime credit"); + expect(html).toContain("Transfer settlement"); + expect(html).toContain("Relayer guardrail"); + expect(html).toContain("Relayer loop"); + expect(html).toContain("Reconciliation schedule"); + expect(html).toContain("flowchain:bridge:reconciliation:schedule:validate"); + expect(html).toContain(`${liveReadinessReport.metrics.realValuePilotAggregateCommandsRun} proof commands`); + expect(html).toContain(`${liveReadinessReport.metrics.bridgeRuntimeCreditLatencySeconds}s`); + expect(html).toContain(`${liveReadinessReport.metrics.bridgeRuntimeCreditTransferSeconds}s`); + expect(html).not.toContain("local-tester-write-token"); + }); + it("renders the ops center from alert and incident reports without secrets", () => { const workbench = buildWorkbenchSnapshot(data, { devnetState, @@ -521,15 +648,36 @@ describe("dashboard fixture", () => { const html = renderToStaticMarkup(createElement(MemoryRouter, null, createElement(OpsView, { workbench }))); expect(html).toContain("Ops center"); + expect(html).toContain("Bridge audit"); + expect(html).toContain("Bridge evidence audit"); expect(html).toContain("Current findings"); expect(html).toContain("Incident commands"); expect(html).toContain("network sends; stores secrets"); + expect(html).toContain("Relayer check contract"); + expect(html).toContain("bridge-relayer-check-contract-failed"); expect(html).toContain("Relayer loop"); expect(html).toContain("Escalation dry run"); + expect(html).toContain("Autorecovery drill"); + expect(html).toContain("Windows service plan"); + expect(html).toContain("Systemd service plan"); + expect(html).toContain("Public RPC automation"); + expect(html).toContain("Tester gateway E2E"); + expect(html).toContain("Tester gateway proof"); + expect(html).toContain("Ops install proof"); expect(html).toContain("public-rpc-not-ready"); expect(html).not.toContain("local-tester-write-token"); }); + it("renders the alerts route without exposing tester secrets", () => { + const html = renderToStaticMarkup(createElement(MemoryRouter, null, createElement(AlertsView, { data }))); + + expect(html).toContain("Alerts"); + expect(html).toContain("Verifier failed"); + expect(html).toContain("UPSTREAM_LOSS"); + expect(html).toContain("next action"); + expect(html).not.toContain("local-tester-write-token"); + }); + it("renders bridge readiness live-blocked without env values", () => { const configuredButHidden = "https://example.invalid/rpc-redacted"; const workbench = buildWorkbenchSnapshot(data, { diff --git a/apps/dashboard/src/views/BridgePilotView.tsx b/apps/dashboard/src/views/BridgePilotView.tsx index 1f403212..01ab47b1 100644 --- a/apps/dashboard/src/views/BridgePilotView.tsx +++ b/apps/dashboard/src/views/BridgePilotView.tsx @@ -1,5 +1,6 @@ import { useEffect, useMemo, useState } from "react"; import { + Activity, AlertTriangle, ArrowRightLeft, ChevronDown, @@ -7,6 +8,7 @@ import { Copy, ExternalLink, Info, + ListChecks, Lock, ShieldCheck, Wallet, @@ -84,6 +86,35 @@ function statusFromReadiness(readiness: BridgeReadiness | null): DashboardStatus return "pending"; } +function text(value: unknown, fallback = "not recorded"): string { + if (value === null || value === undefined || value === "") { + return fallback; + } + return String(value); +} + +function statusFromMetric(value: unknown, fallback: DashboardStatus = "pending"): DashboardStatus { + const normalized = text(value, "").toLowerCase(); + if (normalized === "passed" || normalized === "ready" || normalized === "verified" || normalized === "true") { + return "verified"; + } + if (normalized === "failed" || normalized === "failure") { + return "failed"; + } + if (normalized === "stale") { + return "stale"; + } + if (normalized === "blocked" || normalized === "pending" || normalized === "false") { + return "pending"; + } + return fallback; +} + +function secondsLabel(value: unknown): string { + const parsed = text(value); + return parsed === "not recorded" ? parsed : `${parsed}s`; +} + function isBytes32(value: string): boolean { return /^0x[0-9a-fA-F]{64}$/.test(value); } @@ -310,6 +341,8 @@ function errorMessage(error: unknown): string { export function BridgePilotView({ workbench }: BridgePilotViewProps) { const readiness = asReadiness(workbench.controlPlane.bridgeLiveReadiness); + const liveReadinessReport = isRecord(workbench.raw.liveReadinessReport) ? workbench.raw.liveReadinessReport : null; + const liveMetrics = isRecord(liveReadinessReport?.metrics) ? liveReadinessReport.metrics : {}; const [walletAddress, setWalletAddress] = useState(null); const [chainId, setChainId] = useState(null); const [amountEth, setAmountEth] = useState("0.00001"); @@ -349,6 +382,96 @@ export function BridgePilotView({ workbench }: BridgePilotViewProps) { const usdEstimateLabel = usdEstimate ?? (priceStatus === "loading" ? "Loading ETH/USD quote" : "USD quote unavailable"); + const runtimeCreditStatus = liveMetrics.bridgeRuntimeCreditValidationStatus ?? liveMetrics.bridgeRuntimeCreditStatus; + const pilotAggregateStatus = liveMetrics.realValuePilotAggregateStatus; + const pilotAggregateReady = liveMetrics.realValuePilotAggregateReady === true; + const pilotAggregateCommandCount = text(liveMetrics.realValuePilotAggregateCommandsRun, "0"); + const bridgeCommandMatrixStatus = text(liveMetrics.bridgeCommandMatrixStatus); + const bridgeCommandMatrixReady = liveMetrics.bridgeCommandMatrixReady === true; + const bridgeCommandMatrixCommands = text(liveMetrics.bridgeCommandMatrixCommands, "0"); + const bridgeCommandMatrixLiveBroadcastCommands = text(liveMetrics.bridgeCommandMatrixLiveBroadcastCommands, "0"); + const bridgeCommandMatrixAckGaps = text(liveMetrics.bridgeCommandMatrixBroadcastAckGaps, "0"); + const bridgeNoSecretAuditStatus = text(liveMetrics.bridgeNoSecretAuditStatus); + const bridgeNoSecretAuditReady = liveMetrics.bridgeNoSecretAuditReady === true; + const bridgeNoSecretAuditScannedFiles = text(liveMetrics.bridgeNoSecretAuditScannedFiles, "0"); + const bridgeNoSecretAuditFindings = text(liveMetrics.bridgeNoSecretAuditFindings, "0"); + const bridgeNoSecretAuditFailedChecks = text(liveMetrics.bridgeNoSecretAuditFailedChecks, "0"); + const bridgeProofCards = [ + { + label: "Bridge command matrix", + value: bridgeCommandMatrixStatus, + detail: `${bridgeCommandMatrixCommands} commands; ${bridgeCommandMatrixLiveBroadcastCommands} live-broadcast gated; ack gaps ${bridgeCommandMatrixAckGaps}`, + command: "npm run flowchain:bridge:command-matrix", + status: bridgeCommandMatrixReady ? "verified" : statusFromMetric(bridgeCommandMatrixStatus), + Icon: ListChecks, + }, + { + label: "No-secret audit", + value: bridgeNoSecretAuditStatus, + detail: `${bridgeNoSecretAuditScannedFiles} files scanned; findings ${bridgeNoSecretAuditFindings}; failed ${bridgeNoSecretAuditFailedChecks}`, + command: "npm run flowchain:bridge:no-secret-audit", + status: bridgeNoSecretAuditReady ? "verified" : statusFromMetric(bridgeNoSecretAuditStatus), + Icon: ShieldCheck, + }, + { + label: "Pilot aggregate", + value: text(pilotAggregateStatus), + detail: `${pilotAggregateCommandCount} proof commands`, + command: "npm run flowchain:real-value-pilot:e2e", + status: pilotAggregateReady ? "verified" : statusFromMetric(pilotAggregateStatus), + Icon: ListChecks, + }, + { + label: "Runtime credit", + value: text(runtimeCreditStatus), + detail: `${secondsLabel(liveMetrics.bridgeRuntimeCreditLatencySeconds)} to spendable credit`, + command: "npm run flowchain:bridge:runtime-credit:validate", + status: statusFromMetric(runtimeCreditStatus), + Icon: Activity, + }, + { + label: "Transfer settlement", + value: secondsLabel(liveMetrics.bridgeRuntimeCreditTransferSeconds), + detail: "wallet credit transfer proof", + command: "npm run flowchain:bridge:runtime-credit:validate", + status: liveMetrics.bridgeRuntimeCreditReady === true ? "verified" : statusFromMetric(runtimeCreditStatus), + Icon: ArrowRightLeft, + }, + { + label: "Relayer guardrail", + value: text(liveMetrics.bridgeRelayerGuardrailStatus), + detail: "fail-closed cursor and broadcast checks", + command: "npm run flowchain:bridge:relayer:guardrail:validate", + status: statusFromMetric(liveMetrics.bridgeRelayerGuardrailStatus), + Icon: ShieldCheck, + }, + { + label: "Relayer loop", + value: text(liveMetrics.bridgeRelayerLoopValidationStatus), + detail: "start, health, stop, and cleanup proof", + command: "npm run flowchain:bridge:relayer:loop:validate", + status: statusFromMetric(liveMetrics.bridgeRelayerLoopValidationStatus), + Icon: ListChecks, + }, + { + label: "Reconciliation schedule", + value: text(liveMetrics.bridgeReconciliationScheduleStatus), + detail: `${text(liveMetrics.bridgeReconciliationScheduleIntervalMinutes, "not recorded")} minute cadence; no mutation ${text(liveMetrics.bridgeReconciliationScheduleNoMutation, "false")}`, + command: "npm run flowchain:bridge:reconciliation:schedule:validate", + status: + liveMetrics.bridgeReconciliationScheduleReady === true + ? "verified" + : statusFromMetric(liveMetrics.bridgeReconciliationScheduleStatus), + Icon: Activity, + }, + ] satisfies Array<{ + label: string; + value: string; + detail: string; + command: string; + status: DashboardStatus; + Icon: typeof Activity; + }>; useEffect(() => { const controller = new AbortController(); @@ -522,6 +645,31 @@ export function BridgePilotView({ workbench }: BridgePilotViewProps) {
+
+
+ Bridge runtime proof + Relayer and credit checks + Latest no-secret launch evidence. +
+ {bridgeProofCards.map((card) => { + const Icon = card.Icon; + return ( +
+
+ + +
+ {card.label} +

{card.value}

+ {card.detail} + {card.command} +
+ ); + })} +
+
+
+
+
+ Create, fund, send, inspect + {primaryWalletAddress ? shortId(primaryWalletAddress, 8, 6) : "No tester account selected yet"} +
+ +
+
+ + + + +
+
+
) : null} + {activePanel === "signed" ? ( +
+
+
+ {signedSubmitResult?.accepted ? "Signed intake accepted" : "Local signed intake"} + {signedSubmitResult?.message ?? "Private transaction submission stays on /transactions/submit; public /rpc remains read-gated."} +
+
+
+
endpoint
+
/transactions/submit
+
+
+
mode
+
{signedRuntimeMode}
+
+
+
schema
+
{signedEnvelopeSchema(signedEnvelopeDraft.parsed)}
+
+
+
crypto
+
{signedSubmitResult?.crypto?.ok === true ? "verified" : "pending"}
+
+
+
+ +
+