From 6c30fcfdd5417f899c894aa6baee053c2062b57f Mon Sep 17 00:00:00 2001
From: "Yifeng[Terry] Yu" <125581657+xiaojiou176@users.noreply.github.com>
Date: Tue, 7 Apr 2026 00:44:59 -0700
Subject: [PATCH 1/5] fix(runtime): stabilize search pipeline success path
---
.../src/cortexpilot_orch/planning/intake.py | 1 +
.../scheduler/execute_task_pipeline.py | 6 +
.../scheduler/tool_execution_pipeline.py | 18 +++
.../tests/test_intake_branch_matrix_extra.py | 2 +-
.../tests/test_intake_helpers_coverage.py | 4 +-
.../tests/test_news_digest_template.py | 4 +
.../tests/test_scheduler_helpers.py | 46 ++++++
.../tests/test_search_web_strict.py | 143 +++++++++++++++++-
.../tests/test_tool_pipeline_integration.py | 61 ++++++++
tooling/search/search_engine.py | 35 ++++-
tooling/search_pipeline.py | 14 +-
11 files changed, 311 insertions(+), 23 deletions(-)
diff --git a/apps/orchestrator/src/cortexpilot_orch/planning/intake.py b/apps/orchestrator/src/cortexpilot_orch/planning/intake.py
index 2e2a523..58c400e 100644
--- a/apps/orchestrator/src/cortexpilot_orch/planning/intake.py
+++ b/apps/orchestrator/src/cortexpilot_orch/planning/intake.py
@@ -515,6 +515,7 @@ def _apply_intake_contract_overrides(
if "search" not in normalized_contract_mcp_tools:
normalized_contract_mcp_tools.append("search")
contract["mcp_tool_set"] = normalized_contract_mcp_tools
+ contract.pop("handoff_chain", None)
return sync_role_contract(contract)
diff --git a/apps/orchestrator/src/cortexpilot_orch/scheduler/execute_task_pipeline.py b/apps/orchestrator/src/cortexpilot_orch/scheduler/execute_task_pipeline.py
index baeac10..42588f5 100644
--- a/apps/orchestrator/src/cortexpilot_orch/scheduler/execute_task_pipeline.py
+++ b/apps/orchestrator/src/cortexpilot_orch/scheduler/execute_task_pipeline.py
@@ -438,6 +438,12 @@ def run_execution_pipeline(
if state.failure_reason:
return state
+ assigned_role = agent_role_fn(assigned_agent)
+ if assigned_role in {"SEARCHER", "RESEARCHER"} and state.search_request is not None:
+ state.runner_summary = "Search pipeline completed successfully."
+ state.status = "SUCCESS"
+ return state
+
task_execution_pipeline_module.snapshot_worktree = snapshot_worktree_fn
task_execution_pipeline_module.validate_reviewer_isolation = validate_reviewer_isolation_fn
task_execution_pipeline_module._collect_diff_text = collect_diff_text_fn
diff --git a/apps/orchestrator/src/cortexpilot_orch/scheduler/tool_execution_pipeline.py b/apps/orchestrator/src/cortexpilot_orch/scheduler/tool_execution_pipeline.py
index e1693a2..4914e16 100644
--- a/apps/orchestrator/src/cortexpilot_orch/scheduler/tool_execution_pipeline.py
+++ b/apps/orchestrator/src/cortexpilot_orch/scheduler/tool_execution_pipeline.py
@@ -145,6 +145,13 @@ def _write_public_task_result(
)
+def _profile_mode_from_policy(policy: dict[str, Any] | None) -> str:
+ if not isinstance(policy, dict):
+ return ""
+ value = str(policy.get("profile_mode") or "").strip().lower()
+ return value
+
+
def run_search_pipeline(
run_id: str,
tool_runner: ToolRunner,
@@ -203,6 +210,17 @@ def _normalize_provider(raw: Any) -> str:
verify_repeat = 1
request_policy = request.get("browser_policy") if isinstance(request.get("browser_policy"), dict) else None
+ effective_profile_mode = (
+ _profile_mode_from_policy(request_policy)
+ or _profile_mode_from_policy(contract_policy)
+ )
+ if effective_profile_mode == "allow_profile" and parallel > 1:
+ policy_adjustments["parallel"] = {
+ "from": parallel,
+ "to": 1,
+ "reason": "allow_profile browser sessions are serialized to avoid shared-context flake",
+ }
+ parallel = 1
tasks: list[tuple[str, str, dict[str, Any] | None, str]] = []
task_index = 0
diff --git a/apps/orchestrator/tests/test_intake_branch_matrix_extra.py b/apps/orchestrator/tests/test_intake_branch_matrix_extra.py
index fb6c1e0..8e52e3c 100644
--- a/apps/orchestrator/tests/test_intake_branch_matrix_extra.py
+++ b/apps/orchestrator/tests/test_intake_branch_matrix_extra.py
@@ -205,7 +205,7 @@ def test_intake_service_build_contract_artifacts_non_list(tmp_path: Path, monkey
contract = service.build_contract(intake_id)
assert isinstance(contract, dict)
- assert contract.get("handoff_chain", {}).get("enabled") is True
+ assert "handoff_chain" not in contract
artifacts = contract.get("inputs", {}).get("artifacts", [])
assert isinstance(artifacts, list)
assert any(item.get("name") == "search_requests.json" for item in artifacts)
diff --git a/apps/orchestrator/tests/test_intake_helpers_coverage.py b/apps/orchestrator/tests/test_intake_helpers_coverage.py
index 160f9d7..75b6fc7 100644
--- a/apps/orchestrator/tests/test_intake_helpers_coverage.py
+++ b/apps/orchestrator/tests/test_intake_helpers_coverage.py
@@ -426,13 +426,13 @@ def test_intake_service_answer_and_build_contract(monkeypatch, tmp_path: Path) -
assert any(item.get("name") == "search_requests.json" for item in artifacts)
assert contract["tool_permissions"]["mcp_tools"] == ["codex", "search"]
- # PM owner should force handoff chain in compiled contract.
+ # Search-style compiled contracts should stay direct instead of carrying a PM handoff chain.
response_path = service._store._intake_dir(intake_id) / "response.json"
response = json.loads(response_path.read_text(encoding="utf-8"))
response["plan"]["owner_agent"] = {"role": "PM", "agent_id": "agent-1"}
response_path.write_text(json.dumps(response, ensure_ascii=False, indent=2), encoding="utf-8")
contract_pm = service.build_contract(intake_id)
- assert contract_pm.get("handoff_chain", {}).get("enabled") is True
+ assert "handoff_chain" not in contract_pm
def test_intake_service_auto_run_chain_restores_runner_env(monkeypatch, tmp_path: Path) -> None:
diff --git a/apps/orchestrator/tests/test_news_digest_template.py b/apps/orchestrator/tests/test_news_digest_template.py
index c0776a0..b236c34 100644
--- a/apps/orchestrator/tests/test_news_digest_template.py
+++ b/apps/orchestrator/tests/test_news_digest_template.py
@@ -59,6 +59,7 @@ def test_news_digest_intake_builds_contract_artifact(monkeypatch, tmp_path: Path
assert contract["template_payload"]["sources"] == ["theverge.com", "techcrunch.com"]
assert contract["owner_agent"]["role"] == "TECH_LEAD"
assert contract["assigned_agent"]["role"] == "SEARCHER"
+ assert "handoff_chain" not in contract
assert contract["tool_permissions"]["network"] == "allow"
assert "search" in contract["tool_permissions"]["mcp_tools"]
assert "search" in contract["mcp_tool_set"]
@@ -95,6 +96,8 @@ def test_news_digest_result_builder_and_search_payload() -> None:
assert digest["status"] == "SUCCESS"
assert digest["topic"] == "Seattle AI"
assert len(digest["sources"]) == 2
+ assert digest["summary"].startswith("Collected 2 public-source result(s)")
+ assert "Title A, Title B." in digest["summary"]
payload = search_payload_helpers.build_search_payload(
"run-news",
@@ -331,4 +334,5 @@ def run_search(self, query: str, provider: str | None = None, browser_policy=Non
assert digest_path.exists()
digest_payload = json.loads(digest_path.read_text(encoding="utf-8"))
assert digest_payload["status"] == "FAILED"
+ assert digest_payload["summary"].startswith("The news digest for 'Seattle AI' did not complete successfully.")
assert "来源链路失败" in digest_payload["failure_reason_zh"]
diff --git a/apps/orchestrator/tests/test_scheduler_helpers.py b/apps/orchestrator/tests/test_scheduler_helpers.py
index 8ca3727..385472d 100644
--- a/apps/orchestrator/tests/test_scheduler_helpers.py
+++ b/apps/orchestrator/tests/test_scheduler_helpers.py
@@ -1,4 +1,5 @@
import json
+import threading
import time
from pathlib import Path
@@ -296,6 +297,51 @@ def run_search(self, query: str, provider: str = "chatgpt_web", browser_policy=N
assert verify_failing.get("verify_failures")
+def test_run_search_pipeline_serializes_allow_profile_browser_sessions(tmp_path: Path, monkeypatch) -> None:
+ runs_root = tmp_path / "runs"
+ monkeypatch.setenv("CORTEXPILOT_RUNS_ROOT", str(runs_root))
+ store = RunStore(runs_root=runs_root)
+ run_id = store.create_run("task_search_serialized")
+
+ lock = threading.Lock()
+ active_calls = 0
+ max_concurrent = 0
+
+ class DummyToolRunner:
+ def run_search(self, query: str, provider: str = "chatgpt_web", browser_policy=None, policy_audit=None):
+ nonlocal active_calls, max_concurrent
+ del query, browser_policy, policy_audit
+ with lock:
+ active_calls += 1
+ max_concurrent = max(max_concurrent, active_calls)
+ time.sleep(0.01)
+ with lock:
+ active_calls -= 1
+ return {
+ "ok": True,
+ "provider": provider,
+ "results": [{"href": f"https://{provider}.example.com/result"}],
+ "verification": {"consistent": True},
+ }
+
+ request = {
+ "queries": ["alpha"],
+ "repeat": 2,
+ "parallel": 4,
+ "providers": ["chatgpt_web", "grok_web"],
+ "verify": {"providers": ["chatgpt_web"], "repeat": 1},
+ "browser_policy": {"profile_mode": "allow_profile"},
+ }
+ result = sched._run_search_pipeline(
+ run_id,
+ DummyToolRunner(),
+ request,
+ {"role": "SEARCHER", "agent_id": "agent-1"},
+ )
+ assert result["ok"] is True
+ assert max_concurrent == 1
+
+
def test_orchestrator_replay_error_paths(tmp_path: Path, monkeypatch) -> None:
repo_root = tmp_path / "repo"
repo_root.mkdir()
diff --git a/apps/orchestrator/tests/test_search_web_strict.py b/apps/orchestrator/tests/test_search_web_strict.py
index e16458d..f3e386b 100644
--- a/apps/orchestrator/tests/test_search_web_strict.py
+++ b/apps/orchestrator/tests/test_search_web_strict.py
@@ -3,6 +3,7 @@
from urllib.parse import urlparse
from tooling.search.search_engine import (
+ _activate_chat_input,
_browser_search,
_pick_chat_input_locator,
_url_allowed,
@@ -71,7 +72,7 @@ def content(self) -> str:
assert Path(artifacts["html"]).exists()
-def test_pick_chat_input_locator_prefers_standard_inputs() -> None:
+def test_pick_chat_input_locator_prefers_provider_specific_editors_before_generic_inputs() -> None:
class DummyLocator:
def __init__(self, name: str, count: int) -> None:
self.name = name
@@ -84,12 +85,14 @@ def count(self) -> int:
class DummyPage:
def __init__(self) -> None:
self.locators = {
- "textarea": DummyLocator("textarea", 1),
- "input[type='text']": DummyLocator("input", 1),
+ "[data-placeholder='Ask anything'][contenteditable='true']": DummyLocator("grok-placeholder", 1),
+ ".tiptap.ProseMirror[contenteditable='true']": DummyLocator("grok-tiptap", 1),
+ "[aria-label='Enter a prompt for Gemini'][contenteditable='true']": DummyLocator("aria", 1),
"[role='textbox'][contenteditable='true']": DummyLocator("textbox", 1),
"[contenteditable='true'][role='textbox']": DummyLocator("textbox-reversed", 1),
- "[aria-label='Enter a prompt for Gemini'][contenteditable='true']": DummyLocator("aria", 1),
".ql-editor[contenteditable='true']": DummyLocator("ql-editor", 1),
+ "textarea": DummyLocator("textarea", 1),
+ "input[type='text']": DummyLocator("input", 1),
}
def locator(self, selector: str) -> DummyLocator:
@@ -97,7 +100,7 @@ def locator(self, selector: str) -> DummyLocator:
locator = _pick_chat_input_locator(DummyPage())
assert locator is not None
- assert locator.name == "textarea"
+ assert locator.name == "grok-placeholder"
def test_pick_chat_input_locator_falls_back_to_contenteditable_textbox() -> None:
@@ -113,12 +116,14 @@ def count(self) -> int:
class DummyPage:
def __init__(self) -> None:
self.locators = {
- "textarea": DummyLocator("textarea", 0),
- "input[type='text']": DummyLocator("input", 0),
+ "[data-placeholder='Ask anything'][contenteditable='true']": DummyLocator("grok-placeholder", 0),
+ ".tiptap.ProseMirror[contenteditable='true']": DummyLocator("grok-tiptap", 0),
+ "[aria-label='Enter a prompt for Gemini'][contenteditable='true']": DummyLocator("aria", 0),
"[role='textbox'][contenteditable='true']": DummyLocator("textbox", 1),
"[contenteditable='true'][role='textbox']": DummyLocator("textbox-reversed", 0),
- "[aria-label='Enter a prompt for Gemini'][contenteditable='true']": DummyLocator("aria", 0),
".ql-editor[contenteditable='true']": DummyLocator("ql-editor", 0),
+ "textarea": DummyLocator("textarea", 0),
+ "input[type='text']": DummyLocator("input", 0),
}
def locator(self, selector: str) -> DummyLocator:
@@ -129,6 +134,37 @@ def locator(self, selector: str) -> DummyLocator:
assert locator.name == "textbox"
+def test_pick_chat_input_locator_supports_grok_tiptap_editor() -> None:
+ class DummyLocator:
+ def __init__(self, name: str, count: int) -> None:
+ self.name = name
+ self._count = count
+ self.first = self
+
+ def count(self) -> int:
+ return self._count
+
+ class DummyPage:
+ def __init__(self) -> None:
+ self.locators = {
+ "[data-placeholder='Ask anything'][contenteditable='true']": DummyLocator("grok-placeholder", 1),
+ ".tiptap.ProseMirror[contenteditable='true']": DummyLocator("grok-tiptap", 1),
+ "[aria-label='Enter a prompt for Gemini'][contenteditable='true']": DummyLocator("aria", 0),
+ "[role='textbox'][contenteditable='true']": DummyLocator("textbox", 0),
+ "[contenteditable='true'][role='textbox']": DummyLocator("textbox-reversed", 0),
+ ".ql-editor[contenteditable='true']": DummyLocator("ql-editor", 0),
+ "textarea": DummyLocator("textarea", 0),
+ "input[type='text']": DummyLocator("input", 0),
+ }
+
+ def locator(self, selector: str) -> DummyLocator:
+ return self.locators[selector]
+
+ locator = _pick_chat_input_locator(DummyPage())
+ assert locator is not None
+ assert locator.name == "grok-placeholder"
+
+
def test_pick_chat_input_locator_returns_none_when_no_supported_input_exists() -> None:
class DummyLocator:
def __init__(self) -> None:
@@ -144,6 +180,97 @@ def locator(self, selector: str) -> DummyLocator: # noqa: ARG002
assert _pick_chat_input_locator(DummyPage()) is None
+def test_pick_chat_input_locator_skips_hidden_textarea_for_visible_grok_editor() -> None:
+ class DummyCandidate:
+ def __init__(self, name: str, visible: bool) -> None:
+ self.name = name
+ self._visible = visible
+
+ def is_visible(self) -> bool:
+ return self._visible
+
+ class DummyLocator:
+ def __init__(self, candidates: list[DummyCandidate]) -> None:
+ self._candidates = candidates
+ self.first = candidates[0] if candidates else None
+
+ def count(self) -> int:
+ return len(self._candidates)
+
+ def nth(self, index: int) -> DummyCandidate:
+ return self._candidates[index]
+
+ class DummyPage:
+ def __init__(self) -> None:
+ self.locators = {
+ "[data-placeholder='Ask anything'][contenteditable='true']": DummyLocator(
+ [DummyCandidate("grok-visible", True)]
+ ),
+ ".tiptap.ProseMirror[contenteditable='true']": DummyLocator([]),
+ "[aria-label='Enter a prompt for Gemini'][contenteditable='true']": DummyLocator([]),
+ "[role='textbox'][contenteditable='true']": DummyLocator([]),
+ "[contenteditable='true'][role='textbox']": DummyLocator([]),
+ ".ql-editor[contenteditable='true']": DummyLocator([]),
+ "textarea": DummyLocator([DummyCandidate("hidden-textarea", False)]),
+ "input[type='text']": DummyLocator([]),
+ }
+
+ def locator(self, selector: str) -> DummyLocator:
+ return self.locators[selector]
+
+ locator = _pick_chat_input_locator(DummyPage())
+ assert locator is not None
+ assert locator.name == "grok-visible"
+
+
+def test_activate_chat_input_prefers_normal_click() -> None:
+ events: list[str] = []
+
+ class DummyLocator:
+ def click(self, *, timeout=None, force=False): # noqa: ANN001
+ events.append(f"click:{timeout}:{force}")
+
+ def focus(self) -> None:
+ events.append("focus")
+
+ _activate_chat_input(DummyLocator())
+
+ assert events == ["click:5000:False"]
+
+
+def test_activate_chat_input_falls_back_to_force_click() -> None:
+ events: list[str] = []
+
+ class DummyLocator:
+ def click(self, *, timeout=None, force=False): # noqa: ANN001
+ events.append(f"click:{timeout}:{force}")
+ if not force:
+ raise RuntimeError("overlay intercept")
+
+ def focus(self) -> None:
+ events.append("focus")
+
+ _activate_chat_input(DummyLocator())
+
+ assert events == ["click:5000:False", "click:5000:True"]
+
+
+def test_activate_chat_input_falls_back_to_focus_when_clicks_fail() -> None:
+ events: list[str] = []
+
+ class DummyLocator:
+ def click(self, *, timeout=None, force=False): # noqa: ANN001
+ events.append(f"click:{timeout}:{force}")
+ raise RuntimeError("still blocked")
+
+ def focus(self) -> None:
+ events.append("focus")
+
+ _activate_chat_input(DummyLocator())
+
+ assert events == ["click:5000:False", "click:5000:True", "focus"]
+
+
def test_browser_search_closes_session_before_playwright_exit(monkeypatch, tmp_path: Path) -> None:
order: list[str] = []
diff --git a/apps/orchestrator/tests/test_tool_pipeline_integration.py b/apps/orchestrator/tests/test_tool_pipeline_integration.py
index b53d4f8..ee6f2dd 100644
--- a/apps/orchestrator/tests/test_tool_pipeline_integration.py
+++ b/apps/orchestrator/tests/test_tool_pipeline_integration.py
@@ -202,6 +202,7 @@ def test_tool_pipeline_network_gate_denied(tmp_path: Path, monkeypatch) -> None:
repo = tmp_path / "repo"
repo.mkdir()
_init_repo(repo)
+ (repo / "codex_home").mkdir()
(repo / "README.md").write_text("hello", encoding="utf-8")
_git(["git", "add", "README.md", "policies/command_allowlist.json"], repo)
_git(["git", "commit", "-m", "init"], repo)
@@ -330,6 +331,66 @@ def run_script(self, _script: str, _url: str) -> dict:
)
+def test_tool_pipeline_searcher_short_circuits_before_codex_runner(tmp_path: Path, monkeypatch) -> None:
+ _reset_scheduler_tool_hooks(monkeypatch)
+ repo = tmp_path / "repo"
+ repo.mkdir()
+ _init_repo(repo)
+ (repo / "codex_home").mkdir()
+ (repo / "README.md").write_text("hello", encoding="utf-8")
+ _git(["git", "add", "README.md", "policies/command_allowlist.json"], repo)
+ _git(["git", "commit", "-m", "init"], repo)
+
+ runtime_root = tmp_path / "runtime"
+ runs_root = runtime_root / "runs"
+ worktree_root = runtime_root / "worktrees"
+ _configure_runtime_roots(monkeypatch, runtime_root, runs_root, worktree_root)
+ monkeypatch.setenv("CORTEXPILOT_SEARCH_MODE", "mock")
+ monkeypatch.setenv("CORTEXPILOT_CHAIN_EXEC_MODE", "inline")
+ monkeypatch.setenv("CORTEXPILOT_RUNNER", "codex")
+ monkeypatch.setenv("CORTEXPILOT_MCP_ONLY", "1")
+ monkeypatch.setenv("CORTEXPILOT_ALLOW_CODEX_EXEC", "1")
+
+ search_path = repo / "search_requests.json"
+ search_body = _write_json(
+ search_path,
+ {"queries": ["cortexpilot"], "providers": ["chatgpt_web", "grok_web"], "repeat": 2, "parallel": 2},
+ )
+ _pin_tool_requests(
+ monkeypatch,
+ search_request={"queries": ["cortexpilot"], "providers": ["chatgpt_web", "grok_web"], "repeat": 2, "parallel": 2},
+ )
+
+ artifacts = [
+ {
+ "name": "search_requests.json",
+ "uri": search_path.name,
+ "sha256": _sha256_text(search_body),
+ }
+ ]
+
+ contract = _base_contract("task_search_short_circuit", artifacts)
+ contract["assigned_agent"]["role"] = "SEARCHER"
+ contract_path = repo / "contract.json"
+ _write_json(contract_path, contract)
+
+ monkeypatch.setattr(
+ "cortexpilot_orch.runners.codex_runner.CodexRunner.run_contract",
+ lambda *_args, **_kwargs: (_ for _ in ()).throw(AssertionError("codex runner should not execute after search pipeline success")),
+ )
+
+ monkeypatch.chdir(repo)
+ orch = Orchestrator(repo)
+ run_id = orch.execute_task(contract_path, mock_mode=False)
+
+ manifest = json.loads((runs_root / run_id / "manifest.json").read_text(encoding="utf-8"))
+ run_dir = runs_root / run_id
+ assert manifest["status"] == "SUCCESS"
+ assert (run_dir / "artifacts" / "search_results.json").exists()
+ assert (run_dir / "reports" / "evidence_bundle.json").exists()
+ assert (run_dir / "reports" / "task_result.json").exists()
+
+
def test_tool_pipeline_browser_failure_marks_run_failed(tmp_path: Path, monkeypatch) -> None:
_reset_scheduler_tool_hooks(monkeypatch)
repo = tmp_path / "repo"
diff --git a/tooling/search/search_engine.py b/tooling/search/search_engine.py
index 2d5f5ce..47d3ec6 100644
--- a/tooling/search/search_engine.py
+++ b/tooling/search/search_engine.py
@@ -271,20 +271,45 @@ def _write_web_error_artifacts(artifacts_dir: Path, error: str, page: Any | None
def _pick_chat_input_locator(page: Any) -> Any | None:
selectors = (
- "textarea",
- "input[type='text']",
+ "[data-placeholder='Ask anything'][contenteditable='true']",
+ ".tiptap.ProseMirror[contenteditable='true']",
+ "[aria-label='Enter a prompt for Gemini'][contenteditable='true']",
"[role='textbox'][contenteditable='true']",
"[contenteditable='true'][role='textbox']",
- "[aria-label='Enter a prompt for Gemini'][contenteditable='true']",
".ql-editor[contenteditable='true']",
+ "textarea",
+ "input[type='text']",
)
for selector in selectors:
locator = page.locator(selector)
- if locator.count() > 0:
+ if locator.count() <= 0:
+ continue
+ if not hasattr(locator, "nth"):
return locator.first
+ for index in range(locator.count()):
+ candidate = locator.nth(index)
+ try:
+ if candidate.is_visible():
+ return candidate
+ except Exception: # noqa: BLE001
+ return locator.first
return None
+def _activate_chat_input(locator: Any) -> None:
+ try:
+ locator.click(timeout=5000)
+ return
+ except Exception:
+ pass
+ try:
+ locator.click(timeout=5000, force=True)
+ return
+ except Exception:
+ pass
+ locator.focus()
+
+
def _chat_provider_search(
query: str,
provider: str,
@@ -353,7 +378,7 @@ def _chat_provider_search(
locator = _pick_chat_input_locator(page)
if locator is None:
raise RuntimeError("input box not found")
- locator.click()
+ _activate_chat_input(locator)
locator.fill(query)
locator.press("Enter")
page.wait_for_timeout(3000)
diff --git a/tooling/search_pipeline.py b/tooling/search_pipeline.py
index 8a4a726..2e26e18 100644
--- a/tooling/search_pipeline.py
+++ b/tooling/search_pipeline.py
@@ -275,24 +275,24 @@ def _build_digest_result(
break
normalized_status = str(status_override or "").strip().upper()
- template_label = "资讯摘要" if task_template == "news_digest" else "主题简报"
+ template_label = "news digest" if task_template == "news_digest" else "topic brief"
if normalized_status == "FAILED":
summary = (
- f"“{topic}”{template_label}未能完成。"
- f" {failure_reason_zh or '检索链路未通过,请查看高级证据获取详细失败上下文。'}"
+ f"The {template_label} for '{topic}' did not complete successfully."
+ " Review failure_reason_zh and the evidence bundle for the detailed provider failure context."
).strip()
status = "FAILED"
elif digest_sources:
- preview = "、".join(item["title"] for item in digest_sources[:3])
+ preview = ", ".join(item["title"] for item in digest_sources[:3])
summary = (
- f"已围绕“{topic}”汇总 {len(digest_sources)} 条公开来源,覆盖最近 {time_range} 的检索结果。"
- f" 当前优先可读来源包括:{preview}。"
+ f"Collected {len(digest_sources)} public-source result(s) about '{topic}' from the last {time_range}."
+ f" Current readable source highlights: {preview}."
)
status = "SUCCESS"
failure_reason_zh = None
else:
- summary = f"未检索到与“{topic}”相关的公开来源结果,请稍后重试或调整检索范围。"
+ summary = f"No public-source results were found for '{topic}'. Retry later or widen the search scope."
status = "EMPTY"
failure_reason_zh = failure_reason_zh or "未检索到公开来源结果"
From d845fb751b36625eec26b00c69354e7022f2d7f5 Mon Sep 17 00:00:00 2001
From: "Yifeng[Terry] Yu" <125581657+xiaojiou176@users.noreply.github.com>
Date: Tue, 7 Apr 2026 00:45:33 -0700
Subject: [PATCH 2/5] chore(closeout): land storefront proof and local gate
split
---
.github/workflows/ci.yml | 2 +
.pre-commit-config.yaml | 6 +-
AGENTS.md | 2 +
CLAUDE.md | 2 +
README.md | 178 +++------
apps/dashboard/README.md | 2 +-
apps/dashboard/next-env.d.ts | 2 +-
apps/dashboard/tests/home_page.test.tsx | 20 +-
apps/dashboard/tsconfig.json | 10 +-
.../tests/test_frontdoor_contract_gate.py | 108 ++++++
...st_generate_storefront_proof_pack_index.py | 84 +++++
.../tests/test_github_control_plane.py | 112 ++++++
.../test_storefront_proof_assets_gate.py | 224 +++++++++++
configs/docs_render_manifest.json | 15 +
configs/env.registry.json | 4 +-
configs/github_control_plane_policy.json | 16 +
configs/storefront_proof_bundle_registry.json | 77 ++++
docs/README.md | 38 +-
docs/assets/storefront/README.md | 14 +-
.../dashboard-command-tower-live-1440x900.png | Bin 223633 -> 216913 bytes
.../dashboard-home-live-1440x900.png | Bin 247122 -> 272296 bytes
.../dashboard-live-healthy-loop.gif | Bin 0 -> 12247677 bytes
.../dashboard-runs-live-1440x900.png | Bin 210519 -> 284789 bytes
docs/assets/storefront/demo-status.md | 25 +-
.../storefront/live-capture-requirements.json | 56 +++
docs/assets/storefront/proof-pack-index.json | 166 +++++++++
docs/compatibility/index.html | 6 +-
docs/index.html | 130 ++++---
docs/integrations/index.html | 2 +-
.../news-digest-proof-pack-2026-03-27.json | 35 ++
...s-digest-workflow-case-recap-2026-03-27.md | 1 -
docs/runbooks/onboarding-30min.md | 1 +
docs/runbooks/storefront-share-kit.md | 19 +-
docs/skills/index.html | 2 +-
docs/use-cases/index.html | 351 +++++++++++++++---
package.json | 8 +-
packages/frontend-shared/uiCopy.js | 28 +-
packages/frontend-shared/uiCopy.ts | 28 +-
scripts/check_frontdoor_contract.py | 239 ++++++++++++
scripts/check_github_control_plane.py | 24 ++
scripts/check_storefront_proof_assets.py | 260 +++++++++++++
scripts/ci_local_fast.sh | 29 ++
.../generate_storefront_proof_pack_index.py | 160 ++++++++
scripts/pre_commit_quality_gate.sh | 5 +-
scripts/pre_push_quality_gate.sh | 60 +--
scripts/render_docs.py | 1 +
46 files changed, 2188 insertions(+), 364 deletions(-)
create mode 100644 apps/orchestrator/tests/test_frontdoor_contract_gate.py
create mode 100644 apps/orchestrator/tests/test_generate_storefront_proof_pack_index.py
create mode 100644 apps/orchestrator/tests/test_github_control_plane.py
create mode 100644 apps/orchestrator/tests/test_storefront_proof_assets_gate.py
create mode 100644 configs/storefront_proof_bundle_registry.json
create mode 100644 docs/assets/storefront/dashboard-live-healthy-loop.gif
create mode 100644 docs/assets/storefront/live-capture-requirements.json
create mode 100644 docs/assets/storefront/proof-pack-index.json
create mode 100644 docs/releases/assets/news-digest-proof-pack-2026-03-27.json
create mode 100644 scripts/check_frontdoor_contract.py
create mode 100644 scripts/check_storefront_proof_assets.py
create mode 100644 scripts/ci_local_fast.sh
create mode 100644 scripts/generate_storefront_proof_pack_index.py
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7802779..d828a44 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -299,6 +299,8 @@ jobs:
bash scripts/run_governance_py.sh scripts/check_ci_supply_chain_policy.py
bash scripts/run_governance_py.sh scripts/check_docs_navigation_registry.py
bash scripts/run_governance_py.sh scripts/check_docs_manual_fact_boundary.py
+ bash scripts/run_governance_py.sh scripts/check_frontdoor_contract.py
+ bash scripts/run_governance_py.sh scripts/check_storefront_proof_assets.py
bash scripts/run_governance_py.sh scripts/check_docs_render_freshness.py
bash scripts/check_gitignore_hygiene.sh
bash scripts/check_repo_hygiene.sh
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 3ea1455..efe69a6 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
# Host compatibility hook lane:
# - Hooks run on host by design.
-# - Release-grade canonical full gate remains container-first: `npm run ci` -> `bash scripts/docker_ci.sh ci`.
+# - Release-grade canonical full gate remains strict/manual-first: `npm run ci:strict`.
repos:
- repo: local
hooks:
@@ -23,7 +23,7 @@ repos:
entry: bash scripts/check_repo_hygiene.sh
language: system
pass_filenames: false
- stages: [pre-commit]
+ stages: [manual]
- id: cortexpilot-public-sensitive-surface-gate
name: cortexpilot-public-sensitive-surface-gate
@@ -39,7 +39,7 @@ repos:
language: system
pass_filenames: false
always_run: true
- stages: [pre-commit]
+ stages: [manual]
- id: cortexpilot-root-semantic-cleanliness
name: cortexpilot-root-semantic-cleanliness
diff --git a/AGENTS.md b/AGENTS.md
index 9f83276..9d521c1 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -20,6 +20,8 @@ Work in CortexPilot as a contract-first engineering agent:
## Key Commands
- bootstrap: `npm run bootstrap`
+- local fast CI: `npm run ci`
+- local strict CI: `npm run ci:strict`
- fast verification: `npm run test:quick`
- main local gate: `npm run test`
- host safety scan: `npm run scan:host-process-risks`
diff --git a/CLAUDE.md b/CLAUDE.md
index b74cc00..4201cd5 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -374,6 +374,8 @@ This file mirrors the root AI entrypoint for tools that prefer `CLAUDE.md`.
## Key Commands
- `npm run bootstrap`
+- `npm run ci`
+- `npm run ci:strict`
- `npm run test`
- `npm run test:quick`
- `npm run scan:host-process-risks`
diff --git a/README.md b/README.md
index 925ace8..e5d6b3f 100644
--- a/README.md
+++ b/README.md
@@ -3,43 +3,35 @@
AI Work Command Tower for Codex and Claude Code workflows with Model Context
Protocol (MCP)-readable proof, replay, and Workflow Cases.
-CortexPilot is a contract-first orchestration repo for Codex / Claude Code
-teams that want one operator surface, one governed run path, and one
-replayable evidence trail instead of scattered agents, logs, and scripts.
+CortexPilot gives Codex / Claude Code teams one governed path from the PM
+request to the Workflow Case to Proof & Replay, instead of scattered agents,
+logs, and local scripts.
CortexPilot is a contract-first multi-agent orchestration repository.
-CortexPilot is an AI Work Command Tower built around three product words:
-**Command Tower**, **Workflow Cases**, and **Proof & Replay**.
+The public story is intentionally narrower than the full monorepo:
-The desktop operator surfaces are also being pulled onto the same shared copy
-and shared status-presentation substrate, so `Run Detail` and `Overview` do
-not drift back into page-local wording contracts.
+- **See one proven workflow first**
+- **Choose the right adoption path second**
+- **Open MCP / API / builder / skills surfaces only after the real job is clear**
-The current public story speaks first to Codex / Claude Code teams and second
-to broader AI ops / platform teams. The front door should ride the heat around
-those ecosystems without pretending CortexPilot is already a hosted operator
-service.
+Current public boundary: CortexPilot is a repo-backed operator control plane,
+not a hosted product, and the shipped MCP surface remains **read-only**.
-The public homepage now stays intentionally compressed: start with the first
-success path, use the compatibility matrix as the main router, and let the
-deeper MCP / integration / skills / builder pages carry the detailed
-explanations.
-
-The dashboard public-home now mirrors that same rule: keep the compatibility
-card as the main routing layer, but keep a lighter **Use Cases** side door for
-the fastest proof-first walkthrough instead of rebuilding a second routing
-grid inside the operator surface.
-
-The product name stays **CortexPilot**. If `cortexpilot.ai` is later claimed,
-treat it as a marketing/front-door domain, not as a product rename.
-
-[Quickstart](#quickstart) · [Docs](docs/README.md) · [Architecture](docs/architecture/runtime-topology.md) · [Ecosystem Map](https://xiaojiou176-open.github.io/CortexPilot-public/ecosystem/) · [Compatibility Matrix](https://xiaojiou176-open.github.io/CortexPilot-public/compatibility/) · [Agent Starter Kits](https://xiaojiou176-open.github.io/CortexPilot-public/agent-starters/) · [Integration Guide](https://xiaojiou176-open.github.io/CortexPilot-public/integrations/) · [Skills Guide](https://xiaojiou176-open.github.io/CortexPilot-public/skills/) · [AI + MCP + API Surfaces](https://xiaojiou176-open.github.io/CortexPilot-public/ai-surfaces/) · [MCP Quickstart](https://xiaojiou176-open.github.io/CortexPilot-public/mcp/) · [API Quickstart](https://xiaojiou176-open.github.io/CortexPilot-public/api/) · [Builder Quickstart](https://xiaojiou176-open.github.io/CortexPilot-public/builders/) · [Use Cases](https://xiaojiou176-open.github.io/CortexPilot-public/use-cases/) · [Client Entry Points](packages/frontend-api-client/README.md) · [Contract Entry Points](packages/frontend-api-contract/docs/README.md) · [Spec](docs/specs/00_SPEC.md) · [Releases](https://github.com/xiaojiou176-open/CortexPilot-public/releases)
+[Quickstart](#quickstart) · [First Proven Workflow](https://xiaojiou176-open.github.io/CortexPilot-public/use-cases/) · [Compatibility Matrix](https://xiaojiou176-open.github.io/CortexPilot-public/compatibility/) · [Docs](docs/README.md) · [Architecture](docs/architecture/runtime-topology.md) · [AI + MCP + API Surfaces](https://xiaojiou176-open.github.io/CortexPilot-public/ai-surfaces/) · [Builder Quickstart](https://xiaojiou176-open.github.io/CortexPilot-public/builders/) · [Releases](https://github.com/xiaojiou176-open/CortexPilot-public/releases)


+## Open The Right Door
+
+| If you're here to... | Open this first |
+| --- | --- |
+| evaluate the product story | [First Proven Workflow](https://xiaojiou176-open.github.io/CortexPilot-public/use-cases/) |
+| choose the right Codex / Claude Code / OpenClaw / MCP / skills / builder path | [Compatibility Matrix](https://xiaojiou176-open.github.io/CortexPilot-public/compatibility/) |
+| build on the protocol or package surfaces | [AI + MCP + API Surfaces](https://xiaojiou176-open.github.io/CortexPilot-public/ai-surfaces/) and [Builder Quickstart](https://xiaojiou176-open.github.io/CortexPilot-public/builders/) |
+
The default public loop is simple: **start one workflow case, watch it move
through Command Tower, then inspect Proof & Replay before you trust the
outcome**.
@@ -55,18 +47,23 @@ paths:
| validate the smallest governed path | `CORTEXPILOT_HOST_COMPAT=1 bash scripts/test_quick.sh --no-related` | the quickest repo-side proof path without pretending the full system already ran |
| inspect what the system records | open the run list and `.runtime-cache/` after the quick path | a concrete evidence bundle and replay surface, not just a shell success line |
+A clean first pass should let you:
+
+- create one task from the PM surface
+- watch that task appear in **Command Tower**
+- confirm the linked **Workflow Case**
+- inspect **Proof & Replay** before trusting the result
+
+For the public product story, the current official first proven workflow is
+[`news_digest`](https://xiaojiou176-open.github.io/CortexPilot-public/use-cases/).
+`topic_brief` and `page_brief` are still public showcase paths, not equally
+release-proven baselines.
+
If this repository is close to your use case, star it to track the first public
release, new task templates, and storefront updates.
-The integration / skills / MCP / builder pages now also carry more copy-paste
-drop-in recipes, so external Codex / Claude Code / OpenClaw teams do not have
-to reconstruct the vendored-skill, stdio-MCP, and builder-starter path from
-source files alone.
-
-The host-compatible pre-commit lane now routes repo-owned `scripts/*.py` hooks
-through `bash scripts/run_governance_py.sh`, so the quality gate stays
-deterministic without leaving repo-local `__pycache__` residue behind after a
-clean run.
+If you need contributor setup instead of product evaluation, jump to the
+[30-minute onboarding guide](docs/runbooks/onboarding-30min.md).
## Why CortexPilot Exists
@@ -76,87 +73,21 @@ workflow case, and rerun it without guessing?**
This repository combines:
-- **Command Tower**: one operator surface for governed AI agents, MCP tools, and live run visibility
-- **Workflow Cases**: one stable operating record that ties request, queue, verdict, and linked runs together
-- **Proof & Replay**: one place to inspect evidence bundles, compare reruns, and replay failures without guessing
-- **Operator surfaces**: use the web dashboard or desktop shell to watch and control the same system
-- **Role Contract preview**: intake planning can now expose a resolved role binding summary
- (prompt ref, MCP bundle ref, runtime binding, fail-closed posture) before execution starts,
- while handoff remains a summary/risk evidence surface instead of rewriting the task contract
-- **Role binding receipt**: PM-facing `run_intake(...)` responses and run manifests now
- carry a contract-derived `role_binding_summary`, so skills / MCP / runtime bindings stay
- readable after execution without pretending that summary has execution authority
-- **Role binding read model**: run detail now also exposes a stable
- `role_binding_read_model` so persisted bundle/runtime state is readable from
- read-only surfaces by projecting `contract.json` without promoting that read
- model into execution authority
-- **Skills bundle surface**: qualifying delivery roles now resolve
- `skills_bundle_ref` from the repo-owned `policies/skills_bundle_registry.json`
- surface, so bundle status is no longer a placeholder-only field
-- **Workflow case read model**: control-plane workflow reads now expose a
- `workflow_case_read_model` that points back to the latest linked run's
- persisted `role_binding_summary`, keeping workflow/control-plane reads
- stable without inventing a second execution authority
-- **Workflow detail read-only projection**: dashboard and desktop Workflow Case
- detail views now render that `workflow_case_read_model` directly, so
- operators can inspect bundle/runtime posture from the current case surface
- while `task_contract` remains the only execution authority
-- **Prompt 8 contract convergence**: `docs/api/openapi.cortexpilot.json` plus
- generated `@cortexpilot/frontend-api-contract` artifacts now publish
- run/workflow route bindings and `RoleBindingReadModel` /
- `WorkflowCaseReadModel` shapes from one source instead of relying on a
- stale helper contract plus hand-written overlays
-- **Run Detail read-only projection**: dashboard and desktop Run Detail now
- expose `role_binding_read_model` on their primary operator summary surfaces,
- so the persisted binding summary is visible where operators already inspect
- run status while `task_contract` still owns execution authority
-- **Prompt 9 operator catalog**: `/api/agents` now publishes a registry-backed
- role catalog, `/api/contracts` now exposes a normalized contract inspector
- record, and dashboard/desktop `Agents` + `Contracts` pages project the same
- bundle/runtime truth as read-only operator surfaces without becoming
- execution authority or edit controls
-- **Prompt 10 role configuration desk**: role defaults now have a repo-owned
- control-plane overlay in `policies/role_config_registry.json`, plus
- preview/apply API surfaces for `system_prompt_ref`, `skills_bundle_ref`,
- `mcp_bundle_ref`, and role-level `runtime_binding`; the `Agents` pages act as
- the primary control desk while `Contracts` stays inspector-first and
- `task_contract` remains the only execution authority
-- **Prompt 10 runtime capability truth**: intake preview, run manifests,
- operator copilot, `Contracts`, and `Run Detail` now also surface a derived
- runtime capability summary (`lane`, `compat_api_mode`, `provider_status`,
- `tool_execution`) so runtime/provider posture is readable without pretending
- the system already has full tool parity or runtime replaceability
-- **Prompt 10 smoke-gate hardening**: staged dashboard smoke builds now keep
- their dependency-install log path recreated on each run, and
- `apps/dashboard/lib/types.ts` now explicitly re-exports task-pack/runtime
- helper values so UI-audit builds fail on product regressions instead of
- brittle staging/export drift
-- **Prompt 10 clean-room recovery hardening**: empty-runtime recovery now
- reinstalls package-local `frontend-api-client` dependencies before it runs
- the node smoke bundle, so clean-room verification keeps testing the package
- itself instead of failing on missing local installs
-- **Prompt 10 clean-room cleanup hardening**: clean-room recovery now runs the
- repo-owned workspace-module cleanup before its broad runtime `rm -rf` sweep,
- and that cleanup path can now quarantine stubborn `apps/dashboard/node_modules`
- residue before deletion, so the recovery lane does not abort on transient
- bind-mounted module trees
-- **Prompt 10 quick-gate decoupling**: the `cortexpilot_orch.contract`
- package now lazy-loads `compiler` / `validator` entrypoints, so Quick
- Feedback governance scripts can import `ContractValidator` and schedule
- boundary checks without accidentally pulling the runtime-provider stack or
- requiring `httpx` on light governance Python paths
-- **Quick Feedback light path**: role-config runtime capability summaries now
- resolve through a lightweight provider-capability helper, so control-plane
- previews keep their fail-closed provider posture without forcing GitHub
- quick-path governance checks to import full runtime transport dependencies;
- the staged dashboard UI-audit workspace now also copies the required
- `packages/frontend-*` sources into its temporary root so Next/Turbopack does
- not reject out-of-root symlinks during smoke builds, and repeated pnpm
- `ERR_PNPM_ENOENT` recovery now escalates from fresh-store retries to a
- workspace-local store path instead of repeating the same failing copy route,
- while `provider_resolution` keeps a dead-code-clean compatibility export
- surface for callers that still import lightweight helper names from the
- runtime module
+- **Command Tower**: one operator surface for governed AI work, live run visibility, and queue posture
+- **Workflow Cases**: one stable operating record that ties request, verdict, proof, and linked runs together
+- **Proof & Replay**: one place to inspect evidence bundles, compare reruns, and replay failures before promotion
+- **Operator surfaces**: a web dashboard plus a macOS desktop shell for the same control plane
+- **Read-only inspection surfaces**: repo-local MCP, API, and contract read models that expose truth without turning mirrors into execution authority
+- **Governed boundaries**: fail-closed gates for CI, host safety, repo hygiene, and public-proof honesty
+
+If you need the deeper bundle/runtime/read-model details, open the focused
+entrypoints instead of treating the root README like the whole control-plane
+manual:
+
+- [AI + MCP + API Surfaces](https://xiaojiou176-open.github.io/CortexPilot-public/ai-surfaces/)
+- [Builder Quickstart](https://xiaojiou176-open.github.io/CortexPilot-public/builders/)
+- [Contract Entry Points](packages/frontend-api-contract/docs/README.md)
+- [Spec](docs/specs/00_SPEC.md)
## Quickstart
@@ -532,17 +463,20 @@ current private reporting path on the live public repository. An additional
verified fallback private channel is not yet published and should not be
assumed.
-Current repo-side verification entrypoints:
+Default local verification path:
```bash
-npm run test
+npm run ci
npm run test:quick
-bash scripts/check_repo_hygiene.sh
-npm run scan:workflow-security
-npm run scan:trivy
-npm run security:scan:closeout
+npm run test
```
+`npm run ci` is now the hosted-aligned local fast gate. Use
+`npm run ci:strict`, `npm run docs:check`, `bash scripts/check_repo_hygiene.sh`,
+`npm run scan:workflow-security`, `npm run scan:trivy`, and
+`npm run security:scan:closeout` only when you intentionally want the stricter
+closeout/manual layers.
+
`npm run test:quick` now expects the dashboard clean-room install gate to
prove `jsdom` itself can load, instead of pinning success to the presence of a
specific transitive dependency layout such as `data-urls`.
diff --git a/apps/dashboard/README.md b/apps/dashboard/README.md
index 4b587ec..879b52c 100644
--- a/apps/dashboard/README.md
+++ b/apps/dashboard/README.md
@@ -82,7 +82,7 @@ finished consumer product.
- The dashboard public-docs resolver still treats `/integrations/`,
`/skills/`, and `/compatibility/` as first-class public docs routes so
public-docs base overrides do not strand those CTA links on app-local paths.
-- The same public-home polish keeps the explicit `Open use-case guide` side
+- The same public-home polish keeps the explicit `See first proven workflow` side
door routed through the public-docs resolver, so the proof-first walkthrough
stays visible without turning the dashboard back into a second full routing
matrix.
diff --git a/apps/dashboard/next-env.d.ts b/apps/dashboard/next-env.d.ts
index 9edff1c..6891d77 100644
--- a/apps/dashboard/next-env.d.ts
+++ b/apps/dashboard/next-env.d.ts
@@ -1,6 +1,6 @@
///
repo-backed operator control plane, not a hosted product
+shipped MCP surface remains read-only
+news_digest topic_brief page_brief
+ """, + encoding="utf-8", + ) + (root / "docs" / "use-cases" / "index.html").write_text( + """ +news_digest is the only official release-proven public baseline.
+topic_brief and page_brief are not yet equally release-proven.
+What we still do not claim
+ Open proof summary + Open benchmark summary + Open recap asset + Open proof-pack manifest + Open proof-pack index + Open demo-status ledger + """, + encoding="utf-8", + ) + (root / "docs" / "compatibility" / "index.html").write_text( + """ +read-only MCP
+ See the first proven workflow + """, + encoding="utf-8", + ) + (root / "docs" / "releases" / "assets" / "news-digest-healthy-proof-2026-03-27.md").write_text("ok\n", encoding="utf-8") + (root / "docs" / "releases" / "assets" / "news-digest-benchmark-summary-2026-03-27.md").write_text("ok\n", encoding="utf-8") + (root / "docs" / "releases" / "assets" / "news-digest-workflow-case-recap-2026-03-27.md").write_text("ok\n", encoding="utf-8") + (root / "docs" / "releases" / "assets" / "news-digest-proof-pack-2026-03-27.json").write_text("{}\n", encoding="utf-8") + (root / "docs" / "assets" / "storefront" / "demo-status.md").write_text("ok\n", encoding="utf-8") + (root / "docs" / "assets" / "storefront" / "proof-pack-index.json").write_text("{}\n", encoding="utf-8") + + +def test_frontdoor_contract_gate_passes_with_required_surfaces(tmp_path: Path, monkeypatch) -> None: + module = _load_gate_module() + _write_frontdoor_fixture(tmp_path) + module.ROOT = tmp_path + module.INDEX_PATH = tmp_path / "docs" / "index.html" + module.USE_CASES_PATH = tmp_path / "docs" / "use-cases" / "index.html" + module.COMPATIBILITY_PATH = tmp_path / "docs" / "compatibility" / "index.html" + module.PROOF_SUMMARY_PATH = tmp_path / "docs" / "releases" / "assets" / "news-digest-healthy-proof-2026-03-27.md" + module.BENCHMARK_SUMMARY_PATH = tmp_path / "docs" / "releases" / "assets" / "news-digest-benchmark-summary-2026-03-27.md" + module.WORKFLOW_RECAP_PATH = tmp_path / "docs" / "releases" / "assets" / "news-digest-workflow-case-recap-2026-03-27.md" + module.PROOF_PACK_MANIFEST_PATH = tmp_path / "docs" / "releases" / "assets" / "news-digest-proof-pack-2026-03-27.json" + module.DEMO_STATUS_PATH = tmp_path / "docs" / "assets" / "storefront" / "demo-status.md" + module.PROOF_PACK_INDEX_PATH = tmp_path / "docs" / "assets" / "storefront" / "proof-pack-index.json" + monkeypatch.setattr(sys, "argv", ["check_frontdoor_contract.py"]) + assert module.main() == 0 + + +def test_frontdoor_contract_gate_fails_when_proof_link_drifts( + tmp_path: Path, capsys, monkeypatch +) -> None: + module = _load_gate_module() + _write_frontdoor_fixture(tmp_path) + use_cases = tmp_path / "docs" / "use-cases" / "index.html" + use_cases.write_text( + use_cases.read_text(encoding="utf-8").replace( + "Open benchmark summary", "Open benchmark bundle" + ), + encoding="utf-8", + ) + module.ROOT = tmp_path + module.INDEX_PATH = tmp_path / "docs" / "index.html" + module.USE_CASES_PATH = use_cases + module.COMPATIBILITY_PATH = tmp_path / "docs" / "compatibility" / "index.html" + module.PROOF_SUMMARY_PATH = tmp_path / "docs" / "releases" / "assets" / "news-digest-healthy-proof-2026-03-27.md" + module.BENCHMARK_SUMMARY_PATH = tmp_path / "docs" / "releases" / "assets" / "news-digest-benchmark-summary-2026-03-27.md" + module.WORKFLOW_RECAP_PATH = tmp_path / "docs" / "releases" / "assets" / "news-digest-workflow-case-recap-2026-03-27.md" + module.PROOF_PACK_MANIFEST_PATH = tmp_path / "docs" / "releases" / "assets" / "news-digest-proof-pack-2026-03-27.json" + module.DEMO_STATUS_PATH = tmp_path / "docs" / "assets" / "storefront" / "demo-status.md" + module.PROOF_PACK_INDEX_PATH = tmp_path / "docs" / "assets" / "storefront" / "proof-pack-index.json" + monkeypatch.setattr(sys, "argv", ["check_frontdoor_contract.py"]) + + rc = module.main() + out = capsys.readouterr().out + assert rc == 1 + assert "Open benchmark summary" in out diff --git a/apps/orchestrator/tests/test_generate_storefront_proof_pack_index.py b/apps/orchestrator/tests/test_generate_storefront_proof_pack_index.py new file mode 100644 index 0000000..a792781 --- /dev/null +++ b/apps/orchestrator/tests/test_generate_storefront_proof_pack_index.py @@ -0,0 +1,84 @@ +from __future__ import annotations + +import importlib.util +import json +from pathlib import Path + + +def _load_generator_module() -> object: + script_path = Path(__file__).resolve().parents[3] / "scripts" / "generate_storefront_proof_pack_index.py" + spec = importlib.util.spec_from_file_location("cortexpilot_generate_storefront_proof_pack_index", script_path) + module = importlib.util.module_from_spec(spec) + assert spec and spec.loader + spec.loader.exec_module(module) + return module + + +def test_generate_storefront_proof_pack_index_builds_assets_from_pack_manifest(tmp_path: Path) -> None: + module = _load_generator_module() + root = tmp_path + (root / "configs").mkdir(parents=True, exist_ok=True) + (root / "docs" / "releases" / "assets").mkdir(parents=True, exist_ok=True) + + registry = { + "schema_version": 1, + "artifact_type": "cortexpilot_storefront_proof_bundle_registry", + "vocabulary_contract": { + "proven_workflow_label": "first proven workflow", + "proof_pack_label": "public proof pack", + "showcase_label": "showcase expansion", + }, + "bundles": [ + { + "bundle_id": "news_digest", + "task_template": "news_digest", + "proof_state": "release_proven", + "claim_scope": "official_first_public_baseline", + "authority_level": "repo_side_public_proof", + "public_entrypoint": "docs/use-cases/index.html", + "pack_manifest": "docs/releases/assets/news-digest-proof-pack-2026-03-27.json", + "safe_public_claims": ["claim"], + "forbidden_claims": ["forbidden"], + "capture_contract": { + "healthy_live_capture_gif_present": False, + "healthy_english_first_public_capture_set_present": False, + "current_tracked_dashboard_captures": "local_degraded_non_english_mixed", + }, + "missing_expected_artifacts": ["healthy_live_capture_gif"], + } + ], + } + pack_manifest = { + "artifact_type": "news_digest_public_proof_pack", + "primary_assets": { + "proof_summary_markdown": "docs/releases/assets/news-digest-healthy-proof-2026-03-27.md", + "proof_summary_json": "docs/releases/assets/news-digest-healthy-proof-summary-2026-03-27.json", + "benchmark_summary_markdown": "docs/releases/assets/news-digest-benchmark-summary-2026-03-27.md", + "benchmark_summary_json": "docs/releases/assets/news-digest-benchmark-summary-2026-03-27.json", + "workflow_case_recap_markdown": "docs/releases/assets/news-digest-workflow-case-recap-2026-03-27.md", + "demo_status_markdown": "docs/assets/storefront/demo-status.md", + }, + "supporting_assets": { + "gemini_proof_screenshot": "docs/releases/assets/news-digest-healthy-proof-gemini-2026-03-27.png", + }, + } + registry_path = root / "configs" / "storefront_proof_bundle_registry.json" + registry_path.write_text(json.dumps(registry, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + manifest_path = root / "docs" / "releases" / "assets" / "news-digest-proof-pack-2026-03-27.json" + manifest_path.write_text(json.dumps(pack_manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + + module.ROOT = root + module.REGISTRY_PATH = registry_path + module.OUTPUT_PATH = root / "docs" / "assets" / "storefront" / "proof-pack-index.json" + + registry_payload = module._load_json(registry_path) + registry_payload["source_registry"] = "configs/storefront_proof_bundle_registry.json" + rendered = module.build_index(registry_payload) + + assert rendered["artifact_type"] == "cortexpilot_public_proof_pack_index" + assert rendered["source_registry"] == "configs/storefront_proof_bundle_registry.json" + news = rendered["bundles"][0] + roles = {item["role"] for item in news["assets"]} + assert "healthy_proof_summary" in roles + assert "benchmark_summary_machine" in roles + assert "gemini_proof_screenshot" in roles diff --git a/apps/orchestrator/tests/test_github_control_plane.py b/apps/orchestrator/tests/test_github_control_plane.py new file mode 100644 index 0000000..3a0cd2c --- /dev/null +++ b/apps/orchestrator/tests/test_github_control_plane.py @@ -0,0 +1,112 @@ +from __future__ import annotations + +import importlib.util +import json +import sys +from pathlib import Path + + +def _load_module() -> object: + script_path = Path(__file__).resolve().parents[3] / "scripts" / "check_github_control_plane.py" + spec = importlib.util.spec_from_file_location("cortexpilot_check_github_control_plane", script_path) + module = importlib.util.module_from_spec(spec) + assert spec and spec.loader + spec.loader.exec_module(module) + return module + + +def _policy_payload() -> dict: + return { + "owner": "example", + "repo": "repo", + "default_branch": "main", + "required_actions_permissions": { + "enabled": True, + "allowed_actions": "all", + "sha_pinning_required": True, + }, + "required_environments": ["owner-approved-sensitive"], + "required_checks": ["Quick Feedback"], + "branch_protection_required": True, + "platform_evidence": { + "private_vulnerability_reporting": {"required": True, "mode": "api"}, + "vulnerability_alerts": {"required": True, "mode": "api"}, + "secret_scanning": {"required": True, "mode": "api"}, + "secret_scanning_push_protection": {"required": True, "mode": "api"}, + "secret_scanning_non_provider_patterns": {"required": True, "mode": "api"}, + "secret_scanning_validity_checks": {"required": True, "mode": "api"}, + "dependabot_config": {"required": True, "mode": "repo_file", "path": ".github/dependabot.yml"}, + "codeql_workflow": {"required": True, "mode": "repo_file", "path": ".github/workflows/codeql.yml"}, + "codeql_config": {"required": True, "mode": "repo_file", "path": ".github/codeql/codeql-config.yml"}, + }, + } + + +def _fake_gh_json(secret_non_provider: str, validity_checks: str): + def _impl(path: str) -> tuple[int, dict]: + if path == "repos/example/repo": + return ( + 0, + { + "default_branch": "main", + "security_and_analysis": { + "secret_scanning": {"status": "enabled"}, + "secret_scanning_push_protection": {"status": "enabled"}, + "secret_scanning_non_provider_patterns": {"status": secret_non_provider}, + "secret_scanning_validity_checks": {"status": validity_checks}, + }, + }, + ) + if path == "repos/example/repo/actions/permissions": + return 0, {"enabled": True, "allowed_actions": "all", "sha_pinning_required": True} + if path == "repos/example/repo/branches/main/protection": + return 0, {"required_status_checks": {"contexts": ["Quick Feedback"]}} + if path == "repos/example/repo/environments": + return 0, {"environments": [{"name": "owner-approved-sensitive"}]} + if path == "repos/example/repo/private-vulnerability-reporting": + return 0, {"enabled": True} + if path == "repos/example/repo/vulnerability-alerts": + return 0, {} + if path == "repos/example/repo/code-scanning/default-setup": + return 0, {} + if path == "repos/example/repo/dependabot/alerts?per_page=1": + return 0, [] + raise AssertionError(path) + + return _impl + + +def test_github_control_plane_requires_secret_scanning_subfeatures(tmp_path: Path, monkeypatch) -> None: + module = _load_module() + policy_path = tmp_path / "policy.json" + output_path = tmp_path / "report.json" + policy_path.write_text(json.dumps(_policy_payload(), ensure_ascii=False, indent=2), encoding="utf-8") + + monkeypatch.setattr(module, "ROOT", tmp_path) + monkeypatch.setattr(module, "_gh_json", _fake_gh_json("disabled", "disabled")) + monkeypatch.setattr(module, "_repo_path_exists", lambda _path: True) + monkeypatch.setattr(sys, "argv", ["check_github_control_plane.py", "--policy", str(policy_path), "--output", str(output_path)]) + + rc = module.main() + report = json.loads(output_path.read_text(encoding="utf-8")) + assert rc == 1 + assert any("secret_scanning_non_provider_patterns drift" in item for item in report["errors"]) + assert any("secret_scanning_validity_checks drift" in item for item in report["errors"]) + + +def test_github_control_plane_accepts_enabled_secret_scanning_subfeatures(tmp_path: Path, monkeypatch) -> None: + module = _load_module() + policy_path = tmp_path / "policy.json" + output_path = tmp_path / "report.json" + policy_path.write_text(json.dumps(_policy_payload(), ensure_ascii=False, indent=2), encoding="utf-8") + + monkeypatch.setattr(module, "ROOT", tmp_path) + monkeypatch.setattr(module, "_gh_json", _fake_gh_json("enabled", "enabled")) + monkeypatch.setattr(module, "_repo_path_exists", lambda _path: True) + monkeypatch.setattr(sys, "argv", ["check_github_control_plane.py", "--policy", str(policy_path), "--output", str(output_path)]) + + rc = module.main() + report = json.loads(output_path.read_text(encoding="utf-8")) + assert rc == 0 + assert report["errors"] == [] + assert report["security_and_analysis"]["secret_scanning_non_provider_patterns"]["status"] == "enabled" diff --git a/apps/orchestrator/tests/test_storefront_proof_assets_gate.py b/apps/orchestrator/tests/test_storefront_proof_assets_gate.py new file mode 100644 index 0000000..280c03c --- /dev/null +++ b/apps/orchestrator/tests/test_storefront_proof_assets_gate.py @@ -0,0 +1,224 @@ +from __future__ import annotations + +import importlib.util +import json +import sys +from pathlib import Path + + +def _load_generator_module() -> object: + script_path = Path(__file__).resolve().parents[3] / "scripts" / "generate_storefront_proof_pack_index.py" + spec = importlib.util.spec_from_file_location("cortexpilot_generate_storefront_proof_pack_index", script_path) + module = importlib.util.module_from_spec(spec) + assert spec and spec.loader + spec.loader.exec_module(module) + return module + + +def _load_gate_module() -> object: + script_path = Path(__file__).resolve().parents[3] / "scripts" / "check_storefront_proof_assets.py" + spec = importlib.util.spec_from_file_location("cortexpilot_storefront_proof_assets_gate", script_path) + module = importlib.util.module_from_spec(spec) + assert spec and spec.loader + spec.loader.exec_module(module) + return module + + +def _write_fixture(root: Path) -> None: + (root / "docs" / "assets" / "storefront").mkdir(parents=True, exist_ok=True) + (root / "docs" / "releases" / "assets").mkdir(parents=True, exist_ok=True) + (root / "docs" / "runbooks").mkdir(parents=True, exist_ok=True) + (root / "docs" / "use-cases").mkdir(parents=True, exist_ok=True) + (root / "configs").mkdir(parents=True, exist_ok=True) + + for rel in [ + "docs/releases/assets/news-digest-healthy-proof-2026-03-27.md", + "docs/releases/assets/news-digest-benchmark-summary-2026-03-27.md", + "docs/releases/assets/news-digest-workflow-case-recap-2026-03-27.md", + ]: + path = root / rel + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("ok\n", encoding="utf-8") + + (root / "docs" / "releases" / "assets" / "news-digest-healthy-proof-summary-2026-03-27.json").write_text( + json.dumps({"artifact_type": "healthy-proof-summary"}, ensure_ascii=False, indent=2) + "\n", + encoding="utf-8", + ) + (root / "docs" / "releases" / "assets" / "news-digest-benchmark-summary-2026-03-27.json").write_text( + json.dumps({"artifact_type": "benchmark-summary"}, ensure_ascii=False, indent=2) + "\n", + encoding="utf-8", + ) + (root / "docs" / "releases" / "assets" / "news-digest-healthy-proof-gemini-2026-03-27.png").write_text( + "png\n", + encoding="utf-8", + ) + (root / "docs" / "assets" / "storefront" / "dashboard-home-live-1440x900.png").write_text("png\n", encoding="utf-8") + (root / "docs" / "assets" / "storefront" / "dashboard-command-tower-live-1440x900.png").write_text("png\n", encoding="utf-8") + (root / "docs" / "assets" / "storefront" / "dashboard-runs-live-1440x900.png").write_text("png\n", encoding="utf-8") + (root / "docs" / "assets" / "storefront" / "dashboard-live-healthy-loop.gif").write_text("gif\n", encoding="utf-8") + (root / "docs" / "releases" / "assets" / "news-digest-proof-pack-2026-03-27.json").write_text( + json.dumps( + { + "artifact_type": "news_digest_public_proof_pack", + "primary_assets": { + "proof_summary_markdown": "docs/releases/assets/news-digest-healthy-proof-2026-03-27.md", + "proof_summary_json": "docs/releases/assets/news-digest-healthy-proof-summary-2026-03-27.json", + "benchmark_summary_markdown": "docs/releases/assets/news-digest-benchmark-summary-2026-03-27.md", + "benchmark_summary_json": "docs/releases/assets/news-digest-benchmark-summary-2026-03-27.json", + "workflow_case_recap_markdown": "docs/releases/assets/news-digest-workflow-case-recap-2026-03-27.md", + "demo_status_markdown": "docs/assets/storefront/demo-status.md" + }, + "supporting_assets": { + "gemini_proof_screenshot": "docs/releases/assets/news-digest-healthy-proof-gemini-2026-03-27.png", + "dashboard_home_capture": "docs/assets/storefront/dashboard-home-live-1440x900.png", + "dashboard_command_tower_capture": "docs/assets/storefront/dashboard-command-tower-live-1440x900.png", + "dashboard_runs_capture": "docs/assets/storefront/dashboard-runs-live-1440x900.png", + "healthy_live_capture_gif": "docs/assets/storefront/dashboard-live-healthy-loop.gif" + } + }, + ensure_ascii=False, + indent=2, + ) + + "\n", + encoding="utf-8", + ) + + (root / "docs" / "use-cases" / "index.html").write_text( + """ +The current recap story now has one tracked news_digest Workflow Case asset, tracked healthy local captures and proof assets, and one remaining broader benchmark gap.
+The current benchmark story is a tracked single-run baseline, not a broad release average.
+Global proof-pack index across public proven and showcase bundles
+ """, + encoding="utf-8", + ) + (root / "docs" / "assets" / "storefront" / "demo-status.md").write_text( + """ + | Proof class | Current status | Notes | + | --- | --- | --- | + | Healthy backend-backed dashboard capture set | present | tracked English-first home, Command Tower session, and Runs captures from a clean local runtime root | + | Healthy backend-backed live GIF | present | tracked multi-page walkthrough of the official first public happy path | + ## Truth Boundary + - these tracked captures are safe repo-side proof of a healthy local first public path, not proof of hosted production scale or live GitHub publication state. + """, + encoding="utf-8", + ) + (root / "docs" / "assets" / "storefront" / "live-capture-requirements.json").write_text( + json.dumps( + { + "artifact_type": "cortexpilot_storefront_live_capture_requirements", + "required_assets": [ + {"asset_id": "healthy_live_capture_gif", "status": "present"}, + {"asset_id": "healthy_english_first_dashboard_home_capture", "status": "present"}, + {"asset_id": "healthy_english_first_command_tower_capture", "status": "present"}, + {"asset_id": "healthy_english_first_runs_capture", "status": "present"}, + ], + }, + ensure_ascii=False, + indent=2, + ) + + "\n", + encoding="utf-8", + ) + (root / "docs" / "runbooks" / "storefront-share-kit.md").write_text( + """ + ## Proof Status By Asset Type + - Healthy backend-backed dashboard capture set + - Healthy backend-backed live GIF + ## Safe Post Angles + - safe to reference as repo-tracked proof, not as proof of live GitHub publication + """, + encoding="utf-8", + ) + + (root / "configs" / "storefront_proof_bundle_registry.json").write_text( + json.dumps( + { + "schema_version": 1, + "artifact_type": "cortexpilot_storefront_proof_bundle_registry", + "vocabulary_contract": { + "proven_workflow_label": "first proven workflow", + "proof_pack_label": "public proof pack", + "showcase_label": "showcase expansion", + }, + "bundles": [ + { + "bundle_id": "news_digest", + "task_template": "news_digest", + "proof_state": "release_proven", + "claim_scope": "official_first_public_baseline", + "authority_level": "repo_side_public_proof", + "public_entrypoint": "docs/use-cases/index.html", + "pack_manifest": "docs/releases/assets/news-digest-proof-pack-2026-03-27.json", + "capture_contract": { + "healthy_live_capture_gif_present": True, + "healthy_english_first_public_capture_set_present": True, + "current_tracked_dashboard_captures": "healthy_english_first_backend_backed_local", + }, + "missing_expected_artifacts": [ + "broader_multi_round_benchmark", + ], + }, + { + "bundle_id": "topic_brief", + "proof_state": "showcase_only", + "missing_expected_artifacts": ["dedicated_healthy_proof_summary"], + }, + { + "bundle_id": "page_brief", + "proof_state": "showcase_only", + "missing_expected_artifacts": ["dedicated_healthy_proof_summary"], + }, + ], + }, + ensure_ascii=False, + indent=2, + ) + + "\n", + encoding="utf-8", + ) + + generator = _load_generator_module() + generator.ROOT = root + generator.REGISTRY_PATH = root / "configs" / "storefront_proof_bundle_registry.json" + generator.OUTPUT_PATH = root / "docs" / "assets" / "storefront" / "proof-pack-index.json" + rendered = generator.build_index(generator._load_json(generator.REGISTRY_PATH)) + generator.OUTPUT_PATH.write_text(json.dumps(rendered, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + + +def test_storefront_proof_assets_gate_passes_with_expected_index(tmp_path: Path, monkeypatch) -> None: + module = _load_gate_module() + _write_fixture(tmp_path) + module.ROOT = tmp_path + module.PROOF_PACK_INDEX = tmp_path / "docs" / "assets" / "storefront" / "proof-pack-index.json" + module.DEMO_STATUS_PATH = tmp_path / "docs" / "assets" / "storefront" / "demo-status.md" + module.LIVE_CAPTURE_REQUIREMENTS_PATH = tmp_path / "docs" / "assets" / "storefront" / "live-capture-requirements.json" + module.SHARE_KIT_PATH = tmp_path / "docs" / "runbooks" / "storefront-share-kit.md" + module.USE_CASES_PATH = tmp_path / "docs" / "use-cases" / "index.html" + monkeypatch.setattr(sys, "argv", ["check_storefront_proof_assets.py"]) + assert module.main() == 0 + + +def test_storefront_proof_assets_gate_fails_when_news_digest_loses_release_proven( + tmp_path: Path, capsys, monkeypatch +) -> None: + module = _load_gate_module() + _write_fixture(tmp_path) + index_path = tmp_path / "docs" / "assets" / "storefront" / "proof-pack-index.json" + payload = json.loads(index_path.read_text(encoding="utf-8")) + payload["bundles"][0]["proof_state"] = "showcase_only" + index_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + + module.ROOT = tmp_path + module.PROOF_PACK_INDEX = index_path + module.DEMO_STATUS_PATH = tmp_path / "docs" / "assets" / "storefront" / "demo-status.md" + module.LIVE_CAPTURE_REQUIREMENTS_PATH = tmp_path / "docs" / "assets" / "storefront" / "live-capture-requirements.json" + module.SHARE_KIT_PATH = tmp_path / "docs" / "runbooks" / "storefront-share-kit.md" + module.USE_CASES_PATH = tmp_path / "docs" / "use-cases" / "index.html" + monkeypatch.setattr(sys, "argv", ["check_storefront_proof_assets.py"]) + + rc = module.main() + out = capsys.readouterr().out + assert rc == 1 + assert "release_proven" in out diff --git a/configs/docs_render_manifest.json b/configs/docs_render_manifest.json index 216a2e4..5872ba8 100644 --- a/configs/docs_render_manifest.json +++ b/configs/docs_render_manifest.json @@ -2,6 +2,21 @@ "schema_version": 1, "description": "Minimal generated-doc outputs kept after the public-surface reduction.", "entries": [ + { + "output_path": "docs/assets/storefront/proof-pack-index.json", + "mode": "full_render", + "source_inputs": [ + "scripts/generate_storefront_proof_pack_index.py", + "configs/storefront_proof_bundle_registry.json", + "docs/releases/assets/news-digest-proof-pack-2026-03-27.json" + ], + "generator": "python3 scripts/generate_storefront_proof_pack_index.py", + "freshness_strategy": "timestamp", + "authoritative_for": [ + "public_storefront_proof_pack_index" + ], + "human_editable_regions": "none" + }, { "output_path": "docs/governance/ui-button-coverage-matrix.md", "mode": "full_render", diff --git a/configs/env.registry.json b/configs/env.registry.json index a0b6212..efff430 100644 --- a/configs/env.registry.json +++ b/configs/env.registry.json @@ -6100,9 +6100,9 @@ "scope": "platform", "secret": false, "required": false, - "default": "1", + "default": "0", "owner": "platform", - "description": "Boolean switch controlling mandatory local verification bundle in pre-push gate.", + "description": "Controls the pre-push local verification profile. `0` keeps the default fast bundle, `1` enables the old strict local mirror, and `off` requires break-glass to skip local verification entirely.", "consumers": [ "scripts/pre_push_quality_gate.sh" ] diff --git a/configs/github_control_plane_policy.json b/configs/github_control_plane_policy.json index 0da3778..586c460 100644 --- a/configs/github_control_plane_policy.json +++ b/configs/github_control_plane_policy.json @@ -26,6 +26,22 @@ "required": true, "mode": "api" }, + "secret_scanning": { + "required": true, + "mode": "api" + }, + "secret_scanning_push_protection": { + "required": true, + "mode": "api" + }, + "secret_scanning_non_provider_patterns": { + "required": true, + "mode": "api" + }, + "secret_scanning_validity_checks": { + "required": true, + "mode": "api" + }, "dependabot_config": { "required": true, "mode": "repo_file", diff --git a/configs/storefront_proof_bundle_registry.json b/configs/storefront_proof_bundle_registry.json new file mode 100644 index 0000000..4c588fc --- /dev/null +++ b/configs/storefront_proof_bundle_registry.json @@ -0,0 +1,77 @@ +{ + "schema_version": 1, + "artifact_type": "cortexpilot_storefront_proof_bundle_registry", + "vocabulary_contract": { + "proven_workflow_label": "first proven workflow", + "proof_pack_label": "public proof pack", + "showcase_label": "showcase expansion" + }, + "bundles": [ + { + "bundle_id": "news_digest", + "task_template": "news_digest", + "proof_state": "release_proven", + "claim_scope": "official_first_public_baseline", + "authority_level": "repo_side_public_proof", + "public_entrypoint": "docs/use-cases/index.html", + "pack_manifest": "docs/releases/assets/news-digest-proof-pack-2026-03-27.json", + "safe_public_claims": [ + "news_digest is the only official release-proven first public baseline today", + "the public trust bundle includes proof summary, benchmark summary, Workflow Case recap, and demo-status ledger", + "the benchmark remains a single-run baseline" + ], + "forbidden_claims": [ + "topic_brief is equally release-proven today", + "page_brief is equally release-proven today", + "the current benchmark is a broad multi-run release average", + "current storefront captures prove a fully healthy end-to-end public session" + ], + "capture_contract": { + "healthy_live_capture_gif_present": true, + "healthy_english_first_public_capture_set_present": true, + "current_tracked_dashboard_captures": "healthy_english_first_backend_backed_local" + }, + "missing_expected_artifacts": [ + "broader_multi_round_benchmark" + ] + }, + { + "bundle_id": "topic_brief", + "task_template": "topic_brief", + "proof_state": "showcase_only", + "claim_scope": "public_showcase_path", + "authority_level": "repo_side_story_surface", + "public_entrypoint": "docs/use-cases/index.html", + "safe_public_claims": [ + "topic_brief is a public showcase path with search-backed evidence" + ], + "forbidden_claims": [ + "topic_brief is an equally release-proven baseline today" + ], + "missing_expected_artifacts": [ + "dedicated_healthy_proof_summary", + "dedicated_benchmark_summary", + "share_ready_recap" + ] + }, + { + "bundle_id": "page_brief", + "task_template": "page_brief", + "proof_state": "showcase_only", + "claim_scope": "public_showcase_path", + "authority_level": "repo_side_story_surface", + "public_entrypoint": "docs/use-cases/index.html", + "safe_public_claims": [ + "page_brief is a browser-backed public showcase path" + ], + "forbidden_claims": [ + "page_brief is an equally release-proven baseline today" + ], + "missing_expected_artifacts": [ + "dedicated_healthy_proof_summary", + "dedicated_benchmark_summary", + "share_ready_recap" + ] + } + ] +} diff --git a/docs/README.md b/docs/README.md index 54dd860..87c012f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,29 +15,19 @@ summary for contributors and maintainers. `configs/docs_nav_registry.json` is the machine source of truth for the active docs inventory. This file is the human-readable summary of that registry. -Public-fixture hygiene stays part of the docs contract too: orchestrator probe -and security fixtures must use synthetic token fragments plus generic workspace -sample roots instead of maintainer-local paths or raw token-looking literals. -The same contract now keeps `security_scan.sh` placeholder URIs pinned to the -exact `example.com` host and preserves `.jsonl` hints in portable temp-report -names, while `scripts/check_public_sensitive_surface.py` blocks tracked -local-path/PII/raw-token/runtime-file leaks on current public source surfaces. -The same gate family now includes `scripts/check_github_security_alerts.py`, -which fails closed on open GitHub secret-scanning and code-scanning alerts in -repo hygiene, host-compatible pre-commit quality, a dedicated pre-commit hook, -pre-push, and Quick Feedback. -Workflow governance now has matching repo-owned entrypoints too: -`scripts/check_workflow_static_security.sh` pairs `actionlint` with -`zizmor` in one fail-closed gate, `scripts/check_trivy_repo_scan.sh` -owns the repo-wide Trivy filesystem/dependency lane, and -`scripts/check_secret_scan_closeout.sh` is the closeout wrapper that can rerun -secret scanning against the current tree or a fresh clone. Pull requests also -run the official GitHub Dependency Review action under the repo-owned -`.github/dependency-review-config.yml` policy file. On GitHub-hosted -`trusted_pr`, `untrusted_pr`, and hosted-first `push_main` routes the live -alerts query stays advisory for both Quick Feedback and the hosted policy -slice because the integration token may not be allowed to read the alerts APIs -there and a fresh hosted `push_main` route may not have live analysis yet. +Daily local verification lives in the root [README](../README.md). Treat this +file as the docs inventory map, not as a second CI manual. + +For CI/security/documentation truth, prefer the machine-owned surfaces and +repo-owned gates instead of restating the same rules here: + +- `configs/docs_nav_registry.json` +- `configs/ci_governance_policy.json` +- `configs/github_control_plane_policy.json` +- `scripts/check_public_sensitive_surface.py` +- `scripts/check_workflow_static_security.sh` +- `scripts/check_trivy_repo_scan.sh` +- `scripts/check_secret_scan_closeout.sh` ## Repository Entry @@ -97,6 +87,8 @@ navigation set. - `docs/runbooks/github-storefront-manual-steps.md`: exact GitHub UI values and manual storefront steps - `docs/releases/first-public-release-draft.md`: repo-side draft source for the first public GitHub Release - `docs/assets/storefront/demo-status.md`: status ledger for tracked public demo and proof assets +- `docs/assets/storefront/proof-pack-index.json`: machine-readable public proof bundle index for proven and showcase storefront slices +- `docs/assets/storefront/live-capture-requirements.json`: machine-readable contract for the remaining healthy GIF and English-first public capture deliverables - `docs/assets/storefront/benchmark-methodology.md`: public benchmark evidence contract and wording boundary - `docs/architecture/ecosystem-and-builder-surfaces-v1.md`: ecosystem binding, first-run distribution loop, and current builder/client entry points - `docs/runbooks/render-hosted-operator-pilot.md`: repo-side Git-backed hosted operator blueprint for future guarded Render pilots; use it to stage env/health/rollback/support/security requirements without implying a live hosted service diff --git a/docs/assets/storefront/README.md b/docs/assets/storefront/README.md index 42c7de2..01e8a54 100644 --- a/docs/assets/storefront/README.md +++ b/docs/assets/storefront/README.md @@ -9,14 +9,17 @@ and social-preview use. - `first-loop-storyboard.svg`: shareable storyboard for the PM -> Command Tower -> Runs story - `first-loop-storyboard.png`: exportable static storyboard preview - `first-loop-storyboard.gif`: tracked storyboard animation (not a live product capture) -- `dashboard-home-live-1440x900.png`: tracked real screenshot from the dashboard home page in local degraded mode -- `dashboard-command-tower-live-1440x900.png`: tracked real Command Tower screenshot in local degraded mode -- `dashboard-runs-live-1440x900.png`: tracked real Runs screenshot in local degraded mode -- `dashboard-live-degraded-loop.gif`: tracked real dashboard GIF in local degraded mode +- `dashboard-home-live-1440x900.png`: tracked healthy English-first dashboard home screenshot +- `dashboard-command-tower-live-1440x900.png`: tracked healthy English-first Command Tower session screenshot +- `dashboard-runs-live-1440x900.png`: tracked healthy English-first Runs / Proof & Replay screenshot +- `dashboard-live-healthy-loop.gif`: tracked healthy backend-backed dashboard GIF +- `dashboard-live-degraded-loop.gif`: historical degraded dashboard GIF retained as a truth-boundary reference - `desktop-shell-live-1440x900.png`: tracked real screenshot from the desktop snapshot pipeline - `social-preview-source.svg`: source artwork for a GitHub social preview export - `social-preview-1280x640.png`: exported GitHub social preview candidate - `demo-status.md`: explicit status ledger for real demo/benchmark asset closure +- `proof-pack-index.json`: machine-readable public proof bundle index +- `live-capture-requirements.json`: machine-readable contract for the remaining healthy public capture deliverables ## Rules @@ -27,4 +30,7 @@ and social-preview use. - treat `first-loop-storyboard.gif` as a storyboard animation, not as a real live-product recording - label degraded local screenshots honestly when backend data is unavailable - label degraded local GIF captures honestly when backend data is unavailable +- label healthy local dashboard captures as repo-side local proof, not as proof of hosted production scale or live GitHub publication +- treat `proof-pack-index.json` as the public proof bundle SSOT for repo-tracked proof surfaces +- treat `live-capture-requirements.json` as the SSOT for the remaining healthy GIF / English-first capture deliverables - regenerate tracked exports on macOS with `bash scripts/export_storefront_assets.sh` (supports `inkscape` or `rsvg-convert` as optional SVG fallbacks) diff --git a/docs/assets/storefront/dashboard-command-tower-live-1440x900.png b/docs/assets/storefront/dashboard-command-tower-live-1440x900.png index f567bf4117db1214e0b93ca1e4ff099f97387bd3..a81524527574c34973b39c7dfaefc6ec9411c385 100644 GIT binary patch literal 216913 zcmZsC2Q*x5+b+=(HHjL8B!cJ?ZFEA2h!!n+ixLK-8@&XH8lprGq9n@b!i?T~2}YYy zM(@2dXD9FX{@;JrIhJKDteM&Sd7gdW*L{^w>Z*!l#Pq~CI5=cVFXi9h;1GPn!NL1* z^9Jyj2|nBq9Gu%YO7gN=o@txu1U^)qw4JkPqZ!J(&+k6Axp8ZoxBdMOO%IQp?Q0mE z#QuFCab-ISg?jk-;Wls5G!|}&VTH7DSCF3AYZbDf4ajYeSNi?0y#l?n(ZD?2o(Cekx5IX2|YiqVo6@SAM|0^tl
z8!)d^KE5zk@2BVKZt>x_9_r+=iupP|0f87BgKwd`*|boJB0cCv0D%8*Bs?lDtU8-!
zXpNSYmM+l(gW{xL1!5mt-CW!uI6U@BlM_2Qp@T%q$i$E4Nxz4Yk%q&%*`7HwZBp<%
z>(}~L*Trcq#%MTkJ>WiTPgu0h%2O>s<-G>IGxUWGJKqMKZLd?7xz-;)#yKWj!9Om3
za+vY5+l$V7Z#-!SHdBOAbv4({38C(pq3!;S`crdVOF)-;>FwZ9ygYDmevUj_cPcjl
z)_}PszELH)s`mPrhm0Di(9`)btD
=E0ly;v&gPHjyr!L08YuIn!iYmKS{u(RT)MqP^eyx!&DgSzy&l{JpsP%<*=!Y
zni@V<;X)8rmOx6!!OxVf^hOkw-J?C?S7&roObh|dqO!^Z65^*%%4quv5jGS~?uney
zB;}8=v8nU83%B^4PESvzzmk$xS*Mn KyAs0x9&QKv0+k5%=cD+s_dne+f(!od|vwuM;*3lrK2oB+16X$Bu(9W-Sy
z<9-LrgQ@>n9#pDxR&%FZgG?7!Q|a-J$R22u(BbNR0%7j6xi|gt{IN+%kKp_x8vgX-
z>S;nGMQ)+^)Z-_c{f5^+Tj1Y|LAk9iikKK!5u8IqtF5$amKIl{)V^PwpX$jWUucuO
z(EJve=udOJCH(en1Z#c{p{D__$7)0}NUe76O`6TI<8d)v6rYANU~Pe