mirror of
https://github.com/saymrwulf/proof-aware-crypto-tooling-agent.git
synced 2026-09-04 20:03:40 +00:00
cockpit: --demo flag — one command from zero, custody-inert
The operator ran the documented command and hit the fail-fast (no wallet exists on a fresh machine) — correct behavior, useless instruction. seal_demo_wallet() now seals a throwaway DEMO wallet (fake shell-stub members, DEMO-labeled fields, temp dir named warden-DEMO-*, sample incident/refusal/airgap so every view has content); 'pacta wallet cockpit --demo' serves it. Exactly one of --wallet/--demo required. Verified by running the literal command: all five views 200, demo quorum renders. Suite 130 -> 131. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
1acbaa1a76
commit
b3239dba45
4 changed files with 111 additions and 3 deletions
|
|
@ -44,9 +44,14 @@ is out of scope until reproducible builds).
|
|||
## Serving
|
||||
|
||||
```bash
|
||||
pacta wallet cockpit --demo # no wallet yet? throwaway
|
||||
# DEMO wallet, custody-inert
|
||||
pacta wallet cockpit --wallet ~/my-wallet # 127.0.0.1:8471
|
||||
pacta wallet cockpit --wallet ~/my-wallet --port 9000
|
||||
```
|
||||
|
||||
(Uninstalled, from the repo root:
|
||||
`PYTHONPATH=src:provider/src python3 -m pacta wallet cockpit --demo`.)
|
||||
|
||||
The server binds localhost by default and is not meant to be exposed;
|
||||
there is no authentication because there is nothing to operate.
|
||||
|
|
|
|||
|
|
@ -236,7 +236,8 @@ def build_parser() -> argparse.ArgumentParser:
|
|||
w_mcp.set_defaults(func=cmd_wallet_mcp)
|
||||
|
||||
w_cockpit = wsub.add_parser("cockpit", help="Serve the read-only custody cockpit (local web UI) for the human operator.")
|
||||
w_cockpit.add_argument("--wallet", required=True)
|
||||
w_cockpit.add_argument("--wallet", help="Path to an existing wallet directory.")
|
||||
w_cockpit.add_argument("--demo", action="store_true", help="No wallet yet? Serve a throwaway DEMO wallet (fake members, custody-inert) to explore the views.")
|
||||
w_cockpit.add_argument("--host", default="127.0.0.1", help="Bind address (default localhost; the cockpit is not meant to be exposed).")
|
||||
w_cockpit.add_argument("--port", type=int, default=8471)
|
||||
w_cockpit.set_defaults(func=cmd_wallet_cockpit)
|
||||
|
|
@ -680,8 +681,16 @@ def cmd_wallet_card(args: argparse.Namespace) -> int:
|
|||
|
||||
|
||||
def cmd_wallet_cockpit(args: argparse.Namespace) -> int:
|
||||
from .walletui import serve
|
||||
server = serve(args.wallet, host=args.host, port=args.port)
|
||||
from .walletui import seal_demo_wallet, serve
|
||||
if bool(args.wallet) == bool(args.demo):
|
||||
print("error: pass exactly one of --wallet DIR or --demo")
|
||||
return 2
|
||||
wallet_dir = args.wallet
|
||||
if args.demo:
|
||||
wallet_dir = seal_demo_wallet()
|
||||
print("DEMO wallet (throwaway, fake members, custody-inert):")
|
||||
print(f" {wallet_dir}")
|
||||
server = serve(wallet_dir, host=args.host, port=args.port)
|
||||
host, port = server.server_address[0], server.server_address[1]
|
||||
print(f"warden cockpit (READ-ONLY) on http://{host}:{port} - Ctrl-C to stop")
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -335,6 +335,82 @@ def render_inspect(result: dict[str, Any] | None,
|
|||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# demo wallet - custody-inert, for exploring the cockpit from zero
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def seal_demo_wallet(root: str | Path | None = None) -> Path:
|
||||
"""Seal a throwaway DEMO wallet: four fake quorum members (shell stubs,
|
||||
NOT the verified binaries), a genesis ledger, a fresh keypair, and one
|
||||
sample incident / refusal / airgap request so every view has content.
|
||||
|
||||
Custody-inert by construction: nothing here can sign anything real,
|
||||
and the wallet lives in a throwaway directory whose name says DEMO.
|
||||
"""
|
||||
import stat
|
||||
|
||||
from .quorum import binary_path
|
||||
from .signing import generate_ed25519_keypair
|
||||
|
||||
if root is None:
|
||||
root = Path(tempfile.mkdtemp(prefix="warden-DEMO-"))
|
||||
root = Path(root)
|
||||
state_dir = root / "state"
|
||||
state_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def _sha(data: bytes) -> str:
|
||||
import hashlib
|
||||
return hashlib.sha256(data).hexdigest()
|
||||
|
||||
members = []
|
||||
for name in ("dalek", "anza", "risc0", "betrusted"):
|
||||
binary = binary_path(name, state_dir)
|
||||
binary.write_text("#!/bin/sh\necho OK\nexit 0\n")
|
||||
binary.chmod(binary.stat().st_mode | stat.S_IEXEC)
|
||||
members.append({
|
||||
"backend": name, "component": f"{name}-ed25519-verified",
|
||||
"semantics": "DEMO", "entry_point": "DEMO",
|
||||
"source_commit": "deadbeef" * 5, "repo_commit": "cafe" * 10,
|
||||
"binary_sha256": _sha(binary.read_bytes()),
|
||||
"backend_cfg": "DEMO", "risk_tier": "R4",
|
||||
"evidence": {"leaf_hash": "00", "leaf_index": 0, "tree_size": 1,
|
||||
"inclusion_proof": [],
|
||||
"sth": {"timestamp": "2099-01-01T00:00:00Z"}},
|
||||
})
|
||||
wallet = Wallet(root / "wallet")
|
||||
for sub in (wallet.keys_dir, wallet.incidents_dir, wallet.receipts_dir,
|
||||
wallet.quarantine_dir, wallet.airgap_dir / "outbox",
|
||||
wallet.airgap_dir / "inbox"):
|
||||
sub.mkdir(parents=True, exist_ok=True)
|
||||
capsule = {
|
||||
"type": "pacta.wallet.custody_capsule.v1",
|
||||
"created_at": _now(), "members": members,
|
||||
"policy": {"require_unanimity": True, "min_members": 4,
|
||||
"require_tier": "R4", "freshness_max_age_days": 0},
|
||||
"signing": {"backend": "DEMO"}, "problems_at_init": [],
|
||||
}
|
||||
wallet.capsule_path.write_text(json.dumps(capsule, indent=2, sort_keys=True) + "\n")
|
||||
wallet._append_ledger("genesis", {
|
||||
"type": "pacta.wallet.ledger_genesis.v1",
|
||||
"capsule_sha256": _sha(json.dumps(capsule, sort_keys=True,
|
||||
separators=(",", ":")).encode())})
|
||||
generate_ed25519_keypair(wallet.keys_dir / "warden.key.pem",
|
||||
wallet.keys_dir / "warden.pub.pem")
|
||||
(wallet.incidents_dir / "incident-0001.json").write_text(json.dumps(
|
||||
{"type": "pacta.wallet.incident.v1", "severity": "divergence",
|
||||
"detail": "DEMO sample: member risc0 returned INVALID where the "
|
||||
"other three returned OK",
|
||||
"at": _now(), "payload_sha256": "ab" * 32}, indent=2))
|
||||
(wallet.receipts_dir / "refusal-0001.json").write_text(json.dumps(
|
||||
{"type": "pacta.wallet.refusal.v1", "code": "POLICY_DENIED",
|
||||
"missing": ["allowlisted destination"],
|
||||
"remediation": "DEMO sample: add destination to policy.json allowlist",
|
||||
"at": _now()}, indent=2))
|
||||
(wallet.airgap_dir / "outbox" / "req-demo.request.json").write_text(json.dumps(
|
||||
{"created_at": _now(), "payload_sha256": "cd" * 32}))
|
||||
return wallet.dir
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# server
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -230,3 +230,21 @@ def test_estate_view_and_estate_md_do_not_drift():
|
|||
# the runtime dimension must exist in BOTH renderings
|
||||
assert "What is running" in estate_md
|
||||
assert "ALWAYS ON" in ESTATE_HTML
|
||||
|
||||
|
||||
def test_demo_wallet_seals_and_serves(tmp_path):
|
||||
from pacta.walletui import seal_demo_wallet
|
||||
wallet_dir = seal_demo_wallet(tmp_path)
|
||||
server = serve(wallet_dir, host="127.0.0.1", port=0)
|
||||
port = server.server_address[1]
|
||||
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
||||
thread.start()
|
||||
try:
|
||||
with urllib.request.urlopen(f"http://127.0.0.1:{port}/") as resp:
|
||||
body = resp.read().decode()
|
||||
assert resp.status == 200
|
||||
assert "dalek-ed25519-verified" in body and "4 pinned" in body
|
||||
assert "chain verified" in body
|
||||
finally:
|
||||
server.shutdown()
|
||||
thread.join(timeout=5)
|
||||
|
|
|
|||
Loading…
Reference in a new issue