feat: make Core reports commercially production-ready

This commit is contained in:
thanhnv
2026-07-29 13:12:11 +07:00
parent 67d482ae7f
commit 7f01cf980a
27 changed files with 1197 additions and 23 deletions
@@ -125,11 +125,14 @@ main { max-width: 1500px; margin: 0 auto; padding: 28px clamp(20px,4vw,48px) 64p
font-size: 9px; font-weight: 850; letter-spacing: .1em; text-transform: uppercase; white-space: nowrap;
}
.pill.pass, .pill.certified, .pill.success, .pill.fresh, .pill.operational { background: var(--green-soft); color: var(--green); }
.pill.warning, .pill.attention, .pill.partial, .pill.stale, .pill.in-progress, .pill.in_progress { background: var(--amber-soft); color: var(--amber); }
.pill.warning, .pill.attention, .pill.ready-with-attention, .pill.ready_with_attention, .pill.partial, .pill.stale, .pill.in-progress, .pill.in_progress { background: var(--amber-soft); color: var(--amber); }
.pill.error, .pill.failed, .pill.fail, .pill.blocked, .pill.non-certified, .pill.non_certified { background: var(--rose-soft); color: var(--rose); }
.pill.neutral, .pill.queued, .pill.unknown, .pill.no-data, .pill.no_data { background: #f2f4f7; color: #475467; }
.hero .pill { border: 1px solid #344054; background: #1d2939; color: #d0d5dd; }
.metric-grid { display: grid; grid-template-columns: repeat(4,minmax(0,1fr)); gap: 14px; }
.metric-grid.readiness-grid { grid-template-columns: repeat(3,minmax(0,1fr)); }
.readiness-grid .metric-value { font-size: 24px; text-transform: capitalize; }
.onboarding-actions { margin-bottom: 18px; }
.metric-card, .panel {
border: 1px solid var(--line); border-radius: 18px; background: white; box-shadow: var(--shadow);
}
@@ -275,6 +278,7 @@ tbody tr:hover { background: #f0fdfa; }
@media (max-width: 1120px) {
.metric-grid { grid-template-columns: repeat(2,1fr); }
.metric-grid.readiness-grid { grid-template-columns: 1fr; }
.two-col, .breakdown-grid, .dossier { grid-template-columns: 1fr; }
.hero-grid { grid-template-columns: 1fr; }
.hero-actions { justify-content: flex-start; }
@@ -27,6 +27,7 @@
? "Unavailable" : new Intl.NumberFormat("en-US").format(Number(value));
const time = (value) => value ? String(value).replace("T", " ").replace("Z", " UTC") : "No timestamp";
const money = (value) => value === null || value === undefined ? "Unavailable" : `$${Number(value).toFixed(6)}`;
const statusLabel = (value) => String(value || "unknown").replaceAll("_", " ");
const pill = (value) => `<span class="pill ${slug(value)}">${esc(String(value || "unknown").replaceAll("_", " "))}</span>`;
async function api(path) {
@@ -122,6 +123,22 @@
const certified = latest.certified === true;
const latestTrace = latest.trace_id || recent[0]?.trace_id;
const maturity = profile.maturity || {};
const readiness = profile.readiness || {};
const dimensions = readiness.dimensions || {};
const core = dimensions.core || { status: "not_assessed", summary: "Core readiness has not been assessed." };
const domain = dimensions.domain_pipeline || { status: "not_assessed", summary: "Domain Pipeline readiness has not been assessed." };
const telemetry = dimensions.provider_telemetry || { status: "not_assessed", summary: "Provider Telemetry readiness has not been assessed." };
const nextActions = readiness.next_actions || [];
const coreReady = ["ready", "ready_with_attention"].includes(core.status);
const readinessActionList = `<div class="findings">${nextActions.map((action, index) => `<div class="finding warning">
<span class="finding-index">${String(index + 1).padStart(2, "0")}</span>
<div><code>${esc(action.code)}</code><p>${esc(action.message)}</p></div>
</div>`).join("")}</div>`;
const readinessCards = `<section class="metric-grid readiness-grid" aria-label="Commercial readiness">
${metric("Core", statusLabel(core.status), core.summary)}
${metric("Domain Pipeline", statusLabel(domain.status), domain.summary)}
${metric("Provider Telemetry", statusLabel(telemetry.status), telemetry.summary)}
</section>`;
const actions = latestTrace
? `<button class="button primary" type="button" data-open-trace="${esc(latestTrace)}">Inspect latest run</button>
<button class="button secondary" type="button" data-open-h6="${esc(latestTrace)}">Open H6</button>`
@@ -129,22 +146,43 @@
$("#overview-view").innerHTML =
hero({
eyebrow: "CASAN Core · Local assurance",
title: latestTrace ? "One prompt. One evidence trail." : "Ready for the first governed run.",
description: "A read-only, offline-capable assurance workspace reconstructed from canonical project evidence. No Platform service or HTML generation on the prompt hot path.",
verdict: latestTrace ? (certified ? "certified" : "review required") : "ready",
title: latestTrace ? "One prompt. One evidence trail." :
coreReady ? "Core reporting is ready." : "Finish Core activation.",
description: latestTrace
? "A read-only, offline-capable assurance workspace reconstructed from canonical project evidence. No Platform service or HTML generation on the prompt hot path."
: coreReady
? "Core visual reporting works without a Domain Pack or application-source changes. Send a governed prompt to create the first run dossier."
: "Core visual reporting works without a Domain Pack or application-source changes. Complete the activation action below, then send a governed prompt.",
verdict: latestTrace ? (certified ? "certified" : "review required") : core.status,
meta: [
`${profile.project_name} · ${profile.edition}`,
latestTrace ? `Trace ${latestTrace}` : "No finalized trace yet",
`Maturity ${maturity.level == null ? "not assessed" : `L${maturity.level}`} · ${maturity.status}`,
`Maturity ${maturity.level == null ? "not assessed" : `L${maturity.level}`} · ${statusLabel(maturity.status)}`,
],
actions,
}) +
`<section class="metric-grid">
}) + readinessCards +
(latestTrace && nextActions.length ? panel(
"Activation attention",
"A selected client route needs action.",
"Healthy routes remain operational; complete this step before relying on the affected integration.",
readinessActionList,
pill(core.status),
) : "") +
(latestTrace ? `<section class="metric-grid">
${metric("Governed records", number(registry.count), `${recent.length} recent records loaded`)}
${metric("Latest H6 quality", latest.telemetry_quality || "Unavailable", "Missing values are disclosed, never coerced to zero")}
${metric("Observed failures", number(failures), `${recent.length ? Math.round((failures / recent.length) * 100) : 0}% of loaded records`)}
${metric("Evidence gates", latestTrace ? "H1 → H7" : "Waiting", latestTrace ? "Control-by-control reconstruction available" : "Evidence appears after a prompt finalizes")}
</section>` +
</section>` : panel(
"First-run onboarding",
"No operational KPIs are shown until evidence exists.",
"CASAN does not turn missing telemetry into zeroes or claim a run is certified before a governed prompt finishes.",
`${nextActions.length ? `<div class="onboarding-actions">${readinessActionList}</div>` : ""}
<div class="two-col">
<div class="notice success"><strong>Available now</strong><br>Local viewer, H1–H7 run dossier, history, H6 AgentOps and on-demand JSON/HTML export.</div>
<div class="notice warning"><strong>Optional expansion</strong><br>Install a Domain Pack only when the team needs project-specific SRS→test acceptance.</div>
</div>`
)) +
disclosure(
"About this local report",
"Edition boundaries and implementation notes are hidden until needed.",
@@ -172,13 +210,22 @@
function renderRuns() {
const rows = state.registry.runs || [];
const selected = state.run;
const hasRuns = rows.length > 0;
const body = hero({
eyebrow: "Run assurance registry",
title: "Every governed run, one evidence trail.",
description: "Select a lifecycle record to reconstruct H1–H7 decisions, inspect sanitized evidence and export an independent dossier.",
verdict: rows.some((row) => row.status === "failed") ? "attention" : "operational",
meta: [`${state.registry.count} records`, `${rows.filter((row) => row.status === "failed").length} failures`, "Local canonical evidence"],
actions: `<button class="button primary" type="button" data-view-target="h6">Open H6 dossier</button>`,
title: hasRuns ? "Every governed run, one evidence trail." : "No governed runs yet.",
description: hasRuns
? "Select a lifecycle record to reconstruct H1–H7 decisions, inspect sanitized evidence and export an independent dossier."
: "The registry will populate after a configured client finalizes its first governed prompt.",
verdict: hasRuns
? (rows.some((row) => row.status === "failed") ? "attention" : "operational")
: "waiting",
meta: hasRuns
? [`${state.registry.count} records`, `${rows.filter((row) => row.status === "failed").length} failures`, "Local canonical evidence"]
: ["No operational counters yet", "Local canonical evidence"],
actions: hasRuns
? `<button class="button primary" type="button" data-view-target="h6">Open H6 dossier</button>`
: "",
}) + (selected ? renderTrace(selected) : "") + recentRunsPanel(rows);
$("#runs-view").innerHTML = body;
bindActions();
@@ -225,10 +272,33 @@
function renderH6() {
const data = state.h6;
if (!data) return;
const hasRuns = Number(data.summary?.runs) > 0;
const query = data.scope || {};
const queryObject = Object.fromEntries(Object.entries(query).filter(([, value]) => value));
const actions = `<a class="button secondary" href="${downloadUrl("/api/v1/reports/h6/export", { ...queryObject, format: "json" })}">JSON evidence</a>
<a class="button primary" href="${downloadUrl("/api/v1/reports/h6/export", { ...queryObject, format: "html" })}">Export dossier</a>`;
if (!hasRuns) {
const provider = state.project?.readiness?.dimensions?.provider_telemetry || {};
$("#h6-view").innerHTML =
hero({
eyebrow: "H6 · AgentOps assurance",
title: "H6 begins with the first governed run.",
description: "No runtime record exists yet, so CASAN hides failure, latency, token and cost counters instead of presenting fabricated zeroes.",
verdict: "waiting",
meta: [data.report_id, `Generated ${time(data.generated_at)}`, "No operational evidence yet"],
actions: "",
}) +
panel(
"H6 onboarding",
"Provider telemetry is optional.",
provider.summary || "Provider token and cost telemetry has not been observed; this does not block Core.",
`<div class="two-col">
<div class="notice success"><strong>Core remains ready</strong><br>Send a prompt through a configured client. CASAN will persist runtime evidence automatically.</div>
<div class="notice warning"><strong>No automatic export</strong><br>Open H6 or export a dossier only when a review artifact is needed.</div>
</div>`,
);
return;
}
const findings = data.findings.length ? `<div class="findings">${data.findings.map((item, index) => `<article class="finding ${slug(item.severity)}">
<span class="finding-index">${String(index + 1).padStart(2, "0")}</span><div><code>${esc(item.code)}</code><p>${esc(item.message)}</p></div></article>`).join("")}</div>`
: `<div class="notice success">No threshold breach was detected in the selected evidence boundary.</div>`;
@@ -174,6 +174,7 @@ def paths(root: str | Path) -> dict[str, Path]:
state = state_root(root)
return {
"config": Path(root).resolve() / ".casan" / "config.json",
"readiness": Path(root).resolve() / ".casan" / "readiness.json",
"receipt": state / "state" / "latest-run.json",
"metrics": state / "logs" / "cost" / "metrics.jsonl",
"provider": state / "logs" / "level5" / "provider-usage.jsonl",
@@ -185,6 +186,7 @@ def paths(root: str | Path) -> dict[str, Path]:
def project_profile(root: str | Path) -> dict[str, Any]:
config = read_json(paths(root)["config"])
readiness = read_json(paths(root)["readiness"])
maturity = config.get("maturity") if isinstance(config.get("maturity"), dict) else {}
return {
"project_id": _text(config.get("project_id"), Path(root).resolve().name),
@@ -199,6 +201,26 @@ def project_profile(root: str | Path) -> dict[str, Any]:
"status": _text(maturity.get("status"), "not_assessed"),
"evidence": _text(maturity.get("evidence")) or None,
},
"readiness": readiness if readiness else {
"schema_version": 1,
"overall_status": "not_assessed",
"dimensions": {
"core": {
"status": "not_assessed",
"summary": "Run `casan readiness --refresh` to assess this installation.",
"acceptance": [],
},
"domain_pipeline": {
"status": "not_assessed",
"summary": "Domain Pipeline readiness has not been assessed.",
},
"provider_telemetry": {
"status": "not_assessed",
"summary": "Provider Telemetry readiness has not been assessed.",
},
},
"next_actions": [],
},
"viewer": {
"mode": "local_core",
"scope": "single_project",
@@ -0,0 +1,524 @@
#!/usr/bin/env python3
"""CASAN commercial-readiness discovery and assessment.
This module is intentionally stdlib-only. It assesses the CASAN-owned
installation contract without changing application source or inventing domain
traceability. Core, Domain Pipeline, and Provider Telemetry are independent
dimensions because each has a different owner and activation lifecycle.
"""
from __future__ import annotations
from datetime import datetime, timezone
import json
import os
from pathlib import Path
from typing import Any
SCHEMA_VERSION = 1
SKIP_DIRECTORIES = {
".casan", ".git", ".hg", ".idea", ".specify", ".svn", ".venv",
"coverage", "dist", "node_modules", "target", "vendor",
}
PROJECT_MARKERS = (
"package.json", "pyproject.toml", "pom.xml", "build.gradle",
"build.gradle.kts", "go.mod", "Cargo.toml", "composer.json",
)
SOURCE_ROOT_NAMES = ("src", "app", "apps", "packages", "lib", "services")
REQUIREMENT_NAMES = (
"requirements.md", "requirement.md", "srs.md", "spec.md",
"product-requirements.md",
)
def utc_now() -> str:
return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
def read_json(path: Path) -> dict[str, Any]:
try:
value = json.loads(path.read_text(encoding="utf-8"))
return value if isinstance(value, dict) else {}
except (OSError, TypeError, ValueError):
return {}
def write_json(path: Path, value: dict[str, Any]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
temporary = path.with_suffix(path.suffix + ".tmp")
temporary.write_text(
json.dumps(value, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
os.replace(temporary, path)
def configure_domain(root: str | Path, manifest: str | Path) -> dict[str, Any]:
"""Select an existing project manifest without copying or editing app code."""
project_root = Path(root).expanduser().resolve()
selected = Path(manifest).expanduser()
if not selected.is_absolute():
selected = project_root / selected
selected = selected.resolve()
try:
relative = selected.relative_to(project_root).as_posix()
except ValueError as error:
raise ValueError("domain manifest must stay inside the project root") from error
if not selected.is_file():
raise ValueError("domain manifest does not exist")
try:
document = json.loads(selected.read_text(encoding="utf-8"))
except (OSError, TypeError, ValueError) as error:
raise ValueError("domain manifest must be valid JSON") from error
if not isinstance(document, dict) or not (
document.get("project_id") or document.get("schema_version")
):
raise ValueError(
"domain manifest must declare project_id or schema_version")
value = {
"schema_version": SCHEMA_VERSION,
"configured_at": utc_now(),
"manifest": relative,
"ownership": "project_source_referenced_by_casan",
}
write_json(project_root / ".casan" / "domain.json", value)
return value
def _relative(root: Path, path: Path) -> str:
return path.relative_to(root).as_posix()
def _bounded_files(root: Path, maximum_depth: int = 5) -> list[Path]:
files: list[Path] = []
for current, directories, names in os.walk(root):
current_path = Path(current)
try:
depth = len(current_path.relative_to(root).parts)
except ValueError:
continue
directories[:] = sorted(
name for name in directories
if name not in SKIP_DIRECTORIES and not name.startswith(".cache")
)
if depth >= maximum_depth:
directories[:] = []
for name in sorted(names):
files.append(current_path / name)
if len(files) >= 10_000:
return files
return files
def discover(root: str | Path) -> dict[str, Any]:
project_root = Path(root).expanduser().resolve()
priority_files: list[Path] = []
for relative in ("apps", "packages", "docs"):
candidate = project_root / relative
if candidate.is_dir():
priority_files.extend(_bounded_files(candidate, maximum_depth=6))
files = list(dict.fromkeys(
priority_files + _bounded_files(project_root)
))
markers = [
name for name in PROJECT_MARKERS
if (project_root / name).is_file()
]
source_roots = [
name for name in SOURCE_ROOT_NAMES
if (project_root / name).is_dir()
]
domain_packs: list[str] = []
manifests: list[str] = []
requirements: list[str] = []
for path in files:
relative = _relative(project_root, path)
lowered = path.name.lower()
if lowered == "domain-pack.yaml":
domain_packs.append(relative)
elif lowered == "project.manifest.json":
manifests.append(relative)
elif (
lowered in REQUIREMENT_NAMES
or ("requirement" in lowered and lowered.endswith(".md"))
):
requirements.append(relative)
return {
"schema_version": SCHEMA_VERSION,
"generated_at": utc_now(),
"project_root": str(project_root),
"project_markers": markers[:32],
"source_roots": source_roots[:32],
"domain_pack_candidates": domain_packs[:64],
"project_manifest_candidates": manifests[:64],
"requirements_candidates": requirements[:64],
"scan": {
"maximum_depth": 5,
"maximum_files_per_root": 10_000,
"priority_roots": [
value for value in ("apps", "packages", "docs")
if (project_root / value).is_dir()
],
"files_observed": len(files),
"truncated": len(files) >= 10_000,
},
}
def _contains(path: Path, *needles: str) -> bool:
try:
if not path.is_file() or path.stat().st_size > 2 * 1024 * 1024:
return False
content = path.read_text(encoding="utf-8", errors="replace")
return all(needle in content for needle in needles)
except OSError:
return False
def _runtime_harness(root: Path, config: dict[str, Any]) -> Path | None:
runtime_path = config.get("runtime_path")
if not isinstance(runtime_path, str) or not runtime_path.strip():
return None
base = Path(runtime_path).expanduser()
if not base.is_absolute():
base = root / base
candidate = base.resolve()
nested = candidate / "packages" / "casan-harness"
if nested.is_dir():
return nested
return candidate if candidate.name == "casan-harness" and candidate.is_dir() else None
def _check(
gate: str,
title: str,
status: str,
summary: str,
evidence: list[str] | None = None,
) -> dict[str, Any]:
return {
"gate": gate,
"title": title,
"status": status,
"summary": summary,
"evidence": evidence or [],
}
def _provider_available(root: Path) -> bool:
candidates = (
root / ".specify" / "logs" / "level5" / "provider-usage.jsonl",
root / ".specify" / "logs" / "cost" / "metrics.jsonl",
)
for path in candidates:
try:
if path.is_file() and path.stat().st_size > 2:
text = path.read_text(encoding="utf-8", errors="replace")[-262_144:]
for line in reversed(text.splitlines()):
try:
row = json.loads(line)
except (TypeError, ValueError):
continue
if not isinstance(row, dict):
continue
if (
isinstance(row.get("provider"), str) and row["provider"].strip()
or isinstance(row.get("model"), str) and row["model"].strip()
or isinstance(row.get("total_tokens"), (int, float))
or isinstance(row.get("cost_estimate"), (int, float))
):
return True
except OSError:
continue
return False
def assess(
root: str | Path,
*,
persist: bool = False,
client_state: dict[str, Any] | None = None,
) -> dict[str, Any]:
project_root = Path(root).expanduser().resolve()
config_path = project_root / ".casan" / "config.json"
lock_path = project_root / ".casan" / "version.lock"
bootstrap_path = project_root / ".casan" / "casan-hook.py"
config = read_json(config_path)
lock = read_json(lock_path)
domain_config = read_json(project_root / ".casan" / "domain.json")
previous_readiness = read_json(project_root / ".casan" / "readiness.json")
if client_state is None:
previous_core = (
(previous_readiness.get("dimensions") or {}).get("core") or {}
)
previous_client_state = previous_core.get("client_activation")
client_state = (
previous_client_state
if isinstance(previous_client_state, dict) else {}
)
discovery = discover(project_root)
clients = [
str(value) for value in config.get("clients", [])
if isinstance(value, str)
]
runtime = _runtime_harness(project_root, config)
h1_ok = bool(config.get("project_id") and config.get("schema_version"))
h2_evidence: list[str] = []
client_integrations: list[dict[str, Any]] = []
for client in clients:
declared_state = (client_state or {}).get(client) or {}
if client == "claude":
relative = ".claude/settings.json"
present = _contains(
project_root / relative, "casan-hook.py", "--client claude")
operational = bool(
present and declared_state.get("operational", True))
elif client == "codex":
relative = ".codex/hooks.json"
present = _contains(
project_root / relative, "casan-hook.py", "--client codex")
trust_state = declared_state
operational = bool(
present and trust_state.get("verified")
and trust_state.get("operational")
)
elif client == "vscode-copilot":
relative = ".casan/casan-hook.py"
present = (project_root / relative).is_file()
operational = bool(
present and declared_state.get("operational", True))
else:
client_integrations.append({
"client": client,
"status": "unsupported",
"evidence": None,
})
continue
if present:
h2_evidence.append(relative)
client_integrations.append({
"client": client,
"status": (
"operational" if operational else
"action_required" if present else
"missing"
),
"evidence": relative if present else None,
})
operational_clients = sum(
item["status"] == "operational" for item in client_integrations)
configured_clients = sum(
item["status"] in {"operational", "action_required"}
for item in client_integrations)
h2_status = (
"warning" if operational_clients and operational_clients < len(client_integrations) else
"pass" if operational_clients else
"action_required" if configured_clients else
"fail"
)
report_engine = (
runtime / "scripts" / "python" / "local_report.py"
if runtime else Path()
)
h3_ok = bool(runtime and report_engine.is_file())
enforcement = str(config.get("enforcement_mode") or "unknown")
h4_status = "pass" if enforcement == "enforce" else "warning"
state_dirs = (
project_root / ".specify" / "logs",
project_root / ".specify" / "state",
)
h5_ok = all(
path.is_dir() and os.access(path, os.W_OK | os.X_OK)
for path in state_dirs
)
provider_available = _provider_available(project_root)
h7_ok = bool(
lock.get("harness_hash")
and lock.get("harness_version")
and bootstrap_path.is_file()
)
trust = (client_state or {}).get("codex") or {}
codex_action_required = (
"codex" in clients
and not (trust.get("verified") and trust.get("operational"))
)
checks = [
_check("H1", "Project contract", "pass" if h1_ok else "fail",
"CASAN project identity and schema are valid." if h1_ok
else "CASAN project configuration is missing or invalid.",
[".casan/config.json"] if h1_ok else []),
_check("H2", "Client activation", h2_status,
f"{operational_clients} configured client route(s) are operational."
if h2_status in {"pass", "warning"} else
"Configured client hooks require an activation step."
if h2_status == "action_required" else
"No complete supported client integration is installed.",
h2_evidence),
_check("H3", "Local report capability", "pass" if h3_ok else "fail",
"Core Local Assurance Viewer and on-demand export are available."
if h3_ok else "The resolved Core report runtime is unavailable.",
[str(report_engine)] if h3_ok else []),
_check("H4", "Enforcement boundary", h4_status,
"Policy enforcement is active." if enforcement == "enforce"
else "Observe mode records evidence but does not enforce decisions.",
[".casan/config.json"]),
_check("H5", "Evidence persistence", "pass" if h5_ok else "fail",
"Local logs and state directories are writable." if h5_ok
else "CASAN evidence directories are missing or not writable.",
[".specify/logs", ".specify/state"] if h5_ok else []),
_check("H6", "Provider telemetry", "pass" if provider_available else "optional_unavailable",
"Provider token/cost telemetry is available." if provider_available
else "Core runtime evidence is available; provider token/cost telemetry is optional and not observed yet."),
_check("H7", "Pinned runtime", "pass" if h7_ok else "fail",
"The project bootstrap and pinned runtime contract are present."
if h7_ok else "The bootstrap or version pin is incomplete.",
[".casan/casan-hook.py", ".casan/version.lock"] if h7_ok else []),
]
blocking = any(item["status"] == "fail" for item in checks)
action_required = any(
item["status"] == "action_required" for item in checks
)
attention = any(item["status"] == "warning" for item in checks)
core_status = (
"blocked" if blocking else
"action_required" if action_required else
"ready_with_attention" if attention else
"ready"
)
manifests = discovery["project_manifest_candidates"]
packs = discovery["domain_pack_candidates"]
selected_manifest = domain_config.get("manifest")
selected_path = (
project_root / selected_manifest
if isinstance(selected_manifest, str) else None
)
if selected_path and selected_path.is_file():
domain_status = "configured"
domain_summary = (
"A project manifest is selected. Run project validation or the "
"project-owned pipeline to produce domain acceptance evidence."
)
elif manifests:
domain_status = "discovered"
domain_summary = (
"Project manifest candidates were discovered but none is selected. "
"Use `casan domain configure <manifest>` when domain acceptance is needed."
)
elif packs:
domain_status = "discovered"
domain_summary = (
"A Domain Pack was discovered, but no project manifest proves a "
"runnable domain pipeline."
)
else:
domain_status = "not_configured"
domain_summary = (
"No Domain Pack is configured. Core prompt assurance and reports "
"remain fully available without modifying application source."
)
telemetry_status = "available" if provider_available else "optional_unavailable"
next_actions: list[dict[str, str]] = []
if blocking:
next_actions.append({
"code": "repair_core",
"message": "Run `casan init` again, then `casan doctor`.",
})
if codex_action_required:
reason = str(trust.get("reason") or trust.get("status") or "not_verified")
next_actions.append({
"code": "verify_codex_trust",
"message": (
"Open `/hooks` in Codex and review the project hooks, then run "
f"`casan doctor` (current trust state: {reason})."
),
})
for integration in client_integrations:
if (
integration["status"] != "operational"
and integration["client"] != "codex"
):
next_actions.append({
"code": "activate_%s" % integration["client"].replace("-", "_"),
"message": (
f"Complete activation for the selected "
f"{integration['client']} client route, then run `casan doctor`."
),
})
optional_actions: list[dict[str, str]] = []
if domain_status != "configured":
optional_actions.append({
"code": "configure_domain_pipeline",
"message": (
"Optional: install/configure a CASAN Domain Pack when the team "
"needs project-specific SRS→test acceptance."
),
})
readiness = {
"schema_version": SCHEMA_VERSION,
"generated_at": utc_now(),
"project_id": str(config.get("project_id") or project_root.name),
"overall_status": core_status,
"dimensions": {
"core": {
"status": core_status,
"summary": (
"Core is ready for governed prompts and visual local reports."
if core_status == "ready" else
"Core is operational through at least one client route; another selected route needs attention."
if core_status == "ready_with_attention" else
"Core requires an explicit activation step."
if core_status == "action_required" else
"Core installation is incomplete."
),
"acceptance": checks,
"client_activation": client_state or {},
"client_integrations": client_integrations,
},
"domain_pipeline": {
"status": domain_status,
"summary": domain_summary,
"domain_packs": packs,
"project_manifests": manifests,
"selected_manifest": selected_manifest
if selected_path and selected_path.is_file() else None,
},
"provider_telemetry": {
"status": telemetry_status,
"summary": (
"Provider token and cost telemetry has been observed."
if provider_available else
"Provider token and cost telemetry is optional and has not "
"been observed; this does not block Core."
),
},
},
"next_actions": next_actions,
"optional_actions": optional_actions,
"discovery_file": ".casan/discovery.json",
}
if persist:
write_json(project_root / ".casan" / "discovery.json", discovery)
write_json(project_root / ".casan" / "readiness.json", readiness)
return readiness
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--root", default=os.getcwd())
parser.add_argument("--write", action="store_true")
arguments = parser.parse_args()
print(json.dumps(
assess(arguments.root, persist=arguments.write),
ensure_ascii=False,
indent=2,
))
@@ -24,6 +24,11 @@ from local_report import (
report_html,
run_report,
)
from readiness import (
assess as assess_readiness,
configure_domain,
discover as discover_project,
)
def find_root(start: str) -> Path:
@@ -302,6 +307,21 @@ def parser() -> argparse.ArgumentParser:
latest = commands.add_parser("latest", help="show the latest finalized prompt receipt")
latest.add_argument("--json", action="store_true")
readiness = commands.add_parser(
"readiness",
help="assess Core, Domain Pipeline, and Provider Telemetry separately",
)
readiness.add_argument("--refresh", action="store_true")
readiness.add_argument("--json", action="store_true")
domain = commands.add_parser(
"domain",
help="discover or select an optional project-owned Domain Pack manifest",
)
domain.add_argument("action", choices=["status", "discover", "configure"])
domain.add_argument("manifest", nargs="?")
domain.add_argument("--json", action="store_true")
view = commands.add_parser("view", help="open a trace in the local Core viewer")
view.add_argument("trace_id", nargs="?")
view.add_argument("--no-open", action="store_true")
@@ -333,6 +353,51 @@ def main(argv=None) -> int:
print(json.dumps(receipt, ensure_ascii=False, indent=2))
return 0 if receipt else 1
return print_receipt(receipt, root)
if args.command == "readiness":
result = assess_readiness(root, persist=args.refresh)
if args.json:
print(json.dumps(result, ensure_ascii=False, indent=2))
else:
dimensions = result["dimensions"]
print(f"CASAN readiness — {result['project_id']}")
print(f" Core {dimensions['core']['status']}")
print(f" Domain Pipeline {dimensions['domain_pipeline']['status']}")
print(f" Provider Telemetry {dimensions['provider_telemetry']['status']}")
for action in result.get("next_actions", []):
print(f" → {action['message']}")
return 2 if result["overall_status"] == "blocked" else 0
if args.command == "domain":
try:
if args.action == "discover":
payload = discover_project(root)
elif args.action == "configure":
if not args.manifest:
raise ValueError(
"`casan domain configure` requires a manifest path")
configure_domain(root, args.manifest)
payload = assess_readiness(root, persist=True)
else:
payload = assess_readiness(root, persist=False)[
"dimensions"]["domain_pipeline"]
except ValueError as error:
print(f"CASAN_DOMAIN_INVALID — {error}", file=sys.stderr)
return 2
if args.json:
print(json.dumps(payload, ensure_ascii=False, indent=2))
elif args.action == "discover":
print("CASAN Domain discovery")
print(
f" Manifests {len(payload['project_manifest_candidates'])}")
print(f" Domain Packs {len(payload['domain_pack_candidates'])}")
print(f" Requirements {len(payload['requirements_candidates'])}")
else:
dimension = (
payload["dimensions"]["domain_pipeline"]
if "dimensions" in payload else payload
)
print(f"CASAN Domain Pipeline — {dimension['status']}")
print(f" {dimension['summary']}")
return 0
if args.command == "viewer":
return viewer_command(root, args.action, args.port, args.no_open)
if args.command == "export":
@@ -27,6 +27,7 @@ CASAN_BIN="$BUNDLE/bin/casan"
test -f "$BUNDLE/packages/casan-harness/assets/local-viewer/index.html"
test -f "$BUNDLE/packages/casan-harness/scripts/python/local_report.py"
test -f "$BUNDLE/packages/casan-harness/scripts/python/local_viewer.py"
test -f "$BUNDLE/packages/casan-harness/scripts/python/readiness.py"
test ! -e "$BUNDLE/packages/casan-control-panel"
test ! -e "$BUNDLE/package.json"
@@ -37,18 +38,26 @@ mkdir -p \
"$PROJECT/.specify/logs/trace-events" \
"$PROJECT/.specify/logs/cost"
python3 - "$PROJECT" <<'PY'
python3 - "$PROJECT" "$BUNDLE" <<'PY'
import json
from pathlib import Path
import sys
root = Path(sys.argv[1])
bundle = Path(sys.argv[2])
(root / ".casan/config.json").write_text(json.dumps({
"schema_version": "21.3",
"project_id": "artifact-project",
"project_name": "Artifact Project",
"edition": "core",
"enforcement_mode": "enforce",
"runtime_path": str(bundle),
"clients": ["vscode-copilot"],
}), encoding="utf-8")
(root / ".casan/casan-hook.py").write_text("# casan-hook.py\n", encoding="utf-8")
(root / ".casan/version.lock").write_text(json.dumps({
"harness_version": "artifact",
"harness_hash": "artifact-hash",
}), encoding="utf-8")
trace_id = "artifact-run-1"
trace = {
@@ -100,6 +109,7 @@ PY
(
cd "$PROJECT"
CASAN_APP_ROOT="$PROJECT" "$CASAN_BIN" readiness --refresh --json > "$WORK/readiness.json"
CASAN_APP_ROOT="$PROJECT" "$CASAN_BIN" report latest | grep -q "view_command=casan view artifact-run-1"
CASAN_APP_ROOT="$PROJECT" "$CASAN_BIN" report export artifact-run-1 \
--format html --output "$WORK/run.html" >/dev/null
@@ -118,6 +128,13 @@ assert d["summary"]["runs"] == 1
assert d["summary"]["tokens"]["total"] is None
assert d["summary"]["cost_usd"]["estimated"] is None
PY
python3 - "$WORK/readiness.json" <<'PY'
import json, sys
d = json.load(open(sys.argv[1], encoding="utf-8"))
assert d["dimensions"]["core"]["status"] == "ready"
assert d["dimensions"]["domain_pipeline"]["status"] == "not_configured"
assert d["dimensions"]["provider_telemetry"]["status"] == "optional_unavailable"
PY
URL="$(CASAN_APP_ROOT="$PROJECT" "$CASAN_BIN" view artifact-run-1 --no-open)"
VIEWER_STARTED=1
@@ -30,6 +30,16 @@ class LocalReportTests(unittest.TestCase):
"enforcement_mode": "enforce",
"maturity": {"level": 4, "status": "assessed"},
}), encoding="utf-8")
(self.root / ".casan" / "readiness.json").write_text(json.dumps({
"schema_version": 1,
"overall_status": "ready",
"dimensions": {
"core": {"status": "ready", "acceptance": []},
"domain_pipeline": {"status": "not_configured"},
"provider_telemetry": {"status": "optional_unavailable"},
},
"next_actions": [],
}), encoding="utf-8")
for path in (
".specify/state",
".specify/logs/trace",
@@ -92,6 +102,8 @@ class LocalReportTests(unittest.TestCase):
self.assertEqual(h6["summary"]["runs"], 0)
self.assertIsNone(h6["summary"]["tokens"]["total"])
self.assertTrue(any(item["code"] == "METRICS_MISSING" for item in h6["findings"]))
profile = REPORT.project_profile(self.root)
self.assertEqual(profile["readiness"]["dimensions"]["core"]["status"], "ready")
def test_run_reconstructs_all_gates_and_redacts_sensitive_fields(self):
self.write_run()
@@ -160,6 +172,10 @@ class LocalReportTests(unittest.TestCase):
self.assertIn('tabindex="-1"', script)
self.assertIn('dossier.focus({ preventScroll: true })', script)
self.assertIn("dossier.scrollIntoView({", script)
self.assertIn("No operational KPIs are shown until evidence exists.", script)
self.assertIn("H6 begins with the first governed run.", script)
self.assertIn("No operational counters yet", script)
self.assertNotIn('"Ready for the first governed run."', script)
if __name__ == "__main__":
@@ -0,0 +1,161 @@
#!/usr/bin/env python3
"""Acceptance tests for CASAN's three-dimensional readiness contract."""
from __future__ import annotations
import importlib.util
import json
from pathlib import Path
import tempfile
import unittest
MODULE = Path(__file__).resolve().parents[1] / "scripts" / "python" / "readiness.py"
SPEC = importlib.util.spec_from_file_location("casan_readiness", MODULE)
READINESS = importlib.util.module_from_spec(SPEC)
assert SPEC.loader
SPEC.loader.exec_module(READINESS)
class ReadinessTests(unittest.TestCase):
def setUp(self):
self.temporary = tempfile.TemporaryDirectory()
self.root = Path(self.temporary.name)
self.runtime = self.root / "runtime"
report = (
self.runtime / "packages" / "casan-harness" / "scripts" /
"python" / "local_report.py"
)
report.parent.mkdir(parents=True)
report.write_text("# report engine\n", encoding="utf-8")
(self.root / ".casan").mkdir()
(self.root / ".casan" / "casan-hook.py").write_text(
"# bootstrap\n", encoding="utf-8")
(self.root / ".casan" / "config.json").write_text(json.dumps({
"schema_version": "21.3",
"project_id": "commercial-core",
"edition": "core",
"enforcement_mode": "enforce",
"runtime_path": str(self.runtime),
"clients": ["codex"],
}), encoding="utf-8")
(self.root / ".casan" / "version.lock").write_text(json.dumps({
"harness_version": "1.0.5",
"harness_hash": "abc123",
}), encoding="utf-8")
(self.root / ".codex").mkdir()
(self.root / ".codex" / "hooks.json").write_text(
'{"hooks":{"SessionStart":[{"hooks":[{"command":"python3 .casan/casan-hook.py --client codex"}]}]}}',
encoding="utf-8",
)
(self.root / ".specify" / "logs").mkdir(parents=True)
(self.root / ".specify" / "state").mkdir(parents=True)
(self.root / "package.json").write_text(
'{"name":"commercial-core"}', encoding="utf-8")
def tearDown(self):
self.temporary.cleanup()
def test_core_is_ready_without_a_domain_pack_or_provider_telemetry(self):
result = READINESS.assess(self.root, client_state={
"codex": {
"verified": True,
"operational": True,
"status": "trusted",
},
})
dimensions = result["dimensions"]
self.assertEqual(result["overall_status"], "ready")
self.assertEqual(dimensions["core"]["status"], "ready")
self.assertEqual(
dimensions["domain_pipeline"]["status"], "not_configured")
self.assertEqual(
dimensions["provider_telemetry"]["status"],
"optional_unavailable",
)
self.assertEqual(
dimensions["core"]["acceptance"][5]["status"],
"optional_unavailable",
)
def test_unverified_codex_trust_is_an_action_not_a_fake_ready_state(self):
result = READINESS.assess(self.root, client_state={
"codex": {
"verified": True,
"operational": False,
"status": "modified",
},
})
self.assertEqual(result["overall_status"], "action_required")
self.assertEqual(
result["dimensions"]["core"]["acceptance"][1]["status"],
"action_required",
)
self.assertTrue(any(
action["code"] == "verify_codex_trust"
for action in result["next_actions"]
))
def test_one_healthy_route_keeps_core_operational_with_attention(self):
config_path = self.root / ".casan" / "config.json"
config = json.loads(config_path.read_text(encoding="utf-8"))
config["clients"] = ["codex", "vscode-copilot"]
config_path.write_text(json.dumps(config), encoding="utf-8")
result = READINESS.assess(self.root, client_state={
"codex": {
"verified": True,
"operational": True,
"status": "trusted",
},
"vscode-copilot": {"operational": False},
})
self.assertEqual(result["overall_status"], "ready_with_attention")
self.assertEqual(
result["dimensions"]["core"]["acceptance"][1]["status"],
"warning",
)
self.assertTrue(any(
action["code"] == "activate_vscode_copilot"
for action in result["next_actions"]
))
def test_discovery_is_casan_owned_and_never_creates_domain_inputs(self):
result = READINESS.assess(self.root, persist=True, client_state={
"codex": {
"verified": True,
"operational": True,
"status": "trusted",
},
})
self.assertEqual(result["dimensions"]["domain_pipeline"]["domain_packs"], [])
self.assertTrue((self.root / ".casan" / "discovery.json").is_file())
self.assertTrue((self.root / ".casan" / "readiness.json").is_file())
self.assertFalse((self.root / "apps").exists())
def test_domain_configuration_references_existing_project_source_only(self):
manifest = self.root / "docs" / "project.manifest.json"
manifest.parent.mkdir()
manifest.write_text(json.dumps({
"schema_version": 1,
"project_id": "commercial-core",
}), encoding="utf-8")
configured = READINESS.configure_domain(
self.root, "docs/project.manifest.json")
self.assertEqual(configured["manifest"], "docs/project.manifest.json")
result = READINESS.assess(self.root, client_state={
"codex": {
"verified": True,
"operational": True,
"status": "trusted",
},
})
self.assertEqual(
result["dimensions"]["domain_pipeline"]["status"], "configured")
self.assertEqual(
result["dimensions"]["domain_pipeline"]["selected_manifest"],
"docs/project.manifest.json",
)
if __name__ == "__main__":
unittest.main()