C-31 work-record sidetrack detector (CUST-WP-0060-T05)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s

Warn on YAML-block ids matching no kind in the canon work-record type
registry (~/the-custodian/canon/standards/work-record-types.yaml, override
via WORK_RECORD_REGISTRY). Detection only; registration of non-workplan
kinds is a later stage. Skips template placeholders and repos without the
registry available.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-07-20 02:07:17 +02:00
parent 814595c317
commit 59770145fd

View file

@ -33,6 +33,7 @@ Checks:
C-28 inbox-stale-unread WARN No Unread inbox messages older than INBOX_STALE_DAYS
C-29 inbox-work-unpromoted WARN No Stale unread message looks like a multi-step work request without a workplan
C-30 scope-current-state-stale WARN No SCOPE.md Current State contradicts live workplan statuses
C-31 work-record-unregistered WARN No YAML-block id matches no kind in the canon work-record type registry (sidetrack detector, CUST-WP-0060)
Usage:
python scripts/consistency_check.py --repo SLUG [--fix] [--no-writeback] [--json] [--api-base URL]
@ -772,6 +773,82 @@ def _infer_slug_from_path(api_base: str, path: str) -> "tuple[str, str] | None":
return None
_WORK_RECORD_FENCE_RE = re.compile(r"```(?:yaml|task)\n(.*?)```", re.S)
_WORK_RECORD_ID_LINE_RE = re.compile(r"^id:\s*[\"']?([A-Za-z0-9_-]+)[\"']?\s*$", re.M)
_WORK_RECORD_ID_LIKE_RE = re.compile(r"^[A-Z][A-Z0-9]*(-[A-Z0-9]+)+$")
_WORK_RECORD_SKIP_DIRS = frozenset({".git", "node_modules", ".venv", "history"})
def _load_work_record_patterns() -> list[re.Pattern] | None:
"""Load id patterns from the canon work-record type registry.
Location: $WORK_RECORD_REGISTRY, else ~/the-custodian/canon/standards/
work-record-types.yaml. Returns None (check skipped) when unavailable.
"""
if not _HAS_YAML:
return None
candidates = []
env = os.environ.get("WORK_RECORD_REGISTRY")
if env:
candidates.append(Path(env))
candidates.append(
Path.home() / "the-custodian/canon/standards/work-record-types.yaml")
for path in candidates:
if not path.is_file():
continue
try:
reg = _yaml.safe_load(path.read_text())
patterns = []
for kind in reg.get("kinds", []):
patterns += [re.compile(p) for p in kind.get("id_patterns", [])]
patterns += [re.compile(lp["pattern"])
for lp in kind.get("legacy_patterns", [])]
return patterns
except Exception:
return None
return None
def _check_unregistered_work_records(repo_dir: Path, report: "ConsistencyReport") -> None:
"""C-31: warn on YAML-block ids matching no registered work-record kind.
Detection only (CUST-WP-0060-T05); registration of non-workplan kinds is
a later stage. An unregistered id scheme is a coordination sidetrack that
will need manual reintegration surface it the day it appears.
"""
patterns = _load_work_record_patterns()
if patterns is None:
return
seen: set[str] = set()
for md in sorted(repo_dir.rglob("*.md")):
if any(part in _WORK_RECORD_SKIP_DIRS for part in md.parts):
continue
try:
text = md.read_text(errors="replace")
except OSError:
continue
for body in _WORK_RECORD_FENCE_RE.findall(text):
for rid in _WORK_RECORD_ID_LINE_RE.findall(body):
if rid in seen or "NNN" in rid or rid.endswith("-TNN"):
continue
if not _WORK_RECORD_ID_LIKE_RE.match(rid):
continue
if any(p.match(rid) for p in patterns):
continue
seen.add(rid)
report.add(
severity="WARN",
check_id="C-31",
message=(
f"[{md.relative_to(repo_dir)}] id '{rid}' matches no "
f"kind in the canon work-record type registry — "
f"unregistered species become sidetracks needing "
f"manual reintegration (work-record-types_v0.1)"
),
fixable=False,
)
def check_repo(api_base: str, repo_slug: str, repo_path_override: str | None = None) -> ConsistencyReport:
"""Run all consistency checks for a registered repo."""
repo = _api_get(api_base, f"/repos/{repo_slug}", return_error=True)
@ -835,6 +912,9 @@ def check_repo(api_base: str, repo_slug: str, repo_path_override: str | None = N
fixable=False,
)
# C-31: work-record sidetrack detector (canon work-record-types registry)
_check_unregistered_work_records(repo_dir, report)
# C-01: workplans/ directory missing
if not workplans_dir.is_dir():
report.add(