Files
aios/tests/test_reopen_shadow_gate_contract.py
T
owner b70c89217b
autonomy-smoke-tests pr81 PASS evidence_sha256=e9b29f42e72804aedf5a269d4507aafa7270b39400c6923bbb014d8a335fe788
autonomy-unit-tests pr81 PASS evidence_sha256=068610c15e2716ad9dbda4df158f891693e53cb7da2c57aef3783e722fb6f354
campaign-approval-gate pr81 PASS evidence_sha256=0616d6aa11496391686dcbd658123e51d8d057e1f02661af7ef8512554e404d8
git-diff-check pr81 PASS evidence_sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
py-compile pr81 PASS evidence_sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
reconciliation-contract pr81 PASS evidence_sha256=f96a26b816cb58aec4639ca90e87da07ed8cf7d041d82fb67d755bbd5085dfca
release-state-machine pr81 PASS evidence_sha256=6cea88752b3e58ae5cee56ff3b1b13eab34117918d6bad9661194c031e2dc157
secret-scan pr81 PASS evidence_sha256=e85624b4ea35c4f7019bc9d9a3d7d44124afca891c3487fe9f550faf6c743245
signer-validator-contract pr81 PASS evidence_sha256=775b7aa27d0c17f48b3feff20c836f14b6ed8ebcb90ec7e0b76d2a13c579268a
aios.owner-final-merge-authorization-v2 real v2 contract tests passed
feat(governed): add /reopenOwnerApproval to silent-release allow-list (V2R2C2 v1.2)
Narrow v1.2 delta: add the Owner approval-reopen POST path to
SILENT_RELEASE_POST_PATHS so reopen can proceed in Shadow without enabling
AIOS_GITOPS_WRITES_ENABLED. Route handler, release:approve guard, global
write check and all other allow-list paths are unchanged.

Adds tests/test_reopen_shadow_gate_contract.py covering:
- reopen is a narrow silent-release POST (route requires release:approve)
- global write gate is not enabled by this source patch
- all existing silent-release POST paths remain present
2026-09-12 14:29:33 +08:00

76 lines
2.8 KiB
Python

from __future__ import annotations
import ast
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
STAGING = ROOT / "active-runtime" / "app" / "aios_model_api_staging.py"
if not STAGING.is_file():
pytest.skip(
"shadow-gate contract runs after package is applied to an AIOS checkout",
allow_module_level=True,
)
def _literal_string_set(node: ast.AST) -> set[str]:
"""Extract string literals from a set or frozenset({...}) literal.
The governed codebase represents allow-lists as ``frozenset({...})``; a
plain ``{...}`` set literal is also accepted. Only string literal members
are allowed in the silent-release allow-list.
"""
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id == "frozenset":
if len(node.args) == 1:
node = node.args[0]
if not isinstance(node, ast.Set):
raise AssertionError("SILENT_RELEASE_POST_PATHS must remain a set literal")
values: set[str] = set()
for elt in node.elts:
if not isinstance(elt, ast.Constant) or not isinstance(elt.value, str):
raise AssertionError("silent-release allow-list must contain only string literals")
values.add(elt.value)
return values
def test_reopen_is_narrow_silent_release_post():
tree = ast.parse(STAGING.read_text(encoding="utf-8"))
allow = None
for node in tree.body:
if not isinstance(node, ast.Assign):
continue
if any(isinstance(t, ast.Name) and t.id == "SILENT_RELEASE_POST_PATHS" for t in node.targets):
allow = _literal_string_set(node.value)
break
assert allow is not None
assert "/reopenOwnerApproval" in allow
# Existing governed silent-release paths must stay present.
assert {
"/createReleaseCandidate",
"/createReleaseIntent",
"/submitSignedApproval",
"/submitOwnerApproval",
"/startDeployment",
}.issubset(allow)
def test_reopen_route_still_requires_release_approve():
import re
source = STAGING.read_text(encoding="utf-8")
# Locate the route handler (the `== "/reopenOwnerApproval"` comparison),
# independent of single/double-quote style on the allow-list entry.
m = re.search(r'==\s*["\']/reopenOwnerApproval["\']', source)
assert m is not None, "reopen handler route comparison not found"
tail = source[m.start() : m.start() + 1200]
assert 'self.need("release:approve")' in tail
assert "reopen_owner_approval(silent_release_service(), data)" in tail
def test_global_write_gate_is_not_enabled_by_source_patch():
source = STAGING.read_text(encoding="utf-8")
# v1.2 must not hard-code or synthesize a global write enable.
assert 'AIOS_GITOPS_WRITES_ENABLED=true' not in source
assert 'AIOS_GITOPS_WRITES_ENABLED="true"' not in source