feat(governance): add product manifest standard readiness
This commit is contained in:
@@ -198,8 +198,14 @@ jobs:
|
||||
;;
|
||||
.gitea/workflows/cd.yaml)
|
||||
;;
|
||||
product.awoooi.yaml)
|
||||
;;
|
||||
docs/LOGBOOK.md)
|
||||
;;
|
||||
docs/schemas/product_awoooi_manifest_v1.schema.json)
|
||||
;;
|
||||
docs/operations/product-awoooi-manifest-standard.snapshot.json)
|
||||
;;
|
||||
docs/operations/awoooi-priority-work-order-readback.snapshot.json)
|
||||
;;
|
||||
docs/operations/p0-cicd-baseline-source-readiness.snapshot.json)
|
||||
@@ -220,6 +226,8 @@ jobs:
|
||||
;;
|
||||
apps/api/src/services/p0_cicd_baseline_source_readiness.py)
|
||||
;;
|
||||
apps/api/src/services/product_awoooi_manifest_standard.py)
|
||||
;;
|
||||
apps/api/src/api/v1/platform/events.py)
|
||||
;;
|
||||
apps/api/src/jobs/ai_slo_watchdog_job.py)
|
||||
@@ -256,6 +264,8 @@ jobs:
|
||||
;;
|
||||
apps/api/tests/test_p0_cicd_baseline_source_readiness_api.py)
|
||||
;;
|
||||
apps/api/tests/test_product_awoooi_manifest_standard_api.py)
|
||||
;;
|
||||
apps/api/tests/test_trust_drift_watchdog.py)
|
||||
;;
|
||||
apps/web/src/app/\[locale\]/governance/tabs/events-tab.tsx)
|
||||
@@ -368,6 +378,7 @@ jobs:
|
||||
src/services/delivery_closure_workbench.py \
|
||||
src/services/heartbeat_report_service.py \
|
||||
src/services/p0_cicd_baseline_source_readiness.py \
|
||||
src/services/product_awoooi_manifest_standard.py \
|
||||
src/services/platform_operator_service.py \
|
||||
src/services/telegram_gateway.py
|
||||
DATABASE_URL="${DATABASE_URL:-postgresql+asyncpg://ci:ci@localhost/ci}" \
|
||||
@@ -380,6 +391,7 @@ jobs:
|
||||
tests/test_delivery_closure_workbench_api.py \
|
||||
tests/e2e_network_test.py::TestHMACVerification::test_valid_hmac_signature \
|
||||
tests/test_p0_cicd_baseline_source_readiness_api.py \
|
||||
tests/test_product_awoooi_manifest_standard_api.py \
|
||||
tests/test_trust_drift_watchdog.py \
|
||||
../../ops/runner/test_read_public_gitea_actions_queue.py \
|
||||
../../ops/runner/test_cd_controlled_runtime_profile.py \
|
||||
|
||||
@@ -361,6 +361,9 @@ from src.services.package_supply_chain_inventory import (
|
||||
from src.services.p0_cicd_baseline_source_readiness import (
|
||||
load_latest_p0_cicd_baseline_source_readiness,
|
||||
)
|
||||
from src.services.product_awoooi_manifest_standard import (
|
||||
load_latest_product_awoooi_manifest_standard,
|
||||
)
|
||||
from src.services.product_code_review_gate import (
|
||||
load_latest_product_code_review_gate,
|
||||
)
|
||||
@@ -967,6 +970,35 @@ async def get_delivery_closure_workbench() -> dict[str, Any]:
|
||||
) from exc
|
||||
|
||||
|
||||
@router.get(
|
||||
"/product-awoooi-manifest-standard",
|
||||
response_model=dict[str, Any],
|
||||
summary="取得 P0-002 product.awoooi.yaml manifest standard readiness",
|
||||
description=(
|
||||
"讀取已提交的 P0-002 product.awoooi.yaml manifest standard readiness;"
|
||||
"此端點只檢查 AWOOOI manifest、schema 與 Data/Security/Runtime contract refs。"
|
||||
"它不建立 repo、不同步 refs、不觸發 workflow、不呼叫 GitHub、"
|
||||
"不讀 secret、不操作 host / K8s。"
|
||||
),
|
||||
)
|
||||
async def get_product_awoooi_manifest_standard() -> dict[str, Any]:
|
||||
"""回傳 P0-002 product.awoooi.yaml manifest standard readiness 只讀快照。"""
|
||||
try:
|
||||
payload = await asyncio.to_thread(load_latest_product_awoooi_manifest_standard)
|
||||
return redact_public_lan_topology(payload)
|
||||
except FileNotFoundError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=str(exc),
|
||||
) from exc
|
||||
except (json.JSONDecodeError, ValueError) as exc:
|
||||
logger.error("product_awoooi_manifest_standard_invalid", error=str(exc))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="P0-002 product.awoooi.yaml manifest standard 快照無效",
|
||||
) from exc
|
||||
|
||||
|
||||
@router.get(
|
||||
"/p0-cicd-baseline-source-readiness",
|
||||
response_model=dict[str, Any],
|
||||
|
||||
157
apps/api/src/services/product_awoooi_manifest_standard.py
Normal file
157
apps/api/src/services/product_awoooi_manifest_standard.py
Normal file
@@ -0,0 +1,157 @@
|
||||
"""P0-002 product.awoooi.yaml manifest standard readiness."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
|
||||
from src.services.snapshot_paths import default_operations_dir, resolve_repo_root
|
||||
|
||||
_DEFAULT_OPERATIONS_DIR = default_operations_dir(Path(__file__))
|
||||
_SNAPSHOT_FILE = "product-awoooi-manifest-standard.snapshot.json"
|
||||
_SCHEMA_VERSION = "product_awoooi_manifest_standard_readiness_v1"
|
||||
_MANIFEST_SCHEMA_VERSION = "product_awoooi_manifest_v1"
|
||||
|
||||
|
||||
def load_latest_product_awoooi_manifest_standard(
|
||||
operations_dir: Path | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Load and validate the committed P0-002 product manifest standard."""
|
||||
directory = operations_dir or _DEFAULT_OPERATIONS_DIR
|
||||
path = directory / _SNAPSHOT_FILE
|
||||
with path.open(encoding="utf-8") as handle:
|
||||
payload = json.load(handle)
|
||||
|
||||
if not isinstance(payload, dict):
|
||||
raise ValueError(f"{path}: expected JSON object")
|
||||
repo_root = resolve_repo_root(path)
|
||||
_enrich_manifest_readiness(payload, repo_root)
|
||||
_require_schema(payload, str(path))
|
||||
_require_operation_boundaries(payload, str(path))
|
||||
_require_rollup_consistency(payload, str(path))
|
||||
return payload
|
||||
|
||||
|
||||
def _enrich_manifest_readiness(payload: dict[str, Any], repo_root: Path) -> None:
|
||||
manifest_meta = _dict(payload.get("manifest"))
|
||||
manifest_path = repo_root / str(manifest_meta.get("path") or "")
|
||||
schema_path = repo_root / str(manifest_meta.get("schema_ref") or "")
|
||||
manifest = _load_yaml_object(manifest_path)
|
||||
|
||||
manifest_meta["present"] = manifest_path.is_file()
|
||||
manifest_meta["schema_present"] = schema_path.is_file()
|
||||
manifest_meta["schema_version"] = str(manifest.get("schema_version") or "")
|
||||
manifest_meta["product_id"] = str(_dict(manifest.get("product")).get("id") or "")
|
||||
manifest_meta["source_control_authority"] = str(
|
||||
_dict(manifest.get("source_control")).get("authority") or ""
|
||||
)
|
||||
manifest_meta["github_status"] = str(
|
||||
_dict(manifest.get("source_control")).get("github_status") or ""
|
||||
)
|
||||
|
||||
sections = _strings(payload.get("required_sections"))
|
||||
missing_sections = [section for section in sections if section not in manifest]
|
||||
refs = _strings(payload.get("required_contract_refs"))
|
||||
missing_refs = [ref for ref in refs if not (repo_root / ref).is_file()]
|
||||
|
||||
rollups = _dict(payload.get("rollups"))
|
||||
rollups["required_section_count"] = len(sections)
|
||||
rollups["present_required_section_count"] = len(sections) - len(missing_sections)
|
||||
rollups["missing_required_section_count"] = len(missing_sections)
|
||||
rollups["missing_required_sections"] = missing_sections
|
||||
rollups["required_contract_ref_count"] = len(refs)
|
||||
rollups["present_contract_ref_count"] = len(refs) - len(missing_refs)
|
||||
rollups["missing_contract_ref_count"] = len(missing_refs)
|
||||
rollups["missing_contract_refs"] = missing_refs
|
||||
rollups["hard_blocker_count"] = len(_list(payload.get("blockers")))
|
||||
rollups["next_action_count"] = len(_list(payload.get("next_actions")))
|
||||
ready_count = rollups["present_required_section_count"] + rollups[
|
||||
"present_contract_ref_count"
|
||||
]
|
||||
required_count = rollups["required_section_count"] + rollups[
|
||||
"required_contract_ref_count"
|
||||
]
|
||||
rollups["source_readiness_percent"] = round(
|
||||
ready_count / max(required_count, 1) * 100
|
||||
)
|
||||
payload["status"] = (
|
||||
"ready_for_product_manifest_adoption"
|
||||
if not missing_sections
|
||||
and not missing_refs
|
||||
and manifest_meta["present"]
|
||||
and manifest_meta["schema_present"]
|
||||
and manifest_meta["schema_version"] == _MANIFEST_SCHEMA_VERSION
|
||||
and manifest_meta["source_control_authority"] == "gitea"
|
||||
and manifest_meta["github_status"] == "stopped_retired_do_not_use"
|
||||
else "blocked_product_manifest_source_missing"
|
||||
)
|
||||
|
||||
|
||||
def _load_yaml_object(path: Path) -> dict[str, Any]:
|
||||
if not path.is_file():
|
||||
return {}
|
||||
with path.open(encoding="utf-8") as handle:
|
||||
loaded = yaml.safe_load(handle)
|
||||
if not isinstance(loaded, dict):
|
||||
raise ValueError(f"{path}: expected YAML object")
|
||||
return loaded
|
||||
|
||||
|
||||
def _require_schema(payload: dict[str, Any], label: str) -> None:
|
||||
actual = payload.get("schema_version")
|
||||
if actual != _SCHEMA_VERSION:
|
||||
raise ValueError(
|
||||
f"{label}: expected schema_version={_SCHEMA_VERSION}, got {actual!r}"
|
||||
)
|
||||
|
||||
|
||||
def _require_operation_boundaries(payload: dict[str, Any], label: str) -> None:
|
||||
boundaries = _dict(payload.get("operation_boundaries"))
|
||||
if boundaries.get("read_only_api_allowed") is not True:
|
||||
raise ValueError(f"{label}: read_only_api_allowed must be true")
|
||||
if boundaries.get("manifest_write_allowed") is not True:
|
||||
raise ValueError(f"{label}: manifest_write_allowed must be true for P0-002 source")
|
||||
blocked_flags = {
|
||||
"remaining_product_repo_write_allowed",
|
||||
"repo_creation_allowed",
|
||||
"refs_sync_allowed",
|
||||
"workflow_trigger_allowed",
|
||||
"github_api_allowed",
|
||||
"host_or_k8s_write_allowed",
|
||||
"secret_read_allowed",
|
||||
"raw_session_or_sqlite_read_allowed",
|
||||
}
|
||||
allowed = sorted(flag for flag in blocked_flags if boundaries.get(flag) is not False)
|
||||
if allowed:
|
||||
raise ValueError(f"{label}: operation boundaries must remain false: {allowed}")
|
||||
|
||||
|
||||
def _require_rollup_consistency(payload: dict[str, Any], label: str) -> None:
|
||||
rollups = _dict(payload.get("rollups"))
|
||||
sections = _strings(payload.get("required_sections"))
|
||||
refs = _strings(payload.get("required_contract_refs"))
|
||||
blockers = _list(payload.get("blockers"))
|
||||
next_actions = _list(payload.get("next_actions"))
|
||||
if rollups.get("required_section_count") != len(sections):
|
||||
raise ValueError(f"{label}: required_section_count mismatch")
|
||||
if rollups.get("required_contract_ref_count") != len(refs):
|
||||
raise ValueError(f"{label}: required_contract_ref_count mismatch")
|
||||
if rollups.get("hard_blocker_count") != len(blockers):
|
||||
raise ValueError(f"{label}: hard_blocker_count mismatch")
|
||||
if rollups.get("next_action_count") != len(next_actions):
|
||||
raise ValueError(f"{label}: next_action_count mismatch")
|
||||
|
||||
|
||||
def _dict(value: Any) -> dict[str, Any]:
|
||||
return value if isinstance(value, dict) else {}
|
||||
|
||||
|
||||
def _list(value: Any) -> list[Any]:
|
||||
return value if isinstance(value, list) else []
|
||||
|
||||
|
||||
def _strings(value: Any) -> list[str]:
|
||||
return [str(item) for item in _list(value) if str(item)]
|
||||
46
apps/api/tests/test_product_awoooi_manifest_standard_api.py
Normal file
46
apps/api/tests/test_product_awoooi_manifest_standard_api.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from src.api.v1.agents import router
|
||||
from src.services.product_awoooi_manifest_standard import (
|
||||
load_latest_product_awoooi_manifest_standard,
|
||||
)
|
||||
|
||||
|
||||
def test_product_awoooi_manifest_standard_loader_reports_ready_manifest():
|
||||
payload = load_latest_product_awoooi_manifest_standard()
|
||||
|
||||
assert payload["schema_version"] == "product_awoooi_manifest_standard_readiness_v1"
|
||||
assert payload["status"] == "ready_for_product_manifest_adoption"
|
||||
assert payload["priority"] == "P0-002"
|
||||
assert payload["readback"]["workplan_id"] == "P0-002"
|
||||
assert payload["manifest"]["present"] is True
|
||||
assert payload["manifest"]["schema_present"] is True
|
||||
assert payload["manifest"]["schema_version"] == "product_awoooi_manifest_v1"
|
||||
assert payload["manifest"]["product_id"] == "awoooi"
|
||||
assert payload["manifest"]["source_control_authority"] == "gitea"
|
||||
assert payload["manifest"]["github_status"] == "stopped_retired_do_not_use"
|
||||
assert payload["rollups"]["source_readiness_percent"] == 100
|
||||
assert payload["rollups"]["missing_required_section_count"] == 0
|
||||
assert payload["rollups"]["missing_contract_ref_count"] == 0
|
||||
assert payload["operation_boundaries"]["read_only_api_allowed"] is True
|
||||
assert payload["operation_boundaries"]["remaining_product_repo_write_allowed"] is False
|
||||
assert payload["operation_boundaries"]["github_api_allowed"] is False
|
||||
assert payload["operation_boundaries"]["secret_read_allowed"] is False
|
||||
|
||||
|
||||
def test_product_awoooi_manifest_standard_endpoint_returns_snapshot():
|
||||
app = FastAPI()
|
||||
app.include_router(router, prefix="/api/v1")
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.get("/api/v1/agents/product-awoooi-manifest-standard")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["schema_version"] == "product_awoooi_manifest_standard_readiness_v1"
|
||||
assert data["status"] == "ready_for_product_manifest_adoption"
|
||||
assert data["rollups"]["source_readiness_percent"] == 100
|
||||
assert data["operation_boundaries"]["repo_creation_allowed"] is False
|
||||
@@ -1,3 +1,21 @@
|
||||
## 2026-06-29 — 13:05 P0-002 product.awoooi.yaml manifest standard readiness
|
||||
|
||||
**完成內容**:
|
||||
- 新增根目錄 `product.awoooi.yaml`,作為 AWOOOI 自身產品 manifest 標準樣板;source control authority 固定為 Gitea,GitHub 狀態固定 `stopped_retired_do_not_use`。
|
||||
- 新增 `docs/schemas/product_awoooi_manifest_v1.schema.json` 與 `docs/operations/product-awoooi-manifest-standard.snapshot.json`。
|
||||
- 新增 `product_awoooi_manifest_standard` loader 與 `/api/v1/agents/product-awoooi-manifest-standard` 只讀 API,檢查 manifest、schema、Data / Security / Runtime contract refs 與 operation boundaries。
|
||||
- Gitea CD controlled-runtime profile 納入 P0-002 manifest/schema/snapshot/service/test,避免回退 full API suite;`ops/runner/test_cd_controlled_runtime_profile.py` 補測試鎖住此行為。
|
||||
|
||||
**驗證結果**:
|
||||
- `python3.11 -m json.tool docs/schemas/product_awoooi_manifest_v1.schema.json docs/operations/product-awoooi-manifest-standard.snapshot.json`:通過。
|
||||
- `python3.11` 解析 `product.awoooi.yaml`:通過。
|
||||
- `python3.11 -m py_compile apps/api/src/services/product_awoooi_manifest_standard.py apps/api/src/api/v1/agents.py`:通過。
|
||||
- `DATABASE_URL=postgresql+asyncpg://ci:ci@localhost/ci PYTHONPATH=apps/api python3.11 -m pytest apps/api/tests/test_product_awoooi_manifest_standard_api.py apps/api/tests/test_p0_cicd_baseline_source_readiness_api.py ops/runner/test_cd_controlled_runtime_profile.py -q`:`7 passed`。
|
||||
- CD controlled-runtime 同等測試清單:`78 passed`。
|
||||
- `node scripts/ci/check-gitea-step-env-secrets.js`、`python3.11 ops/runner/guard-gitea-runner-pressure.py --root .`、`git diff --check`:通過。
|
||||
|
||||
**邊界**:未使用 GitHub / `gh` / GitHub API;未建立 repo;未 sync refs;未 workflow_dispatch;未讀 token / cookie / session / secret / auth / `.env`;未操作 host / Docker / K8s / DB;未 force push。
|
||||
|
||||
## 2026-06-29 — 12:06 P0-004 CI/CD baseline source readiness readback
|
||||
|
||||
**完成內容**:
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"schema_version": "product_awoooi_manifest_standard_readiness_v1",
|
||||
"generated_at": "2026-06-29T13:05:00+08:00",
|
||||
"status": "ready_for_product_manifest_adoption",
|
||||
"priority": "P0-002",
|
||||
"scope": "product_manifest_standard",
|
||||
"readback": {
|
||||
"workplan_id": "P0-002",
|
||||
"workplan_title": "建立 product.awoooi.yaml 產品 manifest 標準",
|
||||
"scorecard_completion_percent": 55,
|
||||
"safe_next_step": "adopt_product_manifest_schema_for_remaining_products_without_repo_creation_or_secret_read"
|
||||
},
|
||||
"manifest": {
|
||||
"path": "product.awoooi.yaml",
|
||||
"schema_ref": "docs/schemas/product_awoooi_manifest_v1.schema.json",
|
||||
"product_id": "awoooi",
|
||||
"source_control_authority": "gitea",
|
||||
"github_status": "stopped_retired_do_not_use"
|
||||
},
|
||||
"required_sections": [
|
||||
"product",
|
||||
"source_control",
|
||||
"runtime",
|
||||
"contracts",
|
||||
"operation_boundaries"
|
||||
],
|
||||
"required_contract_refs": [
|
||||
"docs/evaluations/backup_dr_readiness_matrix_2026-06-04.json",
|
||||
"docs/security/SECURITY-SUPPLY-CHAIN-PROGRESS.md",
|
||||
"docs/evaluations/runtime_surface_inventory_2026-06-05.json"
|
||||
],
|
||||
"blockers": [],
|
||||
"next_actions": [
|
||||
"add_product_awoooi_manifest_to_remaining_product_repos_after_gitea_inventory_source_is_available",
|
||||
"keep_github_status_stopped_retired_do_not_use_for_all_manifests",
|
||||
"project_manifest_fields_to_product_governance_ui_after_schema_adoption"
|
||||
],
|
||||
"rollups": {
|
||||
"required_section_count": 5,
|
||||
"present_required_section_count": 5,
|
||||
"missing_required_section_count": 0,
|
||||
"required_contract_ref_count": 3,
|
||||
"present_contract_ref_count": 3,
|
||||
"missing_contract_ref_count": 0,
|
||||
"source_readiness_percent": 100,
|
||||
"hard_blocker_count": 0,
|
||||
"next_action_count": 3
|
||||
},
|
||||
"operation_boundaries": {
|
||||
"read_only_api_allowed": true,
|
||||
"manifest_write_allowed": true,
|
||||
"remaining_product_repo_write_allowed": false,
|
||||
"repo_creation_allowed": false,
|
||||
"refs_sync_allowed": false,
|
||||
"workflow_trigger_allowed": false,
|
||||
"github_api_allowed": false,
|
||||
"host_or_k8s_write_allowed": false,
|
||||
"secret_read_allowed": false,
|
||||
"raw_session_or_sqlite_read_allowed": false
|
||||
}
|
||||
}
|
||||
97
docs/schemas/product_awoooi_manifest_v1.schema.json
Normal file
97
docs/schemas/product_awoooi_manifest_v1.schema.json
Normal file
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "product_awoooi_manifest_v1",
|
||||
"title": "AWOOOI product manifest",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"schema_version",
|
||||
"product",
|
||||
"source_control",
|
||||
"runtime",
|
||||
"contracts",
|
||||
"operation_boundaries"
|
||||
],
|
||||
"properties": {
|
||||
"schema_version": {
|
||||
"const": "product_awoooi_manifest_v1"
|
||||
},
|
||||
"product": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["id", "display_name", "owner", "lifecycle", "priority"],
|
||||
"properties": {
|
||||
"id": { "type": "string", "minLength": 1 },
|
||||
"display_name": { "type": "string", "minLength": 1 },
|
||||
"owner": { "type": "string", "minLength": 1 },
|
||||
"lifecycle": { "enum": ["production", "development", "retired"] },
|
||||
"priority": { "enum": ["P0", "P1", "P2", "P3"] }
|
||||
}
|
||||
},
|
||||
"source_control": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"authority",
|
||||
"repo",
|
||||
"development_branch",
|
||||
"production_branch",
|
||||
"github_status"
|
||||
],
|
||||
"properties": {
|
||||
"authority": { "const": "gitea" },
|
||||
"repo": { "type": "string", "pattern": "^[^/]+/[^/]+$" },
|
||||
"development_branch": { "type": "string", "minLength": 1 },
|
||||
"production_branch": { "type": "string", "minLength": 1 },
|
||||
"github_status": { "const": "stopped_retired_do_not_use" }
|
||||
}
|
||||
},
|
||||
"runtime": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["production_url", "delivery_workbench_api"],
|
||||
"properties": {
|
||||
"production_url": { "type": "string", "pattern": "^https://[^\\s]+$" },
|
||||
"delivery_workbench_api": { "type": "string", "pattern": "^/api/v1/" }
|
||||
}
|
||||
},
|
||||
"contracts": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"data_contract_ref",
|
||||
"security_contract_ref",
|
||||
"runtime_inventory_ref"
|
||||
],
|
||||
"properties": {
|
||||
"data_contract_ref": { "type": "string", "minLength": 1 },
|
||||
"security_contract_ref": { "type": "string", "minLength": 1 },
|
||||
"runtime_inventory_ref": { "type": "string", "minLength": 1 }
|
||||
}
|
||||
},
|
||||
"operation_boundaries": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"read_only_manifest_allowed",
|
||||
"github_api_allowed",
|
||||
"repo_creation_allowed",
|
||||
"refs_sync_allowed",
|
||||
"workflow_trigger_allowed",
|
||||
"host_or_k8s_write_allowed",
|
||||
"secret_read_allowed",
|
||||
"raw_session_or_sqlite_read_allowed"
|
||||
],
|
||||
"properties": {
|
||||
"read_only_manifest_allowed": { "const": true },
|
||||
"github_api_allowed": { "const": false },
|
||||
"repo_creation_allowed": { "const": false },
|
||||
"refs_sync_allowed": { "const": false },
|
||||
"workflow_trigger_allowed": { "const": false },
|
||||
"host_or_k8s_write_allowed": { "const": false },
|
||||
"secret_read_allowed": { "const": false },
|
||||
"raw_session_or_sqlite_read_allowed": { "const": false }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,14 @@ def test_web_changes_stay_on_controlled_runtime_profile() -> None:
|
||||
assert "UI-only changes are verified by the" in text
|
||||
|
||||
|
||||
def test_product_manifest_changes_stay_on_controlled_runtime_profile() -> None:
|
||||
text = _workflow_text()
|
||||
assert "product.awoooi.yaml)" in text
|
||||
assert "docs/schemas/product_awoooi_manifest_v1.schema.json)" in text
|
||||
assert "apps/api/src/services/product_awoooi_manifest_standard.py)" in text
|
||||
assert "tests/test_product_awoooi_manifest_standard_api.py" in text
|
||||
|
||||
|
||||
def test_controlled_runtime_skips_b5_before_docker_socket_use() -> None:
|
||||
text = _workflow_text()
|
||||
b5_start = text.index("- name: Integration Tests (B5")
|
||||
|
||||
29
product.awoooi.yaml
Normal file
29
product.awoooi.yaml
Normal file
@@ -0,0 +1,29 @@
|
||||
schema_version: product_awoooi_manifest_v1
|
||||
product:
|
||||
id: awoooi
|
||||
display_name: AWOOOI / AiOps
|
||||
owner: wooo
|
||||
lifecycle: production
|
||||
priority: P0
|
||||
source_control:
|
||||
authority: gitea
|
||||
repo: wooo/awoooi
|
||||
development_branch: dev
|
||||
production_branch: main
|
||||
github_status: stopped_retired_do_not_use
|
||||
runtime:
|
||||
production_url: https://awoooi.wooo.work
|
||||
delivery_workbench_api: /api/v1/agents/delivery-closure-workbench
|
||||
contracts:
|
||||
data_contract_ref: docs/evaluations/backup_dr_readiness_matrix_2026-06-04.json
|
||||
security_contract_ref: docs/security/SECURITY-SUPPLY-CHAIN-PROGRESS.md
|
||||
runtime_inventory_ref: docs/evaluations/runtime_surface_inventory_2026-06-05.json
|
||||
operation_boundaries:
|
||||
read_only_manifest_allowed: true
|
||||
github_api_allowed: false
|
||||
repo_creation_allowed: false
|
||||
refs_sync_allowed: false
|
||||
workflow_trigger_allowed: false
|
||||
host_or_k8s_write_allowed: false
|
||||
secret_read_allowed: false
|
||||
raw_session_or_sqlite_read_allowed: false
|
||||
Reference in New Issue
Block a user