fix(pipeline): make deploy-staging gate conditional on self-hosting repo (ORCH-35)
All checks were successful
CI / test (push) Successful in 10s
CI / test (pull_request) Successful in 10s

This commit is contained in:
Dev Agent
2026-06-05 10:36:46 +03:00
parent e0b6e92b09
commit e0c14fae5f
2 changed files with 134 additions and 27 deletions

View File

@@ -441,6 +441,26 @@ def check_deploy_status(repo: str, work_item_id: str, branch: str | None = None)
# ---------------------------------------------------------------------------
# Self-hosting detection: staging-infra (localhost:8501) exists ONLY for the
# orchestrator repo itself (self-hosting). Other repos have no staging instance
# and their deployer prompts know nothing about it -- the gate must be a no-op
# for them. The repo value is the plain gitea repo name (ProjectConfig.repo),
# matching what _run_qg/advance_stage pass in. See ORCH-35 / PR #31.
# ---------------------------------------------------------------------------
SELF_HOSTING_REPO = "orchestrator"
def is_self_hosting_repo(repo: str) -> bool:
"""Return True iff repo is the self-hosted orchestrator (has staging infra).
Comparison is case-insensitive and strips whitespace for safety, but in
practice repo comes from the gitea webhook payload .repository.name which
is always lowercase (confirmed via projects.py registry entry).
"""
return (repo or "").strip().lower() == SELF_HOSTING_REPO.lower()
def _parse_staging_status(content: str) -> tuple[bool, str]:
"""Parse a 15-staging-log.md body and map its `staging_status:` frontmatter to a
quality-gate verdict. Reads ONLY the machine-readable YAML field, never prose.
@@ -505,16 +525,26 @@ def check_staging_status(repo: str, work_item_id: str, branch: str | None = None
Gate the deploy-staging -> deploy transition on the deployer's machine-readable
verdict in 15-staging-log.md frontmatter (staging_status: SUCCESS|FAILED).
Mirrors check_deploy_status (БАГ 8): reads ONLY the machine-readable YAML field,
never the body prose. The deployer runs the staging test suite against localhost:8501
and writes the verdict into 15-staging-log.md.
ORCH-35 conditional gate (Variant A):
- Non-self-hosting repos (anything other than "orchestrator") have no staging
instance and no deployer knowledge of it -> gate is an immediate pass.
- Self-hosting repo ("orchestrator") -> real check: reads ONLY the machine-
readable staging_status: field from YAML frontmatter, never body prose.
Lookup order: worktree -> origin/main -> not found.
Mirrors check_deploy_status (БАГ 8) for the self-hosting path.
Lookup order (self-hosting only): worktree -> origin/main -> not found.
Returns:
(True, ...) -> staging_status: SUCCESS
(True, "Staging gate N/A for <repo>") -> non-self-hosting repo (instant pass)
(True, ...) -> staging_status: SUCCESS (self-hosting path)
(False, ...) -> staging_status: FAILED, missing field, or no frontmatter
"""
# Variant A: non-self-hosting repos have no staging infra -- skip entirely.
if not is_self_hosting_repo(repo):
return True, f"Staging gate N/A for {repo}"
# Self-hosting (orchestrator) path: real verdict check.
repo_path = _repo_path(repo, branch)
log_path = os.path.join(repo_path, f"docs/work-items/{work_item_id}/15-staging-log.md")
@@ -526,7 +556,7 @@ def check_staging_status(repo: str, work_item_id: str, branch: str | None = None
return False, f"Error reading staging log: {e}"
return _parse_staging_status(content)
# Not in the feature worktree the deployer may have merged it into main.
# Not in the feature worktree -- the deployer may have merged it into main.
main_content = _staging_log_from_main(repo, work_item_id)
if main_content is not None:
return _parse_staging_status(main_content)