fix(staging_check): B6 reads registry from running staging instance env
All checks were successful
CI / test (push) Successful in 12s
CI / test (pull_request) Successful in 16s

B6 false-FAILed because it built the project registry from the
launcher process-env via a host-path hack (sys.path.insert +
importlib.reload), not from the running staging instance. Run from the
host, ORCH_PROJECTS_JSON is unset -> default ET+ORCH registry -> false
FAIL -> spurious deploy-staging -> development rollback.

Variant (v) per ADR-001: remove the host-path hack; canonically run the
suite INSIDE orchestrator-staging via docker exec so src.projects
resolves from /app (PYTHONPATH) with .env.staging. Verdict logic
extracted into pure _evaluate_b6(known) -> (passed, detail) +
_known_project_ids_from_registry() / _run_b6() with deterministic FAIL on
source unavailability. deployer.md and STAGING_CHECK.md updated to the
docker exec command. src/projects.py, .env* and checks A/B4/B5/C
untouched.

Refs: ORCH-048

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-06 07:03:31 +00:00
parent d6744c3c05
commit 28d019a1e2
4 changed files with 230 additions and 26 deletions

View File

@@ -8,8 +8,14 @@ Checks:
Block C — E2E (create task in SANDBOX → trigger pipeline via /webhook/plane
→ verify branch + job enqueued → CLEANUP in finally)
Usage (inside the container or with correct env set):
python3 scripts/staging_check.py [--base-url http://localhost:8501] [--mode stub|full-real]
Usage — CANONICAL: run INSIDE the orchestrator-staging container (ORCH-048, ADR-001)
so B6 reads the registry from the running instance's own env (.env.staging):
docker exec orchestrator-staging \
python3 /repos/orchestrator/scripts/staging_check.py \
--base-url http://localhost:8501 [--mode stub|full-real]
Running from the host leaves ORCH_PROJECTS_JSON unset → B6 falls back to the
default (ET+ORCH) registry → false FAIL. See docs/operations/STAGING_CHECK.md.
Exit code: 0 = all PASS, non-zero = at least one FAIL.
@@ -214,6 +220,59 @@ SANDBOX_PROJECT_ID = "8c5a3025-4f9d-4190-b79f-fa06276bb27e"
PROD_ET_PROJECT_ID = "7a79f0a9-5278-49cd-9007-9a338f238f9c"
PROD_ORCH_PROJECT_ID = "8da6aa25-a60e-44d6-a1e2-d8ae59aa7d6a"
B6_LABEL = "B6 Registry: sandbox present, prod ET/ORCH absent"
def _evaluate_b6(known: set[str]) -> tuple[bool, str]:
"""Pure verdict logic for the B6 registry-isolation check (ORCH-048).
PASS ⟺ SANDBOX ∈ known ∧ PROD_ET ∉ known ∧ PROD_ORCH ∉ known (TR-2).
``detail`` keeps the human-readable ``sandbox=…, prod-ET=…, prod-ORCH=…``
format (TR-3). Isolated from any I/O so both outcomes are unit-testable
without a live staging instance or docker (02-trz §9, ADR-001).
"""
sandbox_present = SANDBOX_PROJECT_ID in known
et_absent = PROD_ET_PROJECT_ID not in known
orch_absent = PROD_ORCH_PROJECT_ID not in known
passed = sandbox_present and et_absent and orch_absent
detail = (
f"sandbox={'YES' if sandbox_present else 'NO'}, "
f"prod-ET={'NO(good)' if et_absent else 'YES(BAD!)'}, "
f"prod-ORCH={'NO(good)' if orch_absent else 'YES(BAD!)'}"
)
return passed, detail
def _known_project_ids_from_registry() -> set[str]:
"""Registry of the *running staging instance* — its own process-env (ORCH-048).
The suite is canonically run INSIDE ``orchestrator-staging`` via
``docker exec`` (ADR-001), so ``src.projects`` resolves through the
container's ``PYTHONPATH=/app`` to ``/app/src/projects.py`` and reads
``ORCH_PROJECTS_JSON`` from ``.env.staging``. This reflects exactly the
registry the live instance serves webhooks with — no host-path hack, no HTTP
bootstrap dependency.
"""
from src.projects import known_plane_project_ids
return known_plane_project_ids()
def _run_b6(results: Results) -> None:
"""Run the B6 registry-isolation check and record its verdict.
Builds the known-id set from the running instance's registry and applies
``_evaluate_b6``. Any failure to obtain the registry yields a deterministic
FAIL with a clear detail (TR-4) — never an unhandled exception and never a
false PASS.
"""
try:
known = _known_project_ids_from_registry()
except Exception as e:
results.add(B6_LABEL, False, f"registry source unavailable: {e}")
return
passed, detail = _evaluate_b6(known)
results.add(B6_LABEL, passed, detail)
def block_b(results: Results):
print(f"\n{_BOLD}[Block B] ACCESS{_RESET}")
@@ -260,28 +319,11 @@ def block_b(results: Results):
except Exception as e:
results.add("B5 Gitea: orchestrator-sandbox accessible, push=true", False, str(e))
# B6 — Registry: sandbox in known IDs, prod ET/ORCH NOT in known IDs
try:
# Import from inside the container (script runs in /repos/orchestrator context)
sys.path.insert(0, "/repos/orchestrator")
# Force reload to pick up container env
import importlib
if "src.projects" in sys.modules:
importlib.reload(sys.modules["src.projects"])
from src.projects import known_plane_project_ids
known = known_plane_project_ids()
sandbox_present = SANDBOX_PROJECT_ID in known
et_absent = PROD_ET_PROJECT_ID not in known
orch_absent = PROD_ORCH_PROJECT_ID not in known
ok = sandbox_present and et_absent and orch_absent
detail = (
f"sandbox={'YES' if sandbox_present else 'NO'}, "
f"prod-ET={'NO(good)' if et_absent else 'YES(BAD!)'}, "
f"prod-ORCH={'NO(good)' if orch_absent else 'YES(BAD!)'}"
)
results.add("B6 Registry: sandbox present, prod ET/ORCH absent", ok, detail)
except Exception as e:
results.add("B6 Registry: sandbox present, prod ET/ORCH absent", False, str(e))
# B6 — Registry: sandbox in known IDs, prod ET/ORCH NOT in known IDs (ORCH-048).
# Reads the registry of the running staging instance from its own process-env
# (canonical: docker exec inside orchestrator-staging — ADR-001). No host-path
# hack; deterministic FAIL if the registry source is unavailable (TR-4).
_run_b6(results)
# ---------------------------------------------------------------------------