"""Phase 7 exit-criteria smoke test. Exercises the whole Phase 7 surface end-to-end on a synthetic vault: 1. ``muse commit --sign --attest --meta '{...}' --quorum-signers a,b --quorum-org @team`` 2. ``muse verify-commit --attest --verify-identity`` → tier 2 (HMAC; ICP gated by the network-isolated test environment) AND ``quorum_valid=True``. Real network calls are intentionally avoided — the ICP canister query is disabled via ``--no-icp`` so the test deterministically stops at tier 2 and exercises the full software path WITHOUT a live IC dependency. Why this test exists: The user's Phase 7 spec ends with:: Phase 7 exit criteria --------------------- `muse commit --sign --attest --meta '{...}' ` produces a commit verifiable via `muse verify-commit --attest --verify-identity`; ICP anchor visible on MuseHub note detail page; org-quorum commits sign and verify end-to-end on a fixture vault. This file proves the CLI surface meets that contract on a fixture vault. The MuseHub note-detail page rendering is covered separately by ``test_musehub_provenance_badge_strip.py``. """ from __future__ import annotations import argparse import io import json import os import pathlib import sys from typing import Any import pytest from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey from cryptography.hazmat.primitives.serialization import ( Encoding, NoEncryption, PrivateFormat, ) from muse.core.attestation import ( AnchorReceipt, AttestationRecord, MuseAttestationProvider, VerifyResult, register_attestation_provider, unregister_attestation_provider, unregister_identity_provider, ) from muse.core.provenance import encode_public_key from muse.plugins.knowtation.quorum import ( PROVIDER_DOMAIN as IDENTITY_DOMAIN, register_knowtation_identity_provider, ) # Same write-once stub that the existing 7.3 / 7.5 suites use. _HMAC_DOMAIN = "knowtation" class _StubAttestProvider: """Always-valid attestation provider used for the smoke test.""" def __init__(self) -> None: self.calls: list[tuple[str, dict[str, object]]] = [] def compute_attestation( self, snapshot_id: str, commit_meta: dict[str, object] ) -> AttestationRecord: self.calls.append((snapshot_id, dict(commit_meta))) return { "id": "f" * 64, "action": "write", "path": str(commit_meta.get("path", "")), "timestamp": str(commit_meta.get("timestamp", "")), "content_hash": snapshot_id, "sig": "smoke-sig", "algorithm": "smoke", "provider_id": _HMAC_DOMAIN, } def anchor(self, record: AttestationRecord) -> AnchorReceipt: return { "tx_id": "smoke-tx", "anchor_url": "", "anchored_at": "", "provider_id": _HMAC_DOMAIN, } def verify(self, record: AttestationRecord, receipt: AnchorReceipt) -> VerifyResult: return {"valid": True, "reason": "", "depth": 0, "provider_id": _HMAC_DOMAIN} def _new_key_pem(path: pathlib.Path) -> tuple[Ed25519PrivateKey, str]: """Generate an Ed25519 keypair, write the PEM, return (priv, pub_b64).""" priv = Ed25519PrivateKey.generate() _raw, pub_b64 = encode_public_key(priv) pem = priv.private_bytes( encoding=Encoding.PEM, format=PrivateFormat.PKCS8, encryption_algorithm=NoEncryption(), ) path.write_bytes(pem) return priv, pub_b64 def _write_identity( dir_: pathlib.Path, handle: str, *, kind: str, public_key: str = "", members: list[str] | None = None, quorum_threshold: int = 0, ) -> None: dir_.mkdir(parents=True, exist_ok=True) fname = ("_at_" + handle[1:]) if handle.startswith("@") else handle body: dict[str, Any] = { "handle": handle, "kind": kind, "public_key": public_key, } if members is not None: body["members"] = members if quorum_threshold: body["quorum_threshold"] = quorum_threshold (dir_ / f"{fname}.json").write_text(json.dumps(body), encoding="utf-8") def _capture_run(fn: Any, *args: Any, **kwargs: Any) -> tuple[int | None, str, str]: """Run *fn* capturing stdout/stderr; tolerate SystemExit.""" out_buf = io.StringIO() err_buf = io.StringIO() real_out, real_err = sys.stdout, sys.stderr code: int | None = None try: sys.stdout, sys.stderr = out_buf, err_buf try: fn(*args, **kwargs) except SystemExit as exc: code = ( exc.code if isinstance(exc.code, int) else (0 if exc.code is None else 1) ) finally: sys.stdout, sys.stderr = real_out, real_err return code, out_buf.getvalue(), err_buf.getvalue() @pytest.fixture(autouse=True) def _registry_cleanup() -> Any: unregister_attestation_provider(_HMAC_DOMAIN) unregister_identity_provider(IDENTITY_DOMAIN) yield unregister_attestation_provider(_HMAC_DOMAIN) unregister_identity_provider(IDENTITY_DOMAIN) def test_full_phase7_round_trip( tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch, ) -> None: """commit --sign --attest --meta --quorum → verify-commit verifies all tiers.""" # ── 1. Fixture vault setup ─────────────────────────────────────────── os.chdir(tmp_path) from muse.cli.commands.init import run as init_run init_run(argparse.Namespace( bare=False, template=None, default_branch="main", force=False, domain="knowtation", as_json=False, )) (tmp_path / "notes").mkdir(exist_ok=True) (tmp_path / "notes" / "smoke.md").write_text( "---\ntitle: Smoke\n---\n# smoke\n", encoding="utf-8", ) # ── 2. Attestation provider + identity directory ──────────────────── register_attestation_provider(_HMAC_DOMAIN, _StubAttestProvider()) identity_dir = tmp_path / "identity-cache" monkeypatch.setenv("MUSE_IDENTITY_DIR", str(identity_dir)) register_knowtation_identity_provider(repo_root=None) # Primary signing key — injected via MUSE_AGENT_KEY so the engine's # offline path picks it up without a registered hub. primary_pem_path = tmp_path / "primary.pem" _new_key_pem(primary_pem_path) monkeypatch.setenv("MUSE_AGENT_KEY", primary_pem_path.read_text()) monkeypatch.setenv("MUSE_AGENT_HANDLE", "smoke-agent") # Two co-signers + the org record they're members of. alice_priv, alice_pub = _new_key_pem(tmp_path / "alice.pem") bob_priv, bob_pub = _new_key_pem(tmp_path / "bob.pem") monkeypatch.setenv("MUSE_QUORUM_KEY_ALICE_PATH", str(tmp_path / "alice.pem")) monkeypatch.setenv("MUSE_QUORUM_KEY_BOB_PATH", str(tmp_path / "bob.pem")) _write_identity(identity_dir, "alice", kind="human", public_key=alice_pub) _write_identity(identity_dir, "bob", kind="human", public_key=bob_pub) _write_identity( identity_dir, "@team", kind="org", members=["alice", "bob"], quorum_threshold=2, ) # ── 3. muse commit --sign --attest --meta --quorum-signers ────────── from muse.cli.commands import commit as commit_cmd args = argparse.Namespace( message="phase7-smoke", allow_empty=False, dry_run=False, section=None, track=None, emotion=None, author=None, agent_id="smoke-agent", model_id=None, toolchain_id=None, event_type=None, meta=json.dumps({"phase": "7-smoke", "topic": "exit-criteria"}), sign=True, # primary key resolved from MUSE_AGENT_KEY env var. attest=True, attest_config=None, quorum_signers="alice,bob", quorum_org="@team", quorum_threshold=2, quorum_keys=None, fmt="json", ) code, out, err = _capture_run(commit_cmd.run, args) assert code in (0, None), f"commit failed exit={code}\nstdout={out}\nstderr={err}" # ── 4. Read back the commit and confirm metadata wiring ──────────── from muse.core.store import get_head_commit_id, read_commit head = get_head_commit_id(tmp_path, "main") assert head is not None commit = read_commit(tmp_path, head) assert commit is not None, "head commit unreadable" assert "attestation" in commit.metadata, "commit missing attestation record" assert "quorum" in commit.metadata, "commit missing quorum metadata" assert "meta" in commit.metadata, "commit missing --meta payload" meta_payload = json.loads(commit.metadata["meta"]) assert meta_payload == {"phase": "7-smoke", "topic": "exit-criteria"} # ── 5. muse verify-commit --attest --verify-identity --no-icp ────── # --no-icp keeps the test offline; tier caps at HMAC_ATTESTED (2). from muse.cli.commands import verify_commit as verify_cmd vargs = argparse.Namespace( commit_id=head, attest=True, no_icp=True, icp_canister=None, icp_timeout=5.0, json_out=True, verify_identity=True, identity_domain="knowtation", ) vcode, vout, verr = _capture_run(verify_cmd.run, vargs) assert vout, f"verify-commit produced no JSON output. stderr={verr}" payload = json.loads(vout) # Tier 2 = signed (Ed25519) + HMAC-attested. Tier 3 (ICP-anchored) is # gated by --no-icp; the canister query is intentionally skipped so this # smoke test stays offline. assert payload["tier"] == 2, ( f"expected HMAC-attested tier; got {payload}" ) assert payload["valid"] is True assert payload["hmac_valid"] is True quorum = payload["quorum"] assert quorum["valid"] is True, f"quorum verification failed: {quorum}" assert quorum["valid_signers"] == 2 assert quorum["threshold"] == 2 assert quorum["org_handle"] == "@team" assert quorum["depth"] >= 2 # @team → alice + bob = depth 2