check.py python
121 lines 4.5 KB
Raw
sha256:0e9549ec7b463911bc08b7d586dc320b1ac9b1f5c943ee7e3865dcc6cb0f6f83 chore(governance): sync handover+roadmap to 84db8c8 (drift:… Human 1 day ago
1 """Fail-closed footprint self-integrity hard gate (§KH3.4).
2
3 Detects the precise, narrow condition that motivated this module: a kit-owned
4 file is *declared* in ``.overseer/version.lock`` (``origin`` other than
5 ``preserved``) but is completely *absent* from the working tree — the exact
6 gap that let 13 self-vendored files (``.cursor/rules/*``,
7 ``.cursor/skills/*/SKILL.md``, ``.overseer/policy/*.yaml``,
8 ``.overseer/STANDING-DECISIONS.reference.md``) go unrendered on this very
9 repo for three days without any automated check ever objecting.
10
11 This gate checks strictly against what ``version.lock`` **already records**
12 as installed — never against a fresh ``resolve_footprint`` re-render of the
13 current kit templates. That distinction matters: a kit template that has
14 never been through a completed ``overseer sync`` yet is a *drift* condition
15 (already covered by the existing ``overseer status`` drift check), not a
16 "declared but missing" condition — only entries the lock itself already
17 promised are in scope here. It also deliberately never inspects file
18 *content* — only existence. A kit-owned file whose content differs from its
19 recorded lock hash is a different, softer condition (stale hash, upstream
20 template change, or an unmarked customization) that stays on the existing
21 opt-in ``overseer status --check-footprint`` content-digest path; see
22 §KH3.3 for why conflating the two would risk false-closing this gate for any
23 consumer repo with a legitimate, not-yet-``preserved`` drift.
24 """
25
26 from __future__ import annotations
27
28 from dataclasses import dataclass
29 from pathlib import Path
30
31 from cli.version_lock import (
32 ORIGIN_PRESERVED,
33 LockError,
34 VersionLock,
35 entry_origin,
36 lock_path,
37 read_version_lock,
38 )
39
40 REMEDIATION = "ok sync"
41
42
43 @dataclass(frozen=True)
44 class FootprintIntegrityReport:
45 """Result of a self-footprint existence probe — no shell, no content reads."""
46
47 state: str # ok | missing | unreadable | not_applicable
48 message: str
49 remediation: str | None
50 missing: tuple[str, ...] = ()
51
52 @property
53 def ok(self) -> bool:
54 return self.state in {"ok", "not_applicable"}
55
56
57 def check_footprint_integrity(
58 repo_root: Path,
59 *,
60 lock: VersionLock | None = None,
61 ) -> FootprintIntegrityReport:
62 """Resolve a ``FootprintIntegrityReport`` for the current on-disk state.
63
64 Pure function of ``(repo_root filesystem state, version.lock contents)``
65 at call time (§KH3.8 data-integrity) — every non-``preserved`` entry
66 already declared in ``version.lock`` is checked with a single
67 ``Path.is_file()``; any absence fails closed as ``missing``. A lock that
68 exists but cannot be parsed also fails closed as ``unreadable`` rather
69 than optimistically reporting ``ok``. A repo with no ``version.lock`` at
70 all has nothing declared yet, so it is vacuously ``not_applicable``
71 rather than a failure.
72
73 ``lock`` is an optional pre-computed value — callers that already loaded
74 it for another purpose (``overseer status``) pass it through to avoid a
75 second read (§KH3.5); callers with no prior value (``overseer review
76 --freeze``, ``overseer governance-sync``) simply omit it and this
77 function reads it itself.
78 """
79 if lock is None:
80 path = lock_path(repo_root)
81 if not path.is_file():
82 return FootprintIntegrityReport(
83 state="not_applicable",
84 message="no version.lock yet — nothing declared to check",
85 remediation=None,
86 )
87 try:
88 lock = read_version_lock(path)
89 except LockError as exc:
90 return FootprintIntegrityReport(
91 state="unreadable",
92 message=f"could not read version.lock — failing closed: {exc}",
93 remediation="ok init",
94 )
95
96 missing = tuple(
97 sorted(
98 entry.path
99 for entry in lock.footprint
100 if entry_origin(entry) != ORIGIN_PRESERVED
101 and not (repo_root / entry.path).is_file()
102 )
103 )
104
105 if missing:
106 listed = ", ".join(missing)
107 return FootprintIntegrityReport(
108 state="missing",
109 message=(
110 f"{len(missing)} kit-owned footprint file(s) declared in "
111 f"version.lock are absent from disk: {listed}"
112 ),
113 remediation=REMEDIATION,
114 missing=missing,
115 )
116
117 return FootprintIntegrityReport(
118 state="ok",
119 message="all kit-owned footprint files declared in version.lock are present",
120 remediation=None,
121 )
File History 1 commit
sha256:6abcf1fa82a7a621ccbc945f19acdba5bc0db54569599404a1452fb4a096a199 fix(ISR): default require_independent_second_reviewer to require Human minor 1 day ago