diff --git a/.github/workflows/ci-staging.yml b/.github/workflows/ci-staging.yml index 772a960..7f2484c 100644 --- a/.github/workflows/ci-staging.yml +++ b/.github/workflows/ci-staging.yml @@ -164,6 +164,11 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Verify screenshot inventory and mirrors + shell: pwsh + working-directory: . + run: ./scripts/capture-screenshots.ps1 -VerifyExisting + - name: Set up Node.js uses: actions/setup-node@v4 with: @@ -219,18 +224,26 @@ jobs: test -f docs/docker-regtest.md test -f docs/supported-bitcoin-core.md test -f docs/verified-scenarios.md + test -f docs/scenario-authoring.md + test -f docs/proof-bundles.md + test -f docs/threat-model.md + test -f docs/curriculum-map.md test -f docs/testing-and-ci.md + test -f docs/release-notes-v0.1.0.md + test -f docs/migration-v0.1.0.md + test -f docs/release-readiness.md - name: Summarize release gate shell: bash run: | { echo "## BitScope release readiness" + echo "- Release candidate: v0.1.0" echo "- Backend tests passed" echo "- Verified Scenarios security, cleanup, redaction, report, and export gates passed" echo "- Verified Scenarios passed twice against disposable Bitcoin Core 28.1 datadirs" echo "- Community Treasury Recovery flagship and Core-reaching attacks passed" echo "- Frontend type check, lint, and production build passed" echo "- Docker Compose config validated" - echo "- Required teaching and operations docs are present" + echo "- Required teaching, threat-model, migration, and release docs are present" } >> "$GITHUB_STEP_SUMMARY" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c83f7a0..7d9a1c1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,6 +8,8 @@ BitScope is a local-first Bitcoin Core learning laboratory. Contributions should - Use regtest for mining, wallet mutation, signing, funding, and broadcast workflows. - Review [the limitations](docs/limitations.md) before proposing hosted services, address-history features, or mainnet behavior. - Check [the supported Bitcoin Core policy](docs/supported-bitcoin-core.md) when changing RPC parameters or wallet behavior. +- Read [the scenario authoring contract](docs/scenario-authoring.md) before adding or changing a Verified Scenario. +- Read [the threat model](docs/threat-model.md) before changing trust boundaries, token handling, RPC capabilities, evidence, or cleanup. ## Local Setup @@ -50,6 +52,7 @@ For a containerized regtest environment, follow [the Docker guide](docs/docker-r - Keep request models bounded and return stable `BitScopeError` codes with secret-safe details. - Do not expose RPC credentials, private keys, seed material, xprvs, wallet unlock data, or unfiltered upstream errors. - Add focused unit tests for service behavior and security boundaries. +- Preserve deterministic, recursively redacted evidence and verify every artifact hash before export. ### Frontend @@ -85,13 +88,15 @@ Run the backend suite: ```powershell cd backend -python -m pytest +python -m pytest --ignore=tests/live_node ``` -Build the frontend: +Type-check, lint, and build the frontend: ```powershell cd frontend +npm run typecheck +npm run lint npm run build ``` @@ -103,6 +108,8 @@ docker compose config Changes to real RPC workflows should also run the opt-in tests described in [Live Bitcoin Core RPC Testing](docs/live-rpc-testing.md). These tests must use an isolated regtest node and disposable wallets. +Scenario changes must additionally cover parsing, state transitions, expected and unexpected failure classification, success and failure cleanup, interruption recovery, redaction, manifest hashing, report generation, and deterministic export. The pinned live test must create all wallets, addresses, outpoints, transactions, and blocks within its own disposable datadir. + ## Documentation Update documentation in the same change when behavior, configuration, supported Bitcoin Core versions, commands, routes, or safety constraints change. Prefer durable user and contributor guidance over implementation backlogs or completed planning documents. @@ -116,6 +123,7 @@ Use relative links for repository documents and verify that every referenced pat - Explain the user-visible outcome and safety implications in the pull request. - List the exact verification commands and results. - Call out any live-node test prerequisites or intentionally deferred follow-up work. +- Do not claim that regtest evidence is a security audit, formal proof, production approval, or custody validation. - Keep the branch current with `main` and resolve CI failures before requesting review. ## Reporting Security Issues diff --git a/README.md b/README.md index 9820a4c..59d3fba 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,31 @@ # BitScope -**An interactive Bitcoin Core laboratory powered entirely by your own node.** +> **BitScope is a reproducible Bitcoin protocol laboratory that constructs, executes, attacks and verifies Bitcoin transactions against a real Bitcoin Core node.** -BitScope is a learning-focused Bitcoin Core explorer and laboratory inspired by *Learning Bitcoin from the Command Line*. It is not a hosted block explorer clone: every major screen connects UI output back to `bitcoin-cli`, JSON-RPC methods, raw Bitcoin Core JSON, and a plain-English explanation. +BitScope is local-first, regtest-focused, Bitcoin Core-backed, reproducible, educational, evidence-driven, and designed for protocol experimentation. Its Verified Scenarios create isolated wallets and current-run transactions, exercise valid and intentionally invalid paths, classify Bitcoin Core outcomes, record lifecycle evidence, clean owned state, and export deterministic proof bundles. + +BitScope is not a hosted explorer, mainnet wallet, custody service, replacement for Bitcoin Core, production treasury coordinator, or guarantee that a policy is safe for real funds. A successful regtest scenario proves only the behavior observed under its recorded Bitcoin Core version, configuration, and assumptions. ## Screenshots -These screenshots show BitScope interacting with a local regtest node: node status, guided Demo Mode, wallet workflows, transaction inspection, script exploration, and the concept library. +These screenshots show the learner-first BitScope experience backed by a local regtest node. Regenerate the complete set with `.\scripts\capture-screenshots.ps1`; see [Screenshot Capture](docs/screenshots/README.md) for validation and review requirements. -| Node dashboard | Demo Mode | +| Learner Home | First Transaction | | --- | --- | -| ![BitScope node dashboard](docs/screenshots/dashboard.png) | ![BitScope Demo Mode](docs/screenshots/demo-mode.png) | +| ![BitScope learner Home](docs/screenshots/home.png) | ![BitScope First Transaction guided lab](docs/screenshots/first-transaction.png) | -| Wallet lab | Transaction explorer | +| Node Details | Learning Path | | --- | --- | -| ![BitScope wallet lab](docs/screenshots/wallet-lab.png) | ![BitScope transaction explorer](docs/screenshots/transaction-explorer.png) | +| ![BitScope Node Details](docs/screenshots/node-details.png) | ![BitScope curriculum Learning Path](docs/screenshots/learning-path.png) | -| Script Lab | Learning library | +| Reviewer Mode | Concept Library | | --- | --- | -| ![BitScope Script Lab](docs/screenshots/script-lab.png) | ![BitScope learning library](docs/screenshots/learning-library.png) | +| ![BitScope Reviewer Mode](docs/screenshots/reviewer-mode.png) | ![BitScope Concept Library](docs/screenshots/learning-library.png) | ## What BitScope Teaches +- Verified transaction workflows: lifecycle, opt-in RBF replacement, multisig PSBT coordination, absolute-height CLTV, and Community Treasury Recovery. +- Evidence review: expected versus unexpected failures, backend-recorded lifecycle events, redacted Core output, deterministic manifests, and proof-bundle inspection. - Local node state: chain selection, sync, pruning, peers, network reachability, and live status. - Blocks and transactions: headers, proof of work, Merkle roots, inputs, outputs, witnesses, fees, locktime, and the UTXO model. - Wallet and regtest workflows: wallet loading, descriptors, addresses, UTXOs, mining, faucet sends, confirmations, and safe local spending. @@ -41,6 +45,7 @@ These screenshots show BitScope interacting with a local regtest node: node stat Implemented learning surfaces include: +- Verified Scenarios, Reviewer Mode, Curriculum and Challenge Mode, policy comparison, lifecycle timelines, and deterministic proof export. - Dashboard, Live monitor, Peer/privacy dashboard, Integrations, and RPC explorer. - Blocks, Transactions, Tx Control, Mempool, Fees, Address, and Local Indexer. - Wallet, Regtest, Multisig, PSBT, Timelocks, Descriptors, Taproot, Keys. @@ -194,10 +199,17 @@ CI runs backend hardening gates, the complete live suite twice against clean dis - [Docker regtest](docs/docker-regtest.md): full local stack, reset, and configuration. - [Regtest guide](docs/regtest-guide.md): mining, coinbase maturity, and demo flow. - [Demo script](docs/demo-script.md): reviewer-facing walkthrough. +- [Verified Scenarios](docs/verified-scenarios.md): catalogue and proved scenario behavior. +- [Scenario authoring](docs/scenario-authoring.md): closed-definition, execution, evidence, cleanup, and review contract. +- [Proof bundles](docs/proof-bundles.md): deterministic archive structure and verification. +- [Threat model](docs/threat-model.md): assets, trust boundaries, mitigations, and residual risk. +- [Curriculum map](docs/curriculum-map.md): LBCLI Chapters 3–13 mapped only to implemented capabilities. +- [Learner-first implementation plan](docs/learner-first-experience-plan.md): pre-v0.1.0 Home, navigation, flagship journey, shared lab pattern, and usability gates. - [Live RPC testing](docs/live-rpc-testing.md): isolated live-node pytest lifecycle and regtest failure mitigations. - [Testing and CI](docs/testing-and-ci.md): Phase 10 commands, coverage map, and release-readiness gates. - [Supported Bitcoin Core](docs/supported-bitcoin-core.md): pinned CI version, support policy, and deterministic regtest coverage. - [Limitations](docs/limitations.md): no hosted APIs, address-history limits, mainnet safety. +- [Release readiness](docs/release-readiness.md): v0.1.0 evidence, checklist, verification, and deferred work. - [Contributing](CONTRIBUTING.md): development workflow, verification commands, safety invariants, and pull-request expectations. ## Demo Story diff --git a/docs-site/index.html b/docs-site/index.html index 6706c5c..7c684b3 100644 --- a/docs-site/index.html +++ b/docs-site/index.html @@ -6,7 +6,7 @@ BitScope Docs @@ -27,18 +27,18 @@
-

Bitcoin Core lab

-

Learn Bitcoin from your own node.

+

Reproducible Bitcoin protocol laboratory

+

Construct, attack, and verify transactions against Bitcoin Core.

- BitScope is a local-first explorer and laboratory for Bitcoin Core RPC, regtest workflows, - wallets, transactions, scripts, PSBTs, descriptors, fees, peers, and node limits. + BitScope is local-first, regtest-focused, educational, and evidence-driven. Reviewed scenarios + record Bitcoin Core outcomes, expected failures, lifecycle events, cleanup, and deterministic proof bundles.

- BitScope Demo Mode screen + BitScope learner Home screen
@@ -46,32 +46,33 @@

Learn Bitcoin from your own node.

Screenshots

-

Interactive workflows, not a hosted explorer clone.

+

Real local-node workflows, not a hosted explorer.

These screens show BitScope interacting with a local regtest node and pairing each UI result with - command-line and JSON-RPC context. + command-line and JSON-RPC context. BitScope is not a mainnet wallet, custody service, production + treasury coordinator, security audit, or replacement for Bitcoin Core.

@@ -88,11 +89,11 @@

Hosted docs, local execution.

Frontend

-

Next.js pages for dashboards, labs, command explanations, and educational workflows.

+

Next.js pages for dashboards, Verified Scenarios, Reviewer Mode, curriculum, policy comparison, and labs.

Backend

-

FastAPI services translate Bitcoin Core RPC responses into friendly, secret-safe learning data.

+

FastAPI executes closed reviewed workflows and emits typed, redacted, hash-covered evidence.

Bitcoin Core

@@ -108,8 +109,8 @@

Start with regtest.

cd BitScope .\scripts\compose.ps1 up --build

- Open http://localhost:3000, then use Demo Mode to create a clean wallet, mine mature - regtest coins, send a transaction, decode script, and export the command trail. + Open http://localhost:3000/capstone-demo for the canonical Reviewer Mode walkthrough, + or use the shorter Demo Mode for basic wallet and transaction onboarding.

diff --git a/docs-site/screenshots/dashboard.png b/docs-site/screenshots/dashboard.png deleted file mode 100644 index a67a1c9..0000000 Binary files a/docs-site/screenshots/dashboard.png and /dev/null differ diff --git a/docs-site/screenshots/demo-mode.png b/docs-site/screenshots/demo-mode.png deleted file mode 100644 index 1224859..0000000 Binary files a/docs-site/screenshots/demo-mode.png and /dev/null differ diff --git a/docs-site/screenshots/first-transaction.png b/docs-site/screenshots/first-transaction.png new file mode 100644 index 0000000..8fb9a71 Binary files /dev/null and b/docs-site/screenshots/first-transaction.png differ diff --git a/docs-site/screenshots/home.png b/docs-site/screenshots/home.png new file mode 100644 index 0000000..84f983b Binary files /dev/null and b/docs-site/screenshots/home.png differ diff --git a/docs-site/screenshots/learning-library.png b/docs-site/screenshots/learning-library.png index 8ef9008..cec8752 100644 Binary files a/docs-site/screenshots/learning-library.png and b/docs-site/screenshots/learning-library.png differ diff --git a/docs-site/screenshots/learning-path.png b/docs-site/screenshots/learning-path.png new file mode 100644 index 0000000..52dfb41 Binary files /dev/null and b/docs-site/screenshots/learning-path.png differ diff --git a/docs-site/screenshots/node-details.png b/docs-site/screenshots/node-details.png new file mode 100644 index 0000000..f169e9d Binary files /dev/null and b/docs-site/screenshots/node-details.png differ diff --git a/docs-site/screenshots/reviewer-mode.png b/docs-site/screenshots/reviewer-mode.png new file mode 100644 index 0000000..3626db3 Binary files /dev/null and b/docs-site/screenshots/reviewer-mode.png differ diff --git a/docs-site/screenshots/script-lab.png b/docs-site/screenshots/script-lab.png deleted file mode 100644 index 504ccd0..0000000 Binary files a/docs-site/screenshots/script-lab.png and /dev/null differ diff --git a/docs-site/screenshots/transaction-explorer.png b/docs-site/screenshots/transaction-explorer.png deleted file mode 100644 index 1524fd8..0000000 Binary files a/docs-site/screenshots/transaction-explorer.png and /dev/null differ diff --git a/docs-site/screenshots/wallet-lab.png b/docs-site/screenshots/wallet-lab.png deleted file mode 100644 index 42795f2..0000000 Binary files a/docs-site/screenshots/wallet-lab.png and /dev/null differ diff --git a/docs/architecture.md b/docs/architecture.md index fbe2d27..dc53e61 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -86,6 +86,8 @@ Services keep Bitcoin-specific behavior out of route handlers: - `IntegrationService`: JSON-RPC client examples, wallet RPC paths, SSE, and optional ZMQ configuration. - `KeyService`: public descriptor, xpub, derivation path, watch-only wallet, and hardware-wallet PSBT education. - `LearningService`: concept catalog and RPC method reference. +- `CurriculumService` and `ChallengeService`: implemented-only LBCLI mapping, progressive hints, and owner-scoped evidence-backed completion. +- `PolicyComparisonService`: typed exact, derived, estimated, unknown, and unsupported policy metrics. - `LabSessionService`: SQLite-backed isolated lab ownership, resume, reset, export, and safe wallet cleanup. - `ScenarioCatalog`: immutable registry of reviewed, versioned scenario definitions and their run availability. - `ScenarioService`: ownership-scoped, optimistic-revision run creation, live regtest preparation, and dispatch to reviewed scenario-specific executors. @@ -96,6 +98,9 @@ Services keep Bitcoin-specific behavior out of route handlers: - `EvidenceService`: typed evidence capture that keeps Bitcoin Core output separate from BitScope interpretation, recursively redacts credentials and private-key material, emits canonical JSON, and attaches a hash-backed reference to the owning run. - `ScenarioArtifactStore`: bounded, run-scoped evidence files with server-generated paths, canonical-content checks, and SHA-256 verification on every read. - `ProofBundleService`: deterministic Markdown reports, conditional transcript/command/assertion files, SHA-256 manifests, and ZIP exports with fixed metadata. Bundles are evidence, not attestations or audits. +- `AttackVerificationService`: applicability-aware expected/unexpected/not-applicable classification with bounded safe Core observations. +- `LifecycleRecorder`: typed ordered transaction events and explicit RBF, CPFP, timelock, confirmation, and cleanup relationships derived only from persisted evidence. +- `ReviewerDemoService`: clean-session orchestration, treasury default with lifecycle fallback, interrupted-run recovery, safe reset, and proof-export readiness. Scenario runs are stored transactionally beside their owning lab sessions. Their identity fields and recorded histories are append-only, state changes use explicit transitions and revision checks, and reset creates a new run rather than rewriting the old run. Runs that may own resources cannot be reset or deleted until cleanup is recorded as complete. @@ -121,7 +126,7 @@ All routes are prefixed with `/api`. | Scripts and data | `/scripts/decode`, `/scripts/template`, `/scripts/test-spend`, `/scripts/create-op-return` | | PSBT, multisig, timelocks | `/psbt/create`, `/psbt/decode`, `/psbt/wallet-process`, `/psbt/finalize`, `/multisig/create`, `/multisig/fund`, `/multisig/spend-psbt`, `/timelocks/transaction`, `/timelocks/script-template` | | Descriptors and Taproot | `/descriptors/analyze`, `/descriptors/wallet/{wallet_name}`, `/taproot/inspect` | -| Learning and integrations | `/rpc/methods`, `/rpc/execute`, `/learn/concepts`, `/learn/rpc-methods`, `/integrations/rpc-examples`, `/keys/guide` | +| Learning and integrations | `/rpc/methods`, `/rpc/execute`, `/learn/concepts`, `/learn/rpc-methods`, `/learn/curriculum`, `/learn/challenges`, `/learn/policies`, `/learn/policies/compare`, `/integrations/rpc-examples`, `/keys/guide` | ## Frontend Architecture @@ -155,6 +160,9 @@ The frontend uses the Next.js app router. Pages are organized by learning workfl | `/keys` | Descriptor, xpub, derivation path, watch-only, and hardware-wallet PSBT education | | `/rpc` | Safe RPC method explorer | | `/learn` | Concept library connected to pages and RPC methods | +| `/curriculum` | LBCLI Chapters 3–13 and evidence-backed Challenge Mode | +| `/scenarios` | Verified Scenario catalogue, persistent runs, lifecycle, and proof export | +| `/policies/compare` | Typed comparison of the proved treasury and simple 2-of-3 multisig | ## Shared UI Contracts @@ -206,6 +214,20 @@ Public hosting is not appropriate for: For public visibility, the repository includes `docs-site/`, a static GitHub Pages documentation site. For real learning workflows, users should run BitScope locally or through the Docker regtest stack. +## Evidence and Reviewer Flow + +```text +reviewed definition + -> session-owned executor + -> Bitcoin Core observation / classified negative path + -> typed redacted evidence + optimistic run checkpoint + -> backend-derived lifecycle and assertions + -> deterministic report, manifest, and ZIP + -> Reviewer Mode / independent bundle inspection +``` + +Browser state is a convenience pointer only. Scenario truth, revision, evidence, lifecycle, completion, and cleanup remain backend-owned. + ## Bitcoin Core Limits Bitcoin Core validates the chain, tracks the UTXO set, and exposes local node state. It does not provide complete arbitrary address history by default. diff --git a/docs/capstone-expansion-plan.md b/docs/capstone-expansion-plan.md index 7472389..a84b907 100644 --- a/docs/capstone-expansion-plan.md +++ b/docs/capstone-expansion-plan.md @@ -366,7 +366,7 @@ Do not assume the flagship three-branch policy is supported. Implement the prove - [x] Record pinned live-node status and the reason it was not run locally. - [x] Identify legacy BDB multisig dependence. - [x] Create this expansion plan without adding a production feature. -- [ ] Run the pinned 28.1 live tests when a disposable node or Docker daemon is available. +- [x] Run the pinned 28.1 live tests against two disposable clean datadirs in CI. ### Phase 1 - Verified Scenarios domain @@ -390,7 +390,7 @@ Do not assume the flagship three-branch policy is supported. Implement the prove - [x] RBF replacement: opt-in signaling, exact insufficient-fee rejection, original eviction, replacement confirmation, evidence, and cleanup. - [x] Multisig PSBT: one-key signer contexts, one-signature incompleteness, 2-of-3 completion, preflight, confirmation, deterministic evidence, and cleanup. - [x] CLTV timelock: real P2WSH funding, ephemeral BIP143 signing, exact premature/final-sequence/low-nLockTime rejections, bounded maturity, confirmation, deterministic evidence, and cleanup. -- [ ] Add CPFP and OP_RETURN only after the mandatory four are complete or in parallel without weakening them. +- [x] Keep CPFP and OP_RETURN as explicitly deferred optional scenarios after completing the mandatory four. - [x] Give every mandatory foundational scenario a meaningful proved negative path, live test, evidence, and cleanup. ### Phase 4 - Community Treasury Recovery @@ -443,11 +443,13 @@ Do not assume the flagship three-branch policy is supported. Implement the prove ### Phase 11 - Documentation and release -- [ ] Update product identity, architecture, limitations, demo, live testing, and support docs. -- [ ] Add scenario authoring, proof bundle, threat model, curriculum, and verified-scenario docs. -- [ ] Inspect tags and release history before selecting a semantic version. -- [ ] Prepare release notes, migration notes, screenshots, verification commands, and limitations. +- [x] Update product identity, architecture, limitations, demo, live testing, and support docs. +- [x] Add scenario authoring, proof bundle, threat model, curriculum, and verified-scenario docs. +- [x] Inspect local/remote tags and GitHub releases before selecting `v0.1.0` as the first release candidate. +- [x] Complete release preparation: notes, migration guidance, verification commands, honest limitations, and six current learner-first screenshots. +- [ ] Capture completed states from the manual clean Reviewer Mode walkthrough. +- [ ] Run the final `workflow_dispatch` release-readiness gate from the Phase 11 commit. ## Current delivery status -Phases 1 through 10 are implemented and validated in CI. Transaction lifecycle, RBF replacement, multisig PSBT, real absolute-height CLTV, and Community Treasury Recovery use typed definitions, persistent state, protected mutation routes, deterministic proof bundles, proved negative paths, session-owned cleanup, and disposable Core 28.1 live tests. The flagship treasury additionally exports a specialized Proof of Spendability with exact compatibility, policy, branch, expected-rejection, evidence, and cleanup checks. The shared attack and lifecycle frameworks preserve bounded Core evidence and render recorded relationships without frontend inference. Curriculum mapping covers LBCLI Chapters 3-13 using only implemented pages and scenarios. Six progressively hinted challenges validate owner-scoped scenario runs, assertions, cleanup, Core identity, and canonical artifact hashes before unlocking final explanations or completion exports. The policy comparison tool separates exact facts, deterministic derivations, bounded estimates, unknowns, and unsupported values while comparing the proved treasury with simple 2-of-3 multisig at a learner-selected fee rate. Reviewer Mode orchestrates a clean persistent lab, defaults to the treasury with a lifecycle fallback, exposes actual Core evidence and commands, restores interrupted runs, records recovery cleanup before replacement, downloads the deterministic bundle, and shows final cleanup. CI runs explicit security, cleanup, interruption, redaction, report, and export gates; type-checks and lints the frontend; validates Compose; and repeats the complete live suite in separate disposable Core 28.1 containers and datadirs. PR #15 passed the backend, frontend, Compose, and both pinned live-node matrix entries. Optional CPFP and OP_RETURN scenarios remain deferred. Phase 11 documentation and release preparation is next. +Phases 0 through 11 are implemented and ready for review. The `v0.1.0` release package is prepared, while completed-state captures from the clean Reviewer Mode walkthrough, the two-person usability observation, the final dispatched release gate, merge, and tagging remain explicitly open release gates. Transaction lifecycle, RBF replacement, multisig PSBT, real absolute-height CLTV, and Community Treasury Recovery use typed definitions, persistent state, protected mutation routes, deterministic proof bundles, proved negative paths, session-owned cleanup, and disposable Core 28.1 live tests. The flagship treasury additionally exports a specialized Proof of Spendability with exact compatibility, policy, branch, expected-rejection, evidence, and cleanup checks. The shared attack and lifecycle frameworks preserve bounded Core evidence and render recorded relationships without frontend inference. Curriculum mapping covers LBCLI Chapters 3-13 using only implemented pages and scenarios. Six progressively hinted challenges validate owner-scoped scenario runs, assertions, cleanup, Core identity, and canonical artifact hashes before unlocking final explanations or completion exports. The policy comparison tool separates exact facts, deterministic derivations, bounded estimates, unknowns, and unsupported values while comparing the proved treasury with simple 2-of-3 multisig at a learner-selected fee rate. Reviewer Mode orchestrates a clean persistent lab, defaults to the treasury with a lifecycle fallback, exposes actual Core evidence and commands, restores interrupted runs, records recovery cleanup before replacement, downloads the deterministic bundle, and shows final cleanup. CI runs explicit security, cleanup, interruption, redaction, report, and export gates; type-checks and lints the frontend; validates Compose; and repeats the complete live suite in separate disposable Core 28.1 containers and datadirs. PR #15 passed the backend, frontend, Compose, and both pinned live-node matrix entries. Optional CPFP and OP_RETURN scenarios remain deferred. diff --git a/docs/curriculum-map.md b/docs/curriculum-map.md new file mode 100644 index 0000000..888671b --- /dev/null +++ b/docs/curriculum-map.md @@ -0,0 +1,41 @@ +# Curriculum Map + +BitScope maps *Learning Bitcoin from the Command Line* (LBCLI) Chapters 3–13 to implemented local pages, reviewed scenarios, and Bitcoin Core RPC methods. It links to the original course rather than reproducing it. The typed source of truth is `CurriculumService`; `/curriculum` renders that backend mapping. + +| Chapter | BitScope objective | Pages | Verified Scenarios | +| --- | --- | --- | --- | +| 3 | Understand the node, wallet, descriptors, and UTXOs | `/`, `/wallet`, `/descriptors`, `/regtest` | Transaction lifecycle | +| 4 | Construct, preflight, broadcast, and confirm transactions | `/transactions`, `/regtest`, `/mempool`, `/scenarios` | Transaction lifecycle | +| 5 | Distinguish opt-in RBF from CPFP | `/tx-control`, `/mempool`, `/fees`, `/scenarios` | RBF replacement | +| 6 | Build and inspect threshold policies | `/multisig`, `/psbt`, `/policies/compare`, `/scenarios` | Multisig PSBT; Community Treasury Recovery | +| 7 | Coordinate partial signing and finalization with PSBT | `/psbt`, `/multisig`, `/keys`, `/scenarios` | Multisig PSBT; Community Treasury Recovery | +| 8 | Use locktime and data-carrying outputs | `/timelocks`, `/data-tx`, `/transactions`, `/script` | CLTV timelock | +| 9 | Read and test Bitcoin Script conditions | `/script`, `/script-lab`, `/transactions` | Transaction lifecycle | +| 10 | Compare P2SH/P2WSH-style policy wrappers | `/script-lab`, `/multisig`, `/transactions`, `/scenarios` | Multisig PSBT; CLTV; treasury | +| 11 | Enforce CLTV and CSV timelocks | `/timelocks`, `/script-lab`, `/policies/compare`, `/scenarios` | CLTV; treasury | +| 12 | Compose conditional spending paths | `/script-lab`, `/script`, `/scenarios` | Community Treasury Recovery | +| 13 | Design, attack, and prove a policy | `/capstone-demo`, `/scenarios`, `/policies/compare`, `/script-lab`, `/multisig`, `/psbt`, `/timelocks` | Community Treasury Recovery | + +Each API entry also includes its original LBCLI chapter URL, prerequisites, RPC methods, guided exercise, independent challenge, verification criteria, and any implementation limitation. + +## Challenge Mode + +Six progressive challenges reuse backend-owned scenario evidence: + +1. signal opt-in RBF; +2. replace an RBF transaction with a higher fee; +3. complete a 2-of-3 PSBT; +4. prove a premature CLTV failure; +5. diagnose a `testmempoolaccept` rejection; and +6. complete the treasury recovery path. + +Public challenge definitions exclude hidden assertion IDs, hint text, and completion explanations. Hints are released one level at a time. Verification accepts only a run ID and owning session ID, then checks the backend-owned terminal result, cleanup, Core identity, required assertions, evidence presence, and stored artifact hashes. The frontend cannot self-report completion. + +## Honest Capability Boundaries + +- Chapter 5 includes CPFP construction and an explicit lifecycle relationship, but the optional CPFP Verified Scenario remains deferred. +- Chapter 8 includes OP_RETURN construction, but the optional OP_RETURN Verified Scenario remains deferred. +- Chapters 9–13 use implemented templates and closed scenarios; BitScope is not a general-purpose Script interpreter. +- Treasury delays, fee rates, and signer contexts are teaching parameters, not production recommendations. + +The detailed API and accessibility contract remains in [Curriculum Mapping and Challenge Mode](curriculum-and-challenge-mode.md). diff --git a/docs/demo-script.md b/docs/demo-script.md index b36a0a9..28c6815 100644 --- a/docs/demo-script.md +++ b/docs/demo-script.md @@ -81,6 +81,8 @@ Capture these states at a desktop width and one narrow/mobile width: Never use a screenshot containing RPC credentials, mutation tokens, private keys, browser storage, or a persistent developer wallet. +For v0.1.0, the repository tracks the current learner-first UI screenshots under `docs/screenshots/`, including Reviewer Mode before execution. Completed Reviewer Mode checkpoint screenshots remain a release-readiness item until they can be captured from a clean disposable Core 28.1 demonstration; do not substitute mocked data or fabricate a completed state. + ## Likely reviewer questions ### Is this scripted UI data? diff --git a/docs/learner-first-experience-plan.md b/docs/learner-first-experience-plan.md new file mode 100644 index 0000000..4f0411c --- /dev/null +++ b/docs/learner-first-experience-plan.md @@ -0,0 +1,361 @@ +# Learner-First Experience Implementation Plan + +## Decision + +BitScope does not need a visual redesign. It needs a calmer first decision, journey-oriented information architecture, progressive disclosure, and one flagship learning flow that unifies capabilities already implemented. + +The product promise for the interface should be: + +> **See how Bitcoin works through your own Bitcoin Core node.** + +The release identity remains: + +> **BitScope is a reproducible Bitcoin protocol laboratory that constructs, executes, attacks and verifies Bitcoin transactions against a real Bitcoin Core node.** + +The first sentence explains the learner benefit; the second defines the technical product. Neither implies production custody or security approval. + +Do not create the `v0.1.0` tag until the mandatory release scope below is implemented, validated, demonstrated, and photographed against a clean disposable Core 28.1 node. + +## Implementation Status + +Workstreams 1–4 were implemented locally on 2026-07-27: + +- `/` is now the learner Home and the full node dashboard is preserved at `/node` with progressive disclosure. +- Navigation is organized as Home, Learn, Explore, Build, and Developer, while existing routes remain available. +- `/learn/first-transaction` presents the registered backend-owned `transaction-lifecycle` run as a guided prepare, execute, inspect, expected-failure, proof, and cleanup journey. +- Shared learning components provide question-first headers, environment checks, visible step state, one dominant action, result explanations, collapsed technical evidence, and deliberate next steps. +- Multisig, PSBT, timelocks, and fee bumping use the shared question-first and progressive-disclosure pattern. + +Local verification passed frontend type-check, lint with three pre-existing non-blocking warnings, production build, all seven changed page HTTP/heading/active-navigation checks, and the 12 focused reviewer/lifecycle backend tests. + +A protected functional walkthrough also completed the same `transaction-lifecycle` path used by the learner UI against the configured local regtest node: + +- terminal state `verified`, revision 5; +- four passed assertions; +- one expected overspend rejection and zero unexpected failures; +- nine evidence records and nine lifecycle events; +- proof report HTTP 200; +- two proof-bundle downloads with matching SHA-256 `bf687c5d79c8bcd9b08f4849a234951462bdeb76daec5dcc4c429aa4f08ede95`; and +- cleanup `completed`, lab `cleaned`, with the session wallet no longer loaded. + +The configured node identified itself as `/Satoshi:28.0.0/`, so this first functional walkthrough is not the pinned Core 28.1 release evidence. + +A second isolated functional walkthrough was then run against a checksum-verified Bitcoin Core 28.1 Windows distribution on a fresh height-0 regtest datadir and non-conflicting ports: + +- Core identified itself as `/Satoshi:28.1.0/`; +- run `8ddb04df-3a15-41b6-b1ac-d8b3e90eb136` reached `verified`, revision 5; +- four assertions passed with one expected overspend rejection and zero unexpected failures; +- nine evidence records and nine lifecycle events were persisted; +- the report returned HTTP 200; +- two 18-entry proof bundles had matching SHA-256 `7adbbd79cf54132a1d91575ce0b3c969271a1f21eede3211d92c6fd65e2c53cf`; and +- cleanup completed, the lab was marked cleaned, and its session wallet was no longer loaded. + +The isolated backend and Core processes were stopped after verification. Their disposable datadir, database, artifacts, and logs remain under `C:\tmp\bitscope-release-core281-20260727` for local inspection. + +A rendered learner walkthrough then used another fresh Core 28.1 regtest node: + +- browser-created run `d371632e-56db-442b-acb1-574b5983511c` reached `verified`, revision 5; +- four assertions passed with one expected overspend rejection and zero unexpected failures; +- nine evidence records and nine lifecycle events were rendered; +- two proof-bundle downloads matched at SHA-256 `b611d3b2b0094b3919536e9511d8b7565d888a4ec145a27590379a9ef9466cd6`; +- cleanup completed and the session-owned wallet was unloaded; and +- desktop and narrow-viewport captures covered active navigation, Core 28.1 readiness, lifecycle stages, expected failure, progressive disclosure, and export. + +Keyboard accessibility was checked deterministically. The journey uses native links, buttons, and `details`/`summary`, exposes navigation state through `aria-current`, `aria-expanded`, and `aria-controls`, and provides a global high-contrast `:focus-visible` outline. This closes the rendered walkthrough, responsive, screenshot, and keyboard-accessibility gates. The two-person participant-usability gate remains open. + +## Evidence from the Current Product + +- `frontend/app/page.tsx` is a node-status dashboard with eight equal-weight cards, best block hash, RPC commands, and raw details. +- `SidebarNavigation` offers Dashboard, Demo, Learning Path, and Concept Library as four competing starting points. +- The grouped sidebar is an improvement over a flat list, but its labels still expose implementation modules before learner intent. +- `DemoMode` already creates a wallet, mines maturity, sends, decodes, and exports a command trail, but it is a separate one-shot product area. +- `transaction-lifecycle` already proves UTXO selection, construction, signing, preflight, mempool, confirmation, decoding, expected rejection, lifecycle evidence, and cleanup. +- Curriculum, Challenge Mode, lifecycle timelines, command cards, scenario assertions, policy comparison, and Reviewer Mode can be reused rather than rebuilt. +- The dark theme, green accent, typography, card system, active navigation state, and responsive foundation should remain. + +## Goals + +1. Give a first-time learner one obvious next action. +2. Organize navigation by intent: Home, Learn, Explore, Build, Developer. +3. Keep technical depth without presenting it all at once. +4. Make “Your first Bitcoin transaction” the product's center. +5. Use a consistent learning-page structure and one visually dominant action. +6. Preserve direct links, keyboard access, mobile behavior, Core provenance, and safety guards. +7. Keep backend-owned scenario/evidence truth; browser progress must never manufacture verification. + +## Non-goals + +- No new visual brand, component library, analytics vendor, hosted API, or remote account system. +- No deletion of existing specialist tools. +- No arbitrary Bitcoin Script execution trace. Core decoding and BitScope interpretation must remain separately labelled. +- No mainnet mutation, production wallet, custody, treasury-coordination, or policy-safety claim. +- No CPFP or OP_RETURN Verified Scenario in this UX project. + +## Mandatory Scope Before v0.1.0 + +### Workstream 1 — Home and Node Progressive Disclosure + +Move the existing full dashboard to `/node` and keep `/` as a learner-oriented Home page. + +Home must contain: + +1. heading: “See how Bitcoin works”; +2. short explanation that work uses the learner's own Core node safely on regtest; +3. compact environment summary: Core connection, chain, readiness, height, wallet/funds readiness when known; +4. one primary action: **Start your first transaction**; +5. two secondary actions: **Explore something** and **View node details**; +6. one contextual continuation card when a non-authoritative local learning pointer exists; and +7. honest unavailable states when backend, Core, regtest, wallet, or funds are not ready. + +The Home summary may reuse `fetchNodeStatus`. Wallet/funds readiness must come from an explicit bounded backend response or remain “not checked”; it must not be inferred from stale browser state. + +`/node` must show: + +- first: connection, chain, readiness, sync, height, peers, and mempool count; +- `
` “More node details”: headers, mempool memory, relay fee, chain size, best block hash; +- `
` “Under the hood”: CLI, RPC methods, parameters, raw JSON, and developer notes. + +Acceptance criteria: + +- `/` has one visually dominant green action. +- Essential status is understandable without opening technical details. +- Every old dashboard datum remains available on `/node`. +- Backend unavailable, wrong network, syncing, and warning states are visible and accessible. +- Existing root bookmarks intentionally land on the new Home; `/node` is linked from Home and navigation. + +Primary files: + +- `frontend/app/page.tsx` +- new `frontend/app/node/page.tsx` +- new `frontend/components/LearnerHome.tsx` +- new or extracted `frontend/components/NodeStatusDashboard.tsx` +- `frontend/lib/api.ts` only if an explicit readiness response is needed + +### Workstream 2 — Intent-Oriented Navigation + +Replace “Start Here” plus architecture-oriented groups with: + +| Group | Destinations | +| --- | --- | +| Home | `/` | +| Learn | first transaction, `/curriculum`, `/learn`, `/scenarios`, `/capstone-demo` | +| Explore | `/transactions`, `/blocks`, `/address`, `/script`, `/node`, `/mempool`, `/live`, `/peers`, `/fees` | +| Build | `/wallet`, `/regtest`, `/tx-control`, `/multisig`, `/psbt`, `/timelocks`, `/data-tx`, `/script-lab`, `/taproot`, `/policies/compare` | +| Developer | `/rpc`, `/descriptors`, `/keys`, `/integrations`, `/indexer` | + +Labels should describe intent or a question before abbreviations. Technical names remain in descriptions and “Under the hood.” + +Rules: + +- Home and Learn are immediately visible. +- Only the active group auto-expands. +- Mobile navigation remains horizontally usable and keyboard accessible. +- Active page highlight and `aria-current` remain unambiguous. +- Do not add decorative icons to every item; add a group icon only if it materially improves recognition. +- Reduce uppercase category typography; use title case for primary navigation. + +Compatibility: + +- Keep all current routes. +- `/demo` may redirect to or explain the first-transaction journey only after feature parity is established. +- Do not break documented deep links. + +Primary file: + +- `frontend/components/SidebarNavigation.tsx` + +### Workstream 3 — “Your First Bitcoin Transaction” + +Create `/learn/first-transaction` as the flagship guided journey. + +The learner-visible sequence: + +1. **Prepare** — verify backend, Core, regtest, isolated lab, wallet, and spendable maturity. +2. **Create an address** — explain ownership and scriptPubKey. +3. **Fund the wallet** — mine regtest blocks and explain 101-block coinbase maturity. +4. **Choose a UTXO** — show outpoint, amount, confirmations, and why it is spendable. +5. **Build the transaction** — show destination, input, output, change, fee, version, locktime, and sequence. +6. **Sign and preflight** — distinguish wallet authorization from Core mempool policy. +7. **Broadcast and confirm** — show mempool then block inclusion. +8. **Inspect what changed** — compare spent input, new outputs/change, fee, scripts/witness, raw transaction, and relevant RPC calls. +9. **Try the expected failure** — reuse the one-satoshi overspend evidence and explain why rejection is useful. +10. **Finish safely** — show evidence export and cleanup; recommend the next lesson. + +Implementation decision: + +- Reuse `LabSessionService`, wallet/regtest/transaction primitives, the registered `transaction-lifecycle` definition/executor, attack classification, lifecycle recorder, evidence service, and proof exporter. +- Do not implement a second transaction validator in the frontend. +- Prefer a guided presentation adapter over duplicating the scenario's Bitcoin logic. The adapter may expose bounded stage metadata and persisted evidence, while scenario execution remains backend-owned. +- If the current executor cannot pause safely at learner stages, first deliver a guided “prepare → execute → inspect recorded stages” flow. A later version may add resumable per-stage execution only by extending the shared state machine and cleanup contract. +- Store only a convenience progress pointer locally. Completion, assertions, evidence, and cleanup remain backend-owned. + +Script explanation: + +- For the known transaction template, display locking condition, witness/unlocking data, and an educational stack explanation derived from typed persisted evidence. +- Label this as **BitScope explanation**. +- Do not claim Bitcoin Core returned an instruction-by-instruction execution trace unless a real reviewed Core trace source is added. + +Acceptance criteria: + +- A new learner can complete the flow from one primary Home action without visiting Wallet, Regtest, or Transactions separately. +- Every displayed txid, address, UTXO, fee, script, Core result, and lifecycle state comes from the current backend-owned run. +- Wrong-network and token failures stop before mutation with actionable language. +- Refresh/recovery does not create a false completed state. +- Success and failure paths clean only session-owned wallets. +- The final screen links to UTXOs, fees/mempool, multisig/PSBT, and timelock lessons in a deliberate order. + +Likely files: + +- new `frontend/app/learn/first-transaction/page.tsx` +- new `frontend/components/FirstTransactionJourney.tsx` +- shared learning components from Workstream 4 +- a narrow backend presentation model/route if current scenario responses do not expose the required staged data +- tests beside scenario/reviewer/curriculum route tests + +### Workstream 4 — Shared Learning Page Pattern + +Create shared components: + +- `LabHeader`: learner question, short objective, concepts, estimated scope; +- `EnvironmentChecklist`: backend/Core/network/wallet/funds checks; +- `StepProgress`: ordered current/completed/upcoming steps; +- `PrimaryActionCard`: exactly one dominant action; +- `ResultExplanation`: what happened and why; +- `UnderTheHood`: collapsed CLI/RPC/parameters/raw/hex/developer material; +- `NextStepCard`: one recommended continuation plus optional alternatives. + +Apply them before release to: + +1. first transaction; +2. multisig; +3. PSBT; +4. timelocks; and +5. fee bumping. + +Question-first headings: + +- Multisig: “How can several keys control the same bitcoin?” +- PSBT: “How do people sign a transaction separately?” +- Timelocks: “How can Bitcoin delay when funds are spent?” +- Fee bumping: “What can I do when a transaction confirms too slowly?” + +Do not rewrite backend behavior merely to adopt the layout. + +Acceptance criteria: + +- One primary action per visible stage. +- Technical material is reachable but collapsed by default. +- Native semantic controls, visible focus, heading hierarchy, alert/live-region behavior, and narrow layouts pass. +- Shared components do not hide safety warnings or expected-failure evidence. + +## Post-v0.1.0 Follow-up + +### Workstream 5 — Apply the Pattern Across Remaining Labs + +Migrate wallet/funding, Script Lab, Taproot, OP_RETURN, policy comparison, explorers, and developer pages. Use the same question/objective/prepare/action/result/under-the-hood/next structure where it fits; do not force step progress onto read-only search pages. + +### Workstream 6 — Local Launcher + +Design one local launcher that: + +- checks Python, Node, Docker/Compose, Bitcoin Core, ports, and configuration; +- generates a random local mutation token only with explicit operator confirmation; +- prepares matching backend/frontend ignored environment files; +- starts the chosen native or Compose stack; +- waits for health; and +- opens Home. + +Safety constraints: + +- never overwrite existing credentials or datadirs silently; +- never start against mainnet for guided mutation; +- show exact files/processes it will create; +- offer dry-run and readiness-only modes; and +- keep the current `check-local.ps1` and Compose wrapper as reusable primitives. + +### Workstream 7 — Usability Study + +Test with at least: + +1. someone new to Bitcoin Core; +2. an LBCLI learner; and +3. a Bitcoin developer. + +Use the same neutral task: “Use BitScope to create and understand one Bitcoin transaction.” Do not explain navigation first. + +Record: + +- first click; +- time to start the journey; +- points of hesitation; +- terminology questions; +- whether they complete the transaction; +- whether they can explain input, output, change, fee, script condition, preflight, broadcast, and confirmation; +- whether they discover technical details intentionally; and +- whether experts can still reach RPC/raw evidence quickly. + +Do not add third-party analytics. Use an anonymized observation template and obtain participant consent. + +## Testing Strategy + +### Backend + +- readiness/presentation model parsing and bounds; +- network/token/origin failure; +- owner-scoped progress and evidence; +- refresh/interruption recovery; +- no duplicate execution; +- exact lifecycle/evidence mapping; +- cleanup after success/failure; +- redaction and deterministic export. + +### Frontend + +- TypeScript, ESLint, and production build. +- Component tests only if they can reuse a lightweight repository-supported setup; do not introduce a large E2E framework solely for this redesign. +- Browser walkthrough at desktop and mobile widths: + - Home primary action; + - navigation group/active states; + - first-transaction prepare, execute, inspect, failure, export, and cleanup; + - keyboard-only path; + - backend offline and wrong-network states; + - collapsed technical details. + +### Live Core + +Keep the two clean Core 28.1 matrix passes. Add one live test through the exact guided presentation service/route if the new journey introduces a backend adapter. Never duplicate the expensive scenario merely for a different UI. + +## Release Gates + +Before `v0.1.0`: + +- [x] Workstreams 1–4 implemented. +- [x] All existing routes retained or intentionally redirected. +- [x] Backend, frontend, Compose, security, cleanup, redaction, proof, and both Core 28.1 matrix runs green. +- [x] Clean `/learn/first-transaction` walkthrough completed at desktop and mobile widths. +- [x] New Home, compact node summary, first transaction stages, expected failure, under-the-hood details, and final cleanup screenshots captured without secrets. +- [ ] At least one novice and one Bitcoin-experienced reviewer complete the journey without navigation coaching; record findings and resolve release-blocking confusion. +- [x] README, demo script, curriculum, architecture, screenshots, release notes, migration notes, and release checklist updated to match the implemented experience. +- [ ] Manual `workflow_dispatch` release-readiness gate green. +- [ ] PR merged before tag creation. + +Workstreams 5–7 can continue after `v0.1.0`, except the two-person pre-release usability check above. + +## Recommended Implementation Sequence + +1. Extract the current dashboard into `/node`; build static/real-data Home states. +2. Replace sidebar taxonomy while preserving routes and active accessibility. +3. Define the first-transaction presentation contract from existing scenario evidence. +4. Build shared learning components and the guided journey. +5. Apply the pattern to multisig, PSBT, timelocks, and fee bumping. +6. Add tests and clean live presentation coverage. +7. Perform browser/mobile/accessibility verification and capture screenshots. +8. Run the small pre-release usability check; fix release-blocking confusion. +9. Update release documents, dispatch release readiness, merge, then tag. + +## Deferred Decisions + +- Whether `/demo` redirects to the first transaction or remains a named “Quick Demo.” +- Whether learning progress stays as a local convenience pointer or gains a backend profile model. No account system should be introduced for v0.1.0. +- Whether per-stage scenario execution is worth the state-machine complexity. The first release may use one safe execution followed by staged inspection. +- Whether group icons improve recognition after typography and hierarchy changes; validate rather than decorate by default. diff --git a/docs/limitations.md b/docs/limitations.md index 8cdf953..fe6b434 100644 --- a/docs/limitations.md +++ b/docs/limitations.md @@ -1,5 +1,19 @@ # BitScope Limitations +Verified Scenarios prove behavior only under the recorded software version, configuration, network, and scenario assumptions. A passing run is not a statement that an equivalent production policy is safe. + +## Evidence and Policy Claims + +- A BitScope proof bundle is reproducible, hash-covered run evidence. It is not an independent audit, formal verification, cryptographic attestation, or production-spend approval. +- Bitcoin Core policy and wallet behavior can change between versions. The flagship Proof of Spendability requires the pinned Core 28.1 runtime. +- Script and policy privacy observations may be qualitative. They must be labelled as interpretation rather than measured anonymity or confidentiality. +- A successful regtest test does not make a treasury policy production-ready. +- Hardware-wallet behavior, air-gapped signing, organizational separation, key ceremonies, backups, and disaster recovery are unproven unless exercised with the actual devices and procedures. + +## Regtest + +Regtest provides deterministic block production and isolated funds, but it does not reproduce a public fee market, adversarial propagation, miner diversity, reorg incentives, public-chain privacy, or operational custody risk. Fee rates and five-/ten-block treasury delays are bounded teaching values, not recommendations. + ## No Hosted Blockchain APIs BitScope does not use hosted blockchain APIs or third-party indexers. All blockchain data comes from the user's own Bitcoin Core node. @@ -38,6 +52,22 @@ Pruned nodes may not have old block data available. BitScope should return a fri Regtest often has no meaningful fee market. Fee estimates may be unavailable or unrealistic. BitScope should explain unavailable estimates instead of inventing values. -## Mainnet +## Networks and Custody + +Mainnet funds are real. Normal application mutation routes require configured and runtime regtest and reject mainnet, testnet, and signet. Read-only exploration does not turn BitScope into a wallet, custody system, or production coordinator. + +The Community Treasury Recovery signer wallets are isolated educational wallet contexts inside one Bitcoin Core process. They do not represent independent organizations or hardware custodians. The CLTV lesson briefly holds an ephemeral private scalar in Python process memory; it is excluded from RPC, persistence, and evidence, but Python cannot guarantee immediate memory zeroization. + +## Runtime and Cleanup + +- Stale regtest state can invalidate assumptions; live CI therefore uses fresh disposable datadirs twice. +- Interrupted work is recoverable only from persisted checkpoints. In-memory work before the last checkpoint is not durable. +- Cleanup is limited to session-owned wallet namespaces and is verified, but host crashes and external Core failures can still require operator inspection. +- The reviewed multisig compatibility path uses Core 28.1's `-deprecatedrpc=create_bdb`; it is not guidance for new production wallets. + +## Deferred Scope -Mainnet funds are real. BitScope disables spending actions by default on mainnet. Read-only exploration is allowed. +- CPFP construction and lifecycle relationships exist, but the optional CPFP Verified Scenario remains deferred. +- OP_RETURN construction exists, but the optional OP_RETURN Verified Scenario and negative-standardness catalogue remain deferred. +- Taproot inspection exists; the flagship treasury policy is proved as P2WSH, not Taproot. +- General-purpose Script execution, full-chain address indexing, production fee selection, and hardware-wallet orchestration are out of scope for v0.1.0. diff --git a/docs/migration-v0.1.0.md b/docs/migration-v0.1.0.md new file mode 100644 index 0000000..3244388 --- /dev/null +++ b/docs/migration-v0.1.0.md @@ -0,0 +1,40 @@ +# Migration Notes for v0.1.0 + +This is the first tagged-release candidate; there is no supported earlier release schema to migrate from. These notes apply to existing development checkouts. + +## Application Dependencies + +Recreate or update the backend virtual environment from `backend/requirements.txt`. Frontend linting now uses ESLint 9 with the pinned Next.js 16.2.10 configuration; use `npm ci` from the committed lockfile on a clean runner. + +```powershell +cd backend +.\.venv\Scripts\python.exe -m pip install -r requirements.txt +cd ..\frontend +npm ci +``` + +## Configuration + +Compare local ignored environment files with the committed examples. Use: + +- a unique `BITSCOPE_LOCAL_ACCESS_TOKEN`; +- the same value for `NEXT_PUBLIC_BITSCOPE_LOCAL_ACCESS_TOKEN`; +- a regtest-only RPC endpoint for mutation workflows; +- exact local CORS origins and trusted hosts; and +- writable paths for the lab-session SQLite database and scenario artifact root. + +Do not copy real credentials into documentation, screenshots, proof bundles, or committed files. + +## Persistent Development State + +Scenario tables are created alongside the existing lab-session database without rewriting prior lab documents. Persisted scenario runs depend on their registered scenario version, artifact files, Core datadir, and owned wallets. A stale development database or reset Core datadir can therefore make an old run non-resumable. + +For the v0.1.0 reviewer demonstration, prefer a new disposable Core 28.1 regtest datadir and new BitScope data volume. Preserve old development data only if you need it for inspection; do not present stale runs as current evidence. + +## Bitcoin Core + +The release pin remains Core 28.1. Start the reviewed compatibility node with regtest, server/RPC, fallback fee, txindex, and `-deprecatedrpc=create_bdb` when running the legacy multisig path. Do not reuse a mainnet or valuable-wallet datadir. + +## Post-migration Checks + +Run the commands in [Release Readiness](release-readiness.md), verify `/api/health` reports `0.1.0`, confirm Core reports `regtest`, and start Reviewer Mode with a newly generated session. Old proof bundles remain self-describing evidence for their recorded scenario/Core version; do not rewrite them. diff --git a/docs/proof-bundles.md b/docs/proof-bundles.md new file mode 100644 index 0000000..5a829fd --- /dev/null +++ b/docs/proof-bundles.md @@ -0,0 +1,60 @@ +# Proof Bundles + +A BitScope proof bundle is a deterministic ZIP of redacted scenario evidence. It helps a reviewer reproduce and inspect one run. It is not a signature, attestation, audit, formal proof, or production approval. + +## Export Preconditions + +The backend resolves the run through its owning lab session and exact scenario version, loads every referenced artifact, verifies each stored SHA-256 identity, recursively redacts the run and evidence again, and generates the archive from canonical bytes. The treasury-specific Proof of Spendability reports `VERIFIED` only when the scenario is verified, cleanup is complete, Core is exactly 28.1, required policy evidence exists, and every typed check passes or is rejected as expected. + +## Archive Layout + +Files are rooted under `bitscope-proof/`: + +| Path | Presence | Purpose | +| --- | --- | --- | +| `manifest.json` | always | Schema/run identity, final result, disclaimer, and SHA-256 entry for every other file | +| `report.md` | always | Human-readable run, assertion, failure, evidence, cleanup, and reproduction summary | +| `evidence/*.json` | when captured | Canonical typed records with Core output separated from BitScope interpretation | +| `commands.sh` | when commands exist | Credential-free reviewed `bitcoin-cli` reproduction commands | +| `rpc-transcript.json` | when RPC evidence exists | Ordered bounded RPC observations | +| `node-context.json` | when node evidence exists | Runtime chain, Core version, height, and safe node commands | +| `assertions.json` | when assertions exist | Typed assertion definitions and results | +| `lifecycle.json` | when lifecycle evidence exists | Ordered backend-recorded events and explicit relationships | +| `proof-of-spendability.json` | treasury only | Public policy, branch checks, exact expected rejections, compatibility, and cleanup | + +The archive uses sorted paths, canonical JSON, normalized ZIP metadata, and fixed timestamps. Exporting the same persisted terminal revision twice must produce byte-identical ZIP bytes. + +## Manifest Verification + +Extract the archive into a new directory, never over an existing application or wallet directory. Verify that: + +1. every archive member is under `bitscope-proof/`; +2. `manifest.json` lists every file except itself exactly once; +3. no unlisted file is trusted; +4. each listed file's SHA-256 equals its manifest value; +5. run ID, scenario ID/version, final result, Core version, and cleanup state match the report; and +6. the disclaimer and tested assumptions are retained when sharing results. + +PowerShell example: + +```powershell +Expand-Archive .\bitscope-proof.zip .\verified-proof +$manifest = Get-Content .\verified-proof\bitscope-proof\manifest.json | ConvertFrom-Json +foreach ($entry in $manifest.files) { + $path = Join-Path .\verified-proof\bitscope-proof $entry.path + $actual = (Get-FileHash -Algorithm SHA256 $path).Hash.ToLowerInvariant() + if ($actual -ne $entry.sha256) { throw "Hash mismatch: $($entry.path)" } +} +``` + +`commands.sh` is educational input, not an automatically trusted script. Read it before executing and use only an isolated regtest node. + +## Redaction Boundary + +Bundles may intentionally contain public descriptors, public keys, addresses, txids, outpoints, transaction hex, PSBT metadata, scripts, Core version/configuration observations, and wallet labels. They must not contain RPC credentials, mutation tokens, private keys, seeds, xprvs, wallet passphrases, authorization headers, or raw environment/settings dumps. + +Redaction reduces accidental disclosure; it cannot prove that a novel secret shape was absent. Inspect a bundle before publishing it. + +## Reproducibility Boundary + +Hashes prove byte identity after export. They do not prove who ran BitScope, that the host was uncompromised, that Core was an official binary, or that the evidence applies to another version/configuration. Reproduction can also yield different txids and addresses because each safe run creates fresh state; compare asserted behavior and evidence structure rather than fixed chain identifiers. diff --git a/docs/release-notes-v0.1.0.md b/docs/release-notes-v0.1.0.md new file mode 100644 index 0000000..c83edfd --- /dev/null +++ b/docs/release-notes-v0.1.0.md @@ -0,0 +1,41 @@ +# BitScope v0.1.0 Release Notes + +`v0.1.0` is the first BitScope release candidate. Repository and GitHub history contain no earlier tags or releases, and the backend/frontend manifests already identify the application as `0.1.0`. + +## Highlights + +- Five immutable Verified Scenarios: transaction lifecycle, opt-in RBF replacement, 2-of-3 multisig PSBT, real P2WSH CLTV, and Community Treasury Recovery. +- Expected invalid paths classified separately from unexpected failures, with bounded redacted Bitcoin Core observations. +- Persistent scenario revisions, interrupted-run recovery, session-owned cleanup, and deterministic evidence/proof exports. +- Community Treasury Recovery's three-branch P2WSH Miniscript policy with immediate 2-of-3 operators, five-block 2-of-3 recovery, and ten-block 2-of-3 emergency. +- Specialized Proof of Spendability, backend-recorded lifecycle timelines, Reviewer Mode, policy comparison, LBCLI curriculum mapping, and six evidence-backed challenges. +- Blocking CI with backend security/cleanup/redaction/export gates, frontend type-check/lint/build, Compose validation, and two independent disposable Bitcoin Core 28.1 live runs. + +## Compatibility + +- Reference runtime: Bitcoin Core 28.1 on regtest. +- The legacy multisig lesson requires `-deprecatedrpc=create_bdb`. +- Mainnet, testnet, and signet remain read-only through normal application routes. +- Node.js 22 and Python 3.12 are the CI reference toolchains. + +## Security and Evidence + +- Mutation routes require a local token and allowed browser origin. +- Runtime chain is verified before state-changing scenario work. +- RPC clients use reviewed capability allowlists and globally forbid secret/export/unlock/shutdown methods. +- Proof artifacts are recursively redacted, canonicalized, SHA-256 covered, and packaged deterministically. +- Proof bundles are reproducible evidence, not audits, attestations, formal proofs, or production approvals. + +## Known Limitations + +- Regtest does not reproduce a real fee market, public propagation, production custody, or organizational controls. +- Treasury signer wallets are educational contexts inside one Core process. +- Hardware-wallet, air-gap, backup, and disaster-recovery behavior is unproven. +- The flagship is P2WSH; Taproot policy execution is deferred. +- CPFP and OP_RETURN labs exist, but their optional Verified Scenarios remain deferred. +- The multisig compatibility path depends on deprecated BDB support in Core 28.1. +- The tracked screenshot set now covers the learner Home, First Transaction entry state, Node Details, Learning Path, Reviewer Mode entry state, and Concept Library. Completed Reviewer Mode checkpoint screenshots remain a release gate until captured from a clean Core 28.1 run. + +## Verification + +See [Release Readiness](release-readiness.md) for exact commands and recorded CI evidence, [Migration Notes](migration-v0.1.0.md) for existing development checkouts, and [Limitations](limitations.md) before using the laboratory. diff --git a/docs/release-readiness.md b/docs/release-readiness.md new file mode 100644 index 0000000..9fa8d80 --- /dev/null +++ b/docs/release-readiness.md @@ -0,0 +1,156 @@ +# v0.1.0 Release Readiness + +## Version Decision + +- Local tags: none. +- Remote tags: none. +- GitHub releases: none. +- Existing backend and frontend version: `0.1.0`. +- Selected first release candidate: `v0.1.0`. + +No tag or GitHub release should be created until the Phase 11 documentation is merged, the manual Reviewer Mode screenshot/walkthrough item below is resolved, and the mandatory pre-release scope in [Learner-First Experience Implementation Plan](learner-first-experience-plan.md) is complete. + +## Blocking Checklist + +- [x] README uses the reproducible Bitcoin protocol laboratory identity and rejects unsupported product claims. +- [x] Architecture, limitations, demo, live testing, supported Core, Verified Scenarios, authoring, proof bundles, threat model, curriculum, and contribution guidance are present. +- [x] Release notes and migration notes are prepared. +- [x] Backend unit/service suite passes. +- [x] Security, network, interruption, cleanup, redaction, manifest, report, and export gates pass. +- [x] Frontend type-check, ESLint, and production build pass. +- [x] Docker Compose configuration validates. +- [x] Community Treasury Recovery and the full live suite pass twice against separate disposable Core 28.1 datadirs in PR #15. +- [x] Existing screenshots are real local-node UI captures and contain no intentional credentials or private key material. +- [x] Implement Workstreams 1–4 in the [Learner-First Experience Implementation Plan](learner-first-experience-plan.md), including the guided backend-owned first-transaction journey. +- [x] Complete the protected learner transaction-lifecycle, deterministic export, and cleanup gate against a fresh checksum-verified Core 28.1 regtest datadir. +- [x] Complete the rendered Core 28.1 learner walkthrough, responsive review, screenshot capture, and deterministic keyboard-accessibility audit in the learner-first plan. +- [ ] Complete the pre-release participant-usability gate in the learner-first plan. +- [ ] Complete one manual `/capstone-demo` browser walkthrough against a clean disposable Core 28.1 node and capture the reviewer states listed in [Demo Script](demo-script.md). The current automation environment exposes no browser, so this item is not represented as complete. +- [ ] Run the `workflow_dispatch` release-readiness job from the final Phase 11 commit so the documentation-presence gate and all blocking jobs execute together. +- [x] Merge PR #15 before tagging. + +## Verification Commands + +Backend: + +```powershell +cd backend +.\.venv\Scripts\python.exe -m pytest --ignore=tests/live_node +``` + +Focused hardening gate: + +```powershell +cd backend +.\.venv\Scripts\python.exe -m pytest tests/test_security.py tests/test_mutation_route_security.py tests/test_network_safety.py tests/test_scenario_models.py tests/test_scenario_service.py tests/test_transaction_lifecycle_scenario.py tests/test_attack_verification_service.py tests/test_evidence_service.py tests/test_reviewer_demo_service.py +``` + +Frontend and Compose: + +```powershell +cd frontend +npm run typecheck +npm run lint +npm run build +cd .. +docker compose config --quiet +``` + +Pinned live node: + +```powershell +cd backend +$env:BITSCOPE_LIVE_RPC_TESTS = "1" +$env:BITCOIN_NETWORK = "regtest" +$env:BITCOIN_RPC_HOST = "127.0.0.1" +$env:BITCOIN_RPC_PORT = "18443" +$env:BITCOIN_RPC_USER = "" +$env:BITCOIN_RPC_PASSWORD = "" +.\.venv\Scripts\python.exe -m pytest tests/live_node -v +``` + +Use only a disposable Core 28.1 regtest node. CI is the authoritative recorded execution for the two-clean-datadir gate. + +## Recorded Results + +PR #15 run `30221071976`: + +- Backend tests: passed. +- Bitcoin Core 28.1 integration, first clean datadir: passed. +- Bitcoin Core 28.1 integration, second clean datadir: passed. +- Frontend build job, including type-check and lint: passed. +- Docker staging configuration: passed. + +The tag/manual-only release-readiness job was correctly skipped for the pull-request event and must be dispatched after the final documentation commit. + +Local Phase 11 verification on 2026-07-27: + +- backend non-live suite: `319 passed`; +- focused hardening suite: `86 passed`; +- frontend type-check: passed; +- ESLint: passed with three documented non-blocking legacy warnings; +- production build: passed with `/capstone-demo`, `/curriculum`, `/policies/compare`, and `/scenarios`; +- Docker Compose configuration: passed; +- readiness script: passed; +- `/api/health`: HTTP 200, version `0.1.0`, network `regtest`; and +- `/capstone-demo`: HTTP 200 and Reviewer content present. + +Local learner-first functional walkthrough on 2026-07-27: + +- Home, node, first transaction, multisig, PSBT, timelocks, and fee-bumping routes returned HTTP 200 with one `h1`, the intended question heading, and an unambiguous active navigation item. +- The protected `transaction-lifecycle` run reached `verified` at revision 5 with four passed assertions, one expected overspend rejection, and zero unexpected failures. +- The run persisted nine evidence records and nine lifecycle events. +- The proof report returned HTTP 200, and two proof-bundle downloads had the same SHA-256: `bf687c5d79c8bcd9b08f4849a234951462bdeb76daec5dcc4c429aa4f08ede95`. +- Cleanup completed, the lab was marked cleaned, and the session-owned wallet was no longer loaded. +- The configured node reported `/Satoshi:28.0.0/`; this is functional evidence only and does not satisfy the pinned Core 28.1 release gate. +- The configured browser surface exposed no browser, so responsive, keyboard, screenshot, and end-to-end rendered interaction checks remain open. + +Isolated Core 28.1 learner functional gate on 2026-07-27: + +- The cached `bitcoin-28.1-win64.zip` SHA-256 matched the Bitcoin Core 28.1 checksum manifest, and `bitcoind --version` returned `v28.1.0`. +- A fresh, isolated node started at regtest height 0 and identified itself as `/Satoshi:28.1.0/`. +- Protected run `8ddb04df-3a15-41b6-b1ac-d8b3e90eb136` reached `verified` at revision 5 with four passed assertions, one expected rejection, and zero unexpected failures. +- The run persisted nine evidence records and nine lifecycle events. +- The report returned HTTP 200; two 18-entry bundle downloads matched at SHA-256 `7adbbd79cf54132a1d91575ce0b3c969271a1f21eede3211d92c6fd65e2c53cf`. +- Cleanup completed, the lab was cleaned, and the session-owned wallet was not loaded afterward. +- The isolated backend and Core processes were stopped; evidence remains in `C:\tmp\bitscope-release-core281-20260727`. +- The browser runtime still exposed no browser, so no rendered interaction, responsive/keyboard result, or screenshot is claimed. + +Rendered Core 28.1 learner walkthrough on 2026-07-27: + +- A second fresh, isolated Core 28.1 regtest node backed the rendered `/learn/first-transaction` walkthrough. +- Browser-created run `d371632e-56db-442b-acb1-574b5983511c` reached `verified` at revision 5 with four passed assertions, one expected overspend rejection, and zero unexpected failures. +- The rendered result exposed nine evidence records, nine lifecycle events, the expected-failure explanation, the collapsed technical-detail disclosure, and the proof-bundle action. +- Two proof-bundle downloads matched at SHA-256 `b611d3b2b0094b3919536e9511d8b7565d888a4ec145a27590379a9ef9466cd6`. +- Cleanup completed, the lab was marked cleaned, and the session-owned wallet was no longer loaded. +- Desktop and narrow-viewport captures verified the active navigation state, Core `/Satoshi:28.1.0/` readiness, completed lifecycle, expected rejection, and proof-export state. +- Keyboard accessibility was audited deterministically: interactive elements use native links, buttons, and `details`/`summary`; navigation exposes `aria-current`, `aria-expanded`, and `aria-controls`; and the global stylesheet guarantees a high-contrast offset `:focus-visible` outline. +- This satisfies the rendered walkthrough, responsive, screenshot, and keyboard-accessibility release gate. Participant observation remains a separate open gate. + +## Screenshot Inventory + +The tracked release package includes six real local-node screenshots generated by `scripts/capture-screenshots.ps1`: + +- `home.png` +- `first-transaction.png` +- `node-details.png` +- `learning-path.png` +- `reviewer-mode.png` +- `learning-library.png` + +The tracked `reviewer-mode.png` shows the real pre-execution Reviewer Mode surface. Completed checkpoint screenshots are deliberately not fabricated; capture them only from the clean Core 28.1 demonstration described in the remaining checklist item. + +The Core 28.1 learner walkthrough also produced real desktop and narrow-viewport captures during the release review. They remain review evidence rather than tracked files because the in-app browser supplied them directly to the review session. + +## Tag and Release Procedure + +After merge and a green final release-readiness dispatch: + +1. update local `main` from `origin/main`; +2. verify the merge commit and checklist; +3. create annotated tag `v0.1.0`; +4. push only that tag; +5. create the GitHub release from [v0.1.0 Release Notes](release-notes-v0.1.0.md); and +6. attach or link the approved screenshots and verification evidence. + +Tagging and publishing are intentionally separate, explicit actions; this preparation does not perform either. diff --git a/docs/scenario-authoring.md b/docs/scenario-authoring.md new file mode 100644 index 0000000..5a36973 --- /dev/null +++ b/docs/scenario-authoring.md @@ -0,0 +1,71 @@ +# Scenario Authoring + +This document is the review checklist for adding or changing a Verified Scenario. The current catalogue and behavior are described in [Verified Scenarios](verified-scenarios.md). + +## Closed Definition + +A scenario is an immutable, backend-owned `ScenarioDefinition` with a semantic version, objective, ordered phases, typed steps, declared dependencies/artifacts, typed assertions, cleanup rules, and required regtest mutation capability. + +Definitions may not contain arbitrary RPC method names, shell commands, Python expressions, dynamic imports, or browser-supplied executable parameters. A new operation requires a reviewed model variant and a backend executor implementation. + +Every mutating scenario must: + +1. begin with runtime-chain verification; +2. generate wallets, addresses, outpoints, txids, and blocks within the current run; +3. resolve resources through the owning active lab session; +4. recheck regtest at each mutation boundary; +5. finish with cleanup as the final declared step; and +6. make cleanup failure incompatible with `verified`. + +## Executor Contract + +Use the least-powerful RPC capability that supports the workflow. Persist explicit `running`, `verifying`, `cleaning`, and terminal checkpoints with one-revision increments. Evidence must be written before the revision that references it; losing a revision race must remove the newly written unreferenced artifact. + +Expected invalid paths are first-class behavior. Declare attack applicability, prefer structured Core fields and numeric RPC codes, use bounded message markers only when Core exposes no narrower field, and fail closed if the observed reason differs. Keep expected failures, unexpected failures, and not-applicable attacks distinct. + +## Evidence Contract + +For each meaningful observation, capture: + +- scenario/run/step identity; +- Bitcoin Core method and bounded safe result; +- BitScope interpretation in a separate field; +- credential-free reproduction commands; +- timestamps and canonical typed values; and +- artifact identity and SHA-256 hash. + +Never serialize settings, headers, environment variables, RPC URLs with credentials, private keys, seeds, xprvs, wallet unlock material, or unfiltered exceptions. Use the shared redaction service rather than scenario-specific string replacement. + +## Cleanup and Recovery + +Normal and exceptional executor paths must enter session-owned cleanup. Cleanup may unload only wallets recorded under the session namespace. Record the cleanup result and lifecycle event. If cleanup fails, terminate as `cleanup_failed`. + +An interrupted persisted `running`, `verifying`, or `cleaning` run may be recovered only through the shared recovery path: claim the current revision, enter cleanup, preserve the historical run as `incomplete`, and create a replacement only after cleanup is recorded. + +## Required Tests + +Add fast deterministic tests for: + +- definition parsing, invalid operations, dependencies, and duplicate identifiers; +- valid/invalid transitions and optimistic revisions; +- positive execution and exact expected failure; +- mismatched rejection as an unexpected failure; +- duplicate execution; +- cleanup after success, executor failure, evidence failure, and interruption; +- network mismatch and mutation-route protection; +- recursive redaction, deterministic artifact hashes, report, and ZIP export; and +- session ownership and tamper rejection. + +Add one opt-in live test against the pinned disposable Core version. It must verify regtest before mutation, use UUID-derived wallet names, generate current-run addresses and txids, mine maturity, avoid fixed chain artifacts, clean up in `finally`, and leave non-owned wallets untouched. + +## Registration and Versioning + +Register a definition only after its executor and live test pass. Increment the scenario version when its steps, assertions, expected Core behavior, policy, or exported evidence contract changes. Do not silently reinterpret persisted runs; historical exports must resolve the exact registered version or fail explicitly. + +Before review, update: + +- [Verified Scenarios](verified-scenarios.md); +- [Live RPC Testing](live-rpc-testing.md); +- [Supported Bitcoin Core](supported-bitcoin-core.md) when compatibility changes; +- [Threat Model](threat-model.md) when a trust boundary changes; and +- [Curriculum Map](curriculum-map.md) only when an implemented capability genuinely exists. diff --git a/docs/screenshots/README.md b/docs/screenshots/README.md new file mode 100644 index 0000000..21e16a4 --- /dev/null +++ b/docs/screenshots/README.md @@ -0,0 +1,42 @@ +# Screenshot Capture + +The tracked screenshots are generated from a running local BitScope frontend with Chrome's built-in headless capture mode. The process is repository-owned and does not depend on Codex, an open browser tab, or a reusable personal Chrome profile. + +From the repository root: + +```powershell +.\scripts\capture-screenshots.ps1 +``` + +The command: + +- checks that Chrome and `http://localhost:3000` are available; +- uses a separate fresh disposable Chrome profile for every route so navigation and scroll state cannot leak between captures; +- captures the six documented routes at `1440x1000` and device scale factor `1`; +- validates every PNG signature, file size, and pixel dimensions before publishing; +- updates both `docs/screenshots/` and `docs-site/screenshots/`; and +- removes its temporary profile and staging directory. + +Use `-CheckOnly` to validate prerequisites without changing files: + +```powershell +.\scripts\capture-screenshots.ps1 -CheckOnly +``` + +Use `-VerifyExisting` to validate the tracked inventory, dimensions, and byte-for-byte docs-site mirrors without requiring Chrome or a running frontend: + +```powershell +.\scripts\capture-screenshots.ps1 -VerifyExisting +``` + +Useful overrides: + +```powershell +.\scripts\capture-screenshots.ps1 ` + -BaseUrl http://localhost:3000 ` + -ChromePath "C:\Program Files\Google\Chrome\Application\chrome.exe" ` + -Width 1440 ` + -Height 1000 +``` + +Capture only real local-node states. Review each updated image for credentials, private key material, personal paths, and misleading verification claims before committing it. diff --git a/docs/screenshots/dashboard.png b/docs/screenshots/dashboard.png deleted file mode 100644 index a67a1c9..0000000 Binary files a/docs/screenshots/dashboard.png and /dev/null differ diff --git a/docs/screenshots/demo-mode.png b/docs/screenshots/demo-mode.png deleted file mode 100644 index 1224859..0000000 Binary files a/docs/screenshots/demo-mode.png and /dev/null differ diff --git a/docs/screenshots/first-transaction.png b/docs/screenshots/first-transaction.png new file mode 100644 index 0000000..8fb9a71 Binary files /dev/null and b/docs/screenshots/first-transaction.png differ diff --git a/docs/screenshots/home.png b/docs/screenshots/home.png new file mode 100644 index 0000000..84f983b Binary files /dev/null and b/docs/screenshots/home.png differ diff --git a/docs/screenshots/learning-library.png b/docs/screenshots/learning-library.png index 8ef9008..cec8752 100644 Binary files a/docs/screenshots/learning-library.png and b/docs/screenshots/learning-library.png differ diff --git a/docs/screenshots/learning-path.png b/docs/screenshots/learning-path.png new file mode 100644 index 0000000..52dfb41 Binary files /dev/null and b/docs/screenshots/learning-path.png differ diff --git a/docs/screenshots/node-details.png b/docs/screenshots/node-details.png new file mode 100644 index 0000000..f169e9d Binary files /dev/null and b/docs/screenshots/node-details.png differ diff --git a/docs/screenshots/reviewer-mode.png b/docs/screenshots/reviewer-mode.png new file mode 100644 index 0000000..3626db3 Binary files /dev/null and b/docs/screenshots/reviewer-mode.png differ diff --git a/docs/screenshots/script-lab.png b/docs/screenshots/script-lab.png deleted file mode 100644 index 504ccd0..0000000 Binary files a/docs/screenshots/script-lab.png and /dev/null differ diff --git a/docs/screenshots/transaction-explorer.png b/docs/screenshots/transaction-explorer.png deleted file mode 100644 index 1524fd8..0000000 Binary files a/docs/screenshots/transaction-explorer.png and /dev/null differ diff --git a/docs/screenshots/wallet-lab.png b/docs/screenshots/wallet-lab.png deleted file mode 100644 index 42795f2..0000000 Binary files a/docs/screenshots/wallet-lab.png and /dev/null differ diff --git a/docs/supported-bitcoin-core.md b/docs/supported-bitcoin-core.md index 3404b25..c7a406e 100644 --- a/docs/supported-bitcoin-core.md +++ b/docs/supported-bitcoin-core.md @@ -22,7 +22,7 @@ The integration lifecycle uses a temporary container and datadir, creates a uniq - RBF fee bumping; - CPFP child construction. -The container and its wallet state are removed after every CI run, including failed runs. +The complete live suite runs twice in independent CI matrix entries. Each entry receives a fresh container and datadir, and the container plus volumes are removed even after failure. PR #15 demonstrated both clean Core 28.1 entries passing alongside backend, frontend, and Compose gates. The multisig paths require the pinned node's explicit `-deprecatedrpc=create_bdb` compatibility option. This is a tested legacy-wallet constraint, not support guidance for new production wallet designs. @@ -42,3 +42,7 @@ $env:BITCOIN_RPC_PASSWORD = "" ``` Never point this test suite at a persistent wallet or a non-regtest node. When live tests are explicitly enabled, a configuration or runtime-chain mismatch is a test failure rather than a skip. + +## Upgrade policy + +Do not move the pin merely because a newer Core version exists. A proposed upgrade must prove all mandatory scenarios, exact negative classifications, treasury policy branches, deterministic export, and cleanup against the candidate; update compatibility documentation and expected results; record the reason; and preserve a clear rollback path. Version-specific text classifiers require particular scrutiny. diff --git a/docs/threat-model.md b/docs/threat-model.md new file mode 100644 index 0000000..29d53b4 --- /dev/null +++ b/docs/threat-model.md @@ -0,0 +1,61 @@ +# BitScope Threat Model + +## Scope + +This threat model covers the local Next.js frontend, FastAPI backend, Bitcoin Core RPC connection, SQLite lab/scenario state, deterministic artifact store, proof exports, disposable live-test nodes, and the operator running them. It does not claim to secure production funds, remote multi-tenant hosting, independent custodians, or hardware devices. + +## Assets + +- Bitcoin Core RPC credentials and the local mutation token. +- Wallet and signer state inside Bitcoin Core. +- Lab/session ownership records and scenario revisions. +- Public transaction, descriptor, PSBT, assertion, lifecycle, and proof artifacts. +- The integrity of failure classification and cleanup claims. +- The operator's persistent wallets and datadirs, which must remain outside scenario ownership. + +## Trust Boundaries + +```text +Browser --local HTTP + browser-visible mutation token--> FastAPI +FastAPI --server-held RPC credentials + allowlisted methods--> Bitcoin Core +FastAPI --typed records + atomic writes--> SQLite/artifact store +Reviewer --downloaded deterministic ZIP--> independent inspection tools +CI runner --ephemeral credentials--> disposable Core 28.1 container/datadir +``` + +The browser is not trusted to report transaction state, assertion results, evidence hashes, lifecycle events, ownership, or cleanup. Bitcoin Core is the source of runtime chain and transaction-policy observations. BitScope interpretation remains distinct from Core output. + +## Threats, Controls, and Residual Risk + +| Threat | Primary controls | Residual risk / operator action | +| --- | --- | --- | +| Malicious browser origin | Exact configured CORS origins, origin validation on mutations, trusted-host middleware, local-only deployment guidance | An origin explicitly trusted by the operator can submit requests; do not expose the app publicly | +| Exposed local access token | Token required on mutation routes, constant-time comparison, no token in proof artifacts, unique local value guidance | The token is browser-visible by design; rotate it if exposed and do not reuse another secret | +| Incorrect runtime network | Configured-network check plus live `getblockchaininfo` verification before mutation and within scenario boundaries | A compromised Core endpoint could lie; protect the host and RPC transport | +| Unsafe RPC methods | Capability-specific clients and a global forbidden-method set for private-key export, wallet unlock, seed replacement, backup, encryption, private signing, and shutdown | Reviewed allowlists still permit powerful regtest mutations; review every capability expansion | +| Secret leakage in logs or evidence | Typed evidence, recursive key/value redaction, bounded upstream details, credential-free commands, canary tests, secret-free public settings | Novel secret formats may evade pattern matching; inspect exports before sharing | +| Stale regtest state | Current-run UUID wallet namespaces, generated addresses/outpoints, clean CI datadirs, two independent live passes | Native local nodes can retain unrelated state; use a disposable datadir for demonstrations | +| Scenario interruption | Persistent revisions/checkpoints, optimistic concurrency, explicit recovery to `incomplete`, cleanup before replacement | Work since the last checkpoint is lost; process/host failure can require manual Core inspection | +| Unclean wallet state | Session ownership records, namespace validation, cleanup on success/failure, `cleanup_failed` terminal state, CI container deletion | Core may be unavailable during cleanup; never interpret cleanup failure as verification | +| Invalid policy assumptions | Closed typed definitions, exact public policy evidence, required assertions, negative-path classification, versioned scenario catalogue | Tests cannot prove organizational procedures, secure backups, privacy, or production economics | +| Bitcoin Core version differences | Pinned Core 28.1 live matrix, runtime version evidence, exact flagship compatibility check, documented upgrade policy | Other versions may produce different RPC shapes, messages, policy decisions, or wallet behavior | +| Misinterpreted expected failure | Applicability checks, structured fields/RPC codes, narrow bounded markers, distinct expected/unexpected/skipped states | Text markers remain necessary for some Core 28.1 failures; a changed message must fail closed | +| False proof or security claims | Manifest disclaimers, Core-output/interpretation separation, honest limitations, `VERIFIED` requirements, reviewer documentation | A recipient can ignore disclaimers or receive a modified archive; independently verify hashes and context | +| Artifact traversal or overwrite | Server-generated run paths, normalized relative keys, bounded files, atomic writes, idempotence/conflict checks, read-time SHA-256 verification | Filesystem compromise outside BitScope can still delete artifacts or replace application code | +| Duplicate/concurrent execution | Optimistic run revision, transactional store, unique run/step records, duplicate-result validation | Multiple backend processes require the same durable database and must preserve transaction semantics | +| Cleanup targets another wallet | Random session namespace, recorded ownership, ownership validation before unload | An operator with direct RPC access can alter wallet state outside BitScope | + +## Security Assumptions + +1. The host, local user account, Python/Node runtimes, Bitcoin Core binary/container, and repository checkout are not malicious. +2. RPC is reachable only across a trusted local or container network; BitScope does not provide RPC transport encryption. +3. The operator uses a disposable regtest datadir for live scenarios and does not point mutation tests at valuable wallets. +4. The pinned container tag resolves to the intended Core 28.1 image; higher-assurance deployments should additionally pin and verify an image digest. +5. SHA-256 detects changed bundle content but does not identify who created it. Bundles are unsigned. +6. Public keys, scripts, addresses, transaction hex, and PSBT metadata are not treated as secrets; private key material and credentials are. + +## Security Review Triggers + +Revisit this model when adding a network, RPC method, remote deployment, authentication mechanism, signer type, hardware device, artifact schema, scenario-definition capability, persistent multi-process execution, Core version, or production-oriented claim. + +Report vulnerabilities privately as described in [CONTRIBUTING.md](../CONTRIBUTING.md). Do not place credentials or exploitable details in a public issue. diff --git a/docs/verified-scenarios.md b/docs/verified-scenarios.md index 56137db..a0a109d 100644 --- a/docs/verified-scenarios.md +++ b/docs/verified-scenarios.md @@ -2,6 +2,18 @@ Verified Scenarios are reviewed backend workflows, not user-supplied RPC scripts. A definition may compose only the closed step and assertion unions in `app.models.scenario`; execution belongs in a backend-owned adapter with the narrowest RPC capability that can perform the workflow. +## Catalogue + +| Scenario | Version | Proved positive path | Proved negative boundary | +| --- | --- | --- | --- | +| Transaction lifecycle | 1.0.0 | preflight, mempool, confirmation, decoding | one-satoshi overspend rejected as `bad-txns-in-belowout` | +| RBF replacement | 1.0.0 | higher-fee replacement evicts and confirms | insufficient incremental fee rejected by `bumpfee` | +| Multisig PSBT | 1.0.0 | staged 2-of-3 signing, finalization, confirmation | one signature cannot finalize | +| CLTV timelock | 1.0.0 | unchanged spend accepted at exact height | premature, final-sequence, and low-locktime variants rejected | +| Community Treasury Recovery | 1.0.0 | immediate, five-block recovery, ten-block emergency | six threshold/timelock/sequence failures exactly classified | + +Every catalogue entry is regtest-only, uses current-run resources, records cleanup, exports deterministic evidence, and has pinned live-node coverage. CPFP and OP_RETURN remain implemented labs but deferred optional scenarios. + ## Authoring contract 1. Define the objective, phases, typed artifact references, assertions, and cleanup as an immutable versioned `ScenarioDefinition`. @@ -72,3 +84,5 @@ All five mandatory scenarios export typed, ordered lifecycle evidence and a dete ## Challenge completion reuse Challenge Mode does not add a second transaction validator. Each challenge identifies one reviewed scenario plus a bounded subset of its typed assertions and canonical evidence. Completion requires the owner-scoped run to be verified, cleanup to be complete, Bitcoin Core identity to be recorded, every required assertion to have passed, and every required artifact to load with its stored SHA-256 identity. The final explanation remains locked until those checks pass, and the exported completion document cites the exact artifact hashes. See [Curriculum Mapping and Challenge Mode](curriculum-and-challenge-mode.md). + +See [Scenario Authoring](scenario-authoring.md) for the contribution checklist and [Proof Bundles](proof-bundles.md) for the export and independent-verification contract. diff --git a/frontend/app/globals.css b/frontend/app/globals.css index 3a0308c..36df908 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -48,6 +48,11 @@ select { font: inherit; } +:where(a, button, input, textarea, select, summary, [tabindex]):focus-visible { + outline: 3px solid rgb(var(--color-brass)); + outline-offset: 3px; +} + pre, code { max-width: 100%; diff --git a/frontend/app/learn/first-transaction/page.tsx b/frontend/app/learn/first-transaction/page.tsx new file mode 100644 index 0000000..74b7fb3 --- /dev/null +++ b/frontend/app/learn/first-transaction/page.tsx @@ -0,0 +1,5 @@ +import { FirstTransactionJourney } from "@/components/FirstTransactionJourney"; + +export default function FirstTransactionPage() { + return ; +} diff --git a/frontend/app/node/page.tsx b/frontend/app/node/page.tsx new file mode 100644 index 0000000..a72b9e5 --- /dev/null +++ b/frontend/app/node/page.tsx @@ -0,0 +1,121 @@ +import { CommandExplanationCard } from "@/components/CommandExplanationCard"; +import { StatusCard } from "@/components/StatusCard"; +import { WarningBox } from "@/components/WarningBox"; +import { fetchNodeStatus } from "@/lib/api"; + +export const dynamic = "force-dynamic"; + +export default async function NodePage() { + let nodeStatus: Awaited> | null = null; + let errorMessage = ""; + + try { + nodeStatus = await fetchNodeStatus(); + } catch (error) { + errorMessage = error instanceof Error ? error.message : "Bitcoin Core node status is not available."; + } + + return ( +
+
+

Node details

+

What does my Bitcoin Core node see?

+

+ Start with chain readiness and connected peers. Open the technical sections when you want the complete RPC view. +

+
+ + {nodeStatus ? ( + <> +
+ {nodeStatus.chain ?? "unknown chain"} + + {nodeStatus.initial_block_download ? "syncing" : "ready"} + + + {nodeStatus.pruned ? "pruned" : "full blocks available"} + +
+ + {nodeStatus.warnings.length ? ( + + {nodeStatus.warnings.map((warning) =>

{warning}

)} +
+ ) : null} + +
+

Essential status

+
+ + + + +
+
+ +
+ More node details +
+ + + + +
+
+ +
+
+ +
+ Under the hood: Core RPC +
+ +
+
+ + ) : ( + +

{errorMessage}

+

Start the FastAPI server and Bitcoin Core, then refresh this page.

+
+ )} + + + BitScope uses Bitcoin Core RPC only. Hosted blockchain APIs and remote indexers are intentionally outside the project. + +
+ ); +} + +function formatNumber(value: number | null) { + return value === null ? "unavailable" : new Intl.NumberFormat("en-US").format(value); +} + +function formatPercent(value: number | null) { + return value === null ? "unavailable" : `${(value * 100).toFixed(2)}%`; +} + +function formatBytes(value: number | null) { + if (value === null) return "unavailable"; + if (value < 1024) return `${value} B`; + const units = ["KB", "MB", "GB", "TB"]; + let scaled = value / 1024; + let unitIndex = 0; + while (scaled >= 1024 && unitIndex < units.length - 1) { + scaled /= 1024; + unitIndex += 1; + } + return `${scaled.toFixed(1)} ${units[unitIndex]}`; +} + +function formatBtcPerKvb(value: number | null) { + return value === null ? "unavailable" : `${value.toFixed(8)} BTC/kvB`; +} diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx index c7388a9..ff28587 100644 --- a/frontend/app/page.tsx +++ b/frontend/app/page.tsx @@ -1,5 +1,4 @@ -import { CommandExplanationCard } from "@/components/CommandExplanationCard"; -import { StatusCard } from "@/components/StatusCard"; +import Link from "next/link"; import { WarningBox } from "@/components/WarningBox"; import { fetchNodeStatus } from "@/lib/api"; @@ -15,108 +14,82 @@ export default async function HomePage() { errorMessage = error instanceof Error ? error.message : "Bitcoin Core node status is not available."; } + const ready = nodeStatus !== null && nodeStatus.chain === "regtest" && !nodeStatus.initial_block_download; + return (
-
-

Node status dashboard

-

Your Bitcoin Core node

-

- Live chain, network, and mempool state from Bitcoin Core RPC. -

-
- - {nodeStatus ? ( - <> -
- - {nodeStatus.chain ?? "unknown chain"} - - - {nodeStatus.initial_block_download ? "syncing" : "ready"} - - - {nodeStatus.pruned ? "pruned" : "full blocks available"} - -
- - {nodeStatus.warnings.length ? ( - - {nodeStatus.warnings.map((warning) => ( -

{warning}

- ))} -
- ) : null} - -
- - - - - - - - +
+
+
+

Your local Bitcoin laboratory

+

See how Bitcoin works

+

+ Create a real regtest transaction with your own Bitcoin Core node, then inspect what happened from wallet funding to confirmation. +

+
+ + Start your first transaction + + + Explore something + +
- + +
+
- - - ) : ( - + {!nodeStatus ? ( +

{errorMessage}

-

Start the FastAPI server and Bitcoin Core, then refresh this page.

+

You can still browse the learning library. Start FastAPI and Bitcoin Core before running an interactive lab.

- )} + ) : null} - - BitScope will use Bitcoin Core RPC only. Hosted blockchain APIs and remote indexers are intentionally outside the project. - +
+
+

Choose by intent

+

What would you like to understand?

+
+
+ + + +
+
+ +

+ BitScope talks only to your configured Bitcoin Core node. Guided labs use isolated regtest sessions and keep execution evidence on the backend. +

); } -function formatNumber(value: number | null) { - return value === null ? "unavailable" : new Intl.NumberFormat("en-US").format(value); -} - -function formatPercent(value: number | null) { - return value === null ? "unavailable" : `${(value * 100).toFixed(2)}%`; -} - -function formatBytes(value: number | null) { - if (value === null) { - return "unavailable"; - } - if (value < 1024) { - return `${value} B`; - } - const units = ["KB", "MB", "GB", "TB"]; - let scaled = value / 1024; - let unitIndex = 0; - while (scaled >= 1024 && unitIndex < units.length - 1) { - scaled /= 1024; - unitIndex += 1; - } - return `${scaled.toFixed(1)} ${units[unitIndex]}`; +function HomeCard({ href, title, description }: { href: string; title: string; description: string }) { + return ( + +

{title}

+

{description}

+ Open path → + + ); } -function formatBtcPerKvb(value: number | null) { - return value === null ? "unavailable" : `${value.toFixed(8)} BTC/kvB`; +function formatNumber(value: number | null) { + return value === null ? "unknown" : new Intl.NumberFormat("en-US").format(value); } diff --git a/frontend/components/FirstTransactionJourney.tsx b/frontend/components/FirstTransactionJourney.tsx new file mode 100644 index 0000000..e151f53 --- /dev/null +++ b/frontend/components/FirstTransactionJourney.tsx @@ -0,0 +1,430 @@ +"use client"; + +import Link from "next/link"; +import { useEffect, useMemo, useState } from "react"; +import { + advanceReviewerDemo, + downloadReviewerProofBundle, + fetchReviewerDemo, + fetchScenarioEvidence, + fetchScenarioLifecycle, + ReviewerDemoRun, + ReviewerEvidenceRecord, + startReviewerDemo, + TransactionLifecycleEvent, + TransactionLifecycleTimeline +} from "@/lib/api"; +import { + EnvironmentChecklist, + LabHeader, + NextStepCard, + PrimaryActionCard, + ResultExplanation, + StepProgress, + UnderTheHood +} from "@/components/learning/LearningLab"; + +const POINTER_KEY = "bitscope-first-transaction-run"; +const SCENARIO_ID = "transaction-lifecycle"; + +const JOURNEY_STEPS = [ + { id: "prepare", label: "Prepare" }, + { id: "address", label: "Create address" }, + { id: "fund", label: "Fund wallet" }, + { id: "utxo", label: "Choose UTXO" }, + { id: "build", label: "Build" }, + { id: "sign", label: "Sign & check" }, + { id: "broadcast", label: "Confirm" }, + { id: "inspect", label: "Inspect" }, + { id: "failure", label: "Expected failure" }, + { id: "finish", label: "Finish safely" } +] as const; + +export function FirstTransactionJourney() { + const [demo, setDemo] = useState(null); + const [evidence, setEvidence] = useState([]); + const [lifecycle, setLifecycle] = useState(null); + const [loading, setLoading] = useState(true); + const [working, setWorking] = useState(false); + const [error, setError] = useState(""); + const [notice, setNotice] = useState(""); + const [downloaded, setDownloaded] = useState(false); + + useEffect(() => { + let active = true; + async function restore() { + const pointer = readPointer(); + if (!pointer) { + setLoading(false); + return; + } + try { + const restored = await fetchReviewerDemo(pointer.runId, pointer.labSessionId); + if (!active || restored.run.scenario_id !== SCENARIO_ID) return; + await accept(restored, active); + setNotice("Your backend-owned lab was restored. Continue from its last persisted checkpoint."); + } catch { + clearPointer(); + } finally { + if (active) setLoading(false); + } + } + void restore(); + return () => { + active = false; + }; + }, []); + + async function accept(next: ReviewerDemoRun, active = true) { + if (!active) return; + setDemo(next); + writePointer(next); + const [nextEvidence, nextLifecycle] = await Promise.all([ + fetchScenarioEvidence(next.run.run_id, next.lab.session_id), + fetchScenarioLifecycle(next.run.run_id, next.lab.session_id) + ]); + if (!active) return; + setEvidence(nextEvidence.evidence); + setLifecycle(nextLifecycle); + } + + async function start() { + setWorking(true); + setError(""); + setNotice(""); + try { + const started = await startReviewerDemo(SCENARIO_ID); + await accept(started); + setNotice("A clean, isolated regtest lab is ready. Next, let BitScope verify Bitcoin Core."); + } catch (caught) { + setError(messageFrom(caught, "The guided lab could not start.")); + } finally { + setWorking(false); + } + } + + async function advance() { + if (!demo) return; + setWorking(true); + setError(""); + setNotice(""); + try { + const advanced = await advanceReviewerDemo(demo); + await accept(advanced); + setNotice( + advanced.run.current_state === "ready" + ? "Bitcoin Core and regtest are verified. The next action performs one safe transaction run, then opens every recorded stage for inspection." + : "The transaction run reached a persisted terminal checkpoint. Explore each recorded stage below." + ); + } catch (caught) { + setError(`${messageFrom(caught, "The guided run stopped.")} The backend did not silently skip a checkpoint.`); + try { + await accept(await fetchReviewerDemo(demo.run.run_id, demo.lab.session_id)); + } catch { + // Preserve the last persisted client snapshot when the backend is unavailable. + } + } finally { + setWorking(false); + } + } + + async function downloadProof() { + if (!demo?.can_export) return; + setWorking(true); + setError(""); + try { + const blob = await downloadReviewerProofBundle(demo); + const url = URL.createObjectURL(blob); + const anchor = document.createElement("a"); + anchor.href = url; + anchor.download = `bitscope-first-transaction-${demo.run.run_id}.zip`; + anchor.click(); + URL.revokeObjectURL(url); + setDownloaded(true); + setNotice("Your deterministic proof bundle was downloaded."); + } catch (caught) { + setError(messageFrom(caught, "The proof bundle could not be downloaded.")); + } finally { + setWorking(false); + } + } + + const terminal = demo ? !demo.can_advance : false; + const progress = useMemo( + () => + JOURNEY_STEPS.map((step, index) => ({ + ...step, + status: terminal + ? ("complete" as const) + : demo?.run.current_state === "ready" + ? index === 0 + ? ("complete" as const) + : index === 1 + ? ("active" as const) + : ("upcoming" as const) + : index === 0 + ? ("active" as const) + : ("upcoming" as const) + })), + [demo, terminal] + ); + + const primary = primaryActionFor(demo); + + return ( +
+ + + + + + + {error ? ( +
+ The lab paused. {error} +
+ ) : null} + {notice ?

{notice}

: null} + {loading ?

Checking for a previous guided run…

: null} + + {!loading && primary ? ( + +

Each action persists a backend revision before the next checkpoint begins.

+
+ ) : null} + + {terminal && demo ? ( + <> + +

+ Bitcoin Core version {demo.run.bitcoin_core_version ?? "unknown"} produced the evidence for this run. BitScope recorded {lifecycle?.events.length ?? 0} lifecycle events and {evidence.length} evidence records; the browser did not infer the result. +

+
+ + + + + + + + ) : null} +
+ ); +} + +function JourneyResults({ lifecycle, demo }: { lifecycle: TransactionLifecycleTimeline | null; demo: ReviewerDemoRun }) { + const events = lifecycle?.events ?? []; + const groups = [ + { title: "Prepare, address, and fund", types: ["wallet_prepared"] }, + { title: "Choose a UTXO", types: ["utxo_selected"] }, + { title: "Build the transaction", types: ["raw_transaction_created", "transaction_funded", "psbt_created"] }, + { title: "Sign and preflight", types: ["psbt_partially_signed", "psbt_completed", "transaction_finalized", "mempool_preflight_completed"] }, + { title: "Broadcast and confirm", types: ["transaction_broadcast", "transaction_entered_mempool", "transaction_confirmed"] }, + { title: "Finish safely", types: ["scenario_cleaned_up"] } + ]; + + return ( +
+
+

Inspect the recorded journey

+

What did Bitcoin Core actually do?

+

The executor completed one safe run. These cards now reveal it in learning order.

+
+
+ {groups.map((group, index) => { + const matching = events.filter((event) => group.types.includes(event.event_type)); + return ( +
+
+ {index + 1} +
+

{group.title}

+ {matching.length ? ( +
    + {matching.map((event) => )} +
+ ) : ( +

This outcome is represented in the scenario evidence rather than a separate lifecycle event.

+ )} +
+
+
+ ); + })} + +
+

Expected failure: an invalid overspend

+ {demo.run.expected_failures.length ? ( + demo.run.expected_failures.map((failure) => ( +
+

Rejected as expected · {failure.code}

+

{failure.safe_message}

+
+ )) + ) : ( +

No expected rejection was persisted for this run.

+ )} +
+
+ + +

+ A transaction does not send coins into an account. It spends earlier outputs and creates new outputs with locking conditions. The summaries above are BitScope’s teaching interpretation of typed Core results—not an opcode-by-opcode execution trace from Bitcoin Core. +

+
+ + + {events.length ? ( +
    + {events.map((event) => ( +
  1. +

    {event.event_type.replaceAll("_", " ")}

    + + {event.cli_command.executable} {event.cli_command.arguments.join(" ")} + +
    + Raw safe result +
    +                    {JSON.stringify(event.raw_safe_core_result, null, 2)}
    +                  
    +
    +
  2. + ))} +
+ ) : ( +

No lifecycle events were returned.

+ )} +
+ +

+ Want the general-purpose view?{" "} + + Open this run in scenario tools + + . +

+
+ ); +} + +function EventSummary({ event }: { event: TransactionLifecycleEvent }) { + return ( +
  • +

    {event.event_type.replaceAll("_", " ")}

    +

    {event.explanation}

    + {event.transaction_id ?

    txid {event.transaction_id}

    : null} +
  • + ); +} + +function primaryActionFor(demo: ReviewerDemoRun | null) { + if (!demo) { + return { + title: "Prepare a safe lab", + description: "Create an isolated session. Nothing is broadcast to mainnet or testnet.", + label: "Start guided lab" + }; + } + if (!demo.can_advance) return null; + if (demo.run.current_state === "created") { + return { + title: "Verify your environment", + description: "Ask the backend to verify Bitcoin Core, its version, and the regtest safety boundary.", + label: "Verify Core and regtest" + }; + } + return { + title: "Create your first transaction", + description: "Run the registered transaction lifecycle once. BitScope will then reveal each backend-recorded stage for inspection.", + label: "Create and verify transaction" + }; +} + +function messageFrom(caught: unknown, fallback: string) { + return caught instanceof Error ? caught.message : fallback; +} + +function writePointer(demo: ReviewerDemoRun) { + try { + window.localStorage.setItem(POINTER_KEY, JSON.stringify({ runId: demo.run.run_id, labSessionId: demo.lab.session_id })); + } catch { + // The pointer is only a convenience; the backend remains authoritative. + } +} + +function readPointer(): { runId: string; labSessionId: string } | null { + try { + const raw = window.localStorage.getItem(POINTER_KEY); + if (!raw) return null; + const parsed = JSON.parse(raw) as { runId?: unknown; labSessionId?: unknown }; + return typeof parsed.runId === "string" && typeof parsed.labSessionId === "string" + ? { runId: parsed.runId, labSessionId: parsed.labSessionId } + : null; + } catch { + return null; + } +} + +function clearPointer() { + try { + window.localStorage.removeItem(POINTER_KEY); + } catch { + // Nothing else depends on this convenience pointer. + } +} diff --git a/frontend/components/MultisigLab.tsx b/frontend/components/MultisigLab.tsx index 76b0d42..63016ac 100644 --- a/frontend/components/MultisigLab.tsx +++ b/frontend/components/MultisigLab.tsx @@ -5,6 +5,7 @@ import { FormEvent, useEffect, useState } from "react"; import { CommandExplanationCard } from "@/components/CommandExplanationCard"; import { StatusCard } from "@/components/StatusCard"; import { WarningBox } from "@/components/WarningBox"; +import { LabHeader, NextStepCard, UnderTheHood } from "@/components/learning/LearningLab"; import { MultisigCreateResponse, MultisigFundResponse, @@ -87,13 +88,12 @@ export function MultisigLab() { return (
    -
    -

    Multisig laboratory

    -

    Create, fund, and spend multisig

    -

    - Generate wallet-backed public keys, register an m-of-n multisig address, fund it on regtest, then spend from it with a PSBT. -

    -
    +

    Create or load a wallet, mine 101 blocks to make spendable coins, create the multisig address, fund it, then build the PSBT spend. Destination fields expect Bitcoin addresses, not wallet names.

    @@ -159,6 +159,12 @@ export function MultisigLab() { {created ? : null} {funded ? : null} {spent ? : null} +
    ); } @@ -182,7 +188,9 @@ function CreateResult({ result }: { result: MultisigCreateResponse }) { ))}
    - + + +
    ); } @@ -199,7 +207,9 @@ function FundResult({ result }: { result: MultisigFundResponse }) { Open funding transaction - + + + ); } @@ -214,7 +224,9 @@ function SpendResult({ result }: { result: MultisigSpendResponse }) { - + + + ); } diff --git a/frontend/components/PsbtLab.tsx b/frontend/components/PsbtLab.tsx index fef7d73..652af67 100644 --- a/frontend/components/PsbtLab.tsx +++ b/frontend/components/PsbtLab.tsx @@ -4,6 +4,7 @@ import { FormEvent, useState } from "react"; import { CommandExplanationCard } from "@/components/CommandExplanationCard"; import { StatusCard } from "@/components/StatusCard"; import { WarningBox } from "@/components/WarningBox"; +import { LabHeader, NextStepCard, UnderTheHood } from "@/components/learning/LearningLab"; import { PsbtCreateResponse, PsbtDecodeResponse, @@ -81,13 +82,12 @@ export function PsbtLab() { return (
    -
    -

    PSBT laboratory

    -

    Build, inspect, and finalize PSBTs

    -

    - Practice the Partially Signed Bitcoin Transaction flow without broadcasting raw transactions. -

    -
    +

    BitScope can create, decode, process, and finalize PSBTs here. It does not broadcast the final transaction.

    @@ -177,6 +177,12 @@ export function PsbtLab() { {created ? : null} {processed ? : null} {finalized ? : null} +
    ); } @@ -203,44 +209,50 @@ function DecodeSummary({ decoded }: { decoded: PsbtDecodeResponse }) { - + + + ); } function CreateResult({ result }: { result: PsbtCreateResponse }) { return ( - + + + ); } function ProcessResult({ result }: { result: PsbtProcessResponse }) { return ( - + + + ); } @@ -259,15 +271,17 @@ function FinalizeResult({ result }: { result: PsbtFinalizeResponse }) { ) : null} - + + + ); } diff --git a/frontend/components/SidebarNavigation.tsx b/frontend/components/SidebarNavigation.tsx index 31df653..16993f3 100644 --- a/frontend/components/SidebarNavigation.tsx +++ b/frontend/components/SidebarNavigation.tsx @@ -16,70 +16,60 @@ type NavigationGroup = { }; const START_ITEMS: NavigationItem[] = [ - { href: "/", label: "Dashboard" }, - { href: "/demo", label: "Demo" }, - { href: "/curriculum", label: "Learning Path" }, - { href: "/learn", label: "Concept Library" } + { href: "/", label: "Home" }, + { href: "/learn/first-transaction", label: "First Transaction" } ]; const NAVIGATION_GROUPS: NavigationGroup[] = [ { - id: "core-basics", - label: "Bitcoin Core Basics", + id: "learn", + label: "Learn", items: [ - { href: "/live", label: "Node & Live Status" }, - { href: "/peers", label: "Peers" }, - { href: "/blocks", label: "Blocks" }, - { href: "/mempool", label: "Mempool" }, - { href: "/fees", label: "Fees" } + { href: "/curriculum", label: "Learning Path" }, + { href: "/learn", label: "Concept Library" }, + { href: "/demo", label: "Quick Demo" }, + { href: "/scenarios", label: "Verified Scenarios" }, + { href: "/capstone-demo", label: "Reviewer Demo" } ] }, { - id: "wallets-funding", - label: "Wallets & Funding", + id: "explore", + label: "Explore", items: [ - { href: "/regtest", label: "Regtest" }, - { href: "/wallet", label: "Wallet" }, + { href: "/transactions", label: "Transactions" }, + { href: "/blocks", label: "Blocks" }, { href: "/address", label: "Addresses" }, - { href: "/keys", label: "Keys" }, - { href: "/descriptors", label: "Descriptors" } + { href: "/script", label: "Scripts" }, + { href: "/node", label: "Node Details" }, + { href: "/mempool", label: "Mempool" }, + { href: "/live", label: "Live Status" }, + { href: "/peers", label: "Peers" }, + { href: "/fees", label: "Fees" } ] }, { - id: "transactions", - label: "Transactions", + id: "build", + label: "Build", items: [ - { href: "/transactions", label: "Transaction Explorer" }, + { href: "/wallet", label: "Wallet" }, + { href: "/regtest", label: "Regtest Controls" }, { href: "/tx-control", label: "Fee Bumping" }, { href: "/multisig", label: "Multisig" }, { href: "/psbt", label: "PSBT" }, { href: "/timelocks", label: "Timelocks" }, - { href: "/data-tx", label: "OP_RETURN Data" } - ] - }, - { - id: "script", - label: "Bitcoin Script", - items: [ - { href: "/script", label: "Script Explorer" }, + { href: "/data-tx", label: "OP_RETURN Data" }, { href: "/script-lab", label: "Script Lab" }, - { href: "/taproot", label: "Taproot" } - ] - }, - { - id: "practice-proof", - label: "Practice & Proof", - items: [ - { href: "/scenarios", label: "Verified Scenarios" }, - { href: "/policies/compare", label: "Policy Comparison" }, - { href: "/capstone-demo", label: "Reviewer Demo" } + { href: "/taproot", label: "Taproot" }, + { href: "/policies/compare", label: "Policy Comparison" } ] }, { - id: "advanced-tools", - label: "Advanced Tools", + id: "developer", + label: "Developer", items: [ { href: "/rpc", label: "RPC Explorer" }, + { href: "/descriptors", label: "Descriptors" }, + { href: "/keys", label: "Keys" }, { href: "/integrations", label: "Integrations" }, { href: "/indexer", label: "Local Indexer" } ] @@ -90,7 +80,7 @@ export function SidebarNavigation() { const pathname = usePathname(); const currentGroupId = groupForPath(pathname)?.id ?? null; const [openGroups, setOpenGroups] = useState>( - () => new Set(["core-basics", ...(currentGroupId ? [currentGroupId] : [])]) + () => new Set(["learn", ...(currentGroupId ? [currentGroupId] : [])]) ); useEffect(() => { @@ -182,7 +172,8 @@ function NavigationLink({ item, active }: { item: NavigationItem; active: boolea } function isActivePath(pathname: string, href: string): boolean { - return href === "/" ? pathname === "/" : pathname === href || pathname.startsWith(`${href}/`); + if (href === "/" || href === "/learn") return pathname === href; + return pathname === href || pathname.startsWith(`${href}/`); } function groupForPath(pathname: string): NavigationGroup | undefined { diff --git a/frontend/components/TimelockLab.tsx b/frontend/components/TimelockLab.tsx index 24d2091..e349a7a 100644 --- a/frontend/components/TimelockLab.tsx +++ b/frontend/components/TimelockLab.tsx @@ -4,6 +4,7 @@ import { FormEvent, useEffect, useState } from "react"; import { CommandExplanationCard } from "@/components/CommandExplanationCard"; import { StatusCard } from "@/components/StatusCard"; import { WarningBox } from "@/components/WarningBox"; +import { LabHeader, NextStepCard, UnderTheHood } from "@/components/learning/LearningLab"; import { LocktimeTransactionResponse, TimelockScriptResponse, @@ -66,13 +67,12 @@ export function TimelockLab() { return (
    -
    -

    Timelock laboratory

    -

    Explore locktime, CLTV, CSV, and sequence

    -

    - Build a regtest transaction with explicit nLockTime and sequence values, then generate CLTV or CSV script templates for opcode-level inspection. -

    -
    +

    Transaction locktime only matters when at least one input sequence is non-final. CLTV is an absolute script lock; CSV is relative and depends on input sequence age.

    @@ -127,6 +127,12 @@ export function TimelockLab() { {transaction ? : null} {script ? : null} +
    ); } @@ -140,9 +146,13 @@ function TransactionResult({ result }: { result: LocktimeTransactionResponse }) - - - + +
    + + + +
    +
    ); } @@ -156,8 +166,12 @@ function ScriptResult({ result }: { result: TimelockScriptResponse }) { - - + +
    + + +
    +
    ); } diff --git a/frontend/components/TransactionControlLab.tsx b/frontend/components/TransactionControlLab.tsx index 9f5124b..0510c68 100644 --- a/frontend/components/TransactionControlLab.tsx +++ b/frontend/components/TransactionControlLab.tsx @@ -5,6 +5,7 @@ import { FormEvent, useState } from "react"; import { CommandExplanationCard } from "@/components/CommandExplanationCard"; import { StatusCard } from "@/components/StatusCard"; import { WarningBox } from "@/components/WarningBox"; +import { LabHeader, NextStepCard, UnderTheHood } from "@/components/learning/LearningLab"; import { CpfpChildResponse, RbfBumpResponse, @@ -41,13 +42,12 @@ export function TransactionControlLab() { return (
    -
    -

    Transaction control lab

    -

    Study RBF and CPFP policy

    -

    - Inspect unconfirmed transaction policy, bump wallet-owned replaceable transactions, and build child-pays-for-parent transactions on regtest. -

    -
    +

    RBF and CPFP actions can broadcast transactions. BitScope only enables the action endpoints when the backend is configured for regtest.

    @@ -82,6 +82,12 @@ export function TransactionControlLab() { +
    ); } @@ -110,15 +116,17 @@ function PolicyResult({ policy }: { policy: TransactionPolicyResponse }) { - + + + ); } @@ -263,7 +271,9 @@ function RbfResult({ result }: { result: RbfBumpResponse }) {
      {result.errors.map((item) =>
    • {item}
    • )}
    ) : null} - + + + {result.replacement_txid ? : null} ); @@ -278,7 +288,9 @@ function CpfpResult({ result }: { result: CpfpChildResponse }) { - + + + {result.child_txid ? : null} ); diff --git a/frontend/components/learning/LearningLab.tsx b/frontend/components/learning/LearningLab.tsx new file mode 100644 index 0000000..1efffb5 --- /dev/null +++ b/frontend/components/learning/LearningLab.tsx @@ -0,0 +1,204 @@ +import Link from "next/link"; +import type { ReactNode } from "react"; + +export type LearningStepStatus = "complete" | "active" | "upcoming"; + +export function LabHeader({ + eyebrow = "Guided learning", + question, + description, + badges = [] +}: { + eyebrow?: string; + question: string; + description: string; + badges?: string[]; +}) { + return ( +
    +

    {eyebrow}

    +

    {question}

    +

    {description}

    + {badges.length ? ( +
    + {badges.map((badge) => ( + + {badge} + + ))} +
    + ) : null} +
    + ); +} + +export function EnvironmentChecklist({ + items +}: { + items: Array<{ label: string; detail: string; status: "ready" | "pending" | "blocked" }>; +}) { + return ( +
    +

    Before you begin

    +
      + {items.map((item) => ( +
    • + + + {item.label} + {item.detail} + +
    • + ))} +
    +
    + ); +} + +export function StepProgress({ + steps +}: { + steps: Array<{ id: string; label: string; status: LearningStepStatus }>; +}) { + return ( + + ); +} + +export function PrimaryActionCard({ + title, + description, + actionLabel, + onAction, + disabled = false, + busy = false, + children +}: { + title: string; + description: string; + actionLabel: string; + onAction: () => void; + disabled?: boolean; + busy?: boolean; + children?: ReactNode; +}) { + return ( +
    +
    +
    +

    {title}

    +

    {description}

    + {children} +
    + +
    +
    + ); +} + +export function ResultExplanation({ + title, + children, + tone = "success" +}: { + title: string; + children: ReactNode; + tone?: "success" | "neutral" | "warning"; +}) { + const toneClass = + tone === "success" + ? "border-forest/40 bg-forest/10" + : tone === "warning" + ? "border-brass/50 bg-brass/10" + : "border-stone-300 bg-panel"; + return ( +
    +

    {title}

    +
    {children}
    +
    + ); +} + +export function UnderTheHood({ summary = "Under the hood", children }: { summary?: string; children: ReactNode }) { + return ( +
    + {summary} +
    {children}
    +
    + ); +} + +export function NextStepCard({ + title, + description, + href, + actionLabel, + alternatives = [] +}: { + title: string; + description: string; + href: string; + actionLabel: string; + alternatives?: Array<{ href: string; label: string }>; +}) { + return ( + + ); +} diff --git a/scripts/capture-screenshots.ps1 b/scripts/capture-screenshots.ps1 new file mode 100644 index 0000000..97c4049 --- /dev/null +++ b/scripts/capture-screenshots.ps1 @@ -0,0 +1,222 @@ +param( + [string]$BaseUrl = "http://localhost:3000", + [string]$ChromePath, + [int]$Width = 1440, + [int]$Height = 1000, + [int]$RenderBudgetMilliseconds = 5000, + [int]$TimeoutSeconds = 45, + [switch]$CheckOnly, + [switch]$VerifyExisting +) + +$ErrorActionPreference = "Stop" +$root = Split-Path -Parent $PSScriptRoot +$outputDirectory = Join-Path (Join-Path $root "docs") "screenshots" +$docsSiteOutputDirectory = Join-Path (Join-Path $root "docs-site") "screenshots" + +$captures = @( + [pscustomobject]@{ file = "home.png"; route = "/"; title = "Learner Home" } + [pscustomobject]@{ file = "first-transaction.png"; route = "/learn/first-transaction"; title = "First Transaction" } + [pscustomobject]@{ file = "node-details.png"; route = "/node"; title = "Node Details" } + [pscustomobject]@{ file = "learning-path.png"; route = "/curriculum"; title = "Learning Path" } + [pscustomobject]@{ file = "reviewer-mode.png"; route = "/capstone-demo"; title = "Reviewer Mode" } + [pscustomobject]@{ file = "learning-library.png"; route = "/learn"; title = "Concept Library" } +) + +function Resolve-ChromePath { + param([string]$RequestedPath) + + if ($RequestedPath) { + if (-not (Test-Path -LiteralPath $RequestedPath -PathType Leaf)) { + throw "Chrome was not found at the requested path: $RequestedPath" + } + return (Resolve-Path -LiteralPath $RequestedPath).Path + } + + $candidates = @( + "$env:ProgramFiles\Google\Chrome\Application\chrome.exe" + "${env:ProgramFiles(x86)}\Google\Chrome\Application\chrome.exe" + "$env:LOCALAPPDATA\Google\Chrome\Application\chrome.exe" + ) | Where-Object { $_ } + + foreach ($candidate in $candidates) { + if (Test-Path -LiteralPath $candidate -PathType Leaf) { + return (Resolve-Path -LiteralPath $candidate).Path + } + } + + $command = Get-Command chrome.exe -ErrorAction SilentlyContinue + if ($command) { + return $command.Source + } + + throw "Google Chrome was not found. Install Chrome or pass -ChromePath." +} + +function Test-PageReady { + param([string]$Url) + + try { + $response = Invoke-WebRequest -UseBasicParsing -Uri $Url -TimeoutSec 10 + if ($response.StatusCode -lt 200 -or $response.StatusCode -ge 400) { + throw "HTTP $($response.StatusCode)" + } + } catch { + throw "BitScope is not ready at $Url. Start the frontend before capturing. $($_.Exception.Message)" + } +} + +function Get-PngDimensions { + param([string]$Path) + + $stream = [System.IO.File]::OpenRead($Path) + try { + $header = New-Object byte[] 24 + if ($stream.Read($header, 0, $header.Length) -ne $header.Length) { + throw "PNG header is incomplete: $Path" + } + } finally { + $stream.Dispose() + } + + $signature = @(137, 80, 78, 71, 13, 10, 26, 10) + for ($index = 0; $index -lt $signature.Count; $index++) { + if ($header[$index] -ne $signature[$index]) { + throw "Chrome did not produce a PNG: $Path" + } + } + + $pngWidth = [System.Net.IPAddress]::NetworkToHostOrder([BitConverter]::ToInt32($header, 16)) + $pngHeight = [System.Net.IPAddress]::NetworkToHostOrder([BitConverter]::ToInt32($header, 20)) + return [pscustomobject]@{ width = $pngWidth; height = $pngHeight } +} + +function Test-ExistingCaptures { + $expectedNames = @($captures | ForEach-Object { $_.file } | Sort-Object) + + foreach ($directory in @($outputDirectory, $docsSiteOutputDirectory)) { + if (-not (Test-Path -LiteralPath $directory -PathType Container)) { + throw "Screenshot directory is missing: $directory" + } + $actualNames = @(Get-ChildItem -LiteralPath $directory -Filter "*.png" -File | ForEach-Object { $_.Name } | Sort-Object) + $difference = Compare-Object -ReferenceObject $expectedNames -DifferenceObject $actualNames + if ($difference) { + throw "Unexpected screenshot inventory in $directory`: $($difference | Out-String)" + } + } + + foreach ($capture in $captures) { + $primary = Join-Path $outputDirectory $capture.file + $mirror = Join-Path $docsSiteOutputDirectory $capture.file + foreach ($path in @($primary, $mirror)) { + $file = Get-Item -LiteralPath $path + if ($file.Length -lt 10000) { + throw "Screenshot is unexpectedly small ($($file.Length) bytes): $path" + } + $dimensions = Get-PngDimensions -Path $path + if ($dimensions.width -ne $Width -or $dimensions.height -ne $Height) { + throw "Screenshot dimensions are $($dimensions.width)x$($dimensions.height), expected ${Width}x${Height}: $path" + } + } + + $primaryHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $primary).Hash + $mirrorHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $mirror).Hash + if ($primaryHash -ne $mirrorHash) { + throw "Screenshot mirror differs for $($capture.file)" + } + Write-Host "$($capture.file): ${Width}x${Height} $primaryHash" + } + + Write-Host "Existing screenshot inventory and mirrors are valid." +} + +if ($VerifyExisting) { + Test-ExistingCaptures + exit 0 +} + +$resolvedChrome = Resolve-ChromePath -RequestedPath $ChromePath +$normalizedBaseUrl = $BaseUrl.TrimEnd("/") +Test-PageReady -Url $normalizedBaseUrl + +Write-Host "Chrome: $resolvedChrome" +Write-Host "BitScope: $normalizedBaseUrl" +Write-Host "Viewport: ${Width}x${Height}" + +if ($CheckOnly) { + Write-Host "Screenshot prerequisites are ready." + exit 0 +} + +New-Item -ItemType Directory -Path $outputDirectory -Force | Out-Null +New-Item -ItemType Directory -Path $docsSiteOutputDirectory -Force | Out-Null + +$temporaryRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("bitscope-screenshots-" + [guid]::NewGuid().ToString("N")) +$profileDirectory = Join-Path $temporaryRoot "chrome-profile" +$stagingDirectory = Join-Path $temporaryRoot "staging" +New-Item -ItemType Directory -Path $profileDirectory, $stagingDirectory -Force | Out-Null + +try { + foreach ($capture in $captures) { + $target = Join-Path $stagingDirectory $capture.file + $captureProfileDirectory = Join-Path $profileDirectory ([System.IO.Path]::GetFileNameWithoutExtension($capture.file)) + New-Item -ItemType Directory -Path $captureProfileDirectory -Force | Out-Null + $url = "$normalizedBaseUrl$($capture.route)" + Write-Host "Capturing $($capture.title): $url" + + $arguments = @( + "--headless=new" + "--disable-gpu" + "--disable-background-networking" + "--disable-default-apps" + "--disable-extensions" + "--disable-sync" + "--force-device-scale-factor=1" + "--no-first-run" + "--run-all-compositor-stages-before-draw" + "--user-data-dir=$captureProfileDirectory" + "--virtual-time-budget=$RenderBudgetMilliseconds" + "--window-size=$Width,$Height" + "--screenshot=$target" + $url + ) + + $process = Start-Process -FilePath $resolvedChrome -ArgumentList $arguments -PassThru -WindowStyle Hidden + try { + Wait-Process -Id $process.Id -Timeout $TimeoutSeconds -ErrorAction Stop + } catch { + Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue + throw "Chrome timed out after $TimeoutSeconds seconds while capturing $url" + } + + if ($process.ExitCode -ne 0) { + throw "Chrome exited with code $($process.ExitCode) while capturing $url" + } + if (-not (Test-Path -LiteralPath $target -PathType Leaf)) { + throw "Chrome did not create $target" + } + + $file = Get-Item -LiteralPath $target + if ($file.Length -lt 10000) { + throw "Screenshot is unexpectedly small ($($file.Length) bytes): $target" + } + + $dimensions = Get-PngDimensions -Path $target + if ($dimensions.width -ne $Width -or $dimensions.height -ne $Height) { + throw "Screenshot dimensions are $($dimensions.width)x$($dimensions.height), expected ${Width}x${Height}: $target" + } + } + + foreach ($capture in $captures) { + $source = Join-Path $stagingDirectory $capture.file + Copy-Item -LiteralPath $source -Destination (Join-Path $outputDirectory $capture.file) -Force + Copy-Item -LiteralPath $source -Destination (Join-Path $docsSiteOutputDirectory $capture.file) -Force + } + + Test-ExistingCaptures + Write-Host "Updated $($captures.Count) screenshots in docs/screenshots and docs-site/screenshots." +} finally { + if (Test-Path -LiteralPath $temporaryRoot) { + Remove-Item -LiteralPath $temporaryRoot -Recurse -Force -ErrorAction SilentlyContinue + } +}