G0~G8 성과·동맹 측정 OS 작업 일괄 고정
8월 7일까지 워킹트리에만 남아 있던 미커밋 작업을 커밋한다. 여러 사본 폴더(worktree·clone)에 흩어져 있던 중간 스냅샷을 정리하기 전에 원본을 git 이력으로 고정하는 것이 목적이다. - contracts/routes/services: measurement, outcome_trajectory, rupture_repair, deliberate_practice, calibration_transfer, supervision_research, multimodal_alliance, continuous_improvement 계열 신규 모듈과 테스트 - infra/db/init: 07~16 마이그레이션(측정 기반~calibration transfer 실행) - apps/web: 세션 리뷰 카드·관리 화면·E2E 스펙 추가 - docs/ops: G0~G8 라이브 통합·배포·롤백 증거 문서와 evidence JSON/PNG - scripts: smoke·ledger·릴리스 에이전트·NAS 프리뷰 운영 스크립트 engine.public 로그 .bak과 apps/web/test-results 산출물은 커밋에서 제외했다.
This commit is contained in:
parent
93dd8f82d7
commit
16e791e044
390 changed files with 243188 additions and 499 deletions
333
scripts/test_g7_topology_evidence.py
Normal file
333
scripts/test_g7_topology_evidence.py
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
"""Tests for the bounded, metadata-only G7 topology sampler."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
RUNNER_PATH = Path(__file__).with_name("capture-g7-topology-evidence.py")
|
||||
|
||||
|
||||
def load_runner():
|
||||
spec = importlib.util.spec_from_file_location("g7_topology_evidence", RUNNER_PATH)
|
||||
if spec is None or spec.loader is None:
|
||||
raise RuntimeError("G7 topology evidence sampler could not be loaded")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
API_ID = "a" * 64
|
||||
CADDY_ID = "c" * 64
|
||||
API_DIGEST = "sha256:" + "1" * 64
|
||||
CADDY_DIGEST = "sha256:" + "2" * 64
|
||||
|
||||
|
||||
def inspect_item(
|
||||
*,
|
||||
container_id: str,
|
||||
image_digest: str,
|
||||
service: str,
|
||||
pid: int,
|
||||
restart_count: int = 0,
|
||||
started_at: str = "2026-08-07T00:00:00Z",
|
||||
) -> dict[str, object]:
|
||||
return {
|
||||
"Id": container_id,
|
||||
"Name": f"/vignette-{service}-1",
|
||||
"Image": image_digest,
|
||||
"RestartCount": restart_count,
|
||||
"Config": {
|
||||
"Labels": {
|
||||
"com.docker.compose.project": "vignette-preview-20260807",
|
||||
"com.docker.compose.service": service,
|
||||
}
|
||||
},
|
||||
"State": {"Running": True, "Pid": pid, "StartedAt": started_at},
|
||||
}
|
||||
|
||||
|
||||
def stable_inspect() -> str:
|
||||
return json.dumps(
|
||||
[
|
||||
inspect_item(
|
||||
container_id=API_ID,
|
||||
image_digest=API_DIGEST,
|
||||
service="api",
|
||||
pid=101,
|
||||
),
|
||||
inspect_item(
|
||||
container_id=CADDY_ID,
|
||||
image_digest=CADDY_DIGEST,
|
||||
service="caddy",
|
||||
pid=202,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def stats_output() -> str:
|
||||
return "\n".join(
|
||||
[
|
||||
json.dumps(
|
||||
{
|
||||
"ID": API_ID[:12],
|
||||
"CPUPerc": "1.25%",
|
||||
"MemUsage": "128MiB / 2GiB",
|
||||
"MemPerc": "6.25%",
|
||||
"NetIO": "1.5MB / 2MB",
|
||||
"BlockIO": "4KiB / 8KiB",
|
||||
"PIDs": "9",
|
||||
}
|
||||
),
|
||||
json.dumps(
|
||||
{
|
||||
"ID": CADDY_ID[:12],
|
||||
"CPUPerc": "0.50%",
|
||||
"MemUsage": "32MiB / 1GiB",
|
||||
"MemPerc": "3.12%",
|
||||
"NetIO": "3MB / 4MB",
|
||||
"BlockIO": "0B / 1KiB",
|
||||
"PIDs": "4",
|
||||
}
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
SS_OUTPUT = """State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
|
||||
ESTAB 3 5 10.0.0.1:443 10.0.0.2:50000
|
||||
cubic wscale:7,7 rto:204 retrans:1/4 cwnd:10
|
||||
LISTEN 0 128 0.0.0.0:8080 0.0.0.0:*
|
||||
cubic cwnd:10
|
||||
"""
|
||||
|
||||
|
||||
class FixtureRunner:
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
inspect_outputs: list[str] | None = None,
|
||||
unavailable: set[str] | None = None,
|
||||
) -> None:
|
||||
self.inspect_outputs = list(inspect_outputs or [stable_inspect()])
|
||||
self.unavailable = unavailable or set()
|
||||
self.calls: list[tuple[str, ...]] = []
|
||||
|
||||
def available(self, command: str) -> bool:
|
||||
return command not in self.unavailable
|
||||
|
||||
def run(self, args: Sequence[str]) -> str:
|
||||
command = tuple(args)
|
||||
self.calls.append(command)
|
||||
if command[:3] == ("docker", "inspect", "--type"):
|
||||
if len(self.inspect_outputs) > 1:
|
||||
return self.inspect_outputs.pop(0)
|
||||
return self.inspect_outputs[0]
|
||||
if command[:3] == ("docker", "stats", "--no-stream"):
|
||||
return stats_output()
|
||||
if command == ("ss", "-tinm"):
|
||||
return SS_OUTPUT
|
||||
raise AssertionError(f"unexpected command: {command!r}")
|
||||
|
||||
|
||||
class FixtureSource:
|
||||
def __init__(self) -> None:
|
||||
self.files = {
|
||||
"/proc/101/cgroup": "0::/docker/api.scope\n",
|
||||
"/proc/202/cgroup": "0::/docker/caddy.scope\n",
|
||||
"/proc/101/status": "VmRSS:\t100 kB\nVmHWM:\t120 kB\nThreads:\t3\n",
|
||||
"/proc/202/status": "VmRSS:\t50 kB\nVmHWM:\t70 kB\nThreads:\t2\n",
|
||||
"/sys/fs/cgroup/docker/api.scope/memory.current": "104857600\n",
|
||||
"/sys/fs/cgroup/docker/api.scope/memory.peak": "125829120\n",
|
||||
"/sys/fs/cgroup/docker/api.scope/cpu.stat": (
|
||||
"usage_usec 1000\nuser_usec 700\nsystem_usec 300\n"
|
||||
"nr_periods 10\nnr_throttled 1\nthrottled_usec 20\n"
|
||||
),
|
||||
"/sys/fs/cgroup/docker/api.scope/pids.current": "9\n",
|
||||
"/sys/fs/cgroup/docker/caddy.scope/memory.current": "33554432\n",
|
||||
"/sys/fs/cgroup/docker/caddy.scope/memory.peak": "41943040\n",
|
||||
"/sys/fs/cgroup/docker/caddy.scope/cpu.stat": (
|
||||
"usage_usec 500\nuser_usec 300\nsystem_usec 200\n"
|
||||
),
|
||||
"/sys/fs/cgroup/docker/caddy.scope/pids.current": "4\n",
|
||||
}
|
||||
self.directories = {
|
||||
"/proc/101/fd": ["0", "1", "2", "3"],
|
||||
"/proc/202/fd": ["0", "1", "2"],
|
||||
}
|
||||
|
||||
def read_text(self, path: str) -> str:
|
||||
return self.files[path]
|
||||
|
||||
def list_names(self, path: str) -> list[str]:
|
||||
return list(self.directories[path])
|
||||
|
||||
|
||||
class G7TopologyEvidenceTests(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.runner_module = load_runner()
|
||||
|
||||
def config(self, **overrides):
|
||||
values = {
|
||||
"compose_project": "vignette-preview-20260807",
|
||||
"public_host": "api.example.test",
|
||||
"api_container": "vignette-api-1",
|
||||
"api_service": "api",
|
||||
"api_image_digest": API_DIGEST,
|
||||
"caddy_container": "vignette-caddy-1",
|
||||
"caddy_service": "caddy",
|
||||
"caddy_image_digest": CADDY_DIGEST,
|
||||
"samples": 1,
|
||||
"interval_seconds": 1.0,
|
||||
}
|
||||
values.update(overrides)
|
||||
return self.runner_module.CaptureConfig(**values)
|
||||
|
||||
def test_parsers_emit_numeric_metadata_without_endpoints(self) -> None:
|
||||
source = FixtureSource()
|
||||
targets = self.runner_module.pin_targets(self.config(), FixtureRunner(), source)
|
||||
stats = self.runner_module.parse_docker_stats(stats_output(), targets)
|
||||
tcp = self.runner_module.parse_ss_tcp(SS_OUTPUT)
|
||||
proc = self.runner_module.read_proc_metrics(
|
||||
source,
|
||||
proc_root="/proc",
|
||||
target=targets["api"],
|
||||
)
|
||||
cgroup = self.runner_module.read_cgroup_metrics(
|
||||
source,
|
||||
cgroup_root="/sys/fs/cgroup",
|
||||
target=targets["api"],
|
||||
)
|
||||
|
||||
self.assertEqual(134_217_728, stats["api"]["memory_usage_bytes"])
|
||||
self.assertEqual(1_500_000, stats["api"]["network_rx_bytes"])
|
||||
self.assertEqual(2, tcp["connections"])
|
||||
self.assertEqual(3, tcp["recv_q_bytes_total"])
|
||||
self.assertEqual(133, tcp["send_q_bytes_total"])
|
||||
self.assertEqual(1, tcp["retransmit_current_total"])
|
||||
self.assertEqual(4, tcp["retransmit_cumulative_total"])
|
||||
self.assertEqual(102_400, proc["vm_rss_bytes"])
|
||||
self.assertEqual(4, proc["fd_count"])
|
||||
self.assertEqual(1_000, cgroup["cpu_stat"]["usage_usec"])
|
||||
serialized = json.dumps({"stats": stats, "tcp": tcp})
|
||||
self.assertNotIn("10.0.0.1", serialized)
|
||||
self.assertNotIn("10.0.0.2", serialized)
|
||||
|
||||
def test_capture_pins_targets_and_builds_high_water_summary(self) -> None:
|
||||
runner = FixtureRunner()
|
||||
evidence = self.runner_module.capture_evidence(
|
||||
self.config(),
|
||||
runner,
|
||||
FixtureSource(),
|
||||
sleep=lambda _seconds: self.fail("single sample must not sleep"),
|
||||
)
|
||||
|
||||
self.assertEqual("passed", evidence["status"])
|
||||
self.assertEqual(1, evidence["samples_completed"])
|
||||
self.assertEqual(API_ID, evidence["targets"]["api"]["container_id"])
|
||||
self.assertEqual(API_DIGEST, evidence["targets"]["api"]["image_digest"])
|
||||
self.assertEqual("api.example.test", evidence["requested"]["public_host"])
|
||||
self.assertEqual(
|
||||
125_829_120,
|
||||
evidence["summary"]["containers"]["api"]["cgroup_memory_peak_bytes_max"],
|
||||
)
|
||||
scope = evidence["scope"]
|
||||
self.assertTrue(scope["metadata_only"])
|
||||
self.assertFalse(scope["socket_endpoints_retained"])
|
||||
self.assertFalse(scope["cloudflare_edge"]["internal_queue_measured"])
|
||||
self.assertEqual(
|
||||
"separate_external_artifact_required",
|
||||
scope["cloudflare_edge"]["evidence_boundary"],
|
||||
)
|
||||
inspect_calls = [call for call in runner.calls if call[1] == "inspect"]
|
||||
self.assertEqual(3, len(inspect_calls))
|
||||
|
||||
def test_pin_validation_fails_on_pid_restart_and_image_drift(self) -> None:
|
||||
cases = {
|
||||
"init_pid_drift:api": inspect_item(
|
||||
container_id=API_ID,
|
||||
image_digest=API_DIGEST,
|
||||
service="api",
|
||||
pid=999,
|
||||
),
|
||||
"container_restart_drift:api": inspect_item(
|
||||
container_id=API_ID,
|
||||
image_digest=API_DIGEST,
|
||||
service="api",
|
||||
pid=101,
|
||||
restart_count=1,
|
||||
started_at="2026-08-07T00:01:00Z",
|
||||
),
|
||||
"image_digest_drift:api": inspect_item(
|
||||
container_id=API_ID,
|
||||
image_digest="sha256:" + "9" * 64,
|
||||
service="api",
|
||||
pid=101,
|
||||
),
|
||||
}
|
||||
for expected_failure, changed_api in cases.items():
|
||||
with self.subTest(expected_failure=expected_failure):
|
||||
drifted = json.dumps(
|
||||
[
|
||||
changed_api,
|
||||
inspect_item(
|
||||
container_id=CADDY_ID,
|
||||
image_digest=CADDY_DIGEST,
|
||||
service="caddy",
|
||||
pid=202,
|
||||
),
|
||||
]
|
||||
)
|
||||
runner = FixtureRunner(inspect_outputs=[stable_inspect(), drifted])
|
||||
with self.assertRaises(self.runner_module.EvidenceFailure) as caught:
|
||||
self.runner_module.capture_evidence(
|
||||
self.config(), runner, FixtureSource(), sleep=lambda _: None
|
||||
)
|
||||
self.assertEqual(expected_failure, str(caught.exception))
|
||||
|
||||
def test_missing_cgroup_source_fails_closed(self) -> None:
|
||||
source = FixtureSource()
|
||||
del source.files["/sys/fs/cgroup/docker/api.scope/memory.peak"]
|
||||
with self.assertRaises(self.runner_module.EvidenceFailure) as caught:
|
||||
self.runner_module.capture_evidence(
|
||||
self.config(), FixtureRunner(), source, sleep=lambda _: None
|
||||
)
|
||||
self.assertEqual(
|
||||
"cgroup_source_missing:api:memory.peak",
|
||||
str(caught.exception),
|
||||
)
|
||||
|
||||
def test_missing_command_fails_before_any_runtime_read(self) -> None:
|
||||
runner = FixtureRunner(unavailable={"ss"})
|
||||
with self.assertRaises(self.runner_module.EvidenceFailure) as caught:
|
||||
self.runner_module.capture_evidence(
|
||||
self.config(), runner, FixtureSource(), sleep=lambda _: None
|
||||
)
|
||||
self.assertEqual("command_unavailable:ss", str(caught.exception))
|
||||
self.assertEqual([], runner.calls)
|
||||
|
||||
def test_capture_bounds_are_fail_closed(self) -> None:
|
||||
for config, failure in (
|
||||
(self.config(samples=0), "sample_count_out_of_bounds"),
|
||||
(self.config(interval_seconds=0), "sample_interval_out_of_bounds"),
|
||||
(
|
||||
self.config(samples=7_200, interval_seconds=2),
|
||||
"capture_window_out_of_bounds",
|
||||
),
|
||||
):
|
||||
with self.subTest(failure=failure):
|
||||
with self.assertRaises(self.runner_module.EvidenceFailure) as caught:
|
||||
self.runner_module.validate_config(config)
|
||||
self.assertEqual(failure, str(caught.exception))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue