Compare commits
1 Commits
codex/gith
...
codex/awoo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d6788a9ee |
@@ -26,7 +26,7 @@ on:
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: awoooi-ubuntu
|
||||
runs-on: self-hosted
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -1245,12 +1245,6 @@ jobs:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Wait for Host Web Build Pressure
|
||||
# 2026-06-27 Codex: post-deploy Playwright smoke is browser-heavy too.
|
||||
# Refuse to add another smoke run while 110 already has CI/build/smoke
|
||||
# pressure; this gate is read-only and never kills other repo work.
|
||||
run: bash scripts/ci/wait-host-web-build-pressure.sh
|
||||
|
||||
- name: Get Commit Info
|
||||
id: commit
|
||||
run: |
|
||||
|
||||
@@ -38,6 +38,15 @@ from src.core.sse import get_publisher
|
||||
from src.services.agent_market_governance_snapshot import (
|
||||
load_latest_agent_market_governance_snapshot,
|
||||
)
|
||||
from src.services.ai_agent_market_radar_readback import (
|
||||
load_latest_ai_agent_market_radar_readback,
|
||||
)
|
||||
from src.services.ai_technology_radar_readback import (
|
||||
load_latest_ai_technology_radar_readback,
|
||||
)
|
||||
from src.services.ai_technology_report_cadence_readback import (
|
||||
load_latest_ai_technology_report_cadence_readback,
|
||||
)
|
||||
from src.services.agent_service import (
|
||||
AgentService,
|
||||
TaskState,
|
||||
@@ -79,6 +88,12 @@ from src.services.ai_agent_critic_reviewer_result_capture import (
|
||||
from src.services.ai_agent_deployment_layout import (
|
||||
load_latest_ai_agent_deployment_layout,
|
||||
)
|
||||
from src.services.awoooi_status_cleanup_dashboard import (
|
||||
load_latest_awoooi_status_cleanup_dashboard,
|
||||
)
|
||||
from src.services.github_target_private_backup_evidence_gate import (
|
||||
load_latest_github_target_private_backup_evidence_gate,
|
||||
)
|
||||
from src.services.ai_agent_failure_receipt_no_send_replay import (
|
||||
load_latest_ai_agent_failure_receipt_no_send_replay,
|
||||
)
|
||||
@@ -103,9 +118,6 @@ from src.services.ai_agent_live_read_model_gate import (
|
||||
from src.services.ai_agent_low_medium_risk_whitelist import (
|
||||
load_latest_ai_agent_low_medium_risk_whitelist,
|
||||
)
|
||||
from src.services.ai_agent_market_radar_readback import (
|
||||
load_latest_ai_agent_market_radar_readback,
|
||||
)
|
||||
from src.services.ai_agent_matched_playbook_learning_gap import (
|
||||
load_latest_ai_agent_matched_playbook_learning_gap,
|
||||
)
|
||||
@@ -295,15 +307,6 @@ from src.services.ai_agent_version_lifecycle_update_proposal import (
|
||||
from src.services.ai_provider_route_matrix import (
|
||||
load_latest_ai_provider_route_matrix,
|
||||
)
|
||||
from src.services.ai_technology_radar_readback import (
|
||||
load_latest_ai_technology_radar_readback,
|
||||
)
|
||||
from src.services.ai_technology_report_cadence_readback import (
|
||||
load_latest_ai_technology_report_cadence_readback,
|
||||
)
|
||||
from src.services.awoooi_status_cleanup_dashboard import (
|
||||
load_latest_awoooi_status_cleanup_dashboard,
|
||||
)
|
||||
from src.services.backup_dr_readiness_matrix import (
|
||||
load_latest_backup_dr_readiness_matrix,
|
||||
)
|
||||
@@ -316,9 +319,6 @@ from src.services.backup_notification_policy import (
|
||||
from src.services.backup_restore_drill_approval_package_template import (
|
||||
load_latest_backup_restore_drill_approval_package_template,
|
||||
)
|
||||
from src.services.delivery_closure_workbench import (
|
||||
load_delivery_closure_workbench,
|
||||
)
|
||||
from src.services.dependency_drift_check_plan import (
|
||||
load_latest_dependency_drift_check_plan,
|
||||
)
|
||||
@@ -331,16 +331,15 @@ from src.services.dependency_supply_chain_drift_monitor import (
|
||||
from src.services.dependency_upgrade_approval_package_template import (
|
||||
load_latest_dependency_upgrade_approval_package_template,
|
||||
)
|
||||
from src.services.delivery_closure_workbench import (
|
||||
load_delivery_closure_workbench,
|
||||
)
|
||||
from src.services.docker_build_surface_inventory import (
|
||||
load_latest_docker_build_surface_inventory,
|
||||
)
|
||||
from src.services.gitea_workflow_runner_health import (
|
||||
load_latest_gitea_workflow_runner_health,
|
||||
)
|
||||
from src.services.github_target_private_backup_evidence_gate import (
|
||||
load_latest_github_target_private_backup_evidence_gate,
|
||||
preflight_github_target_owner_response_submission,
|
||||
)
|
||||
from src.services.host_runaway_aiops_loop_readiness import (
|
||||
load_latest_host_runaway_aiops_loop_readiness,
|
||||
)
|
||||
@@ -991,42 +990,6 @@ async def get_github_target_private_backup_evidence_gate() -> dict[str, Any]:
|
||||
) from exc
|
||||
|
||||
|
||||
@router.post(
|
||||
"/github-target-owner-response-intake-preflight",
|
||||
response_model=dict[str, Any],
|
||||
summary="預檢 GitHub target owner response candidate",
|
||||
description=(
|
||||
"只驗證一份 GitHub target owner response candidate 是否符合 read-only intake 規則;"
|
||||
"此端點不持久化 submission、不呼叫 GitHub live API、不建立 repo、不改 visibility、不同步 refs、"
|
||||
"不觸發 workflow、不收 private clone URL credential 或任何 secret value。"
|
||||
),
|
||||
)
|
||||
async def preflight_github_target_owner_response_intake(
|
||||
submission: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
"""Validate a GitHub target owner response candidate without persisting it."""
|
||||
try:
|
||||
payload = await asyncio.to_thread(
|
||||
preflight_github_target_owner_response_submission,
|
||||
submission,
|
||||
)
|
||||
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(
|
||||
"github_target_owner_response_intake_preflight_invalid",
|
||||
error=str(exc),
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="GitHub target owner response intake preflight 無效",
|
||||
) from exc
|
||||
|
||||
|
||||
@router.get(
|
||||
"/agent-12-agent-war-room",
|
||||
response_model=dict[str, Any],
|
||||
|
||||
@@ -435,9 +435,6 @@ def _truth_status(
|
||||
approval_suppressed = _approval_suppresses_repair_execution(approvals)
|
||||
effective_ops = [] if approval_suppressed else _effective_execution_ops(automation_ops)
|
||||
has_execution_records = bool(effective_ops or repair_rows)
|
||||
latest_verification = str(
|
||||
_latest_verification_result(incident, evidence_rows) or ""
|
||||
).lower()
|
||||
stage = "received"
|
||||
stage_status = incident_status.lower()
|
||||
if incident_status in {"RESOLVED", "CLOSED"}:
|
||||
@@ -507,8 +504,7 @@ def _truth_status(
|
||||
if incident_status == "INVESTIGATING" and approvals:
|
||||
if execution_succeeded:
|
||||
blockers.append("incident_open_after_successful_execution")
|
||||
if latest_verification != "success":
|
||||
needs_human = True
|
||||
needs_human = True
|
||||
elif not has_execution_records:
|
||||
blockers.append("incident_still_investigating_after_approval")
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ because AWOOOI policy requires GitHub backup targets to be private.
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -23,39 +22,6 @@ _APPROVAL_PACKAGE_FILE = "github-target-repo-approval-package.snapshot.json"
|
||||
_PROBE_FILE = "github-target-probe.snapshot.json"
|
||||
_CONNECTOR_READBACK_FILE = "github-target-connector-readback.snapshot.json"
|
||||
_MISSING_SOURCE_READINESS_FILE = "github-target-missing-source-readiness.snapshot.json"
|
||||
_PREFLIGHT_SCHEMA_VERSION = "github_target_owner_response_intake_preflight_v1"
|
||||
_PREFLIGHT_MODE = "validate_owner_response_only_no_persist_no_github_write"
|
||||
_SUBMISSION_METADATA_FIELDS = {
|
||||
"github_repo",
|
||||
"response_id",
|
||||
"submission_mode",
|
||||
"template_id",
|
||||
}
|
||||
_FORBIDDEN_KEY_FRAGMENTS = {
|
||||
"api_request_body",
|
||||
"authorization_header",
|
||||
"cookie",
|
||||
"credential",
|
||||
"db_dump",
|
||||
"deploy_key",
|
||||
"git_object_pack",
|
||||
"password",
|
||||
"private_clone_url",
|
||||
"private_key",
|
||||
"repo_archive",
|
||||
"repo_creation_command",
|
||||
"secret_value",
|
||||
"session",
|
||||
"token_value",
|
||||
"visibility_change_command",
|
||||
}
|
||||
_SENSITIVE_VALUE_PATTERNS = (
|
||||
("github_token", re.compile(r"\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\b")),
|
||||
("github_fine_grained_token", re.compile(r"\bgithub_pat_[A-Za-z0-9_]{20,}\b")),
|
||||
("private_key_block", re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----")),
|
||||
("credentialed_url", re.compile(r"[a-z][a-z0-9+.-]*://[^/\s:@]+:[^@\s]+@")),
|
||||
("raw_internal_lan_address", re.compile(r"\b192\.168\.0\.\d+\b")),
|
||||
)
|
||||
|
||||
|
||||
def load_latest_github_target_private_backup_evidence_gate(
|
||||
@@ -90,127 +56,6 @@ def load_latest_github_target_private_backup_evidence_gate(
|
||||
)
|
||||
|
||||
|
||||
def preflight_github_target_owner_response_submission(
|
||||
submission: dict[str, Any],
|
||||
security_dir: Path | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Validate an owner response candidate without storing or executing it."""
|
||||
gate = load_latest_github_target_private_backup_evidence_gate(security_dir)
|
||||
intake = _dict(gate.get("owner_response_intake_readiness"))
|
||||
target_by_template = {
|
||||
str(target.get("owner_response_template_id")): _dict(target)
|
||||
for target in _list(gate.get("targets"))
|
||||
if target.get("owner_response_template_id")
|
||||
}
|
||||
payload = _dict(submission)
|
||||
allowed_modes = set(_strings(intake.get("allowed_submission_modes")))
|
||||
allowed_fields = set(_strings(intake.get("allowed_response_fields")))
|
||||
forbidden_payloads = set(_strings(intake.get("forbidden_payloads")))
|
||||
still_forbidden = set(_strings(intake.get("still_forbidden")))
|
||||
|
||||
submission_mode = str(payload.get("submission_mode") or "")
|
||||
candidate_responses = [_dict(row) for row in _list(payload.get("responses"))]
|
||||
mode_allowed = submission_mode in allowed_modes
|
||||
global_scan_payload = {
|
||||
key: value for key, value in payload.items() if key != "responses"
|
||||
}
|
||||
global_hits = _forbidden_payload_hits(
|
||||
global_scan_payload,
|
||||
forbidden_payloads=forbidden_payloads | still_forbidden,
|
||||
)
|
||||
response_results = [
|
||||
_preflight_owner_response_item(
|
||||
response=response,
|
||||
submission_mode=submission_mode,
|
||||
mode_allowed=mode_allowed,
|
||||
target_by_template=target_by_template,
|
||||
allowed_fields=allowed_fields,
|
||||
forbidden_payloads=forbidden_payloads | still_forbidden,
|
||||
)
|
||||
for response in candidate_responses
|
||||
]
|
||||
passed_count = sum(
|
||||
1 for row in response_results if row["accepted_for_read_only_intake"] is True
|
||||
)
|
||||
blocked_count = len(response_results) - passed_count
|
||||
global_blockers: list[str] = []
|
||||
if not mode_allowed:
|
||||
global_blockers.append("submission_mode_not_allowed")
|
||||
if not candidate_responses:
|
||||
global_blockers.append("response_items_missing")
|
||||
if global_hits:
|
||||
global_blockers.append("forbidden_payload_detected")
|
||||
|
||||
preflight_passed = (
|
||||
not global_blockers and candidate_responses and blocked_count == 0
|
||||
)
|
||||
return {
|
||||
"schema_version": _PREFLIGHT_SCHEMA_VERSION,
|
||||
"generated_at": gate.get("generated_at", ""),
|
||||
"status": "ready_for_read_only_owner_response_intake"
|
||||
if preflight_passed
|
||||
else "blocked_owner_response_intake_preflight",
|
||||
"mode": _PREFLIGHT_MODE,
|
||||
"summary": {
|
||||
"candidate_response_item_count": len(candidate_responses),
|
||||
"preflight_passed_response_item_count": passed_count,
|
||||
"preflight_blocked_response_item_count": blocked_count,
|
||||
"forbidden_payload_hit_count": len(global_hits)
|
||||
+ sum(len(row["forbidden_hits"]) for row in response_results),
|
||||
"unsupported_field_count": sum(
|
||||
len(row["unsupported_fields"]) for row in response_results
|
||||
),
|
||||
"missing_required_field_count": sum(
|
||||
len(row["missing_required_fields"]) for row in response_results
|
||||
),
|
||||
"owner_response_received_count": 0,
|
||||
"owner_response_accepted_count": 0,
|
||||
"safe_credential_accepted_evidence_count": 0,
|
||||
"github_missing_target_create_private_repo_ready_count": gate["summary"][
|
||||
"github_missing_target_create_private_repo_ready_count"
|
||||
],
|
||||
"github_missing_target_refs_sync_ready_count": gate["summary"][
|
||||
"github_missing_target_refs_sync_ready_count"
|
||||
],
|
||||
"execution_authorized": False,
|
||||
"write_performed": False,
|
||||
"github_api_write_allowed": False,
|
||||
"repo_creation_authorized": False,
|
||||
"visibility_change_authorized": False,
|
||||
"refs_sync_authorized": False,
|
||||
"secret_value_collection_allowed": False,
|
||||
},
|
||||
"submission_mode": submission_mode,
|
||||
"submission_mode_allowed": mode_allowed,
|
||||
"allowed_submission_modes": sorted(allowed_modes),
|
||||
"global_blockers": global_blockers,
|
||||
"global_forbidden_hits": global_hits,
|
||||
"responses": response_results,
|
||||
"operation_boundaries": {
|
||||
"preflight_only": True,
|
||||
"persist_submission_allowed": False,
|
||||
"read_only_markdown_response_allowed": True,
|
||||
"redacted_metadata_pointer_allowed": True,
|
||||
"github_api_write_allowed": False,
|
||||
"repo_creation_allowed": False,
|
||||
"visibility_change_allowed": False,
|
||||
"refs_sync_allowed": False,
|
||||
"workflow_trigger_allowed": False,
|
||||
"private_clone_url_collection_allowed": False,
|
||||
"secret_value_collection_allowed": False,
|
||||
},
|
||||
"authorization_flags": {
|
||||
"owner_response_execution_authorized": False,
|
||||
"repo_creation_authorized": False,
|
||||
"visibility_change_authorized": False,
|
||||
"refs_sync_authorized": False,
|
||||
"workflow_trigger_authorized": False,
|
||||
"secret_value_collection_allowed": False,
|
||||
"private_clone_url_collection_allowed": False,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def build_github_target_private_backup_evidence_gate(
|
||||
*,
|
||||
decision: dict[str, Any],
|
||||
@@ -248,14 +93,6 @@ def build_github_target_private_backup_evidence_gate(
|
||||
}
|
||||
owner_summary = _dict(owner_response.get("summary"))
|
||||
owner_response_intake_readiness = _owner_response_intake_readiness(owner_response)
|
||||
safe_credential_evidence_intake_readiness = (
|
||||
_safe_credential_evidence_intake_readiness(
|
||||
owner_response=owner_response,
|
||||
required_target_count=len(
|
||||
[row for row in decisions if row.get("approval_required") is True]
|
||||
),
|
||||
)
|
||||
)
|
||||
owner_template_by_repo = {
|
||||
str(row.get("github_repo")): _dict(row)
|
||||
for row in _list(owner_response.get("response_templates"))
|
||||
@@ -377,25 +214,6 @@ def build_github_target_private_backup_evidence_gate(
|
||||
- private_backup_verified_count,
|
||||
"safe_credential_required_count": len(approval_required_targets),
|
||||
"safe_credential_accepted_evidence_count": 0,
|
||||
"safe_credential_evidence_intake_ready": safe_credential_evidence_intake_readiness[
|
||||
"intake_ready"
|
||||
],
|
||||
"safe_credential_required_redacted_evidence_ref_count": safe_credential_evidence_intake_readiness[
|
||||
"required_redacted_evidence_ref_count"
|
||||
],
|
||||
"safe_credential_evidence_ref_rule_count": safe_credential_evidence_intake_readiness[
|
||||
"evidence_ref_rule_count"
|
||||
],
|
||||
"safe_credential_redaction_example_count": safe_credential_evidence_intake_readiness[
|
||||
"redaction_example_count"
|
||||
],
|
||||
"safe_credential_forbidden_payload_count": safe_credential_evidence_intake_readiness[
|
||||
"forbidden_payload_count"
|
||||
],
|
||||
"safe_credential_quarantine_lane_count": safe_credential_evidence_intake_readiness[
|
||||
"quarantine_lane_count"
|
||||
],
|
||||
"safe_credential_raw_payload_storage_allowed": False,
|
||||
"owner_response_received_count": _int(
|
||||
owner_summary.get("received_response_count")
|
||||
),
|
||||
@@ -446,7 +264,6 @@ def build_github_target_private_backup_evidence_gate(
|
||||
"public_repo_allowed": False,
|
||||
},
|
||||
"owner_response_intake_readiness": owner_response_intake_readiness,
|
||||
"safe_credential_evidence_intake_readiness": safe_credential_evidence_intake_readiness,
|
||||
"targets": targets,
|
||||
"acceptance_requirements": _acceptance_requirements(owner_response),
|
||||
"rejection_rules": _rejection_rules(owner_response),
|
||||
@@ -476,89 +293,6 @@ def build_github_target_private_backup_evidence_gate(
|
||||
}
|
||||
|
||||
|
||||
def _preflight_owner_response_item(
|
||||
*,
|
||||
response: dict[str, Any],
|
||||
submission_mode: str,
|
||||
mode_allowed: bool,
|
||||
target_by_template: dict[str, dict[str, Any]],
|
||||
allowed_fields: set[str],
|
||||
forbidden_payloads: set[str],
|
||||
) -> dict[str, Any]:
|
||||
template_id = str(response.get("template_id") or "")
|
||||
target = target_by_template.get(template_id, {})
|
||||
required_fields = set(_strings(target.get("owner_response_required_fields")))
|
||||
acceptable_decisions = set(
|
||||
_strings(target.get("owner_response_acceptable_decisions"))
|
||||
)
|
||||
response_fields = set(response)
|
||||
supported_fields = allowed_fields | _SUBMISSION_METADATA_FIELDS
|
||||
unsupported_fields = sorted(response_fields - supported_fields)
|
||||
missing_required_fields = sorted(
|
||||
field
|
||||
for field in required_fields
|
||||
if not _has_response_value(response.get(field))
|
||||
)
|
||||
evidence_refs = sorted(
|
||||
set(_response_strings(response.get("redacted_evidence_refs")))
|
||||
| set(_response_strings(response.get("evidence_refs")))
|
||||
)
|
||||
decision = str(response.get("decision") or "")
|
||||
blockers: list[str] = []
|
||||
if not mode_allowed:
|
||||
blockers.append("submission_mode_not_allowed")
|
||||
if not target:
|
||||
blockers.append("unknown_or_unrequested_template_id")
|
||||
if unsupported_fields:
|
||||
blockers.append("unsupported_response_fields")
|
||||
if missing_required_fields:
|
||||
blockers.append("required_fields_missing")
|
||||
if acceptable_decisions and decision not in acceptable_decisions:
|
||||
blockers.append("decision_not_allowed_for_template")
|
||||
if not evidence_refs:
|
||||
blockers.append("redacted_evidence_refs_missing")
|
||||
|
||||
forbidden_hits = _forbidden_payload_hits(
|
||||
response,
|
||||
forbidden_payloads=forbidden_payloads,
|
||||
)
|
||||
evidence_ref_hits = _evidence_ref_hits(evidence_refs)
|
||||
if forbidden_hits:
|
||||
blockers.append("forbidden_payload_detected")
|
||||
if evidence_ref_hits:
|
||||
blockers.append("unsafe_evidence_ref_detected")
|
||||
|
||||
accepted = not blockers
|
||||
return {
|
||||
"template_id": template_id,
|
||||
"github_repo": str(
|
||||
response.get("github_repo") or target.get("github_repo") or ""
|
||||
),
|
||||
"submission_mode": submission_mode,
|
||||
"status": "preflight_passed_read_only_intake_candidate"
|
||||
if accepted
|
||||
else "blocked_owner_response_candidate",
|
||||
"accepted_for_read_only_intake": accepted,
|
||||
"decision": decision,
|
||||
"allowed_decision_count": len(acceptable_decisions),
|
||||
"required_field_count": len(required_fields),
|
||||
"missing_required_fields": missing_required_fields,
|
||||
"unsupported_fields": unsupported_fields,
|
||||
"redacted_evidence_ref_count": len(evidence_refs),
|
||||
"redacted_evidence_refs": evidence_refs,
|
||||
"forbidden_hits": forbidden_hits,
|
||||
"evidence_ref_hits": evidence_ref_hits,
|
||||
"blockers": sorted(set(blockers)),
|
||||
"owner_response_received": False,
|
||||
"owner_response_accepted": False,
|
||||
"safe_credential_evidence_accepted": False,
|
||||
"execution_authorized": False,
|
||||
"repo_creation_authorized": False,
|
||||
"visibility_change_authorized": False,
|
||||
"refs_sync_authorized": False,
|
||||
}
|
||||
|
||||
|
||||
def _build_target(
|
||||
*,
|
||||
decision: dict[str, Any],
|
||||
@@ -633,21 +367,6 @@ def _build_target(
|
||||
"private_visibility_owner_evidence_ref": connector_readback.get("evidence_ref"),
|
||||
"safe_credential_evidence_status": "not_collected",
|
||||
"safe_credential_evidence_ref": None,
|
||||
"safe_credential_evidence_intake_ready": approval_required,
|
||||
"safe_credential_evidence_submission_status": "waiting_redacted_evidence_ref"
|
||||
if approval_required
|
||||
else "not_required",
|
||||
"safe_credential_required_redacted_evidence_ref": approval_required,
|
||||
"safe_credential_allowed_evidence_ref_types": [
|
||||
"repo_path",
|
||||
"snapshot_path",
|
||||
"redacted_metadata_pointer",
|
||||
]
|
||||
if approval_required
|
||||
else [],
|
||||
"safe_credential_raw_payload_storage_allowed": False,
|
||||
"safe_credential_private_clone_url_collection_allowed": False,
|
||||
"safe_credential_secret_value_collection_allowed": False,
|
||||
"owner_response_template_id": owner_response_template.get("template_id"),
|
||||
"owner_response_submission_status": "waiting_owner_response"
|
||||
if approval_required
|
||||
@@ -968,9 +687,6 @@ def _require_owner_response_intake_consistency(
|
||||
_dict(row) for row in _list(payload.get("intake_preflight_checks"))
|
||||
]
|
||||
acceptance_checks = [_dict(row) for row in _list(payload.get("acceptance_checks"))]
|
||||
redaction_examples = [
|
||||
_dict(row) for row in _list(payload.get("owner_response_redaction_examples"))
|
||||
]
|
||||
summary = _dict(payload.get("summary"))
|
||||
|
||||
if (
|
||||
@@ -996,10 +712,6 @@ def _require_owner_response_intake_consistency(
|
||||
raise ValueError(f"{label}: intake preflight check count must match checks")
|
||||
if _int(summary.get("acceptance_check_count")) != len(acceptance_checks):
|
||||
raise ValueError(f"{label}: acceptance check count must match checks")
|
||||
if _int(summary.get("owner_response_redaction_example_count")) != len(
|
||||
redaction_examples
|
||||
):
|
||||
raise ValueError(f"{label}: redaction example count must match examples")
|
||||
|
||||
template_ids = {
|
||||
str(row.get("template_id")) for row in templates if row.get("template_id")
|
||||
@@ -1011,18 +723,12 @@ def _require_owner_response_intake_consistency(
|
||||
)
|
||||
|
||||
executable_rows = [
|
||||
str(
|
||||
row.get("template_id")
|
||||
or row.get("check_id")
|
||||
or row.get("example_id")
|
||||
or "unknown"
|
||||
)
|
||||
str(row.get("template_id") or row.get("check_id") or "unknown")
|
||||
for row in [
|
||||
*templates,
|
||||
*collection_checks,
|
||||
*preflight_checks,
|
||||
*acceptance_checks,
|
||||
*redaction_examples,
|
||||
]
|
||||
if row.get("execution_authorized") is not False
|
||||
]
|
||||
@@ -1031,31 +737,6 @@ def _require_owner_response_intake_consistency(
|
||||
f"{label}: owner response intake rows must remain non-executing: {executable_rows}"
|
||||
)
|
||||
|
||||
raw_payload_examples = [
|
||||
str(row.get("example_id") or "unknown")
|
||||
for row in redaction_examples
|
||||
if row.get("stored_raw_payload_allowed") is not False
|
||||
]
|
||||
if raw_payload_examples:
|
||||
raise ValueError(
|
||||
f"{label}: redaction examples must not allow raw payload storage: {raw_payload_examples}"
|
||||
)
|
||||
|
||||
required_forbidden_payloads = {
|
||||
"token_value",
|
||||
"secret_value",
|
||||
"private_clone_url_credential",
|
||||
"repo_archive",
|
||||
"git_object_pack",
|
||||
}
|
||||
missing_forbidden_payloads = sorted(
|
||||
required_forbidden_payloads - set(_strings(packet.get("forbidden_payloads")))
|
||||
)
|
||||
if missing_forbidden_payloads:
|
||||
raise ValueError(
|
||||
f"{label}: forbidden payloads missing safe credential blockers: {missing_forbidden_payloads}"
|
||||
)
|
||||
|
||||
|
||||
def _owner_response_intake_readiness(owner_response: dict[str, Any]) -> dict[str, Any]:
|
||||
summary = _dict(owner_response.get("summary"))
|
||||
@@ -1124,104 +805,6 @@ def _owner_response_intake_readiness(owner_response: dict[str, Any]) -> dict[str
|
||||
}
|
||||
|
||||
|
||||
def _safe_credential_evidence_intake_readiness(
|
||||
*,
|
||||
owner_response: dict[str, Any],
|
||||
required_target_count: int,
|
||||
) -> dict[str, Any]:
|
||||
summary = _dict(owner_response.get("summary"))
|
||||
packet = _dict(owner_response.get("owner_response_request_packet"))
|
||||
evidence_ref_rules = _strings(packet.get("evidence_ref_rules"))
|
||||
redaction_examples = [
|
||||
_redaction_example_summary(row)
|
||||
for row in _list(owner_response.get("owner_response_redaction_examples"))
|
||||
]
|
||||
forbidden_payloads = _strings(packet.get("forbidden_payloads"))
|
||||
checks = [
|
||||
*[
|
||||
_dict(row)
|
||||
for row in _list(owner_response.get("owner_response_collection_checks"))
|
||||
],
|
||||
*[_dict(row) for row in _list(owner_response.get("intake_preflight_checks"))],
|
||||
*[_dict(row) for row in _list(owner_response.get("acceptance_checks"))],
|
||||
]
|
||||
quarantine_lanes = sorted(
|
||||
{
|
||||
str(row.get("failure_lane"))
|
||||
for row in checks
|
||||
if str(row.get("failure_lane") or "").startswith("quarantine")
|
||||
}
|
||||
)
|
||||
accepted_evidence_count = 0
|
||||
required_redacted_evidence_ref_count = (
|
||||
required_target_count - accepted_evidence_count
|
||||
)
|
||||
required_forbidden_payloads = {
|
||||
"token_value",
|
||||
"secret_value",
|
||||
"private_clone_url_credential",
|
||||
"repo_archive",
|
||||
"git_object_pack",
|
||||
}
|
||||
intake_ready = (
|
||||
packet.get("execution_authorized") is False
|
||||
and packet.get("not_approval") is True
|
||||
and bool(evidence_ref_rules)
|
||||
and bool(redaction_examples)
|
||||
and _int(summary.get("owner_response_redaction_example_count"))
|
||||
== len(redaction_examples)
|
||||
and required_forbidden_payloads.issubset(set(forbidden_payloads))
|
||||
)
|
||||
|
||||
return {
|
||||
"status": "ready_to_collect_redacted_evidence_refs_not_credentials"
|
||||
if intake_ready
|
||||
else "blocked_safe_credential_evidence_intake_contract_incomplete",
|
||||
"intake_ready": intake_ready,
|
||||
"required_target_count": required_target_count,
|
||||
"accepted_evidence_count": accepted_evidence_count,
|
||||
"required_redacted_evidence_ref_count": required_redacted_evidence_ref_count,
|
||||
"allowed_evidence_ref_types": [
|
||||
"repo_path",
|
||||
"snapshot_path",
|
||||
"redacted_metadata_pointer",
|
||||
],
|
||||
"evidence_ref_rule_count": len(evidence_ref_rules),
|
||||
"evidence_ref_rules": evidence_ref_rules,
|
||||
"redaction_example_count": len(redaction_examples),
|
||||
"redaction_examples": redaction_examples,
|
||||
"forbidden_payload_count": len(forbidden_payloads),
|
||||
"forbidden_payloads": forbidden_payloads,
|
||||
"quarantine_lane_count": len(quarantine_lanes),
|
||||
"quarantine_lanes": quarantine_lanes,
|
||||
"allowed_submission_modes": _strings(packet.get("allowed_submission_modes")),
|
||||
"stored_raw_payload_allowed": False,
|
||||
"secret_value_collection_allowed": False,
|
||||
"private_clone_url_collection_allowed": False,
|
||||
"credential_value_collection_allowed": False,
|
||||
"target_owner_request_dispatch_authorized": False,
|
||||
"action_buttons_allowed": False,
|
||||
"execution_authorized": False,
|
||||
"not_approval": True,
|
||||
}
|
||||
|
||||
|
||||
def _redaction_example_summary(value: Any) -> dict[str, Any]:
|
||||
row = _dict(value)
|
||||
return {
|
||||
"example_id": str(row.get("example_id") or ""),
|
||||
"display_order": _int(row.get("display_order")),
|
||||
"example_status": str(row.get("example_status") or ""),
|
||||
"category": str(row.get("category") or ""),
|
||||
"safe_response_shape": _strings(row.get("safe_response_shape")),
|
||||
"required_redactions": _strings(row.get("required_redactions")),
|
||||
"forbidden_raw_values": _strings(row.get("forbidden_raw_values")),
|
||||
"stored_raw_payload_allowed": False,
|
||||
"execution_authorized": False,
|
||||
"not_approval": row.get("not_approval") is True,
|
||||
}
|
||||
|
||||
|
||||
def _owner_response_template_summary(value: Any) -> dict[str, Any]:
|
||||
row = _dict(value)
|
||||
return {
|
||||
@@ -1290,113 +873,6 @@ def _rejection_rules(owner_response: dict[str, Any]) -> list[str]:
|
||||
]
|
||||
|
||||
|
||||
def _has_response_value(value: Any) -> bool:
|
||||
if isinstance(value, str):
|
||||
return bool(value.strip())
|
||||
if isinstance(value, list):
|
||||
return any(_has_response_value(item) for item in value)
|
||||
return value is not None
|
||||
|
||||
|
||||
def _response_strings(value: Any) -> list[str]:
|
||||
if isinstance(value, str):
|
||||
return [value] if value.strip() else []
|
||||
if isinstance(value, list):
|
||||
return [str(item) for item in value if str(item).strip()]
|
||||
return []
|
||||
|
||||
|
||||
def _forbidden_payload_hits(
|
||||
value: Any,
|
||||
*,
|
||||
forbidden_payloads: set[str],
|
||||
path: str = "$",
|
||||
) -> list[dict[str, str]]:
|
||||
hits: list[dict[str, str]] = []
|
||||
if isinstance(value, dict):
|
||||
for key, child in value.items():
|
||||
key_text = str(key)
|
||||
key_lower = key_text.lower()
|
||||
if (
|
||||
key_lower in forbidden_payloads
|
||||
or any(fragment in key_lower for fragment in _FORBIDDEN_KEY_FRAGMENTS)
|
||||
):
|
||||
hits.append(
|
||||
{
|
||||
"path": f"{path}.{key_text}",
|
||||
"kind": "forbidden_field",
|
||||
"match": key_text,
|
||||
}
|
||||
)
|
||||
hits.extend(
|
||||
_forbidden_payload_hits(
|
||||
child,
|
||||
forbidden_payloads=forbidden_payloads,
|
||||
path=f"{path}.{key_text}",
|
||||
)
|
||||
)
|
||||
return hits
|
||||
if isinstance(value, list):
|
||||
for index, child in enumerate(value):
|
||||
hits.extend(
|
||||
_forbidden_payload_hits(
|
||||
child,
|
||||
forbidden_payloads=forbidden_payloads,
|
||||
path=f"{path}[{index}]",
|
||||
)
|
||||
)
|
||||
return hits
|
||||
if isinstance(value, str):
|
||||
lowered = value.lower()
|
||||
for forbidden in sorted(forbidden_payloads):
|
||||
if forbidden and forbidden.lower() in lowered:
|
||||
hits.append(
|
||||
{
|
||||
"path": path,
|
||||
"kind": "forbidden_payload_label",
|
||||
"match": forbidden,
|
||||
}
|
||||
)
|
||||
for label, pattern in _SENSITIVE_VALUE_PATTERNS:
|
||||
if pattern.search(value):
|
||||
hits.append(
|
||||
{
|
||||
"path": path,
|
||||
"kind": label,
|
||||
"match": label,
|
||||
}
|
||||
)
|
||||
return hits
|
||||
|
||||
|
||||
def _evidence_ref_hits(evidence_refs: list[str]) -> list[dict[str, str]]:
|
||||
hits: list[dict[str, str]] = []
|
||||
for index, evidence_ref in enumerate(evidence_refs):
|
||||
if not (
|
||||
evidence_ref.startswith("docs/")
|
||||
or evidence_ref.startswith("reports/")
|
||||
or evidence_ref.startswith("owner-metadata:")
|
||||
or evidence_ref.startswith("redacted:")
|
||||
):
|
||||
hits.append(
|
||||
{
|
||||
"path": f"evidence_refs[{index}]",
|
||||
"kind": "unsupported_evidence_ref_scheme",
|
||||
"match": evidence_ref,
|
||||
}
|
||||
)
|
||||
for label, pattern in _SENSITIVE_VALUE_PATTERNS:
|
||||
if pattern.search(evidence_ref):
|
||||
hits.append(
|
||||
{
|
||||
"path": f"evidence_refs[{index}]",
|
||||
"kind": label,
|
||||
"match": label,
|
||||
}
|
||||
)
|
||||
return hits
|
||||
|
||||
|
||||
def _dict(value: Any) -> dict[str, Any]:
|
||||
return value if isinstance(value, dict) else {}
|
||||
|
||||
@@ -1412,6 +888,6 @@ def _strings(value: Any) -> list[str]:
|
||||
def _int(value: Any) -> int:
|
||||
if isinstance(value, bool):
|
||||
return int(value)
|
||||
if isinstance(value, int | float):
|
||||
if isinstance(value, (int, float)):
|
||||
return int(value)
|
||||
return 0
|
||||
|
||||
@@ -284,6 +284,7 @@ def _require_boundaries(payload: dict[str, Any]) -> None:
|
||||
summary = _summary(payload)
|
||||
for key in (
|
||||
"manager_registry_accepted_count",
|
||||
"post_enable_readback_passed_count",
|
||||
"runtime_gate_count",
|
||||
"host_write_authorized_count",
|
||||
"active_response_authorized_count",
|
||||
@@ -298,8 +299,7 @@ def _require_boundaries(payload: dict[str, Any]) -> None:
|
||||
passed = _int(summary.get("reviewer_validation_passed_count"))
|
||||
failed = _int(summary.get("reviewer_validation_failed_count"))
|
||||
quarantined = _int(summary.get("reviewer_validation_quarantined_count"))
|
||||
post_enable = _int(summary.get("post_enable_readback_passed_count"))
|
||||
if any(value < 0 for value in (received, accepted, ready, passed, failed, quarantined, post_enable)):
|
||||
if any(value < 0 for value in (received, accepted, ready, passed, failed, quarantined)):
|
||||
raise ValueError("Wazuh manager registry reviewer validation counters 不得為負數")
|
||||
if accepted > received:
|
||||
raise ValueError("owner_registry_export_accepted_count 不得大於 received_count")
|
||||
@@ -307,8 +307,6 @@ def _require_boundaries(payload: dict[str, Any]) -> None:
|
||||
raise ValueError("reviewer_validation_ready_count 不得大於 received_count")
|
||||
if passed > accepted:
|
||||
raise ValueError("reviewer_validation_passed_count 不得大於 accepted_count")
|
||||
if post_enable > passed:
|
||||
raise ValueError("post_enable_readback_passed_count 不得大於 reviewer_validation_passed_count")
|
||||
if failed and passed:
|
||||
raise ValueError("reviewer_validation_failed_count 與 passed_count 不得同時為正")
|
||||
if quarantined and accepted:
|
||||
|
||||
@@ -9237,10 +9237,6 @@ class TelegramGateway:
|
||||
incident_id=incident_id,
|
||||
truth_chain=truth_chain,
|
||||
)
|
||||
km_completion_summary = await _fetch_km_stale_completion_summary_for_incident(
|
||||
incident_id=incident_id,
|
||||
project_id=project_id or "awoooi",
|
||||
)
|
||||
source_extra = _callback_reply_source_envelope_extra(
|
||||
incident_id=incident_id,
|
||||
failure_context="controlled_apply_result",
|
||||
@@ -9249,7 +9245,6 @@ class TelegramGateway:
|
||||
chunk_count=1,
|
||||
callback_action="controlled_apply_result",
|
||||
parse_mode="HTML",
|
||||
km_stale_completion_summary=km_completion_summary,
|
||||
awooop_status_chain=status_snapshot,
|
||||
)
|
||||
lines = [
|
||||
|
||||
@@ -232,30 +232,6 @@ def test_truth_status_marks_open_incident_after_successful_execution() -> None:
|
||||
assert "incident_open_after_successful_execution" in status["blockers"]
|
||||
|
||||
|
||||
def test_truth_status_keeps_verified_controlled_apply_autonomous_when_incident_open() -> None:
|
||||
status = _truth_status(
|
||||
incident={
|
||||
"incident_id": "INC-OPEN-VERIFIED",
|
||||
"status": "INVESTIGATING",
|
||||
"verification_result": "success",
|
||||
},
|
||||
approvals=[{"status": "EXECUTION_SUCCESS", "action": "ansible controlled apply"}],
|
||||
evidence_rows=[{"sensors_attempted": 8, "sensors_succeeded": 6}],
|
||||
automation_ops=[],
|
||||
drift=None,
|
||||
drift_repeat_count=0,
|
||||
gateway_mcp_total=3,
|
||||
legacy_mcp_total=2,
|
||||
outbound_visible_total=1,
|
||||
auto_repair_executions=[{"success": True}],
|
||||
)
|
||||
|
||||
assert status["current_stage"] == "execution_succeeded"
|
||||
assert status["stage_status"] == "success"
|
||||
assert status["needs_human"] is False
|
||||
assert "incident_open_after_successful_execution" in status["blockers"]
|
||||
|
||||
|
||||
def test_truth_status_marks_repeated_pending_drift_as_human_needed() -> None:
|
||||
status = _truth_status(
|
||||
incident=None,
|
||||
|
||||
@@ -8,7 +8,6 @@ import pytest
|
||||
|
||||
from src.services.github_target_private_backup_evidence_gate import (
|
||||
load_latest_github_target_private_backup_evidence_gate,
|
||||
preflight_github_target_owner_response_submission,
|
||||
)
|
||||
from src.services.snapshot_paths import default_security_dir
|
||||
|
||||
@@ -56,17 +55,6 @@ def test_load_github_target_private_backup_evidence_gate_from_committed_snapshot
|
||||
assert snapshot["summary"]["github_missing_target_refs_sync_ready_count"] == 0
|
||||
assert snapshot["summary"]["private_backup_verified_count"] == 4
|
||||
assert snapshot["summary"]["private_visibility_verified_count"] == 4
|
||||
assert snapshot["summary"]["safe_credential_required_count"] == 9
|
||||
assert snapshot["summary"]["safe_credential_accepted_evidence_count"] == 0
|
||||
assert snapshot["summary"]["safe_credential_evidence_intake_ready"] is True
|
||||
assert (
|
||||
snapshot["summary"]["safe_credential_required_redacted_evidence_ref_count"] == 9
|
||||
)
|
||||
assert snapshot["summary"]["safe_credential_evidence_ref_rule_count"] == 5
|
||||
assert snapshot["summary"]["safe_credential_redaction_example_count"] == 5
|
||||
assert snapshot["summary"]["safe_credential_forbidden_payload_count"] == 15
|
||||
assert snapshot["summary"]["safe_credential_quarantine_lane_count"] == 1
|
||||
assert snapshot["summary"]["safe_credential_raw_payload_storage_allowed"] is False
|
||||
assert snapshot["summary"]["owner_response_request_ready"] is True
|
||||
assert snapshot["summary"]["owner_response_required_response_item_count"] == 9
|
||||
assert snapshot["summary"]["owner_response_requested_template_count"] == 9
|
||||
@@ -109,39 +97,6 @@ def test_load_github_target_private_backup_evidence_gate_from_committed_snapshot
|
||||
assert "private_clone_url_credential" in intake["forbidden_payloads"]
|
||||
assert "read_only_markdown_response" in intake["allowed_submission_modes"]
|
||||
assert "create_github_repo" in intake["still_forbidden"]
|
||||
safe_credential_intake = snapshot["safe_credential_evidence_intake_readiness"]
|
||||
assert (
|
||||
safe_credential_intake["status"]
|
||||
== "ready_to_collect_redacted_evidence_refs_not_credentials"
|
||||
)
|
||||
assert safe_credential_intake["intake_ready"] is True
|
||||
assert safe_credential_intake["required_target_count"] == 9
|
||||
assert safe_credential_intake["accepted_evidence_count"] == 0
|
||||
assert safe_credential_intake["required_redacted_evidence_ref_count"] == 9
|
||||
assert safe_credential_intake["evidence_ref_rule_count"] == 5
|
||||
assert safe_credential_intake["redaction_example_count"] == 5
|
||||
assert safe_credential_intake["forbidden_payload_count"] == 15
|
||||
assert safe_credential_intake["quarantine_lane_count"] == 1
|
||||
assert "quarantine_sensitive_payload" in safe_credential_intake["quarantine_lanes"]
|
||||
assert safe_credential_intake["stored_raw_payload_allowed"] is False
|
||||
assert safe_credential_intake["secret_value_collection_allowed"] is False
|
||||
assert safe_credential_intake["private_clone_url_collection_allowed"] is False
|
||||
assert safe_credential_intake["credential_value_collection_allowed"] is False
|
||||
assert safe_credential_intake["execution_authorized"] is False
|
||||
assert safe_credential_intake["not_approval"] is True
|
||||
assert (
|
||||
"private_clone_url_credential" in safe_credential_intake["forbidden_payloads"]
|
||||
)
|
||||
assert "repo_archive" in safe_credential_intake["forbidden_payloads"]
|
||||
assert "git_object_pack" in safe_credential_intake["forbidden_payloads"]
|
||||
assert (
|
||||
"redacted_metadata_pointer"
|
||||
in safe_credential_intake["allowed_evidence_ref_types"]
|
||||
)
|
||||
first_redaction_example = safe_credential_intake["redaction_examples"][0]
|
||||
assert first_redaction_example["example_id"] == "redaction-github-target-doc-ref"
|
||||
assert first_redaction_example["stored_raw_payload_allowed"] is False
|
||||
assert first_redaction_example["execution_authorized"] is False
|
||||
|
||||
targets = {target["github_repo"]: target for target in snapshot["targets"]}
|
||||
assert targets["owenhytsai/awoooi"]["visibility_evidence_status"] == (
|
||||
@@ -157,28 +112,6 @@ def test_load_github_target_private_backup_evidence_gate_from_committed_snapshot
|
||||
targets["owenhytsai/awoooi"]["owner_response_submission_status"]
|
||||
== "waiting_owner_response"
|
||||
)
|
||||
assert (
|
||||
targets["owenhytsai/awoooi"]["safe_credential_evidence_submission_status"]
|
||||
== "waiting_redacted_evidence_ref"
|
||||
)
|
||||
assert (
|
||||
"redacted_metadata_pointer"
|
||||
in targets["owenhytsai/awoooi"]["safe_credential_allowed_evidence_ref_types"]
|
||||
)
|
||||
assert (
|
||||
targets["owenhytsai/awoooi"]["safe_credential_raw_payload_storage_allowed"]
|
||||
is False
|
||||
)
|
||||
assert (
|
||||
targets["owenhytsai/awoooi"][
|
||||
"safe_credential_private_clone_url_collection_allowed"
|
||||
]
|
||||
is False
|
||||
)
|
||||
assert (
|
||||
targets["owenhytsai/awoooi"]["safe_credential_secret_value_collection_allowed"]
|
||||
is False
|
||||
)
|
||||
assert targets["owenhytsai/awoooi"]["owner_response_execution_authorized"] is False
|
||||
assert (
|
||||
"canonical_source"
|
||||
@@ -219,14 +152,6 @@ def test_load_github_target_private_backup_evidence_gate_from_committed_snapshot
|
||||
targets["nexu-io/open-design"]["owner_response_submission_status"]
|
||||
== "not_required"
|
||||
)
|
||||
assert (
|
||||
targets["nexu-io/open-design"]["safe_credential_evidence_submission_status"]
|
||||
== "not_required"
|
||||
)
|
||||
assert (
|
||||
targets["nexu-io/open-design"]["safe_credential_allowed_evidence_ref_types"]
|
||||
== []
|
||||
)
|
||||
|
||||
|
||||
def test_github_target_private_backup_gate_rejects_runtime_authorization(tmp_path):
|
||||
@@ -257,45 +182,6 @@ def test_github_target_private_backup_gate_rejects_owner_response_execution_pack
|
||||
load_latest_github_target_private_backup_evidence_gate(tmp_path)
|
||||
|
||||
|
||||
def test_github_target_private_backup_gate_rejects_raw_payload_redaction_example(
|
||||
tmp_path,
|
||||
):
|
||||
_copy_security_snapshots(tmp_path)
|
||||
owner_response_path = (
|
||||
tmp_path / "github-target-owner-decision-response.snapshot.json"
|
||||
)
|
||||
owner_response = json.loads(owner_response_path.read_text(encoding="utf-8"))
|
||||
owner_response["owner_response_redaction_examples"][0][
|
||||
"stored_raw_payload_allowed"
|
||||
] = True
|
||||
owner_response_path.write_text(json.dumps(owner_response), encoding="utf-8")
|
||||
|
||||
with pytest.raises(ValueError, match="redaction examples"):
|
||||
load_latest_github_target_private_backup_evidence_gate(tmp_path)
|
||||
|
||||
|
||||
def test_github_target_private_backup_gate_requires_safe_credential_blockers(
|
||||
tmp_path,
|
||||
):
|
||||
_copy_security_snapshots(tmp_path)
|
||||
owner_response_path = (
|
||||
tmp_path / "github-target-owner-decision-response.snapshot.json"
|
||||
)
|
||||
owner_response = json.loads(owner_response_path.read_text(encoding="utf-8"))
|
||||
forbidden_payloads = owner_response["owner_response_request_packet"][
|
||||
"forbidden_payloads"
|
||||
]
|
||||
owner_response["owner_response_request_packet"]["forbidden_payloads"] = [
|
||||
payload
|
||||
for payload in forbidden_payloads
|
||||
if payload != "private_clone_url_credential"
|
||||
]
|
||||
owner_response_path.write_text(json.dumps(owner_response), encoding="utf-8")
|
||||
|
||||
with pytest.raises(ValueError, match="forbidden payloads"):
|
||||
load_latest_github_target_private_backup_evidence_gate(tmp_path)
|
||||
|
||||
|
||||
def test_github_target_private_backup_gate_requires_decision_rollup_consistency(
|
||||
tmp_path,
|
||||
):
|
||||
@@ -322,62 +208,6 @@ def test_github_target_private_backup_gate_rejects_missing_source_write_flags(tm
|
||||
load_latest_github_target_private_backup_evidence_gate(tmp_path)
|
||||
|
||||
|
||||
def test_github_target_owner_response_preflight_accepts_redacted_evidence_refs():
|
||||
preflight = preflight_github_target_owner_response_submission(
|
||||
_valid_owner_response_submission()
|
||||
)
|
||||
|
||||
assert (
|
||||
preflight["schema_version"]
|
||||
== "github_target_owner_response_intake_preflight_v1"
|
||||
)
|
||||
assert preflight["status"] == "ready_for_read_only_owner_response_intake"
|
||||
assert preflight["mode"] == "validate_owner_response_only_no_persist_no_github_write"
|
||||
assert preflight["summary"]["candidate_response_item_count"] == 1
|
||||
assert preflight["summary"]["preflight_passed_response_item_count"] == 1
|
||||
assert preflight["summary"]["preflight_blocked_response_item_count"] == 0
|
||||
assert preflight["summary"]["owner_response_received_count"] == 0
|
||||
assert preflight["summary"]["owner_response_accepted_count"] == 0
|
||||
assert preflight["summary"]["safe_credential_accepted_evidence_count"] == 0
|
||||
assert preflight["summary"]["github_api_write_allowed"] is False
|
||||
assert preflight["summary"]["repo_creation_authorized"] is False
|
||||
assert preflight["summary"]["refs_sync_authorized"] is False
|
||||
assert preflight["operation_boundaries"]["persist_submission_allowed"] is False
|
||||
assert preflight["operation_boundaries"]["github_api_write_allowed"] is False
|
||||
assert preflight["operation_boundaries"]["private_clone_url_collection_allowed"] is False
|
||||
assert preflight["authorization_flags"]["owner_response_execution_authorized"] is False
|
||||
assert preflight["responses"][0]["accepted_for_read_only_intake"] is True
|
||||
assert preflight["responses"][0]["owner_response_received"] is False
|
||||
assert preflight["responses"][0]["owner_response_accepted"] is False
|
||||
|
||||
|
||||
def test_github_target_owner_response_preflight_blocks_credentials_and_commands():
|
||||
submission = _valid_owner_response_submission()
|
||||
submission["responses"][0]["private_clone_url_credential"] = (
|
||||
"https://owner:ghp_1234567890abcdefghijklmnopqrstu@github.com/owenhytsai/awoooi.git"
|
||||
)
|
||||
submission["responses"][0]["repo_creation_command"] = (
|
||||
"gh repo create owenhytsai/awoooi --private"
|
||||
)
|
||||
|
||||
preflight = preflight_github_target_owner_response_submission(submission)
|
||||
|
||||
assert preflight["status"] == "blocked_owner_response_intake_preflight"
|
||||
assert preflight["summary"]["candidate_response_item_count"] == 1
|
||||
assert preflight["summary"]["preflight_passed_response_item_count"] == 0
|
||||
assert preflight["summary"]["preflight_blocked_response_item_count"] == 1
|
||||
assert preflight["summary"]["forbidden_payload_hit_count"] >= 3
|
||||
assert preflight["summary"]["owner_response_received_count"] == 0
|
||||
assert preflight["summary"]["owner_response_accepted_count"] == 0
|
||||
assert preflight["summary"]["github_api_write_allowed"] is False
|
||||
response = preflight["responses"][0]
|
||||
assert response["accepted_for_read_only_intake"] is False
|
||||
assert "forbidden_payload_detected" in response["blockers"]
|
||||
assert "unsupported_response_fields" in response["blockers"]
|
||||
assert response["execution_authorized"] is False
|
||||
assert response["repo_creation_authorized"] is False
|
||||
|
||||
|
||||
def _copy_security_snapshots(tmp_path: Path) -> None:
|
||||
source_dir = default_security_dir(Path(__file__))
|
||||
for filename in (
|
||||
@@ -389,34 +219,3 @@ def _copy_security_snapshots(tmp_path: Path) -> None:
|
||||
"github-target-missing-source-readiness.snapshot.json",
|
||||
):
|
||||
shutil.copy(source_dir / filename, tmp_path / filename)
|
||||
|
||||
|
||||
def _valid_owner_response_submission() -> dict[str, object]:
|
||||
return {
|
||||
"submission_mode": "read_only_markdown_response",
|
||||
"responses": [
|
||||
{
|
||||
"template_id": "target-awoooi-refs-blocked",
|
||||
"github_repo": "owenhytsai/awoooi",
|
||||
"owner_role_or_team": "platform-owner",
|
||||
"decision": "hold_pending_refs_truth",
|
||||
"decision_reason": "Need refs truth review before any sync action.",
|
||||
"affected_scope": "awoooi github backup target",
|
||||
"redacted_evidence_refs": [
|
||||
"docs/security/GITEA-GITHUB-MIGRATION-SNAPSHOT.md",
|
||||
"docs/security/source-control-ref-detail-diff.snapshot.json",
|
||||
],
|
||||
"evidence_refs": [
|
||||
"docs/security/source-control-workflow-secret-name-inventory.snapshot.json"
|
||||
],
|
||||
"followup_owner": "platform-owner",
|
||||
"rollback_owner": "platform-owner",
|
||||
"maintenance_window": "not_authorized",
|
||||
"validation_plan": "read-only refs truth review only",
|
||||
"canonical_source": "gitea_main",
|
||||
"github_target_disposition": "existing_private_candidate",
|
||||
"visibility_review_owner": "platform-owner",
|
||||
"refs_truth_review_owner": "platform-owner",
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
@@ -27,13 +27,6 @@ def test_github_target_private_backup_evidence_gate_endpoint_returns_read_only_g
|
||||
assert data["summary"]["github_missing_target_refs_sync_ready_count"] == 0
|
||||
assert data["summary"]["private_backup_verified_count"] == 4
|
||||
assert data["summary"]["private_visibility_verified_count"] == 4
|
||||
assert data["summary"]["safe_credential_evidence_intake_ready"] is True
|
||||
assert data["summary"]["safe_credential_required_redacted_evidence_ref_count"] == 9
|
||||
assert data["summary"]["safe_credential_evidence_ref_rule_count"] == 5
|
||||
assert data["summary"]["safe_credential_redaction_example_count"] == 5
|
||||
assert data["summary"]["safe_credential_forbidden_payload_count"] == 15
|
||||
assert data["summary"]["safe_credential_quarantine_lane_count"] == 1
|
||||
assert data["summary"]["safe_credential_raw_payload_storage_allowed"] is False
|
||||
assert data["summary"]["owner_response_request_ready"] is True
|
||||
assert data["summary"]["owner_response_required_response_item_count"] == 9
|
||||
assert data["summary"]["owner_response_requested_template_count"] == 9
|
||||
@@ -64,88 +57,5 @@ def test_github_target_private_backup_evidence_gate_endpoint_returns_read_only_g
|
||||
assert intake["forbidden_payload_count"] == 15
|
||||
assert intake["execution_authorized"] is False
|
||||
assert intake["not_approval"] is True
|
||||
safe_credential_intake = data["safe_credential_evidence_intake_readiness"]
|
||||
assert (
|
||||
safe_credential_intake["status"]
|
||||
== "ready_to_collect_redacted_evidence_refs_not_credentials"
|
||||
)
|
||||
assert safe_credential_intake["intake_ready"] is True
|
||||
assert safe_credential_intake["required_target_count"] == 9
|
||||
assert safe_credential_intake["required_redacted_evidence_ref_count"] == 9
|
||||
assert safe_credential_intake["evidence_ref_rule_count"] == 5
|
||||
assert safe_credential_intake["redaction_example_count"] == 5
|
||||
assert safe_credential_intake["forbidden_payload_count"] == 15
|
||||
assert safe_credential_intake["quarantine_lane_count"] == 1
|
||||
assert safe_credential_intake["stored_raw_payload_allowed"] is False
|
||||
assert safe_credential_intake["private_clone_url_collection_allowed"] is False
|
||||
assert safe_credential_intake["secret_value_collection_allowed"] is False
|
||||
assert safe_credential_intake["execution_authorized"] is False
|
||||
assert safe_credential_intake["not_approval"] is True
|
||||
assert data["targets"][0]["owner_response_execution_authorized"] is False
|
||||
assert (
|
||||
data["targets"][0]["safe_credential_evidence_submission_status"]
|
||||
== "waiting_redacted_evidence_ref"
|
||||
)
|
||||
assert data["targets"][0]["safe_credential_raw_payload_storage_allowed"] is False
|
||||
assert (
|
||||
data["targets"][0]["safe_credential_private_clone_url_collection_allowed"]
|
||||
is False
|
||||
)
|
||||
assert "192.168.0." not in response.text
|
||||
|
||||
|
||||
def test_github_target_owner_response_intake_preflight_endpoint_blocks_secrets():
|
||||
app = FastAPI()
|
||||
app.include_router(router, prefix="/api/v1")
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.post(
|
||||
"/api/v1/agents/github-target-owner-response-intake-preflight",
|
||||
json={
|
||||
"submission_mode": "read_only_markdown_response",
|
||||
"responses": [
|
||||
{
|
||||
"template_id": "target-awoooi-refs-blocked",
|
||||
"github_repo": "owenhytsai/awoooi",
|
||||
"owner_role_or_team": "platform-owner",
|
||||
"decision": "hold_pending_refs_truth",
|
||||
"decision_reason": "Need refs truth review before sync.",
|
||||
"affected_scope": "awoooi github backup target",
|
||||
"redacted_evidence_refs": [
|
||||
"docs/security/GITEA-GITHUB-MIGRATION-SNAPSHOT.md"
|
||||
],
|
||||
"evidence_refs": [
|
||||
"docs/security/source-control-ref-detail-diff.snapshot.json"
|
||||
],
|
||||
"followup_owner": "platform-owner",
|
||||
"rollback_owner": "platform-owner",
|
||||
"maintenance_window": "not_authorized",
|
||||
"validation_plan": "read-only refs truth review only",
|
||||
"canonical_source": "gitea_main",
|
||||
"github_target_disposition": "existing_private_candidate",
|
||||
"visibility_review_owner": "platform-owner",
|
||||
"refs_truth_review_owner": "platform-owner",
|
||||
"private_clone_url_credential": (
|
||||
"https://owner:ghp_1234567890abcdefghijklmnopqrstu@github.com/owenhytsai/awoooi.git"
|
||||
),
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["schema_version"] == "github_target_owner_response_intake_preflight_v1"
|
||||
assert data["status"] == "blocked_owner_response_intake_preflight"
|
||||
assert data["summary"]["preflight_passed_response_item_count"] == 0
|
||||
assert data["summary"]["preflight_blocked_response_item_count"] == 1
|
||||
assert data["summary"]["owner_response_received_count"] == 0
|
||||
assert data["summary"]["owner_response_accepted_count"] == 0
|
||||
assert data["summary"]["safe_credential_accepted_evidence_count"] == 0
|
||||
assert data["operation_boundaries"]["persist_submission_allowed"] is False
|
||||
assert data["operation_boundaries"]["github_api_write_allowed"] is False
|
||||
assert data["operation_boundaries"]["private_clone_url_collection_allowed"] is False
|
||||
assert data["authorization_flags"]["owner_response_execution_authorized"] is False
|
||||
assert data["responses"][0]["accepted_for_read_only_intake"] is False
|
||||
assert "forbidden_payload_detected" in data["responses"][0]["blockers"]
|
||||
assert "192.168.0." not in response.text
|
||||
|
||||
@@ -83,20 +83,19 @@ def test_iwooos_wazuh_manager_registry_reviewer_validation_contract_has_passed_r
|
||||
|
||||
assert payload["schema_version"] == "iwooos_wazuh_manager_registry_reviewer_validation_readback_v1"
|
||||
assert payload["source_schema_version"] == "wazuh_manager_registry_reviewer_validation_v1"
|
||||
assert payload["status"] == "post_enable_iwooos_readback_passed_no_runtime_no_secret_collection"
|
||||
assert payload["mode"] == "committed_post_enable_iwooos_readback_passed_no_runtime_no_secret_collection"
|
||||
assert payload["status"] == "accepted_for_readonly_posture_only"
|
||||
assert payload["mode"] == "committed_validation_passed_readback_no_runtime_no_secret_collection"
|
||||
assert payload["summary"]["expected_scope_alias_count"] == 6
|
||||
assert payload["summary"]["required_owner_field_count"] == 28
|
||||
assert payload["summary"]["per_host_required_field_count"] == 9
|
||||
assert payload["summary"]["reviewer_validation_check_count"] == 10
|
||||
assert payload["summary"]["outcome_lane_count"] == 14
|
||||
assert payload["summary"]["outcome_lane_count"] == 13
|
||||
assert payload["summary"]["evidence_slot_count"] == 6
|
||||
assert payload["summary"]["forbidden_payload_count"] == 27
|
||||
assert payload["summary"]["owner_registry_export_received_count"] == 1
|
||||
assert payload["summary"]["owner_registry_export_accepted_count"] == 1
|
||||
assert payload["summary"]["reviewer_validation_ready_count"] == 1
|
||||
assert payload["summary"]["reviewer_validation_passed_count"] == 1
|
||||
assert payload["summary"]["post_enable_readback_passed_count"] == 1
|
||||
assert payload["summary"]["reviewer_validation_quarantined_count"] == 0
|
||||
assert payload["summary"]["manager_registry_accepted_count"] == 0
|
||||
assert payload["summary"]["runtime_gate_count"] == 0
|
||||
@@ -116,7 +115,7 @@ def test_iwooos_wazuh_manager_registry_reviewer_validation_evidence_slots_are_ac
|
||||
assert all(item["received"] is True for item in payload["evidence_slots"])
|
||||
assert all(item["accepted"] is True for item in payload["evidence_slots"])
|
||||
assert all(item["quarantined"] is False for item in payload["evidence_slots"])
|
||||
assert all(item["next_gate"] == "manager_registry_acceptance_evidence_review" for item in payload["evidence_slots"])
|
||||
assert all(item["next_gate"] == "post_enable_iwooos_readback" for item in payload["evidence_slots"])
|
||||
assert "managed_core_node_a" in payload["expected_scope_aliases"]
|
||||
assert "manager_registry_agent_counts" in [item["slot_id"] for item in payload["evidence_slots"]]
|
||||
|
||||
@@ -130,7 +129,6 @@ def test_iwooos_wazuh_manager_registry_reviewer_validation_api_is_public_safe()
|
||||
assert data["summary"]["owner_registry_export_received_count"] == 1
|
||||
assert data["summary"]["owner_registry_export_accepted_count"] == 1
|
||||
assert data["summary"]["reviewer_validation_passed_count"] == 1
|
||||
assert data["summary"]["post_enable_readback_passed_count"] == 1
|
||||
assert data["summary"]["manager_registry_accepted_count"] == 0
|
||||
assert data["summary"]["runtime_gate_count"] == 0
|
||||
assert len(data["reviewer_validation_checks"]) == 10
|
||||
@@ -147,10 +145,6 @@ def test_iwooos_wazuh_manager_registry_reviewer_validation_api_is_public_safe()
|
||||
marker == "wazuh_manager_registry_reviewer_validation_passed_count=1"
|
||||
for marker in data["boundary_markers"]
|
||||
)
|
||||
assert any(
|
||||
marker == "wazuh_manager_registry_reviewer_validation_post_enable_readback_passed_count=1"
|
||||
for marker in data["boundary_markers"]
|
||||
)
|
||||
assert any(
|
||||
marker == "wazuh_manager_registry_reviewer_validation_manager_registry_accepted_count=0"
|
||||
for marker in data["boundary_markers"]
|
||||
@@ -208,7 +202,6 @@ def test_iwooos_wazuh_manager_registry_owner_export_validation_api_does_not_pers
|
||||
assert readback["summary"]["owner_registry_export_received_count"] == 1
|
||||
assert readback["summary"]["owner_registry_export_accepted_count"] == 1
|
||||
assert readback["summary"]["reviewer_validation_passed_count"] == 1
|
||||
assert readback["summary"]["post_enable_readback_passed_count"] == 1
|
||||
assert readback["summary"]["manager_registry_accepted_count"] == 0
|
||||
assert readback["summary"]["runtime_gate_count"] == 0
|
||||
|
||||
|
||||
@@ -1092,45 +1092,12 @@ async def test_controlled_apply_result_receipt_marks_callback_reply_evidence(mon
|
||||
},
|
||||
}
|
||||
|
||||
async def fake_fetch_km_completion_summary(*, incident_id, project_id):
|
||||
assert incident_id == "INC-20260627-64472B"
|
||||
assert project_id == "awoooi"
|
||||
return {
|
||||
"schema_version": "km_stale_owner_review_completion_telegram_summary_v1",
|
||||
"status": "no_related_owner_review",
|
||||
"project_id": "awoooi",
|
||||
"incident_id": "INC-20260627-64472B",
|
||||
"missing_reason": "no_matching_completion_item",
|
||||
"ready_count": 10,
|
||||
"blocked_count": 0,
|
||||
"completed_count": 1,
|
||||
"failed_count": 0,
|
||||
"related_total": 0,
|
||||
"writes_on_read": False,
|
||||
"batch_writes_allowed": False,
|
||||
"manual_review_required": True,
|
||||
"triage": {
|
||||
"schema_version": "km_stale_callback_owner_review_triage_v1",
|
||||
"flow_stage": "callback_observed_owner_review_link_missing",
|
||||
"ai_lead_agent": "Hermes",
|
||||
"supporting_agents": ["OpenClaw"],
|
||||
"automation_state": "manual_owner_review_required",
|
||||
"safe_to_auto_repair": False,
|
||||
"blocking_reason": "no_matching_completion_item",
|
||||
"matching_strategy": "related_incident_id_exact_match",
|
||||
},
|
||||
}
|
||||
|
||||
monkeypatch.setattr(TelegramGateway, "alert_chat_id", property(lambda _self: "chat"))
|
||||
monkeypatch.setattr(gateway, "_send_request", fake_send_request)
|
||||
monkeypatch.setattr(
|
||||
"src.services.awooop_truth_chain_service.fetch_truth_chain",
|
||||
fake_fetch_truth_chain,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"src.services.telegram_gateway._fetch_km_stale_completion_summary_for_incident",
|
||||
fake_fetch_km_completion_summary,
|
||||
)
|
||||
|
||||
result = await gateway.send_controlled_apply_result_receipt(
|
||||
incident_id="INC-20260627-64472B",
|
||||
@@ -1156,14 +1123,6 @@ async def test_controlled_apply_result_receipt_marks_callback_reply_evidence(mon
|
||||
assert source_extra["callback_reply"]["action"] == "controlled_apply_result"
|
||||
assert source_extra["callback_reply"]["status"] == "callback_reply_sent"
|
||||
assert source_extra["source_refs"]["incident_ids"] == ["INC-20260627-64472B"]
|
||||
km_snapshot = source_extra["km_stale_completion_summary"]
|
||||
assert (
|
||||
km_snapshot["source_schema_version"]
|
||||
== "km_stale_owner_review_completion_telegram_summary_v1"
|
||||
)
|
||||
assert km_snapshot["status"] == "no_related_owner_review"
|
||||
assert km_snapshot["ready_count"] == 10
|
||||
assert km_snapshot["triage"]["ai_lead_agent"] == "Hermes"
|
||||
snapshot = source_extra["awooop_status_chain"]
|
||||
assert snapshot["repair_state"] == "auto_repaired_verified"
|
||||
assert snapshot["operator_outcome"]["state"] == "completed_verified"
|
||||
|
||||
@@ -13226,7 +13226,7 @@
|
||||
},
|
||||
"evidenceTimeline": {
|
||||
"title": "證據時序",
|
||||
"signal": "監控、AwoooP 真相鏈與受控授權閘門已接成證據時序。",
|
||||
"signal": "監控、AwoooP 真相鏈與人工閘門已接成證據時序。",
|
||||
"interpretation": "目前停在 S4.9 負責人證據等待狀態,避免將可視化誤判為已推進。",
|
||||
"next": "補審查者接受紀錄與負責人回覆。",
|
||||
"ring": "時序已接線;等待 S4.9 證據。"
|
||||
@@ -13259,7 +13259,7 @@
|
||||
"body": "監控、MCP、Ansible、KM與告警資料先進入證據線,讓 IwoooS 能解釋訊號來源與新鮮度。"
|
||||
},
|
||||
"awooopTruth": {
|
||||
"body": "AwoooP 是跨工作線工作狀態與受控授權閘門的真相鏈;IwoooS只讀消費它,不把顯示狀態當批准。"
|
||||
"body": "AwoooP 是跨工作線工作狀態與人工閘門的真相鏈;IwoooS只讀消費它,不把顯示狀態當批准。"
|
||||
},
|
||||
"runtimeGate": {
|
||||
"body": "所有掃描、修復、部署、主機更新與版本來源變更仍集中在執行閘 0;沒有正式決策前不產生執行按鈕。"
|
||||
@@ -13279,7 +13279,7 @@
|
||||
"title": "主機拓樸",
|
||||
"mapTitle": "資安觀測節點 / 開發主機群 A / 開發主機群 B只讀觀測主機網",
|
||||
"detail": "將 資安觀測節點、開發主機群 A / 開發主機群 B、監控工具與AwoooP 真相鏈整合為單一拓樸視角。",
|
||||
"evidence": "目前只呈現觀測窗口、證據位置與受控授權閘門,沒有執行 SSH、掃描或主機設定變更。",
|
||||
"evidence": "目前只呈現觀測窗口、證據位置與人工閘門,沒有執行 SSH、掃描或主機設定變更。",
|
||||
"next": "等執行期閘門與掃描範圍批准後,才把只讀證據轉入受控探測。",
|
||||
"locked": "host_change_authorized=false,scan_authorized=false。"
|
||||
},
|
||||
@@ -13293,7 +13293,7 @@
|
||||
},
|
||||
"evidenceFlow": {
|
||||
"title": "證據流",
|
||||
"mapTitle": "監控 → AwoooP 真相鏈 → 受控授權閘門",
|
||||
"mapTitle": "監控 → AwoooP 真相鏈 → 人工閘門",
|
||||
"detail": "用證據線表示資料如何被收集、脫敏、審查與回寫;這比單純列文件更接近 SOC / XDR的操作體驗。",
|
||||
"evidence": "AwoooP 跨工作線狀態已接線,IwoooS只讀鏡像與進度守門已有證據。",
|
||||
"next": "補審查者接受紀錄與負責人決策紀錄,才可能進入下一個執行期閘門。",
|
||||
@@ -13436,7 +13436,7 @@
|
||||
},
|
||||
"awooop": {
|
||||
"title": "AwoooP 真相鏈",
|
||||
"body": "同步跨工作線工作狀態與受控授權閘門。"
|
||||
"body": "同步跨工作線工作狀態與人工閘門。"
|
||||
},
|
||||
"vibework": {
|
||||
"title": "任務媒合產品 納管",
|
||||
@@ -13807,7 +13807,7 @@
|
||||
},
|
||||
"allProductCoverageSnapshot": {
|
||||
"title": "全產品只讀套用快照",
|
||||
"subtitle": "先回答最常被問的問題:所有專案產品都套用 IwoooS 資安框架,但目前只套可視化、證據欄位與受控授權閘門,不套掃描、修復、部署或阻擋控制。",
|
||||
"subtitle": "先回答最常被問的問題:所有專案產品都套用 IwoooS 資安框架,但目前只套可視化、證據欄位與人工閘門,不套掃描、修復、部署或阻擋控制。",
|
||||
"boundaryTitle": "快照邊界",
|
||||
"boundaryIntro": "以下鍵值固定:預設區只顯示簡版快照;完整三軸明細與分階段套用台帳保留在進階區,執行期就緒仍是 0。",
|
||||
"summary": {
|
||||
@@ -13821,7 +13821,7 @@
|
||||
},
|
||||
"runtimeReady": {
|
||||
"label": "執行期就緒",
|
||||
"detail": "目前 0;未經受控授權閘門不進執行期。"
|
||||
"detail": "目前 0;未經人工閘門不進執行期。"
|
||||
},
|
||||
"nextGate": {
|
||||
"label": "下一閘門",
|
||||
@@ -13856,11 +13856,11 @@
|
||||
},
|
||||
"toolsMonitoring": {
|
||||
"title": "監控、工具與自動化",
|
||||
"body": "只顯示 僅中繼資料證據、阻塞解除條件與受控授權閘門。"
|
||||
"body": "只顯示 僅中繼資料證據、阻塞解除條件與人工閘門。"
|
||||
},
|
||||
"vibeWork": {
|
||||
"title": "任務媒合產品 新專案",
|
||||
"body": "已納入 IwoooS只讀資安框架、繁中可視化、受控授權閘門與執行期=false 旗標。"
|
||||
"body": "已納入 IwoooS只讀資安框架、繁中可視化、人工閘門與執行期=false 旗標。"
|
||||
},
|
||||
"futureProducts": {
|
||||
"title": "未來新增產品",
|
||||
@@ -13875,13 +13875,13 @@
|
||||
"globalSecurityMeshMatrix": {
|
||||
"eyebrow": "全域資安納管矩陣",
|
||||
"title": "所有產品、主機、工具放在同一張表",
|
||||
"subtitle": "將 AwoooI、AwoooP、IwoooS、公開網站群、任務媒合產品、代理賞金協議、資安觀測節點、開發主機與GitHub / Gitea 版本來源放到同一個只讀矩陣,先呈現納管範圍,再決定哪一段要收證或開受控授權閘門。",
|
||||
"subtitle": "將 AwoooI、AwoooP、IwoooS、公開網站群、任務媒合產品、代理賞金協議、資安觀測節點、開發主機與GitHub / Gitea 版本來源放到同一個只讀矩陣,先呈現納管範圍,再決定哪一段要收證或開人工閘門。",
|
||||
"coverageLabel": "覆蓋",
|
||||
"evidenceLabel": "證據",
|
||||
"runtimeLabel": "執行",
|
||||
"nextLabel": "下一步",
|
||||
"boundaryTitle": "全域納管邊界",
|
||||
"boundaryIntro": "以下鍵值固定:這張矩陣只呈現可視化、證據狀態與受控授權閘門,不提供掃描、修復、主機變更、部署或版本來源變更。",
|
||||
"boundaryIntro": "以下鍵值固定:這張矩陣只呈現可視化、證據狀態與人工閘門,不提供掃描、修復、主機變更、部署或版本來源變更。",
|
||||
"summary": {
|
||||
"assets": {
|
||||
"label": "資產列",
|
||||
@@ -16568,7 +16568,7 @@
|
||||
"followupRuntimeGatePointer": {
|
||||
"title": "後續執行期指標",
|
||||
"body": "若決策牽涉掃描、修復、部署、主要來源切換或阻擋升級,草稿只能留下後續閘門指標。",
|
||||
"draft": "只標記後續要走哪個受控授權閘門,不建立執行命令。",
|
||||
"draft": "只標記後續要走哪個人工閘門,不建立執行命令。",
|
||||
"guard": "不切主要來源、不停用 Gitea、不建立執行期閘門。"
|
||||
}
|
||||
}
|
||||
@@ -16638,7 +16638,7 @@
|
||||
"sourceControlSeparation": {
|
||||
"title": "主要來源分離",
|
||||
"body": "GitHub 主要來源切換、Gitea 停用、工作流程 / 機密設定或參照 動作不能由候選直接推進。",
|
||||
"preflight": "只標記需要另開主要來源或版本控制受控授權閘門。",
|
||||
"preflight": "只標記需要另開主要來源或版本控制人工閘門。",
|
||||
"guard": "不切 GitHub 主要來源、不停用 Gitea、不改 工作流程 / 機密設定。"
|
||||
}
|
||||
}
|
||||
@@ -16713,7 +16713,7 @@
|
||||
},
|
||||
"runtimeOrCutoverGateRequired": {
|
||||
"title": "另開執行或切換閘門",
|
||||
"body": "若候選需要掃描、修復、主機更新、主要來源切換或Gitea 停用,必須另開受控授權閘門。",
|
||||
"body": "若候選需要掃描、修復、主機更新、主要來源切換或Gitea 停用,必須另開人工閘門。",
|
||||
"result": "只標記需要哪一種後續閘門。",
|
||||
"guard": "不呼叫 Kali、不開 SSH、不切 GitHub 主要來源、不停用 Gitea。"
|
||||
}
|
||||
@@ -16783,7 +16783,7 @@
|
||||
},
|
||||
"handoffSourceControlPointer": {
|
||||
"title": "主要來源指標包",
|
||||
"body": "GitHub 主要來源切換、Gitea 停用、參照或工作流程 / 機密設定 動作只能作為後續受控授權閘門指標。",
|
||||
"body": "GitHub 主要來源切換、Gitea 停用、參照或工作流程 / 機密設定 動作只能作為後續人工閘門指標。",
|
||||
"handoff": "只標記版本控制與主要來源相關待決事項。",
|
||||
"guard": "不建立專案庫、不改 參照、不改 工作流程 / 機密設定、不停用 Gitea。"
|
||||
}
|
||||
@@ -16853,7 +16853,7 @@
|
||||
},
|
||||
"runtimeCutoverSeparation": {
|
||||
"title": "執行與切換分離",
|
||||
"body": "檢查掃描、修復、主機更新、GitHub 主要來源切換或Gitea 停用是否被留在獨立受控授權閘門。",
|
||||
"body": "檢查掃描、修復、主機更新、GitHub 主要來源切換或Gitea 停用是否被留在獨立人工閘門。",
|
||||
"review": "只標記後續需要哪一類獨立閘門。",
|
||||
"guard": "不呼叫 Kali、不開 SSH、不更新主機、不切主要來源、不停用 Gitea。"
|
||||
}
|
||||
@@ -16929,7 +16929,7 @@
|
||||
},
|
||||
"runtimeOrCutoverGateRequired": {
|
||||
"title": "另開執行或切換閘門",
|
||||
"body": "若結果需要掃描、修復、主機更新、GitHub 主要來源切換或Gitea 停用,必須另開受控授權閘門。",
|
||||
"body": "若結果需要掃描、修復、主機更新、GitHub 主要來源切換或Gitea 停用,必須另開人工閘門。",
|
||||
"result": "只標記需要哪一種後續閘門。",
|
||||
"guard": "不呼叫 Kali、不開 SSH、不更新主機、不切主要來源、不停用 Gitea。"
|
||||
}
|
||||
@@ -17005,7 +17005,7 @@
|
||||
},
|
||||
"runtimeCutoverPointer": {
|
||||
"title": "執行切換指標包",
|
||||
"body": "整理掃描、修復、主機更新、GitHub 主要來源切換或Gitea 停用是否需要另開受控授權閘門。",
|
||||
"body": "整理掃描、修復、主機更新、GitHub 主要來源切換或Gitea 停用是否需要另開人工閘門。",
|
||||
"prepare": "只標記後續可能需要哪一種獨立閘門。",
|
||||
"guard": "不呼叫 Kali、不開 SSH、不更新主機、不切主要來源、不停用 Gitea。"
|
||||
}
|
||||
@@ -17081,7 +17081,7 @@
|
||||
},
|
||||
"runtimeCutoverSeparationCheck": {
|
||||
"title": "執行切換分離檢查",
|
||||
"body": "確認掃描、修復、主機更新、GitHub 主要來源切換或Gitea 停用是否留在獨立受控授權閘門。",
|
||||
"body": "確認掃描、修復、主機更新、GitHub 主要來源切換或Gitea 停用是否留在獨立人工閘門。",
|
||||
"review": "只標記後續是否需要獨立閘門。",
|
||||
"guard": "不呼叫 Kali、不開 SSH、不更新主機、不切主要來源、不停用 Gitea。"
|
||||
}
|
||||
@@ -17157,7 +17157,7 @@
|
||||
},
|
||||
"runtimeOrPrimaryGateRequired": {
|
||||
"title": "另開執行或主要來源閘門",
|
||||
"body": "若後續需要掃描、修復、主機更新、GitHub 主要來源切換或Gitea 停用,必須另開受控授權閘門。",
|
||||
"body": "若後續需要掃描、修復、主機更新、GitHub 主要來源切換或Gitea 停用,必須另開人工閘門。",
|
||||
"result": "只標記後續需要哪一種獨立閘門。",
|
||||
"guard": "不呼叫 Kali、不開 SSH、不更新主機、不切主要來源、不停用 Gitea。"
|
||||
}
|
||||
@@ -17310,7 +17310,7 @@
|
||||
"runtimePrimarySeparated": {
|
||||
"title": "執行與主要來源分離檢查",
|
||||
"body": "確認掃描、修復、主機更新、GitHub 主要來源切換或Gitea 停用都留在獨立閘門。",
|
||||
"confirmation": "只標記後續是否需要獨立受控授權閘門。",
|
||||
"confirmation": "只標記後續是否需要獨立人工閘門。",
|
||||
"guard": "不呼叫 Kali、不開 SSH、不更新主機、不切主要來源、不停用 Gitea。"
|
||||
}
|
||||
}
|
||||
@@ -17385,7 +17385,7 @@
|
||||
},
|
||||
"runtimeOrPrimaryGateRequired": {
|
||||
"title": "另開執行或主要來源閘門",
|
||||
"body": "若後續需要 Kali 掃描、SSH、主機更新、修復、GitHub 主要來源切換或Gitea 停用,必須另開獨立受控授權閘門。",
|
||||
"body": "若後續需要 Kali 掃描、SSH、主機更新、修復、GitHub 主要來源切換或Gitea 停用,必須另開獨立人工閘門。",
|
||||
"result": "只標記需要哪一種後續閘門,不在本看板執行。",
|
||||
"guard": "不呼叫 Kali、不開 SSH、不更新主機、不切主要來源、不停用 Gitea。"
|
||||
}
|
||||
@@ -17461,7 +17461,7 @@
|
||||
},
|
||||
"runtimePrimaryGatePacket": {
|
||||
"title": "執行與主要來源閘門包",
|
||||
"body": "整理後續若要 Kali、SSH、主機更新、修復、GitHub 主要來源切換或Gitea 停用時需要另開的受控授權閘門。",
|
||||
"body": "整理後續若要 Kali、SSH、主機更新、修復、GitHub 主要來源切換或Gitea 停用時需要另開的人工閘門。",
|
||||
"requirement": "只標記後續閘門類型,不在本看板執行。",
|
||||
"guard": "不呼叫 Kali、不開 SSH、不更新主機、不切主要來源、不停用 Gitea。"
|
||||
}
|
||||
@@ -17538,7 +17538,7 @@
|
||||
"runtimePrimarySeparated": {
|
||||
"title": "執行與主要來源分離檢查",
|
||||
"body": "確認 Kali、SSH、主機更新、修復、GitHub 主要來源切換或Gitea 停用都留在獨立閘門。",
|
||||
"confirmation": "只標記後續是否需要獨立受控授權閘門。",
|
||||
"confirmation": "只標記後續是否需要獨立人工閘門。",
|
||||
"guard": "不呼叫 Kali、不開 SSH、不更新主機、不切主要來源、不停用 Gitea。"
|
||||
}
|
||||
}
|
||||
@@ -17629,7 +17629,7 @@
|
||||
"title": "後續節奏改成批次推進",
|
||||
"body": "已完成多層清單;後續應優先收斂 P0 負責人回覆與AwoooP 落地,避免持續拆分低影響核對項。",
|
||||
"unlock": "相同類型的 framework detail 之後合併成一批回報,只有高層閘門 變動才調整 重點。",
|
||||
"guard": "節奏加快不等於放寬安全;執行期與來源-control cutover仍需受控授權閘門。"
|
||||
"guard": "節奏加快不等於放寬安全;執行期與來源-control cutover仍需人工閘門。"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -18114,7 +18114,7 @@
|
||||
},
|
||||
"concreteSecurityBlockerResolution": {
|
||||
"title": "目前阻塞與解除條件",
|
||||
"subtitle": "將 64% 無法前進的原因拆成六個阻塞點。每個阻塞點都標明為什麼卡住,以及要用哪種脫敏證據或受控授權閘門 才能解除;這仍是只讀狀態,不是批准或執行入口。",
|
||||
"subtitle": "將 64% 無法前進的原因拆成六個阻塞點。每個阻塞點都標明為什麼卡住,以及要用哪種脫敏證據或人工閘門 才能解除;這仍是只讀狀態,不是批准或執行入口。",
|
||||
"blockerLabel": "阻塞",
|
||||
"whyLabel": "卡住原因",
|
||||
"unlockLabel": "解除條件",
|
||||
@@ -18225,13 +18225,13 @@
|
||||
},
|
||||
"toolsMonitoring": {
|
||||
"title": "監控、工具與自動化流程",
|
||||
"current": "可套用只讀狀態、阻塞解除條件、證據參照與受控授權閘門 顯示。",
|
||||
"current": "可套用只讀狀態、阻塞解除條件、證據參照與人工閘門 顯示。",
|
||||
"next": "先讓工具輸出 僅中繼資料證據,再接審查者與執行期閘門。",
|
||||
"boundary": "不得讓工具自動觸發修復、部署、機密 收集或外部付費變更。"
|
||||
},
|
||||
"vibeWork": {
|
||||
"title": "任務媒合產品 新專案",
|
||||
"current": "已納入全產品三軸進度、IwoooS可視化、只讀 governance與受控授權閘門 口徑。",
|
||||
"current": "已納入全產品三軸進度、IwoooS可視化、只讀 governance與人工閘門 口徑。",
|
||||
"next": "先補產品 負責人、資料分級、版本來源與部署邊界,再評估後續分階段收嚴。",
|
||||
"boundary": "不得因新專案納管就自動掃描、部署、改 專案庫、改 工作流程或套 阻擋 enforcement。"
|
||||
},
|
||||
@@ -19417,7 +19417,7 @@
|
||||
"boundary": {
|
||||
"title": "執行邊界",
|
||||
"state": "閘門 0",
|
||||
"detail": "目前 IwoooS 是低摩擦框架與只讀證據網;掃描、修復、部署、主機更新與版本來源變更都要獨立受控授權閘門。",
|
||||
"detail": "目前 IwoooS 是低摩擦框架與只讀證據網;掃描、修復、部署、主機更新與版本來源變更都要獨立人工閘門。",
|
||||
"evidence": "守門、快照、LOGBOOK 與邊界鍵都已鎖住。",
|
||||
"next": "等 負責人證據或人工決策後,再另開 follow-up 執行期閘門。",
|
||||
"locked": "runtime 授權仍維持關閉;執行期閘門維持 0。"
|
||||
@@ -20005,7 +20005,7 @@
|
||||
"mutationRequestRejected": {
|
||||
"title": "變更要求拒收檢查",
|
||||
"body": "確認回覆封套沒有夾帶建立專案庫、改可見性、同步分支、修改工作流程或收集機密值的要求。",
|
||||
"failure": "夾帶變更要求時只能拒收或拆到獨立受控授權閘門。",
|
||||
"failure": "夾帶變更要求時只能拒收或拆到獨立人工閘門。",
|
||||
"guard": "不從封套觸發 GitHub、Gitea、分支 / 標籤參照、工作流程、機密設定或執行器動作。"
|
||||
},
|
||||
"followupOwnerTrace": {
|
||||
@@ -20075,7 +20075,7 @@
|
||||
"rejectMutationRequest": {
|
||||
"title": "拒收變更要求",
|
||||
"body": "封套夾帶建立專案庫、改可見性、同步分支、修改工作流程或收集機密值時,直接分流為拒收。",
|
||||
"next": "只記為需另開受控授權閘門的變更要求,不在封套內處理。",
|
||||
"next": "只記為需另開人工閘門的變更要求,不在封套內處理。",
|
||||
"guard": "不從封套觸發 GitHub、Gitea、分支 / 標籤參照、工作流程、機密設定或執行器動作。"
|
||||
},
|
||||
"keepFollowupOwnerWaiting": {
|
||||
@@ -20259,7 +20259,7 @@
|
||||
},
|
||||
"allProducts": {
|
||||
"title": "所有產品先套只讀框架",
|
||||
"body": "核心產品、公開網站、版本來源、主機、監控工具、任務媒合產品與未來產品七類都已套用 IwoooS可視化與受控授權閘門 口徑。"
|
||||
"body": "核心產品、公開網站、版本來源、主機、監控工具、任務媒合產品與未來產品七類都已套用 IwoooS可視化與人工閘門 口徑。"
|
||||
},
|
||||
"runtime": {
|
||||
"title": "執行期仍保持關閉",
|
||||
@@ -20872,10 +20872,6 @@
|
||||
"label": "Reviewer passed",
|
||||
"detail": "一筆脫敏 owner export refs 已通過 no-persist reviewer validation。"
|
||||
},
|
||||
"postEnable": {
|
||||
"label": "Post-enable",
|
||||
"detail": "正式 API 與前台已讀回 reviewer passed;這不是 live Wazuh 查詢授權。"
|
||||
},
|
||||
"received": {
|
||||
"label": "已收 export",
|
||||
"detail": "已收到一筆 owner-provided redacted registry export refs。"
|
||||
|
||||
@@ -13226,7 +13226,7 @@
|
||||
},
|
||||
"evidenceTimeline": {
|
||||
"title": "證據時序",
|
||||
"signal": "監控、AwoooP 真相鏈與受控授權閘門已接成證據時序。",
|
||||
"signal": "監控、AwoooP 真相鏈與人工閘門已接成證據時序。",
|
||||
"interpretation": "目前停在 S4.9 負責人證據等待狀態,避免將可視化誤判為已推進。",
|
||||
"next": "補審查者接受紀錄與負責人回覆。",
|
||||
"ring": "時序已接線;等待 S4.9 證據。"
|
||||
@@ -13259,7 +13259,7 @@
|
||||
"body": "監控、MCP、Ansible、KM與告警資料先進入證據線,讓 IwoooS 能解釋訊號來源與新鮮度。"
|
||||
},
|
||||
"awooopTruth": {
|
||||
"body": "AwoooP 是跨工作線工作狀態與受控授權閘門的真相鏈;IwoooS只讀消費它,不把顯示狀態當批准。"
|
||||
"body": "AwoooP 是跨工作線工作狀態與人工閘門的真相鏈;IwoooS只讀消費它,不把顯示狀態當批准。"
|
||||
},
|
||||
"runtimeGate": {
|
||||
"body": "所有掃描、修復、部署、主機更新與版本來源變更仍集中在執行閘 0;沒有正式決策前不產生執行按鈕。"
|
||||
@@ -13279,7 +13279,7 @@
|
||||
"title": "主機拓樸",
|
||||
"mapTitle": "資安觀測節點 / 開發主機群 A / 開發主機群 B只讀觀測主機網",
|
||||
"detail": "將 資安觀測節點、開發主機群 A / 開發主機群 B、監控工具與AwoooP 真相鏈整合為單一拓樸視角。",
|
||||
"evidence": "目前只呈現觀測窗口、證據位置與受控授權閘門,沒有執行 SSH、掃描或主機設定變更。",
|
||||
"evidence": "目前只呈現觀測窗口、證據位置與人工閘門,沒有執行 SSH、掃描或主機設定變更。",
|
||||
"next": "等執行期閘門與掃描範圍批准後,才把只讀證據轉入受控探測。",
|
||||
"locked": "host_change_authorized=false,scan_authorized=false。"
|
||||
},
|
||||
@@ -13293,7 +13293,7 @@
|
||||
},
|
||||
"evidenceFlow": {
|
||||
"title": "證據流",
|
||||
"mapTitle": "監控 → AwoooP 真相鏈 → 受控授權閘門",
|
||||
"mapTitle": "監控 → AwoooP 真相鏈 → 人工閘門",
|
||||
"detail": "用證據線表示資料如何被收集、脫敏、審查與回寫;這比單純列文件更接近 SOC / XDR的操作體驗。",
|
||||
"evidence": "AwoooP 跨工作線狀態已接線,IwoooS只讀鏡像與進度守門已有證據。",
|
||||
"next": "補審查者接受紀錄與負責人決策紀錄,才可能進入下一個執行期閘門。",
|
||||
@@ -13436,7 +13436,7 @@
|
||||
},
|
||||
"awooop": {
|
||||
"title": "AwoooP 真相鏈",
|
||||
"body": "同步跨工作線工作狀態與受控授權閘門。"
|
||||
"body": "同步跨工作線工作狀態與人工閘門。"
|
||||
},
|
||||
"vibework": {
|
||||
"title": "任務媒合產品 納管",
|
||||
@@ -13807,7 +13807,7 @@
|
||||
},
|
||||
"allProductCoverageSnapshot": {
|
||||
"title": "全產品只讀套用快照",
|
||||
"subtitle": "先回答最常被問的問題:所有專案產品都套用 IwoooS 資安框架,但目前只套可視化、證據欄位與受控授權閘門,不套掃描、修復、部署或阻擋控制。",
|
||||
"subtitle": "先回答最常被問的問題:所有專案產品都套用 IwoooS 資安框架,但目前只套可視化、證據欄位與人工閘門,不套掃描、修復、部署或阻擋控制。",
|
||||
"boundaryTitle": "快照邊界",
|
||||
"boundaryIntro": "以下鍵值固定:預設區只顯示簡版快照;完整三軸明細與分階段套用台帳保留在進階區,執行期就緒仍是 0。",
|
||||
"summary": {
|
||||
@@ -13821,7 +13821,7 @@
|
||||
},
|
||||
"runtimeReady": {
|
||||
"label": "執行期就緒",
|
||||
"detail": "目前 0;未經受控授權閘門不進執行期。"
|
||||
"detail": "目前 0;未經人工閘門不進執行期。"
|
||||
},
|
||||
"nextGate": {
|
||||
"label": "下一閘門",
|
||||
@@ -13856,11 +13856,11 @@
|
||||
},
|
||||
"toolsMonitoring": {
|
||||
"title": "監控、工具與自動化",
|
||||
"body": "只顯示 僅中繼資料證據、阻塞解除條件與受控授權閘門。"
|
||||
"body": "只顯示 僅中繼資料證據、阻塞解除條件與人工閘門。"
|
||||
},
|
||||
"vibeWork": {
|
||||
"title": "任務媒合產品 新專案",
|
||||
"body": "已納入 IwoooS只讀資安框架、繁中可視化、受控授權閘門與執行期=false 旗標。"
|
||||
"body": "已納入 IwoooS只讀資安框架、繁中可視化、人工閘門與執行期=false 旗標。"
|
||||
},
|
||||
"futureProducts": {
|
||||
"title": "未來新增產品",
|
||||
@@ -13875,13 +13875,13 @@
|
||||
"globalSecurityMeshMatrix": {
|
||||
"eyebrow": "全域資安納管矩陣",
|
||||
"title": "所有產品、主機、工具放在同一張表",
|
||||
"subtitle": "將 AwoooI、AwoooP、IwoooS、公開網站群、任務媒合產品、代理賞金協議、資安觀測節點、開發主機與GitHub / Gitea 版本來源放到同一個只讀矩陣,先呈現納管範圍,再決定哪一段要收證或開受控授權閘門。",
|
||||
"subtitle": "將 AwoooI、AwoooP、IwoooS、公開網站群、任務媒合產品、代理賞金協議、資安觀測節點、開發主機與GitHub / Gitea 版本來源放到同一個只讀矩陣,先呈現納管範圍,再決定哪一段要收證或開人工閘門。",
|
||||
"coverageLabel": "覆蓋",
|
||||
"evidenceLabel": "證據",
|
||||
"runtimeLabel": "執行",
|
||||
"nextLabel": "下一步",
|
||||
"boundaryTitle": "全域納管邊界",
|
||||
"boundaryIntro": "以下鍵值固定:這張矩陣只呈現可視化、證據狀態與受控授權閘門,不提供掃描、修復、主機變更、部署或版本來源變更。",
|
||||
"boundaryIntro": "以下鍵值固定:這張矩陣只呈現可視化、證據狀態與人工閘門,不提供掃描、修復、主機變更、部署或版本來源變更。",
|
||||
"summary": {
|
||||
"assets": {
|
||||
"label": "資產列",
|
||||
@@ -16568,7 +16568,7 @@
|
||||
"followupRuntimeGatePointer": {
|
||||
"title": "後續執行期指標",
|
||||
"body": "若決策牽涉掃描、修復、部署、主要來源切換或阻擋升級,草稿只能留下後續閘門指標。",
|
||||
"draft": "只標記後續要走哪個受控授權閘門,不建立執行命令。",
|
||||
"draft": "只標記後續要走哪個人工閘門,不建立執行命令。",
|
||||
"guard": "不切主要來源、不停用 Gitea、不建立執行期閘門。"
|
||||
}
|
||||
}
|
||||
@@ -16638,7 +16638,7 @@
|
||||
"sourceControlSeparation": {
|
||||
"title": "主要來源分離",
|
||||
"body": "GitHub 主要來源切換、Gitea 停用、工作流程 / 機密設定或參照 動作不能由候選直接推進。",
|
||||
"preflight": "只標記需要另開主要來源或版本控制受控授權閘門。",
|
||||
"preflight": "只標記需要另開主要來源或版本控制人工閘門。",
|
||||
"guard": "不切 GitHub 主要來源、不停用 Gitea、不改 工作流程 / 機密設定。"
|
||||
}
|
||||
}
|
||||
@@ -16713,7 +16713,7 @@
|
||||
},
|
||||
"runtimeOrCutoverGateRequired": {
|
||||
"title": "另開執行或切換閘門",
|
||||
"body": "若候選需要掃描、修復、主機更新、主要來源切換或Gitea 停用,必須另開受控授權閘門。",
|
||||
"body": "若候選需要掃描、修復、主機更新、主要來源切換或Gitea 停用,必須另開人工閘門。",
|
||||
"result": "只標記需要哪一種後續閘門。",
|
||||
"guard": "不呼叫 Kali、不開 SSH、不切 GitHub 主要來源、不停用 Gitea。"
|
||||
}
|
||||
@@ -16783,7 +16783,7 @@
|
||||
},
|
||||
"handoffSourceControlPointer": {
|
||||
"title": "主要來源指標包",
|
||||
"body": "GitHub 主要來源切換、Gitea 停用、參照或工作流程 / 機密設定 動作只能作為後續受控授權閘門指標。",
|
||||
"body": "GitHub 主要來源切換、Gitea 停用、參照或工作流程 / 機密設定 動作只能作為後續人工閘門指標。",
|
||||
"handoff": "只標記版本控制與主要來源相關待決事項。",
|
||||
"guard": "不建立專案庫、不改 參照、不改 工作流程 / 機密設定、不停用 Gitea。"
|
||||
}
|
||||
@@ -16853,7 +16853,7 @@
|
||||
},
|
||||
"runtimeCutoverSeparation": {
|
||||
"title": "執行與切換分離",
|
||||
"body": "檢查掃描、修復、主機更新、GitHub 主要來源切換或Gitea 停用是否被留在獨立受控授權閘門。",
|
||||
"body": "檢查掃描、修復、主機更新、GitHub 主要來源切換或Gitea 停用是否被留在獨立人工閘門。",
|
||||
"review": "只標記後續需要哪一類獨立閘門。",
|
||||
"guard": "不呼叫 Kali、不開 SSH、不更新主機、不切主要來源、不停用 Gitea。"
|
||||
}
|
||||
@@ -16929,7 +16929,7 @@
|
||||
},
|
||||
"runtimeOrCutoverGateRequired": {
|
||||
"title": "另開執行或切換閘門",
|
||||
"body": "若結果需要掃描、修復、主機更新、GitHub 主要來源切換或Gitea 停用,必須另開受控授權閘門。",
|
||||
"body": "若結果需要掃描、修復、主機更新、GitHub 主要來源切換或Gitea 停用,必須另開人工閘門。",
|
||||
"result": "只標記需要哪一種後續閘門。",
|
||||
"guard": "不呼叫 Kali、不開 SSH、不更新主機、不切主要來源、不停用 Gitea。"
|
||||
}
|
||||
@@ -17005,7 +17005,7 @@
|
||||
},
|
||||
"runtimeCutoverPointer": {
|
||||
"title": "執行切換指標包",
|
||||
"body": "整理掃描、修復、主機更新、GitHub 主要來源切換或Gitea 停用是否需要另開受控授權閘門。",
|
||||
"body": "整理掃描、修復、主機更新、GitHub 主要來源切換或Gitea 停用是否需要另開人工閘門。",
|
||||
"prepare": "只標記後續可能需要哪一種獨立閘門。",
|
||||
"guard": "不呼叫 Kali、不開 SSH、不更新主機、不切主要來源、不停用 Gitea。"
|
||||
}
|
||||
@@ -17081,7 +17081,7 @@
|
||||
},
|
||||
"runtimeCutoverSeparationCheck": {
|
||||
"title": "執行切換分離檢查",
|
||||
"body": "確認掃描、修復、主機更新、GitHub 主要來源切換或Gitea 停用是否留在獨立受控授權閘門。",
|
||||
"body": "確認掃描、修復、主機更新、GitHub 主要來源切換或Gitea 停用是否留在獨立人工閘門。",
|
||||
"review": "只標記後續是否需要獨立閘門。",
|
||||
"guard": "不呼叫 Kali、不開 SSH、不更新主機、不切主要來源、不停用 Gitea。"
|
||||
}
|
||||
@@ -17157,7 +17157,7 @@
|
||||
},
|
||||
"runtimeOrPrimaryGateRequired": {
|
||||
"title": "另開執行或主要來源閘門",
|
||||
"body": "若後續需要掃描、修復、主機更新、GitHub 主要來源切換或Gitea 停用,必須另開受控授權閘門。",
|
||||
"body": "若後續需要掃描、修復、主機更新、GitHub 主要來源切換或Gitea 停用,必須另開人工閘門。",
|
||||
"result": "只標記後續需要哪一種獨立閘門。",
|
||||
"guard": "不呼叫 Kali、不開 SSH、不更新主機、不切主要來源、不停用 Gitea。"
|
||||
}
|
||||
@@ -17310,7 +17310,7 @@
|
||||
"runtimePrimarySeparated": {
|
||||
"title": "執行與主要來源分離檢查",
|
||||
"body": "確認掃描、修復、主機更新、GitHub 主要來源切換或Gitea 停用都留在獨立閘門。",
|
||||
"confirmation": "只標記後續是否需要獨立受控授權閘門。",
|
||||
"confirmation": "只標記後續是否需要獨立人工閘門。",
|
||||
"guard": "不呼叫 Kali、不開 SSH、不更新主機、不切主要來源、不停用 Gitea。"
|
||||
}
|
||||
}
|
||||
@@ -17385,7 +17385,7 @@
|
||||
},
|
||||
"runtimeOrPrimaryGateRequired": {
|
||||
"title": "另開執行或主要來源閘門",
|
||||
"body": "若後續需要 Kali 掃描、SSH、主機更新、修復、GitHub 主要來源切換或Gitea 停用,必須另開獨立受控授權閘門。",
|
||||
"body": "若後續需要 Kali 掃描、SSH、主機更新、修復、GitHub 主要來源切換或Gitea 停用,必須另開獨立人工閘門。",
|
||||
"result": "只標記需要哪一種後續閘門,不在本看板執行。",
|
||||
"guard": "不呼叫 Kali、不開 SSH、不更新主機、不切主要來源、不停用 Gitea。"
|
||||
}
|
||||
@@ -17461,7 +17461,7 @@
|
||||
},
|
||||
"runtimePrimaryGatePacket": {
|
||||
"title": "執行與主要來源閘門包",
|
||||
"body": "整理後續若要 Kali、SSH、主機更新、修復、GitHub 主要來源切換或Gitea 停用時需要另開的受控授權閘門。",
|
||||
"body": "整理後續若要 Kali、SSH、主機更新、修復、GitHub 主要來源切換或Gitea 停用時需要另開的人工閘門。",
|
||||
"requirement": "只標記後續閘門類型,不在本看板執行。",
|
||||
"guard": "不呼叫 Kali、不開 SSH、不更新主機、不切主要來源、不停用 Gitea。"
|
||||
}
|
||||
@@ -17538,7 +17538,7 @@
|
||||
"runtimePrimarySeparated": {
|
||||
"title": "執行與主要來源分離檢查",
|
||||
"body": "確認 Kali、SSH、主機更新、修復、GitHub 主要來源切換或Gitea 停用都留在獨立閘門。",
|
||||
"confirmation": "只標記後續是否需要獨立受控授權閘門。",
|
||||
"confirmation": "只標記後續是否需要獨立人工閘門。",
|
||||
"guard": "不呼叫 Kali、不開 SSH、不更新主機、不切主要來源、不停用 Gitea。"
|
||||
}
|
||||
}
|
||||
@@ -17629,7 +17629,7 @@
|
||||
"title": "後續節奏改成批次推進",
|
||||
"body": "已完成多層清單;後續應優先收斂 P0 負責人回覆與AwoooP 落地,避免持續拆分低影響核對項。",
|
||||
"unlock": "相同類型的 framework detail 之後合併成一批回報,只有高層閘門 變動才調整 重點。",
|
||||
"guard": "節奏加快不等於放寬安全;執行期與來源-control cutover仍需受控授權閘門。"
|
||||
"guard": "節奏加快不等於放寬安全;執行期與來源-control cutover仍需人工閘門。"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -18114,7 +18114,7 @@
|
||||
},
|
||||
"concreteSecurityBlockerResolution": {
|
||||
"title": "目前阻塞與解除條件",
|
||||
"subtitle": "將 64% 無法前進的原因拆成六個阻塞點。每個阻塞點都標明為什麼卡住,以及要用哪種脫敏證據或受控授權閘門 才能解除;這仍是只讀狀態,不是批准或執行入口。",
|
||||
"subtitle": "將 64% 無法前進的原因拆成六個阻塞點。每個阻塞點都標明為什麼卡住,以及要用哪種脫敏證據或人工閘門 才能解除;這仍是只讀狀態,不是批准或執行入口。",
|
||||
"blockerLabel": "阻塞",
|
||||
"whyLabel": "卡住原因",
|
||||
"unlockLabel": "解除條件",
|
||||
@@ -18225,13 +18225,13 @@
|
||||
},
|
||||
"toolsMonitoring": {
|
||||
"title": "監控、工具與自動化流程",
|
||||
"current": "可套用只讀狀態、阻塞解除條件、證據參照與受控授權閘門 顯示。",
|
||||
"current": "可套用只讀狀態、阻塞解除條件、證據參照與人工閘門 顯示。",
|
||||
"next": "先讓工具輸出 僅中繼資料證據,再接審查者與執行期閘門。",
|
||||
"boundary": "不得讓工具自動觸發修復、部署、機密 收集或外部付費變更。"
|
||||
},
|
||||
"vibeWork": {
|
||||
"title": "任務媒合產品 新專案",
|
||||
"current": "已納入全產品三軸進度、IwoooS可視化、只讀 governance與受控授權閘門 口徑。",
|
||||
"current": "已納入全產品三軸進度、IwoooS可視化、只讀 governance與人工閘門 口徑。",
|
||||
"next": "先補產品 負責人、資料分級、版本來源與部署邊界,再評估後續分階段收嚴。",
|
||||
"boundary": "不得因新專案納管就自動掃描、部署、改 專案庫、改 工作流程或套 阻擋 enforcement。"
|
||||
},
|
||||
@@ -19417,7 +19417,7 @@
|
||||
"boundary": {
|
||||
"title": "執行邊界",
|
||||
"state": "閘門 0",
|
||||
"detail": "目前 IwoooS 是低摩擦框架與只讀證據網;掃描、修復、部署、主機更新與版本來源變更都要獨立受控授權閘門。",
|
||||
"detail": "目前 IwoooS 是低摩擦框架與只讀證據網;掃描、修復、部署、主機更新與版本來源變更都要獨立人工閘門。",
|
||||
"evidence": "守門、快照、LOGBOOK 與邊界鍵都已鎖住。",
|
||||
"next": "等 負責人證據或人工決策後,再另開 follow-up 執行期閘門。",
|
||||
"locked": "runtime 授權仍維持關閉;執行期閘門維持 0。"
|
||||
@@ -20005,7 +20005,7 @@
|
||||
"mutationRequestRejected": {
|
||||
"title": "變更要求拒收檢查",
|
||||
"body": "確認回覆封套沒有夾帶建立專案庫、改可見性、同步分支、修改工作流程或收集機密值的要求。",
|
||||
"failure": "夾帶變更要求時只能拒收或拆到獨立受控授權閘門。",
|
||||
"failure": "夾帶變更要求時只能拒收或拆到獨立人工閘門。",
|
||||
"guard": "不從封套觸發 GitHub、Gitea、分支 / 標籤參照、工作流程、機密設定或執行器動作。"
|
||||
},
|
||||
"followupOwnerTrace": {
|
||||
@@ -20075,7 +20075,7 @@
|
||||
"rejectMutationRequest": {
|
||||
"title": "拒收變更要求",
|
||||
"body": "封套夾帶建立專案庫、改可見性、同步分支、修改工作流程或收集機密值時,直接分流為拒收。",
|
||||
"next": "只記為需另開受控授權閘門的變更要求,不在封套內處理。",
|
||||
"next": "只記為需另開人工閘門的變更要求,不在封套內處理。",
|
||||
"guard": "不從封套觸發 GitHub、Gitea、分支 / 標籤參照、工作流程、機密設定或執行器動作。"
|
||||
},
|
||||
"keepFollowupOwnerWaiting": {
|
||||
@@ -20259,7 +20259,7 @@
|
||||
},
|
||||
"allProducts": {
|
||||
"title": "所有產品先套只讀框架",
|
||||
"body": "核心產品、公開網站、版本來源、主機、監控工具、任務媒合產品與未來產品七類都已套用 IwoooS可視化與受控授權閘門 口徑。"
|
||||
"body": "核心產品、公開網站、版本來源、主機、監控工具、任務媒合產品與未來產品七類都已套用 IwoooS可視化與人工閘門 口徑。"
|
||||
},
|
||||
"runtime": {
|
||||
"title": "執行期仍保持關閉",
|
||||
@@ -20872,10 +20872,6 @@
|
||||
"label": "Reviewer passed",
|
||||
"detail": "一筆脫敏 owner export refs 已通過 no-persist reviewer validation。"
|
||||
},
|
||||
"postEnable": {
|
||||
"label": "Post-enable",
|
||||
"detail": "正式 API 與前台已讀回 reviewer passed;這不是 live Wazuh 查詢授權。"
|
||||
},
|
||||
"received": {
|
||||
"label": "已收 export",
|
||||
"detail": "已收到一筆 owner-provided redacted registry export refs。"
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export default function AwoooPAlertsPage({ params }: { params: { locale: string } }) {
|
||||
redirect(`/${params.locale}/awooop/runs#ai-alert-card-delivery-readback`);
|
||||
}
|
||||
@@ -32,21 +32,6 @@ import { QueueTab } from './tabs/queue-tab'
|
||||
import { AgentMarketTab } from './tabs/agent-market-tab'
|
||||
import { AutomationInventoryTab } from './tabs/automation-inventory-tab'
|
||||
|
||||
const GOVERNANCE_SECTION_IDS = [
|
||||
'slo',
|
||||
'events',
|
||||
'queue',
|
||||
'agent-market',
|
||||
'automation-inventory',
|
||||
] as const
|
||||
|
||||
function normalizeGovernanceSectionId(value: string | null): string | undefined {
|
||||
if (!value) return undefined
|
||||
return GOVERNANCE_SECTION_IDS.includes(value as (typeof GOVERNANCE_SECTION_IDS)[number])
|
||||
? value
|
||||
: undefined
|
||||
}
|
||||
|
||||
export default function GovernancePage({
|
||||
params,
|
||||
}: {
|
||||
@@ -65,32 +50,16 @@ export default function GovernancePage({
|
||||
const activeSection = governanceSections.find(section => section.id === requestedTab) ?? governanceSections[0]
|
||||
|
||||
useEffect(() => {
|
||||
const hashTab = window.location.hash ? window.location.hash.slice(1) : null
|
||||
const tab = normalizeGovernanceSectionId(
|
||||
new URLSearchParams(window.location.search).get('tab') ?? hashTab
|
||||
)
|
||||
const tab = new URLSearchParams(window.location.search).get('tab') ?? undefined
|
||||
setRequestedTab(tab)
|
||||
|
||||
if (!tab) return
|
||||
let cancelled = false
|
||||
const scrollToRequestedSection = () => {
|
||||
if (cancelled) return
|
||||
document.getElementById(tab)?.scrollIntoView({ block: 'start' })
|
||||
}
|
||||
const frame = window.requestAnimationFrame(scrollToRequestedSection)
|
||||
const timers = [150, 600, 1500, 3000].map(delay =>
|
||||
window.setTimeout(scrollToRequestedSection, delay)
|
||||
)
|
||||
return () => {
|
||||
cancelled = true
|
||||
window.cancelAnimationFrame(frame)
|
||||
timers.forEach(timer => window.clearTimeout(timer))
|
||||
}
|
||||
const section = document.getElementById(tab)
|
||||
section?.scrollIntoView({ block: 'start' })
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<AppLayout locale={params.locale}>
|
||||
<div className="min-w-0 overflow-x-hidden">
|
||||
{/* ComplianceBadge 橫幅 — PR 3 接 /governance/compliance-score API */}
|
||||
<GlassCard variant="subtle" padding="sm" className="mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -110,7 +79,7 @@ export default function GovernancePage({
|
||||
return (
|
||||
<Link
|
||||
key={section.id}
|
||||
href={`/${params.locale}/governance?tab=${section.id}#${section.id}`}
|
||||
href={`/${params.locale}/governance#${section.id}`}
|
||||
className="group min-w-0 border bg-white p-3 text-[#141413] no-underline transition hover:border-[#d97757] hover:bg-[#fffaf7]"
|
||||
style={{
|
||||
borderColor: selected ? '#d97757' : '#e0ddd4',
|
||||
@@ -156,7 +125,6 @@ export default function GovernancePage({
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</AppLayout>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2479,7 +2479,7 @@ const wazuhManagerRegistryReviewerValidationBoundaries = [
|
||||
'wazuh_manager_registry_reviewer_validation_required_owner_field_count=28',
|
||||
'wazuh_manager_registry_reviewer_validation_per_host_required_field_count=9',
|
||||
'wazuh_manager_registry_reviewer_validation_check_count=10',
|
||||
'wazuh_manager_registry_reviewer_validation_outcome_lane_count=14',
|
||||
'wazuh_manager_registry_reviewer_validation_outcome_lane_count=13',
|
||||
'wazuh_manager_registry_reviewer_validation_evidence_slot_count=6',
|
||||
'wazuh_manager_registry_reviewer_validation_forbidden_payload_count=27',
|
||||
'wazuh_manager_registry_reviewer_validation_owner_registry_export_received_count=1',
|
||||
@@ -2487,7 +2487,7 @@ const wazuhManagerRegistryReviewerValidationBoundaries = [
|
||||
'wazuh_manager_registry_reviewer_validation_passed_count=1',
|
||||
'wazuh_manager_registry_reviewer_validation_quarantined_count=0',
|
||||
'wazuh_manager_registry_reviewer_validation_manager_registry_accepted_count=0',
|
||||
'wazuh_manager_registry_reviewer_validation_post_enable_readback_passed_count=1',
|
||||
'wazuh_manager_registry_reviewer_validation_post_enable_readback_passed_count=0',
|
||||
'wazuh_manager_registry_reviewer_validation_runtime_gate_count=0',
|
||||
'wazuh_api_live_query_authorized=false',
|
||||
'wazuh_agent_reenroll_authorized=false',
|
||||
@@ -9822,12 +9822,6 @@ function IwoooSWazuhManagerRegistryReviewerValidationBoard() {
|
||||
icon: ClipboardCheck,
|
||||
tone: 'steady',
|
||||
},
|
||||
{
|
||||
key: 'postEnable',
|
||||
value: summary ? String(summary.post_enable_readback_passed_count) : loading ? '...' : '1',
|
||||
icon: SearchCheck,
|
||||
tone: summary?.post_enable_readback_passed_count ? 'steady' : 'locked',
|
||||
},
|
||||
{
|
||||
key: 'received',
|
||||
value: summary ? String(summary.owner_registry_export_received_count) : loading ? '...' : '1',
|
||||
|
||||
344
docs/LOGBOOK.md
344
docs/LOGBOOK.md
@@ -1,261 +1,3 @@
|
||||
## 2026-06-27 — 22:28 AwoooP manual gate payload residue 正式清零
|
||||
|
||||
**背景**:正式 `/zh-TW/awooop/approvals`、Runs、Work Items 的 HTML payload 仍因 Next / i18n namespace 序列化帶出 IwoooS 舊文案 `人工閘門`,每頁各命中 32 次;`/zh-TW/awooop/alerts` 也回 404。這會讓 AwoooP 看起來仍把人工 gate 當作 low / medium / high 的預設終局。
|
||||
|
||||
**完成內容**:
|
||||
- `apps/web/messages/zh-TW.json` 與 `apps/web/messages/en.json` 將 32 組 IwoooS payload 字串從 `人工閘門` 改為 `受控授權閘門`,保留 hard blocker / no-runtime-write 邊界,但不再使用人工 gate 作為產品預設語意。
|
||||
- 新增 `apps/web/src/app/[locale]/awooop/alerts/page.tsx`,將 `/awooop/alerts` 穩定導向 Runs 的 `#ai-alert-card-delivery-readback` 區段,避免 Alerts smoke 變成 404。
|
||||
|
||||
**本地驗證結果**:
|
||||
- JSON parse:`apps/web/messages/zh-TW.json`、`apps/web/messages/en.json` 通過。
|
||||
- i18n mirror:zh-TW / en key count `14468 / 14468`,missing `0 / 0`。
|
||||
- source 搜尋:AwoooP source 與 messages 內 `待人工決策`、`阻塞與人工閘門`、`人工接手`、`人工閘門`、`人工升級`、`owner review`、`owner packet` 命中 `0`。
|
||||
- `pnpm --filter @awoooi/web typecheck`:通過。
|
||||
- `git diff --check`:通過。
|
||||
|
||||
**Gitea / deploy 狀態**:
|
||||
- fix commit:`b2458b933 fix(awooop): remove manual gate payload residue`。
|
||||
- 後續整合 head:`a2733fd43 test(awooop): lock controlled apply receipt km snapshot`,包含 `b2458b933`。
|
||||
- deploy marker:`f98aaa8ee chore(cd): deploy a2733fd [skip ci]`。
|
||||
- Gitea `code-review.yaml #3705` 成功;Gitea `cd.yaml #3704` 成功並產出 deploy marker。
|
||||
|
||||
**production readback**:
|
||||
- `/api/v1/health?_v=f98aaa8ee-manual-residue-clean`:HTTP `200`、`status=healthy`、`environment=prod`、`mock_mode=false`。
|
||||
- HTML readback:Approvals / Runs / Work Items / Alerts 皆 HTTP `200`,舊詞 hit count 全部 `0`。
|
||||
- `/zh-TW/awooop/alerts` final URL 讀回 `/zh-TW/awooop/runs`,不再 404。
|
||||
- 新語意可讀:`待 AI 受控決策`、`阻塞與 AI 受控隊列`、`AI 處置包與工作項`、`受控執行邊界`、`受控授權閘門`、`controlled gate`、`controlled review`。
|
||||
|
||||
**desktop / mobile smoke**:
|
||||
- Chrome headless desktop `1366x900` 與 mobile `390x844`:Approvals / Runs / Work Items / Alerts 皆 HTTP `200`。
|
||||
- visible 舊詞 count `0`、HTML 舊詞 count `0`、`horizontalOverflow=false`。
|
||||
- Alerts final URL:`/zh-TW/awooop/runs#ai-alert-card-delivery-readback`。
|
||||
- Approvals 仍有既有未登入資源 `401` console error;本輪未新增,且不影響 HTML / visible text / layout smoke。
|
||||
|
||||
**未做與邊界**:
|
||||
- 沒有把 critical / secret / destructive / active scan / force push / repo visibility / refs mutation 放寬成自動執行。
|
||||
- 沒有 host / Docker / systemd / Nginx / firewall / K8s / DB / Wazuh runtime 寫操作;沒有讀 secret;沒有 force push。
|
||||
|
||||
**下一步**:
|
||||
- 若後續再清 IwoooS / security 歷史 payload,應只改產品顯示與 controlled / break-glass 語意,不把 `0 / false` 計數假性上修。
|
||||
|
||||
## 2026-06-27 — 21:56 GitHub owner response preflight API 正式讀回完成
|
||||
|
||||
**背景**:上一段已把 GitHub target owner response request packet / templates / forbidden payloads 暴露成 read-only readiness;本段不是文件補充,而是新增正式 POST preflight API,讓 owner-provided redacted evidence refs 可以先被驗證,同時拒收 credential URL、repo creation command、secret-like value 與未允許欄位。
|
||||
|
||||
**完成內容**:
|
||||
- 新增 `POST /api/v1/agents/github-target-owner-response-intake-preflight`。
|
||||
- API 只做 candidate owner response validation:`mode=validate_owner_response_only_no_persist_no_github_write`。
|
||||
- `apps/api/src/services/github_target_private_backup_evidence_gate.py` 新增 `preflight_github_target_owner_response_submission(...)`,依 committed templates / allowed fields / acceptable decisions / evidence refs / forbidden payloads 驗證。
|
||||
- 合法 `read_only_markdown_response` 可回 `ready_for_read_only_owner_response_intake`;含 private clone credential、GitHub token-like string、repo creation command、unsupported field 或 secret-like value 會回 `blocked_owner_response_intake_preflight`。
|
||||
- 回應固定經過 public LAN topology redaction;正式回應未命中 `192.168.0.`。
|
||||
|
||||
**本地驗證結果**:
|
||||
- `python3 -m py_compile apps/api/src/services/github_target_private_backup_evidence_gate.py apps/api/src/api/v1/agents.py`:通過。
|
||||
- `DATABASE_URL=sqlite:///test.db PYTHONPATH=apps/api python3.11 -m pytest apps/api/tests/test_github_target_private_backup_evidence_gate.py apps/api/tests/test_github_target_private_backup_evidence_gate_api.py apps/api/tests/test_delivery_closure_workbench_api.py -q`:`10 passed`。
|
||||
- `python3.11 -m ruff check apps/api/src/services/github_target_private_backup_evidence_gate.py apps/api/src/api/v1/agents.py apps/api/tests/test_github_target_private_backup_evidence_gate.py apps/api/tests/test_github_target_private_backup_evidence_gate_api.py`:通過。
|
||||
- `git diff --check`:通過。
|
||||
|
||||
**Gitea / deploy 狀態**:
|
||||
- code commit:`a68d9e40a feat(github): preflight owner response intake`。
|
||||
- deploy marker:`5b7bd55a9 chore(cd): deploy a68d9e4 [skip ci]`。
|
||||
- `code-review.yaml #3697` 成功;原 `cd.yaml #3696` 後續被更新的 main push supersede / cancel,但已產出 deploy marker。
|
||||
- 最新 `gitea-ssh/main=0624be08d` 已包含 `a68d9e40a`;目前尚未看到比 `5b7bd55a9` 更新且指向 `0624be08d` 的 deploy marker。
|
||||
|
||||
**production API readback**:
|
||||
- `/api/v1/health?_v=a68d9e40-owner-preflight-final`:HTTP `200`、`status=healthy`、`environment=prod`、`mock_mode=false`。
|
||||
- valid POST:HTTP `200`、schema `github_target_owner_response_intake_preflight_v1`、status `ready_for_read_only_owner_response_intake`、`candidate_response_item_count=1`、`preflight_passed_response_item_count=1`、`preflight_blocked_response_item_count=0`、`accepted_for_read_only_intake=true`。
|
||||
- dangerous POST:HTTP `200`、status `blocked_owner_response_intake_preflight`、`preflight_passed_response_item_count=0`、`preflight_blocked_response_item_count=1`、`forbidden_payload_hit_count=4`、blockers 包含 `forbidden_payload_detected` 與 `unsupported_response_fields`。
|
||||
|
||||
**仍維持 0 / false**:
|
||||
- valid / dangerous POST 皆維持 `owner_response_received_count=0`、`owner_response_accepted_count=0`、`safe_credential_accepted_evidence_count=0`。
|
||||
- `github_api_write_allowed=false`、`repo_creation_authorized=false`、`refs_sync_authorized=false`、`persist_submission_allowed=false`、`private_clone_url_collection_allowed=false`。
|
||||
|
||||
**未做**:
|
||||
- 沒有 GitHub repo creation、visibility change、refs sync、workflow trigger、private clone URL collection、secret value collection 或 persistence。
|
||||
- 沒有 host / Docker / systemd / Nginx / firewall / K8s / DB / Wazuh runtime 寫操作;沒有 force push。
|
||||
|
||||
**完成度 / 下一步**:
|
||||
- 本段「owner response intake preflight API / production readback」:`0% -> 100%`。
|
||||
- GitHub backup mirror governance 仍為 blocked;下一個 P0 是設計 no-secret persistence contract 或 workbench UI intake,但只能保存 redacted evidence refs 與 validation receipt,不能保存 private clone credential、secret value 或執行 GitHub write。
|
||||
|
||||
## 2026-06-27|MOMO daily-sales source absence readback 與 cold-start blocker
|
||||
|
||||
**背景**:110 runner / StockPlatform smoke 壓力已止血後,重新跑全主機 cold-start scorecard 與資料 freshness。AWOOOI / IwoooS / Stock / 188 主要 public routes 可用,但整體 cold-start 仍不能宣告 full green;目前主要業務資料 blocker 是 188 MOMO daily sales freshness。
|
||||
|
||||
**執行邊界**:
|
||||
- 本輪只做 read-only preflight、log readback、檔名 / mtime / size 層級來源搜尋與 scorecard 彙整。
|
||||
- 未做 DB write / truncate / restore / manual import,未移動 Drive 檔案,未重啟 Docker / Nginx / K3s / scheduler,未讀 token value、raw session、SQLite、`.env` 或 secret。
|
||||
|
||||
**cold-start / scorecard 結果**:
|
||||
- `scripts/reboot-recovery/post-reboot-readiness-summary.sh` artifact:`/tmp/awoooi-post-reboot-readiness-20260627-codex-rerun/summary.txt`。
|
||||
- `POST_START_RESULT=BLOCKED`、`POST_START_PASS=37`、`POST_START_WARN=3`、`POST_START_BLOCKED=2`、`SERVICE_GREEN=0`。
|
||||
- `PRODUCT_DATA_GREEN=1`、Stock freshness `ok`,latest trading date `2026-06-26`,`STOCK_BLOCKERS=none`。
|
||||
- `BACKUP_CORE_GREEN=1`,但 `DR_ESCROW_BLOCKED=1`、`ESCROW_MISSING_COUNT=5`。
|
||||
- Wazuh route `200`,但 `WAZUH_MANAGER_REGISTRY_ACCEPTED=0`、`WAZUH_RUNTIME_GATE=0`、`RUNTIME_ACTION_AUTHORIZED=0`。
|
||||
- 直接 cold-start rerun:`PASS=88`、`WARN=0`、`BLOCKED=1`;唯一 blocker 是 `188 momo daily sales data stale beyond 3 days`。
|
||||
- 20:48 next-gate dispatch 使用同一份 summary 回傳 `DISPATCH_RC=2`、`SERVICE_GREEN=0`、`NEXT_REQUIRED_GATES=credential_escrow_evidence,wazuh_manager_registry_export`、`DISPATCH_AUTHORIZED=0`、`REQUEST_SENT_COUNT=0`、`HOST_WRITE_AUTHORIZED=0`、`SECRET_VALUE_COLLECTION_ALLOWED=0`,並停在 `NEXT_STEP=restore_service_before_boundary_dispatch`;因此目前不可把 escrow / Wazuh gates 當成已可送出的 owner packet。
|
||||
|
||||
**MOMO readback 結果**:
|
||||
- `scripts/reboot-recovery/momo-drive-token-source-recovery-preflight.sh` 結果:`PASS=20`、`WARN=3`、`BLOCKED=2`。
|
||||
- MOMO health:local / public health 皆 `200`,runtime version `V10.725`,app health `healthy`。
|
||||
- DB daily range:`109061|2025-07-01|2026-06-24`;freshness `3|2026-06-24`。
|
||||
- current monthly 與 sync snapshot parity:`15383|15383|2026-06-01|2026-06-24|2026-06-01|2026-06-24`。
|
||||
- latest import job `57`:`completed|即時業績_當日.xlsx|15383|15383|0`,表示 2026-06-25 13:16-13:18 的已匯入來源處理乾淨,但資料仍只到 `2026-06-24`。
|
||||
- Drive pending intake:`LOCAL_EXACT_DAILY_SOURCE_COUNT=0`;archive / global latest evidence 仍停在 `2026-06-25T04:21:47.000Z`。
|
||||
- `momo-scheduler` 36h log 顯示 Google Drive 連線成功、定期檢查 `當日業績匯入`,但多次回報找不到 Excel;scheduler 是 healthy / registered,不是目前 freshness blocker 的主因。
|
||||
- 188 與本機安全範圍檔名搜尋只找到舊 `即時業績_當日_20260112.xlsx` 候選,未找到可用於 2026-06-26 / 2026-06-27 的合法 daily-sales source。
|
||||
- 20:54 二次 preflight 仍為 `DRIVE_INTAKE_COUNT=0`、archive / global latest `2026-06-25T04:21:47.000Z`、DB daily freshness `3|2026-06-24`、latest import job `57 completed`;`momo-pro-system` / `momo-scheduler` containers 仍 healthy,且最近 scheduler log 只有排程註冊與一般 warning,沒有新 Excel 入站或成功匯入證據。
|
||||
|
||||
**DR / Wazuh gate readback**:
|
||||
- 110 `/backup/scripts/offsite-escrow-evidence-report.sh --no-color` 顯示 rclone offsite configured、full offsite marker fresh、local backup repos checkable,但 5 個 credential escrow marker 全缺:`restic_repository_password`、`offsite_provider_credentials`、`break_glass_admin_credentials`、`dns_registrar_recovery`、`oauth_ai_provider_recovery`。
|
||||
- `scripts/security/wazuh-manager-registry-reviewer-validation.py` 通過 repo contract validation,但 snapshot 仍是 `received=0 accepted=0 runtime_gate=0`;route / transport / index pattern 不能替代 manager registry accepted。
|
||||
- 本輪沒有寫 escrow marker,沒有產生 owner response,沒有查 Wazuh live API / secret,也沒有 Wazuh active response、agent re-enroll、restart、host write 或 Kali active scan。
|
||||
|
||||
**2026-06-27 21:00 gate 補強**:
|
||||
- 新增 `scripts/reboot-recovery/momo-source-arrival-gate.py`,只解析 `momo-drive-token-source-recovery-preflight.sh` 產出的 log 或 stdin,不連線、不查 token、不 import、不移動 Drive、不寫 DB。
|
||||
- 真實 20:54 preflight log 驗證:`MOMO_SOURCE_ARRIVAL_GATE status=blocked_source_absent_fail_closed source_intake=0 freshness=3|2026-06-24 safe_import_preflight_allowed=0 runtime_write_authorized=0 db_write_authorized=0 drive_move_authorized=0 next_step=wait_for_legitimate_daily_sales_source_then_rerun_gate`,exit code `2`。
|
||||
- 合成 source-arrived case 驗證:Drive intake count `1` 且 freshness stale 時,只回 `source_arrived_ready_for_safe_import_preflight`、`safe_import_preflight_allowed=1`,仍固定 `runtime_write_authorized=0`、`db_write_authorized=0`、`drive_move_authorized=0`。
|
||||
- 合成 freshness-green case 驗證:freshness `1|2026-06-26` 時回 `freshness_already_green_recheck_cold_start`,下一步仍是重跑 post-reboot summary,不得直接宣告 full green。
|
||||
|
||||
**結論**:
|
||||
- 目前狀態是 `SERVICE_BLOCKED_MOMO_SOURCE_ABSENCE` / `SOURCE_ABSENT_FAIL_CLOSED`,不是 runner、Docker、Nginx、K3s 或 scheduler 事故。
|
||||
- 禁止用舊 archive、舊 sample、本機舊檔、手寫 DB、truncate / restore 或 manual Drive movement 製造 freshness 假綠。
|
||||
- 解除 blocker 需要新的合法 `即時業績_當日` source 出現在 `當日業績匯入`,或 owner-approved safe source evidence ref;之後才可在 maintenance-safe path 執行匯入,並要求 `sync_success=true`、source 只在成功後移動、daily snapshot / realtime monthly bounds 一致、freshness `<=2`,再重跑 cold-start scorecard。
|
||||
|
||||
**下一步**:
|
||||
- 保持 fail-closed,等待合法來源到位後做 read-only preflight recheck。
|
||||
- 若有 owner-approved source evidence ref,另開 maintenance window 走安全匯入路徑;仍不得在沒有來源證據時宣告 all-green。
|
||||
|
||||
## 2026-06-27|110 Gitea runner 降壓防回彈與 workflow label 收斂
|
||||
|
||||
**背景**:110 CPU 事故已確認主因是 Gitea runner 反覆拉起 StockPlatform headless Chrome smoke;前一輪已停止 `gitea-act-runner-host.service`、清掉 Actions / smoke,並把 live runner labels 收斂為 `awoooi-ubuntu` / `awoooi-host`。本輪目標是防止 cold-start / startup 流程把 runner 又自動拉起,並補齊 AWOOI workflow label 與 post-deploy pressure gate。
|
||||
|
||||
**完成內容**:
|
||||
- `.gitea/workflows/cd.yaml` 的 `post-deploy-checks` 在 checkout 後新增 `Wait for Host Web Build Pressure`,避免 Alert Chain / Source Link / Monitoring / Playwright smoke 疊到 110 既有 build / smoke / load 壓力。
|
||||
- `.gitea/workflows/ansible-lint.yml` 從 `self-hosted` 收斂為 `awoooi-ubuntu`;AWOOI workflows 目前只剩 `awoooi-ubuntu` / `awoooi-host` 兩類 label。
|
||||
- `scripts/reboot-recovery/awoooi-startup-110.sh` 改成預設不自動啟動 Gitea host runner;只有明確設定 `AWOOOI_START_GITEA_RUNNER_ON_BOOT=1` 才允許 startup 拉起 runner。
|
||||
- live `/usr/local/bin/awoooi-startup-110.sh` 已安裝新版,舊檔備份為 `/usr/local/bin/awoooi-startup-110.sh.bak-20260627-runner-inactive`;本輪沒有執行 startup script,也沒有重啟 runner。
|
||||
- closeout 時發現 Docker-wrapped `gitea-runner` 短暫回彈為 running;確認 active task containers `0` 後,已只針對 `gitea-runner` 執行 `docker update --restart=no` 與 `docker stop -t 60`,恢復 `Restart=no Status=exited Running=false`。
|
||||
- `ops/runner/audit-workflow-labels.py` 修正 local fallback,沒有 Gitea auth 但指定 `--local-repo` 時不再輸出假空白。
|
||||
- `ops/runner/check-runner-isolation-readiness.sh` 認得 `awoooi-ubuntu`,避免把新 label 誤判成 unknown / mixed owner。
|
||||
- `ops/runner/README.md` 更新 2026-06-27 runner 降壓狀態、hard-fail pressure gate、startup 開關與 workflow label 邊界。
|
||||
|
||||
**驗證結果**:
|
||||
- `bash -n scripts/reboot-recovery/awoooi-startup-110.sh scripts/ci/wait-host-web-build-pressure.sh ops/runner/check-runner-isolation-readiness.sh ops/runner/audit-runner-pool.sh`:通過。
|
||||
- `python3 -m py_compile ops/runner/audit-workflow-labels.py scripts/ops/host-runaway-process-exporter.py`:通過。
|
||||
- Gitea workflow YAML parse:10 個 workflow 全部通過。
|
||||
- `rg "runs-on: (ubuntu-latest|self-hosted|ubuntu-22.04|ubuntu-24.04)" .gitea/workflows`:無命中。
|
||||
- `ops/runner/audit-workflow-labels.py --repo wooo/awoooi --local-repo wooo/awoooi=/Users/ogt/awoooi`:labels 只剩 `awoooi-host` / `awoooi-ubuntu`。
|
||||
- 110 readback:`gitea-act-runner-host.service=inactive`、Actions containers `0`、active CI groups `0`、StockPlatform orphan groups `0`。
|
||||
- Docker-wrapped `gitea-runner`:`Restart=no Status=exited Running=false`。
|
||||
- 110 readiness:primary labels `awoooi-ubuntu` / `awoooi-host` 均為 `awoooi_dedicated`,`mixed_owner_classes=0`,active action containers `none`。
|
||||
- 110 pressure gate 目前 `GATE_RC=1`,原因是 `load5/core 0.886667 > 0.85`;top process 顯示主要是 `restic` 6h backup,不是 Gitea Actions / Chrome smoke 事故復燃。
|
||||
- 110 local Gitea / Sentry / Alertmanager / Grafana health readback:`200 / 302 / 200 / 200`。
|
||||
|
||||
**邊界與下一步**:
|
||||
- runner inactive 是刻意降壓;未完成限流 / 搬遷前不可直接重開。
|
||||
- 本輪未重啟 Docker / Nginx / firewall / K3s,未 kill process,未讀 raw sessions / SQLite / auth / secret。
|
||||
- 下一個 P0:把 StockPlatform smoke 改成排程限流或搬到非 110 runner;再做全主機 cold-start scorecard 與資料 freshness readback。
|
||||
|
||||
## 2026-06-27|IwoooS Wazuh owner export reviewer validation passed 本地完成
|
||||
## 2026-06-27 — 21:45 IwoooS Wazuh reviewer post-enable readback 正式讀回完成
|
||||
|
||||
**時間與來源**:
|
||||
- 2026-06-27 21:24-21:45 Asia/Taipei。
|
||||
- 來源:feature branch `codex/iwooos-post-enable-readback-20260627`、Gitea main、Gitea Actions public HTML、production API / `/zh-TW/iwooos` desktop / mobile smoke。
|
||||
|
||||
**完成內容**:
|
||||
- `GET /api/v1/iwooos/wazuh-manager-registry-reviewer-validation` 已正式讀回 `post_enable_iwooos_readback_passed_no_runtime_no_secret_collection`。
|
||||
- API service 放寬 `post_enable_readback_passed_count` 從 0 更新為 1,但仍強制 `manager_registry_accepted_count`、`runtime_gate_count`、`host_write_authorized_count`、`active_response_authorized_count` 與 `secret_value_collection_allowed_count` 維持 0。
|
||||
- 前台 `/zh-TW/iwooos` Wazuh manager registry reviewer validation 卡片新增 `Post-enable = 1`,文案明確標示這不是 live Wazuh 查詢授權。
|
||||
- security guard 與 contract tests 已同步 lane count `13 -> 14`,新增 `post_enable_iwooos_readback_passed` 與 `manager_registry_acceptance_evidence_review` 下一關。
|
||||
|
||||
**Gitea / deploy 狀態**:
|
||||
- code commit:`c73ce995e feat(iwooos): mark wazuh reviewer post-enable readback`。
|
||||
- 後續 main commit:`1a8613c9e fix(governance): stabilize automation tab deep link`,包含 `c73ce995e`。
|
||||
- deploy marker:`1a6f8f427 chore(cd): deploy 1a8613c [skip ci]`。
|
||||
- `c73ce995e` 單獨 `code-review.yaml #3693` / `cd.yaml #3692` 因後續 main push 被 concurrency 取消;最新 main `code-review.yaml #3695` 成功,`cd.yaml #3694` 成功。
|
||||
|
||||
**本地驗證結果**:
|
||||
- `DATABASE_URL=sqlite:///test.db python3.11 -m pytest apps/api/tests/test_iwooos_wazuh_manager_registry_reviewer_validation.py apps/api/tests/test_iwooos_runtime_security_readback.py apps/api/tests/test_iwooos_wazuh_managed_host_coverage.py apps/api/tests/test_iwooos_wazuh_api.py -q`:`23 passed`。
|
||||
- `python3 scripts/security/wazuh-manager-registry-reviewer-validation.py --root .`:`post_enable=1 runtime_gate=0`。
|
||||
- `python3 scripts/security/iwooos-frontend-display-redaction-guard.py --root .`:通過。
|
||||
- `python3 scripts/security/security-mirror-progress-guard.py --root .`:通過。
|
||||
- `python3 -m py_compile ...`、JSON parse、`git diff --check`、`pnpm --dir apps/web typecheck`:通過。
|
||||
|
||||
**production API readback**:
|
||||
- `/api/v1/health?_v=1a6f8f427`:HTTP `200`、`status=healthy`、`environment=prod`、`mock_mode=false`。
|
||||
- `GET /api/v1/iwooos/wazuh-manager-registry-reviewer-validation?_v=1a6f8f427-final`:HTTP `200`。
|
||||
- schema:`iwooos_wazuh_manager_registry_reviewer_validation_readback_v1`。
|
||||
- status:`post_enable_iwooos_readback_passed_no_runtime_no_secret_collection`。
|
||||
- mode:`committed_post_enable_iwooos_readback_passed_no_runtime_no_secret_collection`。
|
||||
- summary:`outcome_lane_count=14`、`owner_registry_export_received_count=1`、`owner_registry_export_accepted_count=1`、`reviewer_validation_passed_count=1`、`post_enable_readback_passed_count=1`。
|
||||
- valid redacted sample POST:`accepted_for_readonly_posture_only`、`mode=no_persist_validation_no_runtime_no_secret_collection`;POST-local `post_enable_readback_passed_count=0`,POST 後 GET global 仍維持 `post_enable_readback_passed_count=1`,沒有累加或保存 payload。
|
||||
|
||||
**production browser smoke**:
|
||||
- Desktop `1360x900`:`/zh-TW/iwooos?_v=1a6f8f427-desktop-dom` HTTP `200`、console error `0`、水平溢出 `0`、forbidden hits `0`。
|
||||
- Mobile `384x900`:`/zh-TW/iwooos?_v=1a6f8f427-mobile-dom` HTTP `200`、console error `0`、水平溢出 `0`、forbidden hits `0`。
|
||||
- 前台可見片段:`Post-enable` / `1` / `正式 API 與前台已讀回 reviewer passed;這不是 live Wazuh 查詢授權。`
|
||||
|
||||
**仍維持 0 / false**:
|
||||
- `manager_registry_accepted_count=0`、`runtime_gate_count=0`、`host_write_authorized_count=0`、`active_response_authorized_count=0`、`secret_value_collection_allowed_count=0`。
|
||||
- `wazuh_api_live_query_authorized=false`、`wazuh_agent_reenroll_authorized=false`、`wazuh_agent_restart_authorized=false`、`wazuh_active_response_authorized=false`、`raw_wazuh_payload_storage_allowed=false`、`not_authorization=true`。
|
||||
|
||||
**未做**:
|
||||
- 沒有 host / Docker / systemd / Nginx / firewall / K8s / DB / Wazuh runtime 寫操作;沒有讀 secret 明文;沒有重新註冊 agent;沒有 Wazuh restart;沒有 Wazuh active response;沒有 Kali active scan;沒有 force push。
|
||||
|
||||
**完成度 / 下一步**:
|
||||
- Wazuh reviewer post-enable readback:`85% -> 100%`。
|
||||
- IwoooS 整體:保守 `70% -> 72%`。此段只完成 production API / 前台 readback,不代表 Wazuh 全主機納管或 manager registry accepted 已完成。
|
||||
- 下一個 P0:`manager_registry_acceptance_evidence_review`,必須以 manager registry accepted evidence 與 host/product/agent scope 對帳推進;Dashboard 200、前台可見或 API 200 仍不可當成全主機納管完成。
|
||||
|
||||
## 2026-06-27 — 21:24 GitHub backup owner response intake readiness 正式讀回完成
|
||||
|
||||
**時間與來源**:
|
||||
- 2026-06-27 20:49-21:24 Asia/Taipei。
|
||||
- 來源:feature branch `codex/github-backup-missing-targets-20260627`、Gitea main、Gitea Actions、production API readback。
|
||||
|
||||
**完成內容**:
|
||||
- `GET /api/v1/agents/github-target-private-backup-evidence-gate` 已正式讀回 `owner_response_intake_readiness`。
|
||||
- GitHub target owner response request packet、9 個 response templates、allowed fields、forbidden payloads、collection checks、intake preflight checks 與 acceptance checks 已能由 production API 讀出。
|
||||
- 每個 target 已能讀回 owner response template mapping 與 `owner_response_execution_authorized=false`。
|
||||
- Validator 已防止 owner response request packet、templates 或 checks 夾帶 execution authorization。
|
||||
|
||||
**Gitea / deploy 狀態**:
|
||||
- code commit:`80138e985 feat(api): expose github owner response intake readiness`。
|
||||
- merge / main push commit:`9f5097f66 Merge remote-tracking branch 'gitea/main' into codex/github-backup-missing-targets-20260627`。
|
||||
- deploy marker:`e49c6190e chore(cd): deploy 9f5097f [skip ci]`。
|
||||
- code-review:`#3690` 成功。
|
||||
- CD:`#3689` 成功;tests、build-and-deploy、post-deploy-checks 均完成。
|
||||
- 最新 main 後續 docs marker:`7b2b3db45 docs(awooop): record controlled automation readback [skip ci]`,不改本段 production code 基準。
|
||||
|
||||
**production API readback**:
|
||||
- `/api/v1/health?_v=9f5097f66-github-owner-intake`:HTTP `200`、`status=healthy`、`environment=prod`、`mock_mode=false`。
|
||||
- `GET /api/v1/agents/github-target-private-backup-evidence-gate?_v=9f5097f66-github-owner-intake`:HTTP `200`。
|
||||
- `approval_required_target_count=9`、`private_backup_verified_count=4`、`private_visibility_verified_count=4`。
|
||||
- `github_missing_target_resolution_count=5`、`github_missing_target_create_private_repo_ready_count=0`、`github_missing_target_refs_sync_ready_count=0`。
|
||||
- `owner_response_request_ready=true`、`owner_response_required_response_item_count=9`、`owner_response_requested_template_count=9`、`owner_response_template_count=9`。
|
||||
- `owner_response_allowed_response_field_count=25`、`owner_response_forbidden_payload_count=15`、`owner_response_collection_check_count=6`、`owner_response_intake_preflight_check_count=6`。
|
||||
- `owner_response_request_execution_authorized=false`、`owner_response_received_count=0`、`owner_response_accepted_count=0`、`safe_credential_accepted_evidence_count=0`、`execution_ready_count=0`、`blocked_target_count=9`。
|
||||
- `owner_response_intake_readiness.status=ready_to_collect_read_only_owner_response_not_authorization`、`request_ready=true`、`execution_authorized=false`、`not_approval=true`。
|
||||
- forbidden / redaction proof:`private_clone_url_credential` 仍在 forbidden payloads,`read_only_markdown_response` 是允許 submission mode,`create_github_repo` 仍在 still forbidden,正式 API 回應未命中 `192.168.0.`。
|
||||
|
||||
**Delivery Workbench readback**:
|
||||
- `GET /api/v1/agents/delivery-closure-workbench?_v=9f5097f66-github-owner-intake`:HTTP `200`。
|
||||
- schema:`delivery_closure_workbench_v1`。
|
||||
- GitHub lane:`status=blocked_private_visibility_and_safe_credential_evidence_required`、metric `private_backup_verified=4/9`、`completion_percent=44`、`blocker_count=9`。
|
||||
|
||||
**仍維持 0 / false**:
|
||||
- `github_missing_target_create_private_repo_ready_count=0`、`github_missing_target_refs_sync_ready_count=0`、`owner_response_received_count=0`、`owner_response_accepted_count=0`、`safe_credential_accepted_evidence_count=0`、`execution_ready_count=0`。
|
||||
- `github_api_write_allowed=false`、`repo_creation_allowed=false`、`visibility_change_allowed=false`、`refs_sync_allowed=false`、`workflow_trigger_allowed=false`、`private_clone_url_collection_allowed=false`、`secret_value_collection_allowed=false`。
|
||||
|
||||
**未做**:
|
||||
- 沒有 GitHub repo creation、visibility change、refs sync、workflow trigger、private clone URL collection、secret value collection;沒有 host / Docker / systemd / Nginx / firewall / K8s / DB / Wazuh runtime 寫操作;沒有 force push。
|
||||
|
||||
**完成度 / 下一步**:
|
||||
- 本段「GitHub owner response intake readiness API / production readback」:`85% -> 100%`。
|
||||
- GitHub backup mirror governance 仍為 blocked;下一個 P0 是 owner-provided safe credential evidence / redacted evidence refs intake,但不得收 private clone URL credential 或 secret value。
|
||||
|
||||
## 2026-06-27|AwoooP Approvals controlled automation 文案正式讀回完成
|
||||
|
||||
**背景**:P2-416 D1N 已把 AI Agent 舊 manual gate 規範改為 controlled automation;low / medium / high 風險應走 allowlist、check-mode、controlled apply、verifier、rollback 與 KM / PlayBook writeback,critical 才 break-glass。正式 `/zh-TW/awooop/approvals` 仍因 Next HTML payload 序列化其他 namespace,殘留 `待人工決策`、`阻塞與人工閘門`、`人工接手`、`owner review`、`owner packet` 等舊語意,容易讓 Approvals / Runs / Work Items / Alerts 看起來把人工當預設終局。
|
||||
@@ -329,6 +71,7 @@
|
||||
- 部署後讀回 `GET /api/v1/agents/github-target-private-backup-evidence-gate`,目標確認 `owner_response_request_ready=true`、requested templates `9`、forbidden payloads `15`、collection / preflight checks `6/6`,且 create/private ready 與 refs sync ready 仍維持 `0`。
|
||||
|
||||
## 2026-06-27|IwoooS Wazuh owner export reviewer validation passed 正式讀回完成
|
||||
|
||||
**時間與來源**:
|
||||
- 2026-06-27 20:42-21:22 Asia/Taipei。
|
||||
- 來源:`docs/security/wazuh-manager-registry-reviewer-validation.snapshot.json`、`scripts/security/wazuh-manager-registry-reviewer-validation.py`、API service / tests、Gitea Actions、production API、`/zh-TW/iwooos` desktop / mobile browser smoke。
|
||||
@@ -47854,88 +47597,3 @@ production browser smoke:
|
||||
|
||||
**下一個 P0**:
|
||||
- 開新工作視窗處理 owner-provided redacted Wazuh manager registry export 收件與 reviewer validation passed;只有 evidence 完整、算術一致、6 alias scope parity、Dashboard API repair readback 與 owner / rollback owner 齊全時,才可推進 manager registry accepted,仍不得把 Dashboard 200 或前台可見當成全主機納管完成。
|
||||
|
||||
## 2026-06-27 — 21:47 GitHub safe credential evidence intake readiness 本地完成
|
||||
|
||||
**時間與來源**:
|
||||
- 2026-06-27 21:47 Asia/Taipei。
|
||||
- 來源:feature branch `codex/github-safe-credential-intake-20260627`、`gitea/main=1a6f8f427`。
|
||||
|
||||
**完成內容**:
|
||||
- `apps/api/src/services/github_target_private_backup_evidence_gate.py` 新增 `safe_credential_evidence_intake_readiness` readback,將 GitHub target owner response snapshot 內的 `evidence_ref_rules`、`owner_response_redaction_examples`、`forbidden_payloads`、quarantine lanes 與 submission modes 投影到 API。
|
||||
- summary 新增 safe credential intake counters:`safe_credential_evidence_intake_ready=true`、`safe_credential_required_redacted_evidence_ref_count=9`、`safe_credential_evidence_ref_rule_count=5`、`safe_credential_redaction_example_count=5`、`safe_credential_forbidden_payload_count=15`、`safe_credential_quarantine_lane_count=1`、`safe_credential_raw_payload_storage_allowed=false`。
|
||||
- per-target 新增安全收件狀態:approval-required targets 顯示 `waiting_redacted_evidence_ref`,external target 顯示 `not_required`;allowed ref types 只允許 `repo_path`、`snapshot_path`、`redacted_metadata_pointer`。
|
||||
- 一致性檢查新增 fail-closed guard:redaction example 不得允許 raw payload storage;forbidden payloads 必須包含 `token_value`、`secret_value`、`private_clone_url_credential`、`repo_archive`、`git_object_pack`。
|
||||
|
||||
**本地驗證結果**:
|
||||
- `python3.11 -m ruff format apps/api/src/services/github_target_private_backup_evidence_gate.py apps/api/tests/test_github_target_private_backup_evidence_gate.py apps/api/tests/test_github_target_private_backup_evidence_gate_api.py`:通過,`3 files left unchanged`。
|
||||
- `python3.11 -m ruff check apps/api/src/services/github_target_private_backup_evidence_gate.py apps/api/tests/test_github_target_private_backup_evidence_gate.py apps/api/tests/test_github_target_private_backup_evidence_gate_api.py`:通過。
|
||||
- `python3 -m py_compile apps/api/src/services/github_target_private_backup_evidence_gate.py`:通過。
|
||||
- `DATABASE_URL=sqlite:///test.db PYTHONPATH=apps/api python3.11 -m pytest apps/api/tests/test_github_target_private_backup_evidence_gate.py apps/api/tests/test_github_target_private_backup_evidence_gate_api.py apps/api/tests/test_delivery_closure_workbench_api.py -q`:`9 passed`。
|
||||
- `git diff --check`:通過。
|
||||
- 本地 readback snippet:`safe_credential_evidence_intake_ready=True`、required redacted evidence refs `9`、evidence rules `5`、redaction examples `5`、forbidden payloads `15`、quarantine lanes `1`、raw payload storage allowed `False`、`private_backup_verified_count=4`、`execution_ready_count=0`、`blocked_target_count=9`。
|
||||
|
||||
**完成度與同步狀態**:
|
||||
- 本段「GitHub safe credential evidence intake readiness source/API」本地:`0% -> 85%`。
|
||||
- 尚未 merge/push main、尚未 CD、尚未 production API readback;production 仍以前一版為準直到 Gitea CD 成功與 readback 完成。
|
||||
|
||||
**仍維持 0 / false**:
|
||||
- `safe_credential_accepted_evidence_count=0`、`owner_response_received_count=0`、`owner_response_accepted_count=0`、`github_missing_target_create_private_repo_ready_count=0`、`github_missing_target_refs_sync_ready_count=0`、`execution_ready_count=0`、`blocked_target_count=9`。
|
||||
- `secret_value_collection_allowed=false`、`private_clone_url_collection_allowed=false`、`credential_value_collection_allowed=false`、`stored_raw_payload_allowed=false`、`repo_creation_authorized=false`、`visibility_change_authorized=false`、`refs_sync_authorized=false`、`workflow_trigger_authorized=false`。
|
||||
|
||||
**做過的命令類型**:
|
||||
- 寫入:repo service / test / LOGBOOK。
|
||||
- 只讀:git fetch / merge check、本地 API readback snippet、本地 focused tests。
|
||||
- 未做:沒有 GitHub repo creation、visibility change、refs sync、workflow trigger、private clone URL collection、secret value collection;沒有 host / Docker / systemd / Nginx / firewall / K8s / DB / Wazuh runtime 寫操作;沒有 force push。
|
||||
|
||||
**下一個 P0**:
|
||||
- commit feature,正常 push 到 Gitea;確認 CD idle/success 後 normal push `HEAD:main`,再做 production readback,目標為 `safe_credential_evidence_intake_ready=true`、required refs `9`、rules `5`、redaction examples `5`、forbidden payloads `15`、quarantine lanes `1`,同時 create/private ready `0`、refs sync ready `0`、execution ready `0` 維持不變。
|
||||
|
||||
## 2026-06-27 — 22:35 GitHub safe credential evidence intake readiness production 讀回完成
|
||||
|
||||
**時間與來源**:
|
||||
- 2026-06-27 22:18-22:35 Asia/Taipei。
|
||||
- 來源:feature branch `codex/github-safe-credential-intake-20260627`、main release `30af0fb420`、deploy marker `257544d09`、Gitea Actions 與 production API readback。
|
||||
|
||||
**完成內容**:
|
||||
- GitHub private backup evidence gate 已正式暴露 `safe_credential_evidence_intake_readiness`,可讀出脫敏 evidence ref 收件狀態、redaction examples、forbidden payloads 與 quarantine lanes。
|
||||
- 本段只建立 safe credential evidence 的 read-only intake contract;不代表 safe credential evidence 已 accepted,也不代表 repo creation、visibility change、refs sync 或 GitHub primary switch 已授權。
|
||||
|
||||
**runs 與驗證**:
|
||||
- feature latest:`30af0fb420 Merge remote-tracking branch 'gitea/main' into codex/github-safe-credential-intake-20260627`。
|
||||
- deploy marker:`257544d09 chore(cd): deploy 30af0fb [skip ci]`。
|
||||
- Gitea CD run `3707`:Success。
|
||||
- Gitea code-review run `3708`:Success。
|
||||
- 本地驗證:`python3.11 -m ruff check ...` 通過、`python3 -m py_compile apps/api/src/services/github_target_private_backup_evidence_gate.py` 通過、focused pytest `12 passed`、`pnpm --dir apps/web typecheck` 通過、`git diff --check` 通過。
|
||||
|
||||
**production API readback**:
|
||||
- `GET https://awoooi.wooo.work/api/v1/health`:`200`,`status=healthy`、`environment=prod`、`mock_mode=false`。
|
||||
- `GET https://awoooi.wooo.work/api/v1/agents/github-target-private-backup-evidence-gate`:`200`。
|
||||
- `safe_credential_evidence_intake_ready=True`。
|
||||
- `safe_credential_required_redacted_evidence_ref_count=9`、`safe_credential_evidence_ref_rule_count=5`、`safe_credential_redaction_example_count=5`、`safe_credential_forbidden_payload_count=15`、`safe_credential_quarantine_lane_count=1`。
|
||||
- `safe_credential_raw_payload_storage_allowed=False`、`private_clone_url_collection_allowed=False`、`secret_value_collection_allowed=False`。
|
||||
- top-level status:`ready_to_collect_redacted_evidence_refs_not_credentials`、`intake_ready=True`、`execution_authorized=False`、`not_approval=True`。
|
||||
- first target `owenhytsai/awoooi`:`safe_credential_evidence_submission_status=waiting_redacted_evidence_ref`、`safe_credential_raw_payload_storage_allowed=False`。
|
||||
|
||||
**production delivery readback**:
|
||||
- `GET https://awoooi.wooo.work/api/v1/agents/delivery-closure-workbench`:`200`,`schema_version=delivery_closure_workbench_v1`。
|
||||
- GitHub lane:`lane_id=github`、`status=blocked_private_visibility_and_safe_credential_evidence_required`、`completion_percent=44`、`blocker_count=9`。
|
||||
|
||||
**完成度與同步狀態**:
|
||||
- 本段「GitHub safe credential evidence intake readiness API / production 讀回」:`85% -> 100%`。
|
||||
- GitHub backup mirror governance:仍為 blocked;本段只把安全收件契約正式上線,不代表 private backup gate 已開。
|
||||
|
||||
**仍維持 0 / false**:
|
||||
- `safe_credential_accepted_evidence_count=0`、`owner_response_received_count=0`、`owner_response_accepted_count=0`。
|
||||
- `github_missing_target_create_private_repo_ready_count=0`、`github_missing_target_refs_sync_ready_count=0`、`execution_ready_count=0`、`blocked_target_count=9`、`private_backup_verified_count=4`。
|
||||
- `repo_creation_authorized=false`、`visibility_change_authorized=false`、`refs_sync_authorized=false`、`workflow_trigger_authorized=false`、`secret_value_collection_allowed=false`、`private_clone_url_collection_allowed=false`、`stored_raw_payload_allowed=false`。
|
||||
|
||||
**做過的命令類型**:
|
||||
- 寫入:repo service / test / LOGBOOK,以及正常 Gitea feature / main push。
|
||||
- 只讀:Gitea Actions UI readback、production health、production GitHub gate API、Delivery Workbench API。
|
||||
- 未做:沒有 GitHub repo creation、visibility change、refs sync、workflow trigger、private clone URL collection、secret value collection;沒有 host / Docker / systemd / Nginx / firewall / K8s / DB / Wazuh runtime 寫操作;沒有 force push。
|
||||
|
||||
**下一個 P0**:
|
||||
- `P0-01` GitHub private backup evidence acceptance:逐 target 收 owner-provided redacted evidence refs,讓 `safe_credential_accepted_evidence_count` 從 `0/9` 往上推;仍不得收 private clone URL、secret value、repo archive 或 git object pack。
|
||||
- `P0-02` missing target canonical source owner decision:釐清 `ewoooc`、`bitan-pharmacy`、`tsenyang-website`、`VibeWork`、`agent-bounty-protocol` 的 canonical source before repo creation。
|
||||
- `P0-03` refs sync readiness:canonical source 與 owner decision 未成立前,`refs_sync_ready_count` 必須維持 `0`。
|
||||
|
||||
@@ -296,8 +296,6 @@ NO-GO: truncate, whole-DB restore, manual Drive movement, or manual import witho
|
||||
UNBLOCK: new legitimate PChome daily-sales source appears in 當日業績匯入 or an owner-approved safe import path; import job succeeds with sync_success=true; source file moves only after success; daily_sales_snapshot and realtime_sales_monthly bounds match; MOMO_DAILY_FRESHNESS <= 2.
|
||||
```
|
||||
|
||||
2026-06-27 起,若已有 `momo-drive-token-source-recovery-preflight.sh` log,先跑 `python3 scripts/reboot-recovery/momo-source-arrival-gate.py --preflight-log <log>` 做機器判讀:`blocked_source_absent_fail_closed` 代表繼續等合法來源;`source_arrived_ready_for_safe_import_preflight` 只代表可進另一個 safe import preflight,不代表 DB write、Drive move、manual import 或 runtime write 已授權;`freshness_already_green_recheck_cold_start` 仍必須重跑同一 evidence chain 的 post-reboot summary 後才能更新恢復宣告。
|
||||
|
||||
所有回報必須使用這組詞,避免把「服務面可用」誤報成「整體 DR 完成」。
|
||||
|
||||
### 0.3 Codex 工作站交接判定
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"evidence_slots": [
|
||||
{
|
||||
"accepted": true,
|
||||
"next_gate": "manager_registry_acceptance_evidence_review",
|
||||
"next_gate": "post_enable_iwooos_readback",
|
||||
"quarantined": false,
|
||||
"received": true,
|
||||
"required_fields": [
|
||||
@@ -17,7 +17,7 @@
|
||||
},
|
||||
{
|
||||
"accepted": true,
|
||||
"next_gate": "manager_registry_acceptance_evidence_review",
|
||||
"next_gate": "post_enable_iwooos_readback",
|
||||
"quarantined": false,
|
||||
"received": true,
|
||||
"required_fields": [
|
||||
@@ -30,7 +30,7 @@
|
||||
},
|
||||
{
|
||||
"accepted": true,
|
||||
"next_gate": "manager_registry_acceptance_evidence_review",
|
||||
"next_gate": "post_enable_iwooos_readback",
|
||||
"quarantined": false,
|
||||
"received": true,
|
||||
"required_fields": [
|
||||
@@ -45,7 +45,7 @@
|
||||
},
|
||||
{
|
||||
"accepted": true,
|
||||
"next_gate": "manager_registry_acceptance_evidence_review",
|
||||
"next_gate": "post_enable_iwooos_readback",
|
||||
"quarantined": false,
|
||||
"received": true,
|
||||
"required_fields": [
|
||||
@@ -58,7 +58,7 @@
|
||||
},
|
||||
{
|
||||
"accepted": true,
|
||||
"next_gate": "manager_registry_acceptance_evidence_review",
|
||||
"next_gate": "post_enable_iwooos_readback",
|
||||
"quarantined": false,
|
||||
"received": true,
|
||||
"required_fields": [
|
||||
@@ -74,7 +74,7 @@
|
||||
},
|
||||
{
|
||||
"accepted": true,
|
||||
"next_gate": "manager_registry_acceptance_evidence_review",
|
||||
"next_gate": "post_enable_iwooos_readback",
|
||||
"quarantined": false,
|
||||
"received": true,
|
||||
"required_fields": [
|
||||
@@ -150,11 +150,10 @@
|
||||
"firewall_change",
|
||||
"nginx_reload"
|
||||
],
|
||||
"generated_at": "2026-06-27T21:45:00+08:00",
|
||||
"mode": "committed_post_enable_iwooos_readback_passed_no_runtime_no_secret_collection",
|
||||
"generated_at": "2026-06-27T20:42:31+08:00",
|
||||
"mode": "committed_validation_passed_readback_no_runtime_no_secret_collection",
|
||||
"no_false_green_rules": [
|
||||
"reviewer validation passed 只代表脫敏 owner export refs 通過 no-persist 驗證。",
|
||||
"post-enable IwoooS readback passed 只代表 production API / 前台已讀回 reviewer passed,不代表 live Wazuh 查詢或 runtime action。",
|
||||
"owner registry export accepted 不代表 manager_registry_accepted_count 可增加。",
|
||||
"Dashboard 可見、index pattern 三綠勾、HTTP 200 或 transport observed 不可替代 manager registry counts。",
|
||||
"reviewer accepted 只可更新只讀 posture;active response、agent restart、reenroll、host write、secret rotation 或掃描仍需獨立 runtime gate。"
|
||||
@@ -172,8 +171,7 @@
|
||||
"reject_runtime_action_request",
|
||||
"ready_for_reviewer_validation",
|
||||
"accepted_for_readonly_posture_only",
|
||||
"post_enable_iwooos_readback_passed",
|
||||
"manager_registry_acceptance_evidence_review"
|
||||
"waiting_post_enable_iwooos_readback"
|
||||
],
|
||||
"per_host_required_fields": [
|
||||
"node_alias",
|
||||
@@ -273,9 +271,9 @@
|
||||
},
|
||||
{
|
||||
"check_id": "RV-10",
|
||||
"failure_lane": "post_enable_iwooos_readback_passed_no_runtime",
|
||||
"required_evidence": "production API 與前台 smoke 已讀回 reviewer passed;此讀回只更新 read-only posture,不查 live Wazuh、不保存 raw payload、不開 runtime gate。",
|
||||
"title": "Post-enable IwoooS readback 已讀回但不開 runtime"
|
||||
"failure_lane": "waiting_post_enable_iwooos_readback",
|
||||
"required_evidence": "即使 reviewer 未來接受 evidence,也只能進 read-only posture;必須另有 post-enable readback 才能更新 runtime truth。",
|
||||
"title": "Post-enable IwoooS readback 仍是下一關"
|
||||
}
|
||||
],
|
||||
"schema_version": "wazuh_manager_registry_reviewer_validation_v1",
|
||||
@@ -284,7 +282,7 @@
|
||||
"docs/security/wazuh-agent-visibility-owner-evidence-preflight.snapshot.json",
|
||||
"docs/security/wazuh-managed-host-coverage-gate.snapshot.json"
|
||||
],
|
||||
"status": "post_enable_iwooos_readback_passed_no_runtime_no_secret_collection",
|
||||
"status": "accepted_for_readonly_posture_only",
|
||||
"summary": {
|
||||
"active_response_authorized_count": 0,
|
||||
"evidence_slot_count": 6,
|
||||
@@ -293,11 +291,11 @@
|
||||
"forbidden_payload_count": 27,
|
||||
"host_write_authorized_count": 0,
|
||||
"manager_registry_accepted_count": 0,
|
||||
"outcome_lane_count": 14,
|
||||
"outcome_lane_count": 13,
|
||||
"owner_registry_export_accepted_count": 1,
|
||||
"owner_registry_export_received_count": 1,
|
||||
"per_host_required_field_count": 9,
|
||||
"post_enable_readback_passed_count": 1,
|
||||
"post_enable_readback_passed_count": 0,
|
||||
"required_owner_field_count": 28,
|
||||
"reviewer_validation_check_count": 10,
|
||||
"reviewer_validation_failed_count": 0,
|
||||
|
||||
@@ -41,7 +41,7 @@ resources:
|
||||
images:
|
||||
- name: 192.168.0.110:5000/library/api:IMAGE_TAG_PLACEHOLDER
|
||||
newName: 192.168.0.110:5000/awoooi/api
|
||||
newTag: 30af0fb4202073719f8c8011fe773f806576ede0
|
||||
newTag: f47ee7d966da266db5980190758fd4aabece1a18
|
||||
- name: 192.168.0.110:5000/library/web:IMAGE_TAG_PLACEHOLDER
|
||||
newName: 192.168.0.110:5000/awoooi/web
|
||||
newTag: 30af0fb4202073719f8c8011fe773f806576ede0
|
||||
newTag: f47ee7d966da266db5980190758fd4aabece1a18
|
||||
|
||||
@@ -132,9 +132,9 @@ runner:
|
||||
|
||||
| Job | runner label | 用途 |
|
||||
|-----|--------------|------|
|
||||
| `tests` | `awoooi-host` | API unit + B5 integration tests,直接跑在 110 host runner |
|
||||
| `tests` | `ubuntu-latest` | API unit + B5 integration tests,仍跑在 ci-runner container |
|
||||
| `build-and-deploy` | `awoooi-host` | Harbor login、API/Web image build/push、GitOps deploy,直接跑在 110 host |
|
||||
| `post-deploy-checks` | `awoooi-host` | Alert chain、monitoring coverage、Playwright smoke |
|
||||
| `post-deploy-checks` | `ubuntu-latest` | Alert chain、monitoring coverage、Playwright smoke |
|
||||
|
||||
110 只保留 host-level `act_runner` daemon,並在同一份 config 宣告兩類 label:
|
||||
|
||||
@@ -143,7 +143,9 @@ runner:
|
||||
capacity: 1
|
||||
shutdown_timeout: 1h
|
||||
labels:
|
||||
- "awoooi-ubuntu:docker://192.168.0.110:5000/awoooi/ci-runner:act-22.04"
|
||||
- "ubuntu-latest:docker://192.168.0.110:5000/awoooi/ci-runner:act-22.04"
|
||||
- "ubuntu-22.04:docker://192.168.0.110:5000/awoooi/ci-runner:act-22.04"
|
||||
- "ubuntu-24.04:docker://192.168.0.110:5000/awoooi/ci-runner:act-22.04"
|
||||
- "awoooi-host:host"
|
||||
```
|
||||
|
||||
@@ -206,27 +208,15 @@ AWOOI 的 Docker lock,會和 AWOOI Web image 內的 Next production build 疊
|
||||
- 只讀取 `ps`,不 kill / renice / reset 任何外部 process。
|
||||
- 排除 AWOOI 自身 checkout、local worktree 與 Web Docker build 內的
|
||||
`/app/apps/web` process,避免誤判自己的部署。
|
||||
- 預設最多等待 60 次、每次 10 秒;若仍有外部 build / smoke / CI 壓力,
|
||||
hard fail,避免繼續把新的 browser smoke 疊到 production host。
|
||||
- 只有明確設定 `HOST_WEB_BUILD_PRESSURE_WARN_ONLY=1` 才 warning 放行;這只能
|
||||
用在已確認壓力來源可接受的受控補跑。
|
||||
- 預設最多等待 60 次、每次 10 秒;若仍有外部 build,先以 warning 放行,
|
||||
避免 CD 永久卡住。
|
||||
- 可用 `HOST_WEB_BUILD_PRESSURE_WARN_ONLY=0` 改成 hard fail,但必須先確認
|
||||
runner 隔離與其他 repo build 排程已收斂,避免把 shared runner 壓力轉成
|
||||
部署中斷。
|
||||
|
||||
長期方向仍是 runner 隔離或 build offload;此 gate 是在 shared runner 尚未
|
||||
拆分前,降低重型前端 build 互相踩踏的保守保護層。
|
||||
|
||||
### 第四層補充: startup 不自動重開 Gitea runner
|
||||
|
||||
2026-06-27 110 CPU 事故止血後,`gitea-act-runner-host.service` 維持 inactive 是
|
||||
刻意降壓狀態。`scripts/reboot-recovery/awoooi-startup-110.sh` 仍可修正 runner
|
||||
`shutdown_timeout` 與 labels,也會停用 legacy Docker runner,但預設不會啟動
|
||||
host runner。只有明確設定下列開關時才允許 startup 拉起 runner:
|
||||
|
||||
```bash
|
||||
AWOOOI_START_GITEA_RUNNER_ON_BOOT=1 /usr/local/bin/awoooi-startup-110.sh
|
||||
```
|
||||
|
||||
未完成 runner 限流 / 搬遷前,不要把這個開關加入 systemd environment。
|
||||
|
||||
### 第五層修復: legacy Docker runner drain
|
||||
|
||||
2026-05-21 再次確認 110 同時存在兩個 runner:
|
||||
@@ -380,12 +370,6 @@ runner registration / service:
|
||||
|
||||
三個 split runner smoke 都通過後,才 drain primary runner 並移除混合 labels。
|
||||
|
||||
2026-06-27 live update:110 的 `gitea-act-runner-host.service` 已刻意停在
|
||||
`inactive`;`/home/wooo/act-runner/config.yaml` labels 已收斂為
|
||||
`awoooi-ubuntu` 與 `awoooi-host`,capacity 仍為 `1`。這是降壓與 label isolation
|
||||
狀態;AWOOI workflows 也應只使用 `awoooi-ubuntu` 或 `awoooi-host`,不可再使用
|
||||
`ubuntu-latest` / `self-hosted` 這類泛用 label。這不代表 runner 搬遷完成,也不代表可以直接重開 runner。
|
||||
|
||||
---
|
||||
版本: v2.0 | 更新: 2026-03-29 | 作者: Claude Code
|
||||
變更: v1.0→v2.0 序列建構取代 Job Concurrency Groups
|
||||
|
||||
@@ -179,7 +179,7 @@ def fetch_local_labels(repo: str, branch: str, repo_path: Path) -> tuple[list[Wo
|
||||
|
||||
def label_owner(label: str) -> str:
|
||||
value = label.strip().strip("'\"")
|
||||
if value in {"awoooi-host", "awoooi-ubuntu"}:
|
||||
if value == "awoooi-host":
|
||||
return "awoooi_dedicated"
|
||||
if value == "ewoooc-host":
|
||||
return "foreign_dedicated"
|
||||
@@ -234,13 +234,7 @@ def main() -> int:
|
||||
error: str | None = None
|
||||
if auth is not None:
|
||||
repo_labels, error = fetch_gitea_labels(repo, args.branch, auth)
|
||||
elif repo in local_paths:
|
||||
repo_labels, local_error = fetch_local_labels(repo, args.branch, local_paths[repo])
|
||||
if local_error:
|
||||
errors.append(f"{repo}: {local_error}")
|
||||
labels.extend(repo_labels)
|
||||
continue
|
||||
else:
|
||||
elif repo not in local_paths:
|
||||
error = "gitea_auth_unavailable"
|
||||
|
||||
if error and repo in local_paths:
|
||||
|
||||
@@ -70,7 +70,7 @@ label_owner() {
|
||||
local label="$1"
|
||||
local label_name="${label%%:*}"
|
||||
case "$label_name" in
|
||||
awoooi-host|awoooi-ubuntu|awoooi-*)
|
||||
awoooi-host)
|
||||
printf 'awoooi_dedicated'
|
||||
;;
|
||||
ewoooc-host)
|
||||
|
||||
@@ -184,18 +184,15 @@ fi
|
||||
# ──────────────────────────────────────────────
|
||||
# STEP 6: Gitea Act Runner(CI/CD 核心)
|
||||
# 2026-04-05 Claude Code: 加入 — 解決重開機後 Gitea runner 離線、CD 失效
|
||||
# 2026-06-27 Codex: 110 是 production / registry / observability 主機;
|
||||
# runner 預設維持停用降壓,未完成限流 / 搬遷前不可在 startup 自動拉起。
|
||||
# 重要:必須在 Gitea server 啟動後才能啟動 runner
|
||||
# ──────────────────────────────────────────────
|
||||
log "[6/6] 檢查 Gitea Act Runner(預設不自動啟動)..."
|
||||
log "[6/6] 啟動 Gitea Act Runner..."
|
||||
RUNNER_DIR="/home/wooo/act-runner"
|
||||
RUNNER_SERVICE="gitea-act-runner-host.service"
|
||||
START_GITEA_RUNNER_ON_BOOT="${AWOOOI_START_GITEA_RUNNER_ON_BOOT:-0}"
|
||||
if [ -x "$RUNNER_DIR/act_runner" ] && [ -f "$RUNNER_DIR/config.yaml" ]; then
|
||||
# 若舊的 .runner 配置指向過期 hostname,只有在明確允許啟動 runner
|
||||
# 時才清除重新註冊;預設降壓模式不得碰 registration 狀態。
|
||||
# 若舊的 .runner 配置指向過期 hostname,先清除讓 runner 重新註冊
|
||||
RUNNER_FILE="$RUNNER_DIR/data/.runner"
|
||||
if [ "$START_GITEA_RUNNER_ON_BOOT" = "1" ] && [ -f "$RUNNER_FILE" ]; then
|
||||
if [ -f "$RUNNER_FILE" ]; then
|
||||
OLD_URL=$(python3 -c "import json; d=json.load(open('$RUNNER_FILE')); print(d.get('address',''))" 2>/dev/null || echo "")
|
||||
if [ "$OLD_URL" != "http://192.168.0.110:3001" ]; then
|
||||
log "⚠️ runner 配置過期 ($OLD_URL),清除重新註冊..."
|
||||
@@ -251,14 +248,10 @@ while idx < len(lines):
|
||||
path.write_text("\n".join(output) + "\n")
|
||||
PY
|
||||
|
||||
if [ "$START_GITEA_RUNNER_ON_BOOT" = "1" ]; then
|
||||
if systemctl list-unit-files "$RUNNER_SERVICE" >/dev/null 2>&1; then
|
||||
systemctl enable --now "$RUNNER_SERVICE" >/dev/null 2>&1 || true
|
||||
elif ! pgrep -f "$RUNNER_DIR/act_runner daemon" >/dev/null; then
|
||||
nohup "$RUNNER_DIR/run-host-runner.sh" >> "$RUNNER_DIR/host-runner.log" 2>&1 &
|
||||
fi
|
||||
else
|
||||
log "⏸️ Gitea host runner 維持停用;設定 AWOOOI_START_GITEA_RUNNER_ON_BOOT=1 才允許 startup 啟動"
|
||||
if systemctl list-unit-files "$RUNNER_SERVICE" >/dev/null 2>&1; then
|
||||
systemctl enable --now "$RUNNER_SERVICE" >/dev/null 2>&1 || true
|
||||
elif ! pgrep -f "$RUNNER_DIR/act_runner daemon" >/dev/null; then
|
||||
nohup "$RUNNER_DIR/run-host-runner.sh" >> "$RUNNER_DIR/host-runner.log" 2>&1 &
|
||||
fi
|
||||
|
||||
# 已停用 Docker-wrapped runner;避免它搶走 host label job。
|
||||
@@ -276,11 +269,9 @@ PY
|
||||
|
||||
# 驗證 runner 已連線 Gitea
|
||||
if pgrep -f "$RUNNER_DIR/act_runner daemon" >/dev/null; then
|
||||
log "⚠️ Gitea host act_runner 目前正在執行;請確認是否為受控限流 / 搬遷後狀態"
|
||||
elif [ "$START_GITEA_RUNNER_ON_BOOT" = "1" ]; then
|
||||
log "⚠️ Gitea host act_runner 可能尚未啟動,查看: $RUNNER_DIR/host-runner.log"
|
||||
log "✅ Gitea host act_runner 已啟動"
|
||||
else
|
||||
log "✅ Gitea host act_runner 維持 inactive 降壓狀態"
|
||||
log "⚠️ Gitea host act_runner 可能尚未啟動,查看: $RUNNER_DIR/host-runner.log"
|
||||
fi
|
||||
else
|
||||
log "⚠️ 找不到 act-runner binary/config: $RUNNER_DIR"
|
||||
|
||||
@@ -1,253 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Classify MOMO daily-sales source arrival from a read-only preflight log.
|
||||
|
||||
This parser never connects to MOMO, never imports files, never moves Drive
|
||||
artifacts, and never authorizes DB / host / Drive writes. It turns the existing
|
||||
`momo-drive-token-source-recovery-preflight.sh` evidence into a compact gate so
|
||||
operators can tell whether they should keep waiting for a legitimate source or
|
||||
start a separate safe-import preflight.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
EXPECTED_IMPORT_CONFIG = "當日業績匯入|即時業績_當日"
|
||||
SUMMARY_RE = re.compile(
|
||||
r"^MOMO_DRIVE_TOKEN_SOURCE_PREFLIGHT "
|
||||
r"PASS=(?P<pass>\d+) WARN=(?P<warn>\d+) BLOCKED=(?P<blocked>\d+) "
|
||||
r"HOST=(?P<host>\S+) FRESHNESS_MAX_DAYS=(?P<freshness_max_days>\d+)"
|
||||
)
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Classify MOMO source-arrival readiness from preflight output.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--preflight-log",
|
||||
required=True,
|
||||
help="Path to momo-drive-token-source-recovery-preflight output, or '-' for stdin.",
|
||||
)
|
||||
parser.add_argument("--json", action="store_true", help="Print JSON result.")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def load_text(source: str) -> str:
|
||||
if source == "-":
|
||||
return sys.stdin.read()
|
||||
return Path(source).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def parse_int(value: Any, default: int | None = None) -> int | None:
|
||||
try:
|
||||
return int(str(value).strip())
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
|
||||
|
||||
def parse_pipe(value: str, expected_parts: int) -> list[str]:
|
||||
parts = str(value or "").split("|")
|
||||
if len(parts) < expected_parts:
|
||||
parts.extend([""] * (expected_parts - len(parts)))
|
||||
return parts[:expected_parts]
|
||||
|
||||
|
||||
def parse_preflight(text: str) -> dict[str, Any]:
|
||||
values: dict[str, str] = {}
|
||||
messages = {"ok": [], "warn": [], "blocked": []}
|
||||
summary: dict[str, Any] = {}
|
||||
|
||||
for raw_line in text.splitlines():
|
||||
line = raw_line.strip()
|
||||
if not line:
|
||||
continue
|
||||
summary_match = SUMMARY_RE.match(line)
|
||||
if summary_match:
|
||||
summary = {
|
||||
key: parse_int(value) if key != "host" else value
|
||||
for key, value in summary_match.groupdict().items()
|
||||
}
|
||||
continue
|
||||
if line.startswith("OK: "):
|
||||
messages["ok"].append(line[4:])
|
||||
continue
|
||||
if line.startswith("WARN: "):
|
||||
messages["warn"].append(line[6:])
|
||||
continue
|
||||
if line.startswith("BLOCKED: "):
|
||||
messages["blocked"].append(line[9:])
|
||||
continue
|
||||
if re.match(r"^[A-Z][A-Z0-9_]+(?:\s|$)", line):
|
||||
key, _, value = line.partition(" ")
|
||||
values[key] = value.strip()
|
||||
|
||||
return {"values": values, "messages": messages, "summary": summary}
|
||||
|
||||
|
||||
def monthly_sync_ok(value: str) -> bool:
|
||||
snapshot_count, monthly_count, dmin, dmax, mmin, mmax = parse_pipe(value, 6)
|
||||
snapshot_n = parse_int(snapshot_count, 0) or 0
|
||||
return (
|
||||
snapshot_n > 0
|
||||
and snapshot_count == monthly_count
|
||||
and bool(dmin)
|
||||
and bool(dmax)
|
||||
and dmin == mmin
|
||||
and dmax == mmax
|
||||
)
|
||||
|
||||
|
||||
def latest_import_clean(value: str) -> bool:
|
||||
job_id, status, _file_name, _created, _completed, total, success, errors = parse_pipe(
|
||||
value, 8
|
||||
)
|
||||
return (
|
||||
parse_int(job_id) is not None
|
||||
and status == "completed"
|
||||
and parse_int(total, -1) == parse_int(success, -2)
|
||||
and parse_int(errors, -1) == 0
|
||||
)
|
||||
|
||||
|
||||
def classify(parsed: dict[str, Any]) -> dict[str, Any]:
|
||||
values = parsed["values"]
|
||||
summary = parsed["summary"]
|
||||
messages = parsed["messages"]
|
||||
|
||||
freshness_days_text, latest_daily_date = parse_pipe(values.get("DB_DAILY_FRESHNESS", ""), 2)
|
||||
freshness_days = parse_int(freshness_days_text)
|
||||
freshness_max_days = parse_int(summary.get("freshness_max_days"), 2) or 2
|
||||
drive_intake_count = parse_int(values.get("DRIVE_INTAKE_COUNT"), 0) or 0
|
||||
drive_failed_count = parse_int(values.get("DRIVE_FAILED_COUNT"), 0) or 0
|
||||
drive_archive_latest = values.get("DRIVE_ARCHIVE_LATEST_MODIFIED", "none") or "none"
|
||||
drive_global_latest = values.get("DRIVE_GLOBAL_LATEST_MODIFIED", "none") or "none"
|
||||
|
||||
service_ready = (
|
||||
values.get("MOMO_PUBLIC_HEALTH_CODE") == "200"
|
||||
and values.get("MOMO_HEALTH_CODE") == "200"
|
||||
and values.get("MOMO_APP_HEALTH") == "healthy"
|
||||
and values.get("SCHEDULER_RUNNING") == "true"
|
||||
and values.get("SCHEDULER_HEALTH") == "healthy"
|
||||
)
|
||||
import_config_ok = EXPECTED_IMPORT_CONFIG in values.get("IMPORT_CONFIG", "")
|
||||
sync_ok = monthly_sync_ok(values.get("DB_MONTHLY_SYNC", ""))
|
||||
clean_import = latest_import_clean(values.get("DB_LATEST_DAILY_IMPORT_JOB", ""))
|
||||
freshness_green = (
|
||||
freshness_days is not None and 0 <= freshness_days <= freshness_max_days
|
||||
)
|
||||
freshness_stale = freshness_days is not None and freshness_days > freshness_max_days
|
||||
|
||||
blockers: list[str] = []
|
||||
warnings: list[str] = []
|
||||
status = "blocked_preflight_evidence_incomplete"
|
||||
next_step = "rerun_momo_drive_token_source_recovery_preflight"
|
||||
safe_import_preflight_allowed = False
|
||||
exit_code = 2
|
||||
|
||||
if not summary:
|
||||
blockers.append("preflight_summary_missing")
|
||||
if not service_ready:
|
||||
blockers.append("momo_service_or_scheduler_not_ready")
|
||||
if not import_config_ok:
|
||||
blockers.append("drive_import_config_not_expected_intake")
|
||||
if not sync_ok:
|
||||
blockers.append("current_month_snapshot_realtime_sync_not_proven")
|
||||
if drive_failed_count > 0:
|
||||
warnings.append("drive_failed_folder_has_matching_candidates")
|
||||
|
||||
if blockers:
|
||||
status = "blocked_service_or_evidence_not_ready"
|
||||
next_step = "repair_readonly_preflight_evidence_before_source_or_import_decision"
|
||||
elif freshness_green:
|
||||
status = "freshness_already_green_recheck_cold_start"
|
||||
next_step = "rerun_post_reboot_readiness_summary_with_same_evidence_chain"
|
||||
exit_code = 0
|
||||
elif drive_intake_count > 0 and freshness_stale:
|
||||
status = "source_arrived_ready_for_safe_import_preflight"
|
||||
next_step = "run_owner_approved_safe_import_preflight_no_db_or_drive_write_yet"
|
||||
safe_import_preflight_allowed = True
|
||||
exit_code = 0
|
||||
elif drive_intake_count > 0:
|
||||
status = "source_arrived_freshness_unknown_recheck_before_import"
|
||||
next_step = "rerun_momo_preflight_and_validate_freshness_before_import"
|
||||
safe_import_preflight_allowed = True
|
||||
exit_code = 1
|
||||
elif freshness_stale:
|
||||
status = "blocked_source_absent_fail_closed"
|
||||
next_step = "wait_for_legitimate_daily_sales_source_then_rerun_gate"
|
||||
else:
|
||||
status = "blocked_freshness_unknown_fail_closed"
|
||||
next_step = "rerun_preflight_or_repair_readonly_freshness_readback"
|
||||
|
||||
if not clean_import:
|
||||
warnings.append("latest_daily_import_job_not_clean_completed")
|
||||
|
||||
return {
|
||||
"schema_version": "momo_source_arrival_gate_v1",
|
||||
"status": status,
|
||||
"exit_code": exit_code,
|
||||
"next_step": next_step,
|
||||
"safe_import_preflight_allowed": safe_import_preflight_allowed,
|
||||
"runtime_write_authorized": False,
|
||||
"db_write_authorized": False,
|
||||
"drive_move_authorized": False,
|
||||
"manual_import_authorized": False,
|
||||
"secret_value_collection_allowed": False,
|
||||
"service_ready": service_ready,
|
||||
"import_config_ok": import_config_ok,
|
||||
"current_month_sync_ok": sync_ok,
|
||||
"latest_import_clean": clean_import,
|
||||
"freshness_days": freshness_days,
|
||||
"freshness_latest_date": latest_daily_date or "unknown",
|
||||
"freshness_max_days": freshness_max_days,
|
||||
"drive_intake_count": drive_intake_count,
|
||||
"drive_archive_latest_modified": drive_archive_latest,
|
||||
"drive_global_latest_modified": drive_global_latest,
|
||||
"drive_failed_count": drive_failed_count,
|
||||
"preflight_pass": summary.get("pass", 0),
|
||||
"preflight_warn": summary.get("warn", len(messages["warn"])),
|
||||
"preflight_blocked": summary.get("blocked", len(messages["blocked"])),
|
||||
"blockers": blockers,
|
||||
"warnings": warnings,
|
||||
"no_false_green_rules": [
|
||||
"source_arrived_does_not_authorize_import",
|
||||
"safe_import_preflight_allowed_does_not_authorize_db_write",
|
||||
"freshness_green_requires_post_reboot_summary_recheck",
|
||||
"archive_or_local_old_file_does_not_count_as_new_source",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def print_human(result: dict[str, Any]) -> None:
|
||||
print(
|
||||
"MOMO_SOURCE_ARRIVAL_GATE "
|
||||
f"status={result['status']} "
|
||||
f"source_intake={result['drive_intake_count']} "
|
||||
f"freshness={result['freshness_days']}|{result['freshness_latest_date']} "
|
||||
f"safe_import_preflight_allowed={int(result['safe_import_preflight_allowed'])} "
|
||||
"runtime_write_authorized=0 "
|
||||
"db_write_authorized=0 "
|
||||
"drive_move_authorized=0 "
|
||||
f"next_step={result['next_step']}"
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
result = classify(parse_preflight(load_text(args.preflight_log)))
|
||||
if args.json:
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2, sort_keys=True))
|
||||
else:
|
||||
print_human(result)
|
||||
return int(result["exit_code"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -287,20 +287,6 @@ if [[ "$RUN_MOMO" -eq 1 ]]; then
|
||||
;;
|
||||
esac
|
||||
grep -E 'MOMO_DRIVE_TOKEN_SOURCE_PREFLIGHT|MOMO_HEALTH_VERSION|DB_MONTHLY_SYNC|DB_DAILY_FRESHNESS|DB_LATEST_DAILY_IMPORT_JOB' "$momo_tmp" || true
|
||||
source_gate_output="$("$ROOT_DIR/scripts/reboot-recovery/momo-source-arrival-gate.py" --preflight-log "$momo_tmp" 2>&1)"
|
||||
source_gate_rc=$?
|
||||
printf '%s\n' "$source_gate_output"
|
||||
if grep -q 'status=blocked_source_absent_fail_closed' <<<"$source_gate_output"; then
|
||||
evidence_warn "MOMO source-arrival gate confirms source absent fail-closed"
|
||||
elif grep -q 'status=source_arrived_ready_for_safe_import_preflight' <<<"$source_gate_output"; then
|
||||
boundary_warn "MOMO source arrived; safe import preflight only, DB/Drive/runtime writes remain unauthorized"
|
||||
elif grep -q 'status=freshness_already_green_recheck_cold_start' <<<"$source_gate_output"; then
|
||||
ok "MOMO source-arrival gate reports freshness green; rerun post-reboot summary before updating declaration"
|
||||
elif [[ "$source_gate_rc" -ne 0 ]]; then
|
||||
service_warn "MOMO source-arrival gate did not produce a known state rc=$source_gate_rc"
|
||||
else
|
||||
evidence_warn "MOMO source-arrival gate produced a non-terminal state"
|
||||
fi
|
||||
rm -f "$momo_tmp"
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[3]
|
||||
SCRIPT = ROOT / "scripts" / "reboot-recovery" / "momo-source-arrival-gate.py"
|
||||
|
||||
|
||||
def load_module():
|
||||
spec = importlib.util.spec_from_file_location("momo_source_arrival_gate", SCRIPT)
|
||||
assert spec and spec.loader
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
BASE_PREFLIGHT = """
|
||||
HOST ollama
|
||||
MOMO_HEALTH_CODE 200
|
||||
MOMO_PUBLIC_HEALTH_CODE 200
|
||||
MOMO_APP_HEALTH healthy
|
||||
SCHEDULER_RUNNING true
|
||||
SCHEDULER_HEALTH healthy
|
||||
DRIVE_ARCHIVE_LATEST_MODIFIED 2026-06-25T04:21:47.000Z
|
||||
DRIVE_GLOBAL_LATEST_MODIFIED 2026-06-25T04:21:47.000Z
|
||||
DRIVE_FAILED_COUNT 0
|
||||
DB_MONTHLY_SYNC 15383|15383|2026-06-01|2026-06-24|2026-06-01|2026-06-24
|
||||
DB_LATEST_DAILY_IMPORT_JOB 57|completed|即時業績_當日.xlsx|2026-06-25T13:16:47.359958|2026-06-25T13:18:02.964985|15383|15383|0
|
||||
IMPORT_CONFIG 當日業績匯入|即時業績_當日
|
||||
MOMO_DRIVE_TOKEN_SOURCE_PREFLIGHT PASS=20 WARN=3 BLOCKED=2 HOST=ollama@192.168.0.188 FRESHNESS_MAX_DAYS=2
|
||||
"""
|
||||
|
||||
|
||||
def classify(extra_lines: str):
|
||||
module = load_module()
|
||||
return module.classify(module.parse_preflight(BASE_PREFLIGHT + extra_lines))
|
||||
|
||||
|
||||
def assert_no_write_authorization(result):
|
||||
assert result["runtime_write_authorized"] is False
|
||||
assert result["db_write_authorized"] is False
|
||||
assert result["drive_move_authorized"] is False
|
||||
assert result["manual_import_authorized"] is False
|
||||
assert result["secret_value_collection_allowed"] is False
|
||||
|
||||
|
||||
def test_source_absent_fail_closed():
|
||||
result = classify(
|
||||
"""
|
||||
DRIVE_INTAKE_COUNT 0
|
||||
DB_DAILY_FRESHNESS 3|2026-06-24
|
||||
"""
|
||||
)
|
||||
|
||||
assert result["status"] == "blocked_source_absent_fail_closed"
|
||||
assert result["exit_code"] == 2
|
||||
assert result["safe_import_preflight_allowed"] is False
|
||||
assert_no_write_authorization(result)
|
||||
|
||||
|
||||
def test_source_arrived_allows_only_safe_import_preflight():
|
||||
result = classify(
|
||||
"""
|
||||
DRIVE_INTAKE_COUNT 1
|
||||
DB_DAILY_FRESHNESS 3|2026-06-24
|
||||
"""
|
||||
)
|
||||
|
||||
assert result["status"] == "source_arrived_ready_for_safe_import_preflight"
|
||||
assert result["exit_code"] == 0
|
||||
assert result["safe_import_preflight_allowed"] is True
|
||||
assert_no_write_authorization(result)
|
||||
|
||||
|
||||
def test_freshness_green_requires_cold_start_recheck():
|
||||
result = classify(
|
||||
"""
|
||||
DRIVE_INTAKE_COUNT 0
|
||||
DB_DAILY_FRESHNESS 1|2026-06-26
|
||||
"""
|
||||
)
|
||||
|
||||
assert result["status"] == "freshness_already_green_recheck_cold_start"
|
||||
assert result["next_step"] == "rerun_post_reboot_readiness_summary_with_same_evidence_chain"
|
||||
assert result["exit_code"] == 0
|
||||
assert result["safe_import_preflight_allowed"] is False
|
||||
assert_no_write_authorization(result)
|
||||
@@ -29577,7 +29577,6 @@ def validate(root: Path) -> None:
|
||||
"wazuh_manager_registry_reviewer_validation_owner_registry_export_received_count=1",
|
||||
"wazuh_manager_registry_reviewer_validation_owner_registry_export_accepted_count=1",
|
||||
"wazuh_manager_registry_reviewer_validation_passed_count=1",
|
||||
"wazuh_manager_registry_reviewer_validation_post_enable_readback_passed_count=1",
|
||||
"wazuh_manager_registry_reviewer_validation_manager_registry_accepted_count=0",
|
||||
"wazuh_manager_registry_reviewer_validation_runtime_gate_count=0",
|
||||
]:
|
||||
@@ -29607,7 +29606,6 @@ def validate(root: Path) -> None:
|
||||
"wazuh_manager_registry_reviewer_validation_owner_registry_export_received_count=1",
|
||||
"wazuh_manager_registry_reviewer_validation_owner_registry_export_accepted_count=1",
|
||||
"wazuh_manager_registry_reviewer_validation_passed_count=1",
|
||||
"wazuh_manager_registry_reviewer_validation_post_enable_readback_passed_count=1",
|
||||
"wazuh_manager_registry_reviewer_validation_manager_registry_accepted_count=0",
|
||||
"wazuh_manager_registry_reviewer_validation_runtime_gate_count=0",
|
||||
]:
|
||||
|
||||
@@ -128,9 +128,9 @@ REVIEWER_VALIDATION_CHECKS = [
|
||||
},
|
||||
{
|
||||
"check_id": "RV-10",
|
||||
"title": "Post-enable IwoooS readback 已讀回但不開 runtime",
|
||||
"required_evidence": "production API 與前台 smoke 已讀回 reviewer passed;此讀回只更新 read-only posture,不查 live Wazuh、不保存 raw payload、不開 runtime gate。",
|
||||
"failure_lane": "post_enable_iwooos_readback_passed_no_runtime",
|
||||
"title": "Post-enable IwoooS readback 仍是下一關",
|
||||
"required_evidence": "即使 reviewer 未來接受 evidence,也只能進 read-only posture;必須另有 post-enable readback 才能更新 runtime truth。",
|
||||
"failure_lane": "waiting_post_enable_iwooos_readback",
|
||||
},
|
||||
]
|
||||
|
||||
@@ -147,8 +147,7 @@ OUTCOME_LANES = [
|
||||
"reject_runtime_action_request",
|
||||
"ready_for_reviewer_validation",
|
||||
"accepted_for_readonly_posture_only",
|
||||
"post_enable_iwooos_readback_passed",
|
||||
"manager_registry_acceptance_evidence_review",
|
||||
"waiting_post_enable_iwooos_readback",
|
||||
]
|
||||
|
||||
EVIDENCE_SLOTS = [
|
||||
@@ -293,8 +292,8 @@ def build_snapshot(generated_at: str) -> dict[str, Any]:
|
||||
return {
|
||||
"schema_version": SCHEMA_VERSION,
|
||||
"generated_at": generated_at,
|
||||
"status": "post_enable_iwooos_readback_passed_no_runtime_no_secret_collection",
|
||||
"mode": "committed_post_enable_iwooos_readback_passed_no_runtime_no_secret_collection",
|
||||
"status": "accepted_for_readonly_posture_only",
|
||||
"mode": "committed_validation_passed_readback_no_runtime_no_secret_collection",
|
||||
"scope": "wazuh_manager_registry_owner_export_reviewer_validation",
|
||||
"source_refs": [
|
||||
"docs/security/wazuh-agent-visibility-owner-evidence-preflight.snapshot.json",
|
||||
@@ -316,7 +315,7 @@ def build_snapshot(generated_at: str) -> dict[str, Any]:
|
||||
"reviewer_validation_failed_count": 0,
|
||||
"reviewer_validation_quarantined_count": 0,
|
||||
"manager_registry_accepted_count": 0,
|
||||
"post_enable_readback_passed_count": 1,
|
||||
"post_enable_readback_passed_count": 0,
|
||||
"runtime_gate_count": 0,
|
||||
"host_write_authorized_count": 0,
|
||||
"active_response_authorized_count": 0,
|
||||
@@ -333,7 +332,7 @@ def build_snapshot(generated_at: str) -> dict[str, Any]:
|
||||
"received": True,
|
||||
"accepted": True,
|
||||
"quarantined": False,
|
||||
"next_gate": "manager_registry_acceptance_evidence_review",
|
||||
"next_gate": "post_enable_iwooos_readback",
|
||||
}
|
||||
for slot in EVIDENCE_SLOTS
|
||||
],
|
||||
@@ -356,7 +355,6 @@ def build_snapshot(generated_at: str) -> dict[str, Any]:
|
||||
},
|
||||
"no_false_green_rules": [
|
||||
"reviewer validation passed 只代表脫敏 owner export refs 通過 no-persist 驗證。",
|
||||
"post-enable IwoooS readback passed 只代表 production API / 前台已讀回 reviewer passed,不代表 live Wazuh 查詢或 runtime action。",
|
||||
"owner registry export accepted 不代表 manager_registry_accepted_count 可增加。",
|
||||
"Dashboard 可見、index pattern 三綠勾、HTTP 200 或 transport observed 不可替代 manager registry counts。",
|
||||
"reviewer accepted 只可更新只讀 posture;active response、agent restart、reenroll、host write、secret rotation 或掃描仍需獨立 runtime gate。",
|
||||
@@ -367,12 +365,8 @@ def build_snapshot(generated_at: str) -> dict[str, Any]:
|
||||
def validate(root: Path) -> None:
|
||||
snapshot = load_json(root / SNAPSHOT_PATH)
|
||||
assert_equal("schema_version", snapshot.get("schema_version"), SCHEMA_VERSION)
|
||||
assert_equal("status", snapshot.get("status"), "post_enable_iwooos_readback_passed_no_runtime_no_secret_collection")
|
||||
assert_equal(
|
||||
"mode",
|
||||
snapshot.get("mode"),
|
||||
"committed_post_enable_iwooos_readback_passed_no_runtime_no_secret_collection",
|
||||
)
|
||||
assert_equal("status", snapshot.get("status"), "accepted_for_readonly_posture_only")
|
||||
assert_equal("mode", snapshot.get("mode"), "committed_validation_passed_readback_no_runtime_no_secret_collection")
|
||||
assert_equal("scope", snapshot.get("scope"), "wazuh_manager_registry_owner_export_reviewer_validation")
|
||||
assert_equal("expected_scope_aliases", snapshot.get("expected_scope_aliases"), EXPECTED_SCOPE_ALIASES)
|
||||
assert_equal("required_owner_fields", snapshot.get("required_owner_fields"), REQUIRED_OWNER_FIELDS)
|
||||
@@ -400,13 +394,13 @@ def validate(root: Path) -> None:
|
||||
"owner_registry_export_accepted_count",
|
||||
"reviewer_validation_ready_count",
|
||||
"reviewer_validation_passed_count",
|
||||
"post_enable_readback_passed_count",
|
||||
]:
|
||||
assert_equal(f"summary.{key}", summary.get(key), 1)
|
||||
for key in [
|
||||
"reviewer_validation_failed_count",
|
||||
"reviewer_validation_quarantined_count",
|
||||
"manager_registry_accepted_count",
|
||||
"post_enable_readback_passed_count",
|
||||
"runtime_gate_count",
|
||||
"host_write_authorized_count",
|
||||
"active_response_authorized_count",
|
||||
@@ -421,11 +415,7 @@ def validate(root: Path) -> None:
|
||||
assert_equal(f"evidence_slots.{slot.get('slot_id')}.received", slot.get("received"), True)
|
||||
assert_equal(f"evidence_slots.{slot.get('slot_id')}.accepted", slot.get("accepted"), True)
|
||||
assert_false(f"evidence_slots.{slot.get('slot_id')}.quarantined", slot.get("quarantined"))
|
||||
assert_equal(
|
||||
f"evidence_slots.{slot.get('slot_id')}.next_gate",
|
||||
slot.get("next_gate"),
|
||||
"manager_registry_acceptance_evidence_review",
|
||||
)
|
||||
assert_equal(f"evidence_slots.{slot.get('slot_id')}.next_gate", slot.get("next_gate"), "post_enable_iwooos_readback")
|
||||
|
||||
boundaries = snapshot.get("execution_boundaries", {})
|
||||
for key, value in boundaries.items():
|
||||
@@ -464,7 +454,6 @@ def main() -> None:
|
||||
f"slots={summary['evidence_slot_count']} "
|
||||
f"received={summary['owner_registry_export_received_count']} "
|
||||
f"accepted={summary['owner_registry_export_accepted_count']} "
|
||||
f"post_enable={summary['post_enable_readback_passed_count']} "
|
||||
f"runtime_gate={summary['runtime_gate_count']}"
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user