CUST-WP-0060 test coverage: validate_work_records suite + CI wiring + C-31 comment fix
Critical review of CUST-WP-0060 (T03-T06) found: shipped mechanism proven
only by manual/live-repo runs, no repeatable test suite; tests/ never
wired to CI at all (pre-existing gap, not introduced here).
- tests/test_validate_work_records.py: 30 tests against the live canon
registry/schemas — classify() incl. all grandfathered legacy id
schemes, per-kind schema gates (intake open/closed/promoted, decision
prepared/resolved, engagement prepared), multi-doc yaml handling,
malformed-yaml-only-errors-if-id-registered, main() end-to-end via
subprocess (exit codes, template placeholders, --strict escalation,
terminal-record historical grace incl. the boundary case that grace
must NOT mask real enum violations), and the jsonschema-unavailable
fallback path (the exact failure mode that broke the first Forgejo
CI run before the runner-substrate fix)
- tools/validate_work_records.py: docstring said 'authoritative detector
is fix-consistency C-25' — wrong, it landed as C-31 (C-25..C-30 were
already taken); comment now correct
- .forgejo/workflows/python-tests.yaml: wires tests/ to CI for the first
time in this repo (apt python3/pytest/jsonschema/yaml on the
node:20-bookworm substrate, same pattern as work-records.yaml)
- tests/test_scan_workstream_terminology.py: found one pre-existing,
unrelated failure while establishing the CI-representative baseline
(agentic-resources allowlist entry no longer sets exclude_repo — a
policy question, not a bug this task should resolve silently); marked
xfail(strict=True) with the finding recorded so CI has a clean signal
and a silent 'fix' doesn't go unnoticed either
Local verification with apt-sourced deps (jsonschema 4.10.3, matching the
CI runner's package source, not just pip): 34 passed, 1 known xfailed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 23:09:25 +02:00
|
|
|
"""Unit + integration tests for tools/validate_work_records.py
|
|
|
|
|
(CUST-WP-0060-T04, work-record CI validation gate).
|
|
|
|
|
|
|
|
|
|
Covers:
|
|
|
|
|
- load_registry / classify — id pattern matching against the live canon
|
|
|
|
|
registry, incl. grandfathered legacy schemes (AWQ-, DEC-, OH-, task
|
2026-08-22 22:35:37 +02:00
|
|
|
single-digit / -LEGACY- variants, and the exact MASON-0001 bootstrap)
|
CUST-WP-0060 test coverage: validate_work_records suite + CI wiring + C-31 comment fix
Critical review of CUST-WP-0060 (T03-T06) found: shipped mechanism proven
only by manual/live-repo runs, no repeatable test suite; tests/ never
wired to CI at all (pre-existing gap, not introduced here).
- tests/test_validate_work_records.py: 30 tests against the live canon
registry/schemas — classify() incl. all grandfathered legacy id
schemes, per-kind schema gates (intake open/closed/promoted, decision
prepared/resolved, engagement prepared), multi-doc yaml handling,
malformed-yaml-only-errors-if-id-registered, main() end-to-end via
subprocess (exit codes, template placeholders, --strict escalation,
terminal-record historical grace incl. the boundary case that grace
must NOT mask real enum violations), and the jsonschema-unavailable
fallback path (the exact failure mode that broke the first Forgejo
CI run before the runner-substrate fix)
- tools/validate_work_records.py: docstring said 'authoritative detector
is fix-consistency C-25' — wrong, it landed as C-31 (C-25..C-30 were
already taken); comment now correct
- .forgejo/workflows/python-tests.yaml: wires tests/ to CI for the first
time in this repo (apt python3/pytest/jsonschema/yaml on the
node:20-bookworm substrate, same pattern as work-records.yaml)
- tests/test_scan_workstream_terminology.py: found one pre-existing,
unrelated failure while establishing the CI-representative baseline
(agentic-resources allowlist entry no longer sets exclude_repo — a
policy question, not a bug this task should resolve silently); marked
xfail(strict=True) with the finding recorded so CI has a clean signal
and a silent 'fix' doesn't go unnoticed either
Local verification with apt-sourced deps (jsonschema 4.10.3, matching the
CI runner's package source, not just pip): 34 passed, 1 known xfailed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 23:09:25 +02:00
|
|
|
- load_validators — jsonschema-backed per-kind checks (intake, decision,
|
|
|
|
|
engagement): required-field gates on non-terminal records, historical
|
|
|
|
|
grace on terminal ones
|
|
|
|
|
- iter_blocks — multi-document yaml blocks, malformed-yaml handling that
|
|
|
|
|
only errors when a registered id is actually at stake
|
|
|
|
|
- main() end-to-end via subprocess: exit codes, template-placeholder
|
|
|
|
|
skipping, --strict sidetrack escalation
|
|
|
|
|
|
|
|
|
|
Uses the live canon (this repo) as the default registry/schema source, so
|
|
|
|
|
these tests double as a regression check on the shipped artifacts — not
|
|
|
|
|
just the tool's logic in isolation.
|
|
|
|
|
"""
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import subprocess
|
|
|
|
|
import sys
|
|
|
|
|
import textwrap
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
sys.path.insert(0, str(REPO_ROOT))
|
|
|
|
|
|
|
|
|
|
from tools.validate_work_records import ( # noqa: E402
|
|
|
|
|
classify,
|
|
|
|
|
iter_blocks,
|
|
|
|
|
load_registry,
|
|
|
|
|
load_validators,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
|
|
|
def kinds():
|
|
|
|
|
return load_registry(REPO_ROOT)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
|
|
|
def validators():
|
|
|
|
|
return load_validators(REPO_ROOT)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestClassify:
|
|
|
|
|
def test_workplan_and_task_ids(self, kinds):
|
|
|
|
|
assert classify("CUST-WP-0060", kinds) == "workplan"
|
|
|
|
|
assert classify("CUST-WP-0060-T01", kinds) == "task"
|
|
|
|
|
|
2026-08-23 13:12:02 +02:00
|
|
|
def test_repository_derived_multi_segment_workplan_prefix(self, kinds):
|
|
|
|
|
assert classify("RAPP-OPENBAO-WP-0002", kinds) == "workplan"
|
|
|
|
|
assert classify("RAPP-OPENBAO-WP-0002-T01", kinds) == "task"
|
|
|
|
|
assert classify("RAIL-K8S-WP-0001", kinds) == "workplan"
|
|
|
|
|
assert classify("RAPP--OPENBAO-WP-0002", kinds) is None
|
|
|
|
|
assert classify("1RAPP-OPENBAO-WP-0002", kinds) is None
|
|
|
|
|
|
2026-08-23 14:11:14 +02:00
|
|
|
def test_ad_hoc_daily_container_and_task_ids(self, kinds):
|
2026-08-23 14:45:26 +02:00
|
|
|
assert classify("ACTIVITY-WP-ADHOC-2026-08-23", kinds) == "workplan"
|
|
|
|
|
assert classify("ACTIVITY-WP-ADHOC-2026-08-23-T01", kinds) == "task"
|
|
|
|
|
assert classify("RAPP-OPENBAO-WP-ADHOC-2026-08-23", kinds) == "workplan"
|
|
|
|
|
|
|
|
|
|
def test_unqualified_ad_hoc_ids_are_grandfathered(self, kinds):
|
2026-08-23 14:11:14 +02:00
|
|
|
assert classify("ADHOC-2026-08-23", kinds) == "workplan"
|
|
|
|
|
assert classify("ADHOC-2026-08-23-T01", kinds) == "task"
|
|
|
|
|
|
CUST-WP-0060 test coverage: validate_work_records suite + CI wiring + C-31 comment fix
Critical review of CUST-WP-0060 (T03-T06) found: shipped mechanism proven
only by manual/live-repo runs, no repeatable test suite; tests/ never
wired to CI at all (pre-existing gap, not introduced here).
- tests/test_validate_work_records.py: 30 tests against the live canon
registry/schemas — classify() incl. all grandfathered legacy id
schemes, per-kind schema gates (intake open/closed/promoted, decision
prepared/resolved, engagement prepared), multi-doc yaml handling,
malformed-yaml-only-errors-if-id-registered, main() end-to-end via
subprocess (exit codes, template placeholders, --strict escalation,
terminal-record historical grace incl. the boundary case that grace
must NOT mask real enum violations), and the jsonschema-unavailable
fallback path (the exact failure mode that broke the first Forgejo
CI run before the runner-substrate fix)
- tools/validate_work_records.py: docstring said 'authoritative detector
is fix-consistency C-25' — wrong, it landed as C-31 (C-25..C-30 were
already taken); comment now correct
- .forgejo/workflows/python-tests.yaml: wires tests/ to CI for the first
time in this repo (apt python3/pytest/jsonschema/yaml on the
node:20-bookworm substrate, same pattern as work-records.yaml)
- tests/test_scan_workstream_terminology.py: found one pre-existing,
unrelated failure while establishing the CI-representative baseline
(agentic-resources allowlist entry no longer sets exclude_repo — a
policy question, not a bug this task should resolve silently); marked
xfail(strict=True) with the finding recorded so CI has a clean signal
and a silent 'fix' doesn't go unnoticed either
Local verification with apt-sourced deps (jsonschema 4.10.3, matching the
CI runner's package source, not just pip): 34 passed, 1 known xfailed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 23:09:25 +02:00
|
|
|
def test_new_kind_ids(self, kinds):
|
|
|
|
|
assert classify("BINKY-IN-0001", kinds) == "intake"
|
|
|
|
|
assert classify("BINKY-DEC-2026-001", kinds) == "decision"
|
|
|
|
|
assert classify("BINKY-ENG-2026-001", kinds) == "engagement"
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"legacy_id,expected_kind",
|
|
|
|
|
[
|
|
|
|
|
("AWQ-010", "intake"),
|
|
|
|
|
("DEC-2026-004", "decision"),
|
|
|
|
|
("OH-2026-003", "engagement"),
|
|
|
|
|
("CUST-WP-0060-T1", "task"),
|
2026-08-22 22:35:37 +02:00
|
|
|
("MASON-0001", "workplan"),
|
|
|
|
|
("MASON-0001-T01", "task"),
|
CUST-WP-0060 test coverage: validate_work_records suite + CI wiring + C-31 comment fix
Critical review of CUST-WP-0060 (T03-T06) found: shipped mechanism proven
only by manual/live-repo runs, no repeatable test suite; tests/ never
wired to CI at all (pre-existing gap, not introduced here).
- tests/test_validate_work_records.py: 30 tests against the live canon
registry/schemas — classify() incl. all grandfathered legacy id
schemes, per-kind schema gates (intake open/closed/promoted, decision
prepared/resolved, engagement prepared), multi-doc yaml handling,
malformed-yaml-only-errors-if-id-registered, main() end-to-end via
subprocess (exit codes, template placeholders, --strict escalation,
terminal-record historical grace incl. the boundary case that grace
must NOT mask real enum violations), and the jsonschema-unavailable
fallback path (the exact failure mode that broke the first Forgejo
CI run before the runner-substrate fix)
- tools/validate_work_records.py: docstring said 'authoritative detector
is fix-consistency C-25' — wrong, it landed as C-31 (C-25..C-30 were
already taken); comment now correct
- .forgejo/workflows/python-tests.yaml: wires tests/ to CI for the first
time in this repo (apt python3/pytest/jsonschema/yaml on the
node:20-bookworm substrate, same pattern as work-records.yaml)
- tests/test_scan_workstream_terminology.py: found one pre-existing,
unrelated failure while establishing the CI-representative baseline
(agentic-resources allowlist entry no longer sets exclude_repo — a
policy question, not a bug this task should resolve silently); marked
xfail(strict=True) with the finding recorded so CI has a clean signal
and a silent 'fix' doesn't go unnoticed either
Local verification with apt-sourced deps (jsonschema 4.10.3, matching the
CI runner's package source, not just pip): 34 passed, 1 known xfailed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 23:09:25 +02:00
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
def test_grandfathered_legacy_ids(self, kinds, legacy_id, expected_kind):
|
|
|
|
|
assert classify(legacy_id, kinds) == expected_kind
|
|
|
|
|
|
|
|
|
|
def test_unregistered_id_returns_none(self, kinds):
|
|
|
|
|
assert classify("FOO-QX-001", kinds) is None
|
|
|
|
|
|
2026-08-22 22:35:37 +02:00
|
|
|
def test_risk_register_species(self, kinds):
|
|
|
|
|
assert classify("RISK-F-0008", kinds) == "register-entry"
|
|
|
|
|
assert classify("RISK-REG-0001", kinds) == "register-entry"
|
|
|
|
|
assert classify("RISK-N-0003", kinds) is None
|
|
|
|
|
|
CUST-WP-0060 test coverage: validate_work_records suite + CI wiring + C-31 comment fix
Critical review of CUST-WP-0060 (T03-T06) found: shipped mechanism proven
only by manual/live-repo runs, no repeatable test suite; tests/ never
wired to CI at all (pre-existing gap, not introduced here).
- tests/test_validate_work_records.py: 30 tests against the live canon
registry/schemas — classify() incl. all grandfathered legacy id
schemes, per-kind schema gates (intake open/closed/promoted, decision
prepared/resolved, engagement prepared), multi-doc yaml handling,
malformed-yaml-only-errors-if-id-registered, main() end-to-end via
subprocess (exit codes, template placeholders, --strict escalation,
terminal-record historical grace incl. the boundary case that grace
must NOT mask real enum violations), and the jsonschema-unavailable
fallback path (the exact failure mode that broke the first Forgejo
CI run before the runner-substrate fix)
- tools/validate_work_records.py: docstring said 'authoritative detector
is fix-consistency C-25' — wrong, it landed as C-31 (C-25..C-30 were
already taken); comment now correct
- .forgejo/workflows/python-tests.yaml: wires tests/ to CI for the first
time in this repo (apt python3/pytest/jsonschema/yaml on the
node:20-bookworm substrate, same pattern as work-records.yaml)
- tests/test_scan_workstream_terminology.py: found one pre-existing,
unrelated failure while establishing the CI-representative baseline
(agentic-resources allowlist entry no longer sets exclude_repo — a
policy question, not a bug this task should resolve silently); marked
xfail(strict=True) with the finding recorded so CI has a clean signal
and a silent 'fix' doesn't go unnoticed either
Local verification with apt-sourced deps (jsonschema 4.10.3, matching the
CI runner's package source, not just pip): 34 passed, 1 known xfailed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 23:09:25 +02:00
|
|
|
|
|
|
|
|
class TestValidators:
|
|
|
|
|
def test_intake_open_without_lane_errors(self, validators):
|
|
|
|
|
errs = validators["intake"]({"id": "X-IN-0001", "title": "t", "status": "open"})
|
|
|
|
|
assert any("lane" in e for e in errs)
|
|
|
|
|
|
|
|
|
|
def test_intake_open_with_lane_passes(self, validators):
|
|
|
|
|
errs = validators["intake"](
|
|
|
|
|
{"id": "X-IN-0001", "title": "clean intake item", "status": "open", "lane": "green"}
|
|
|
|
|
)
|
|
|
|
|
assert errs == []
|
|
|
|
|
|
|
|
|
|
def test_intake_closed_without_outcome_errors(self, validators):
|
|
|
|
|
errs = validators["intake"]({"id": "X-IN-0001", "title": "t", "status": "closed"})
|
|
|
|
|
assert any("outcome" in e for e in errs)
|
|
|
|
|
|
|
|
|
|
def test_intake_promoted_without_promoted_to_errors(self, validators):
|
|
|
|
|
errs = validators["intake"](
|
|
|
|
|
{"id": "X-IN-0001", "title": "t", "status": "closed", "outcome": "promoted"}
|
|
|
|
|
)
|
|
|
|
|
assert any("promoted_to" in e for e in errs)
|
|
|
|
|
|
|
|
|
|
def test_intake_promoted_with_promoted_to_passes(self, validators):
|
|
|
|
|
errs = validators["intake"](
|
|
|
|
|
{
|
|
|
|
|
"id": "X-IN-0001",
|
|
|
|
|
"title": "promoted intake item",
|
|
|
|
|
"status": "closed",
|
|
|
|
|
"outcome": "promoted",
|
|
|
|
|
"promoted_to": "CUST-WP-0061",
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
assert errs == []
|
|
|
|
|
|
|
|
|
|
def test_decision_prepared_requires_recommendation_and_fallback(self, validators):
|
|
|
|
|
errs = validators["decision"](
|
|
|
|
|
{"id": "X-DEC-2026-001", "title": "t", "status": "prepared", "lane": "yellow"}
|
|
|
|
|
)
|
|
|
|
|
assert any("agent_recommendation" in e or "fallback_if_no_response" in e
|
|
|
|
|
for e in errs)
|
|
|
|
|
|
|
|
|
|
def test_decision_resolved_requires_outcome_and_resolver(self, validators):
|
|
|
|
|
errs = validators["decision"](
|
|
|
|
|
{"id": "X-DEC-2026-001", "title": "t", "status": "resolved", "lane": "yellow"}
|
|
|
|
|
)
|
|
|
|
|
assert any("outcome" in e or "resolved_at" in e or "resolved_by" in e
|
|
|
|
|
for e in errs)
|
|
|
|
|
|
|
|
|
|
def test_decision_full_prepared_package_passes(self, validators):
|
|
|
|
|
errs = validators["decision"](
|
|
|
|
|
{
|
|
|
|
|
"id": "X-DEC-2026-001",
|
|
|
|
|
"title": "full prepared decision package",
|
|
|
|
|
"status": "prepared",
|
|
|
|
|
"lane": "yellow",
|
|
|
|
|
"agent_recommendation": "approve",
|
|
|
|
|
"fallback_if_no_response": "no degradation",
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
assert errs == []
|
|
|
|
|
|
|
|
|
|
def test_engagement_prepared_requires_material_and_counterparty(self, validators):
|
|
|
|
|
errs = validators["engagement"](
|
|
|
|
|
{"id": "X-ENG-2026-001", "title": "t", "status": "prepared"}
|
|
|
|
|
)
|
|
|
|
|
assert any("prepared_material" in e or "counterparty" in e for e in errs)
|
|
|
|
|
|
|
|
|
|
def test_engagement_queued_bare_passes(self, validators):
|
|
|
|
|
errs = validators["engagement"](
|
|
|
|
|
{"id": "X-ENG-2026-001", "title": "a queued engagement", "status": "queued"}
|
|
|
|
|
)
|
|
|
|
|
assert errs == []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestLoadValidatorsFallback:
|
|
|
|
|
"""The jsonschema-unavailable path — this is the exact failure mode
|
|
|
|
|
that broke the first Forgejo CI run (node:20-bookworm runner had no
|
|
|
|
|
python3-jsonschema until the apt install was added). Must degrade to
|
|
|
|
|
minimal checks, not crash."""
|
|
|
|
|
|
|
|
|
|
def test_fallback_used_when_jsonschema_unimportable(self, monkeypatch):
|
|
|
|
|
monkeypatch.setitem(sys.modules, "jsonschema", None)
|
|
|
|
|
# load_validators does `import jsonschema` internally; force reload
|
|
|
|
|
# of the module under test isn't needed since the import happens at
|
|
|
|
|
# call time, not at module import time.
|
|
|
|
|
import importlib
|
|
|
|
|
import tools.validate_work_records as vwr
|
|
|
|
|
importlib.reload(vwr)
|
|
|
|
|
try:
|
|
|
|
|
validators = vwr.load_validators(REPO_ROOT)
|
|
|
|
|
assert set(validators) == {"intake", "decision", "engagement"}
|
|
|
|
|
errs = validators["intake"]({"id": "X-IN-0001", "status": "open"})
|
|
|
|
|
assert any("missing title" in e for e in errs)
|
|
|
|
|
assert any("missing lane" in e for e in errs)
|
|
|
|
|
errs_ok = validators["intake"](
|
|
|
|
|
{"id": "X-IN-0001", "status": "open", "title": "has a title",
|
|
|
|
|
"lane": "green"}
|
|
|
|
|
)
|
|
|
|
|
assert errs_ok == []
|
|
|
|
|
finally:
|
|
|
|
|
monkeypatch.delitem(sys.modules, "jsonschema", raising=False)
|
|
|
|
|
importlib.reload(vwr)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestIterBlocks:
|
|
|
|
|
def test_multi_document_yaml_block_yields_both_docs(self, tmp_path, kinds):
|
|
|
|
|
md = tmp_path / "doc.md"
|
|
|
|
|
md.write_text(
|
|
|
|
|
"```yaml\nid: CUST-WP-0060\n---\nid: CUST-WP-0060-T01\n```\n",
|
|
|
|
|
encoding="utf-8",
|
|
|
|
|
)
|
|
|
|
|
blocks = list(iter_blocks(md, kinds))
|
|
|
|
|
ids = [b[1]["id"] for b in blocks if b[1]]
|
|
|
|
|
assert ids == ["CUST-WP-0060", "CUST-WP-0060-T01"]
|
|
|
|
|
|
|
|
|
|
def test_malformed_yaml_without_registered_id_is_silently_skipped(self, tmp_path, kinds):
|
|
|
|
|
md = tmp_path / "doc.md"
|
|
|
|
|
md.write_text("```yaml\nid: [unterminated\n```\n", encoding="utf-8")
|
|
|
|
|
results = list(iter_blocks(md, kinds))
|
|
|
|
|
assert all(err is None for _, _, err in results)
|
|
|
|
|
|
|
|
|
|
def test_malformed_yaml_with_registered_id_errors(self, tmp_path, kinds):
|
|
|
|
|
md = tmp_path / "doc.md"
|
|
|
|
|
md.write_text(
|
|
|
|
|
"```yaml\nid: CUST-WP-0060\nbad: [unterminated\n```\n", encoding="utf-8"
|
|
|
|
|
)
|
|
|
|
|
results = list(iter_blocks(md, kinds))
|
|
|
|
|
assert any(err is not None for _, _, err in results)
|
|
|
|
|
|
|
|
|
|
def test_ignores_non_mapping_documents(self, tmp_path, kinds):
|
|
|
|
|
md = tmp_path / "doc.md"
|
|
|
|
|
md.write_text("```yaml\n- just\n- a\n- list\n```\n", encoding="utf-8")
|
|
|
|
|
assert list(iter_blocks(md, kinds)) == []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestMainEndToEnd:
|
|
|
|
|
"""Invokes the real CLI via subprocess against synthetic repo fixtures."""
|
|
|
|
|
|
|
|
|
|
def _run(self, repo: Path, *extra_args: str) -> subprocess.CompletedProcess:
|
|
|
|
|
return subprocess.run(
|
|
|
|
|
[sys.executable, str(REPO_ROOT / "tools/validate_work_records.py"),
|
|
|
|
|
"--repo", str(repo), "--canon", str(REPO_ROOT), *extra_args],
|
|
|
|
|
capture_output=True, text=True,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def test_clean_repo_exits_zero(self, tmp_path):
|
|
|
|
|
(tmp_path / "workplan.md").write_text(
|
|
|
|
|
textwrap.dedent(
|
|
|
|
|
"""
|
|
|
|
|
```yaml
|
|
|
|
|
id: X-IN-0001
|
|
|
|
|
title: "a clean intake item"
|
|
|
|
|
status: open
|
|
|
|
|
lane: green
|
|
|
|
|
```
|
|
|
|
|
"""
|
|
|
|
|
),
|
|
|
|
|
encoding="utf-8",
|
|
|
|
|
)
|
|
|
|
|
result = self._run(tmp_path)
|
|
|
|
|
assert result.returncode == 0, result.stdout
|
|
|
|
|
assert "0 errors" in result.stdout
|
|
|
|
|
|
|
|
|
|
def test_invalid_record_exits_one_with_message(self, tmp_path):
|
|
|
|
|
(tmp_path / "bad.md").write_text(
|
|
|
|
|
textwrap.dedent(
|
|
|
|
|
"""
|
|
|
|
|
```yaml
|
|
|
|
|
id: X-DEC-2026-001
|
|
|
|
|
title: "missing required prepared fields"
|
|
|
|
|
status: prepared
|
|
|
|
|
lane: yellow
|
|
|
|
|
```
|
|
|
|
|
"""
|
|
|
|
|
),
|
|
|
|
|
encoding="utf-8",
|
|
|
|
|
)
|
|
|
|
|
result = self._run(tmp_path)
|
|
|
|
|
assert result.returncode == 1
|
|
|
|
|
assert "X-DEC-2026-001" in result.stdout
|
|
|
|
|
|
|
|
|
|
def test_template_placeholder_ids_are_not_records(self, tmp_path):
|
|
|
|
|
(tmp_path / "AGENTS.md").write_text(
|
|
|
|
|
"```yaml\nid: BINKY-WP-NNNN\n```\n```yaml\nid: X-WP-0001-TNN\n```\n",
|
|
|
|
|
encoding="utf-8",
|
|
|
|
|
)
|
|
|
|
|
result = self._run(tmp_path)
|
|
|
|
|
assert result.returncode == 0
|
|
|
|
|
assert "0 checked" in result.stdout
|
|
|
|
|
|
|
|
|
|
def test_unregistered_id_warns_but_does_not_fail_by_default(self, tmp_path):
|
|
|
|
|
(tmp_path / "notes.md").write_text(
|
|
|
|
|
"```yaml\nid: FOO-QX-001\ntitle: rogue\n```\n", encoding="utf-8"
|
|
|
|
|
)
|
|
|
|
|
result = self._run(tmp_path)
|
|
|
|
|
assert result.returncode == 0
|
|
|
|
|
assert "WARN" in result.stdout
|
|
|
|
|
assert "FOO-QX-001" in result.stdout
|
|
|
|
|
|
|
|
|
|
def test_strict_mode_escalates_sidetrack_warning_to_failure(self, tmp_path):
|
|
|
|
|
(tmp_path / "notes.md").write_text(
|
|
|
|
|
"```yaml\nid: FOO-QX-001\ntitle: rogue\n```\n", encoding="utf-8"
|
|
|
|
|
)
|
|
|
|
|
result = self._run(tmp_path, "--strict")
|
|
|
|
|
assert result.returncode == 1
|
|
|
|
|
|
|
|
|
|
def test_grandfathered_legacy_ids_pass_unmodified(self, tmp_path):
|
|
|
|
|
(tmp_path / "queue.md").write_text(
|
|
|
|
|
textwrap.dedent(
|
|
|
|
|
"""
|
|
|
|
|
```yaml
|
|
|
|
|
id: AWQ-010
|
|
|
|
|
title: "legacy intake, unrenamed"
|
|
|
|
|
lane: green
|
|
|
|
|
status: open
|
|
|
|
|
```
|
|
|
|
|
```yaml
|
|
|
|
|
id: DEC-2026-004
|
|
|
|
|
title: "legacy decision, unrenamed"
|
|
|
|
|
status: resolved
|
|
|
|
|
lane: red
|
|
|
|
|
outcome: approved
|
|
|
|
|
resolved_at: "2026-07-20"
|
|
|
|
|
resolved_by: Bernd
|
|
|
|
|
```
|
|
|
|
|
"""
|
|
|
|
|
),
|
|
|
|
|
encoding="utf-8",
|
|
|
|
|
)
|
|
|
|
|
result = self._run(tmp_path)
|
|
|
|
|
assert result.returncode == 0, result.stdout
|
|
|
|
|
assert "2 checked" in result.stdout
|
|
|
|
|
|
2026-08-22 22:35:37 +02:00
|
|
|
def test_mason_bootstrap_ids_pass_unmodified(self, tmp_path):
|
|
|
|
|
(tmp_path / "legacy-mason.md").write_text(
|
|
|
|
|
textwrap.dedent(
|
|
|
|
|
"""
|
|
|
|
|
```yaml
|
|
|
|
|
id: MASON-0001
|
|
|
|
|
title: "legacy Mason bootstrap"
|
|
|
|
|
status: finished
|
|
|
|
|
```
|
|
|
|
|
```task
|
|
|
|
|
id: MASON-0001-T01
|
|
|
|
|
status: done
|
|
|
|
|
priority: high
|
|
|
|
|
```
|
|
|
|
|
"""
|
|
|
|
|
),
|
|
|
|
|
encoding="utf-8",
|
|
|
|
|
)
|
|
|
|
|
result = self._run(tmp_path, "--strict")
|
|
|
|
|
assert result.returncode == 0, result.stdout
|
|
|
|
|
assert "2 checked" in result.stdout
|
|
|
|
|
|
|
|
|
|
def test_task_fence_rejects_non_task_registered_id(self, tmp_path):
|
|
|
|
|
(tmp_path / "wrong-fence.md").write_text(
|
|
|
|
|
"```task\nid: CUST-WP-0063\nstatus: active\n```\n",
|
|
|
|
|
encoding="utf-8",
|
|
|
|
|
)
|
|
|
|
|
result = self._run(tmp_path)
|
|
|
|
|
assert result.returncode == 1
|
|
|
|
|
assert "registered as workplan, not task" in result.stdout
|
|
|
|
|
|
CUST-WP-0060 test coverage: validate_work_records suite + CI wiring + C-31 comment fix
Critical review of CUST-WP-0060 (T03-T06) found: shipped mechanism proven
only by manual/live-repo runs, no repeatable test suite; tests/ never
wired to CI at all (pre-existing gap, not introduced here).
- tests/test_validate_work_records.py: 30 tests against the live canon
registry/schemas — classify() incl. all grandfathered legacy id
schemes, per-kind schema gates (intake open/closed/promoted, decision
prepared/resolved, engagement prepared), multi-doc yaml handling,
malformed-yaml-only-errors-if-id-registered, main() end-to-end via
subprocess (exit codes, template placeholders, --strict escalation,
terminal-record historical grace incl. the boundary case that grace
must NOT mask real enum violations), and the jsonschema-unavailable
fallback path (the exact failure mode that broke the first Forgejo
CI run before the runner-substrate fix)
- tools/validate_work_records.py: docstring said 'authoritative detector
is fix-consistency C-25' — wrong, it landed as C-31 (C-25..C-30 were
already taken); comment now correct
- .forgejo/workflows/python-tests.yaml: wires tests/ to CI for the first
time in this repo (apt python3/pytest/jsonschema/yaml on the
node:20-bookworm substrate, same pattern as work-records.yaml)
- tests/test_scan_workstream_terminology.py: found one pre-existing,
unrelated failure while establishing the CI-representative baseline
(agentic-resources allowlist entry no longer sets exclude_repo — a
policy question, not a bug this task should resolve silently); marked
xfail(strict=True) with the finding recorded so CI has a clean signal
and a silent 'fix' doesn't go unnoticed either
Local verification with apt-sourced deps (jsonschema 4.10.3, matching the
CI runner's package source, not just pip): 34 passed, 1 known xfailed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 23:09:25 +02:00
|
|
|
def test_terminal_record_grace_does_not_mask_structural_errors(self, tmp_path):
|
|
|
|
|
"""Historical grace waives missing-field ('required') errors on
|
|
|
|
|
terminal records, but a genuine value violation (bad lane enum)
|
|
|
|
|
must still fail — grace is not a blanket skip for closed work."""
|
|
|
|
|
(tmp_path / "archived.md").write_text(
|
|
|
|
|
textwrap.dedent(
|
|
|
|
|
"""
|
|
|
|
|
```yaml
|
|
|
|
|
id: X-DEC-2026-003
|
|
|
|
|
title: "resolved decision with an invalid lane value"
|
|
|
|
|
status: resolved
|
|
|
|
|
lane: not-a-real-lane
|
|
|
|
|
outcome: approved
|
|
|
|
|
resolved_at: "2026-01-01"
|
|
|
|
|
resolved_by: Bernd
|
|
|
|
|
```
|
|
|
|
|
"""
|
|
|
|
|
),
|
|
|
|
|
encoding="utf-8",
|
|
|
|
|
)
|
|
|
|
|
result = self._run(tmp_path)
|
|
|
|
|
assert result.returncode == 1, result.stdout
|
|
|
|
|
assert "X-DEC-2026-003" in result.stdout
|
|
|
|
|
|
|
|
|
|
def test_terminal_record_gets_historical_grace(self, tmp_path):
|
|
|
|
|
"""A resolved/closed record missing spine fields (e.g. an old
|
|
|
|
|
archived decision without `lane`) is not penalised for
|
|
|
|
|
completeness — only for structural errors."""
|
|
|
|
|
(tmp_path / "archived.md").write_text(
|
|
|
|
|
textwrap.dedent(
|
|
|
|
|
"""
|
|
|
|
|
```yaml
|
|
|
|
|
id: X-DEC-2026-002
|
|
|
|
|
title: "old resolved decision, minimal fields"
|
|
|
|
|
status: resolved
|
|
|
|
|
outcome: approved
|
|
|
|
|
resolved_at: "2026-01-01"
|
|
|
|
|
resolved_by: Bernd
|
|
|
|
|
```
|
|
|
|
|
"""
|
|
|
|
|
),
|
|
|
|
|
encoding="utf-8",
|
|
|
|
|
)
|
|
|
|
|
result = self._run(tmp_path)
|
|
|
|
|
assert result.returncode == 0, result.stdout
|