feat: advance repository records and provenance

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a023c0-a0a3-7c03-b395-5a0d2757214d
This commit is contained in:
tegwick 2026-08-21 22:07:48 +02:00
parent 329af60753
commit 35e86d7b85
24 changed files with 1618 additions and 51 deletions

View file

@ -0,0 +1,38 @@
from __future__ import annotations
from pathlib import Path
import pytest
from repo_manager.classification import ClassificationError, validate_classification
from repo_manager.observe import load_classification
def test_classification_contract_accepts_canon_values() -> None:
assert not validate_classification(
{
"category": "tooling",
"domain": "infotech",
"secondary_domains": ["agents"],
"capability_tags": ["repository-control"],
"business_stake": ["technology"],
"business_mechanics": ["control"],
}
)
def test_load_classification_rejects_invalid_authority(tmp_path: Path) -> None:
(tmp_path / ".repo-classification.yaml").write_text(
"repo_classification:\n"
" category: health\n"
" domain: infotech\n"
" secondary_domains: [infotech, nowhere]\n"
" capability_tags: [Bad_Tag]\n",
encoding="utf-8",
)
with pytest.raises(ClassificationError) as raised:
load_classification(tmp_path)
message = str(raised.value)
assert "category" in message
assert "secondary_domains" in message
assert "capability_tags" in message

50
tests/test_identifiers.py Normal file
View file

@ -0,0 +1,50 @@
from __future__ import annotations
from pathlib import Path
import pytest
from repo_manager.identifiers import derive_work_record_uuid, scan_live_identifier_collisions
def _workplan(path: Path, identifier: str, status: str, task_status: str = "todo") -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(
f"""---
id: {identifier}
title: Test
status: {status}
---
## Task
```task
id: {identifier}-T01
status: {task_status}
```
""",
encoding="utf-8",
)
def test_uuid_derivation_is_stable_and_namespace_scoped() -> None:
first = derive_work_record_uuid("helixforge", "RMGR-WP-0005")
assert str(first) == "6dcf854e-9229-569d-90f0-2d9df235a61d"
assert derive_work_record_uuid("helixforge", "RMGR-WP-0005") == first
assert derive_work_record_uuid("client-a", "RMGR-WP-0005") != first
with pytest.raises(ValueError):
derive_work_record_uuid("Client A", "RMGR-WP-0005")
def test_preflight_blocks_live_collision_but_ignores_archived_history(tmp_path: Path) -> None:
one = tmp_path / "one"
two = tmp_path / "two"
_workplan(one / "workplans" / "one.md", "SHARED-WP-0001", "active")
_workplan(two / "workplans" / "two.md", "SHARED-WP-0001", "ready")
report = scan_live_identifier_collisions(tmp_path)
assert report["ok"] is False
assert "SHARED-WP-0001" in report["collisions"]
_workplan(two / "workplans" / "two.md", "SHARED-WP-0001", "archived")
report = scan_live_identifier_collisions(tmp_path)
assert report["ok"] is True

105
tests/test_provenance.py Normal file
View file

@ -0,0 +1,105 @@
from __future__ import annotations
import os
import shutil
import subprocess
from pathlib import Path
from repo_manager.provenance import assistant_report, install_hook
SOURCE_HOOK = Path(__file__).parents[1] / ".githooks" / "prepare-commit-msg"
ASSISTANT_ENV_PREFIXES = ("ASSISTANT_", "CLAUDE", "CODEX_", "GROK", "XAI_")
def _human_env() -> dict[str, str]:
return {
key: value
for key, value in os.environ.items()
if not any(key.startswith(prefix) for prefix in ASSISTANT_ENV_PREFIXES)
}
def _git(repo: Path, *args: str, env: dict[str, str] | None = None) -> None:
subprocess.run(["git", *args], cwd=repo, env=env, check=True, capture_output=True)
def _fixture(tmp_path: Path) -> Path:
repo = tmp_path / "pilot"
repo.mkdir()
_git(repo, "init")
_git(repo, "config", "user.email", "test@example.com")
_git(repo, "config", "user.name", "Test")
hooks = repo / ".githooks"
hooks.mkdir()
shutil.copy2(SOURCE_HOOK, hooks / "prepare-commit-msg")
(hooks / "prepare-commit-msg").chmod(0o755)
_git(repo, "config", "core.hooksPath", str(hooks))
return repo
def _commit(repo: Path, name: str, *, session: str | None = None) -> None:
(repo / name).write_text(name, encoding="utf-8")
_git(repo, "add", name)
env = _human_env()
if session:
env.update(
ASSISTANT_NAME="codex",
ASSISTANT_MODEL="gpt-test",
ASSISTANT_PROCESS="42@test-host",
ASSISTANT_SESSION=session,
)
_git(repo, "commit", "-m", name, env=env)
def test_hook_is_silent_for_humans_and_idempotent_for_assistants(tmp_path: Path) -> None:
message = tmp_path / "message"
message.write_text("Change records\n", encoding="utf-8")
subprocess.run([str(SOURCE_HOOK), str(message)], env=_human_env(), check=True)
assert message.read_text(encoding="utf-8") == "Change records\n"
env = _human_env()
env.update(
ASSISTANT_NAME="codex",
ASSISTANT_MODEL="gpt-test",
ASSISTANT_PROCESS="42@test-host",
ASSISTANT_SESSION="session-a",
)
subprocess.run([str(SOURCE_HOOK), str(message)], env=env, check=True)
subprocess.run([str(SOURCE_HOOK), str(message)], env=env, check=True)
text = message.read_text(encoding="utf-8")
assert text.count("Assistant: codex") == 1
assert text.count("Assistant-Model: gpt-test") == 1
assert text.count("Assistant-Session: session-a") == 1
def test_report_detects_interleaved_sessions_and_unknown_human(tmp_path: Path) -> None:
repo = _fixture(tmp_path)
_commit(repo, "human")
_commit(repo, "a1", session="session-a")
_commit(repo, "b1", session="session-b")
_commit(repo, "a2", session="session-a")
report = assistant_report(repo)
assert report["assistants"]["codex"]["commit_count"] == 3
assert report["assistants"]["codex"]["models"] == ["gpt-test"]
assert report["interleaved_sessions"] == [["session-a", "session-b"]]
assert report["unattributed"]["before_cutover"] == 1
def test_install_sets_only_global_hooks_path(tmp_path: Path, monkeypatch) -> None:
hooks = tmp_path / "hooks"
hooks.mkdir()
shutil.copy2(SOURCE_HOOK, hooks / "prepare-commit-msg")
(hooks / "prepare-commit-msg").chmod(0o755)
global_config = tmp_path / "gitconfig"
monkeypatch.setenv("GIT_CONFIG_GLOBAL", str(global_config))
result = install_hook(hooks)
assert result["ok"] is True
configured = subprocess.run(
["git", "config", "--global", "--get", "core.hooksPath"],
check=True,
capture_output=True,
text=True,
env={**os.environ, "GIT_CONFIG_GLOBAL": str(global_config)},
).stdout.strip()
assert configured == str(hooks.resolve())

View file

@ -0,0 +1,130 @@
"""Intake, decision, dependency, and human-flag receiving-surface coverage."""
from __future__ import annotations
import subprocess
from pathlib import Path
from repo_manager.commands.record import mutate_record
from repo_manager.observe import observe_repository
def _git(repo: Path, *args: str) -> None:
subprocess.run(["git", *args], cwd=repo, check=True, capture_output=True)
def _fixture(tmp_path: Path) -> Path:
repo = tmp_path / "pilot"
repo.mkdir()
_git(repo, "init")
_git(repo, "config", "user.email", "test@example.com")
_git(repo, "config", "user.name", "Test")
(repo / ".repo-classification.yaml").write_text(
"repo_classification:\n category: tooling\n domain: infotech\n",
encoding="utf-8",
)
(repo / "workplans").mkdir()
(repo / "workplans" / "P-WP-0001.md").write_text(
"""---
id: P-WP-0001
type: workplan
title: Pilot
status: active
depends_on: [P-WP-0000]
---
## Blocked task
```task
id: P-WP-0001-T01
status: wait
needs_human: true
intervention_note: Choose the API boundary.
blocking_reason: Awaiting decision.
depends_on: [P-WP-0000-T01]
```
""",
encoding="utf-8",
)
_git(repo, "add", ".")
_git(repo, "commit", "-m", "seed")
return repo
def test_intake_lifecycle_is_file_backed_and_indexed(tmp_path: Path, monkeypatch) -> None:
repo = _fixture(tmp_path)
monkeypatch.setenv("RM_IDEMPOTENCY_PATH", str(tmp_path / "idempotency.json"))
created = mutate_record(
repo,
"intake",
"P-IN-0001",
operation="create",
title="Residual follow-up",
data={"origin": "residual", "origin_ref": "P-WP-0000"},
idempotency_key="intake-create",
)
replay = mutate_record(
repo,
"intake",
"P-IN-0001",
operation="create",
title="Residual follow-up",
data={"origin": "residual", "origin_ref": "P-WP-0000"},
idempotency_key="intake-create",
)
assert created.status == replay.status == "applied"
assert replay.evidence["git_sha"] == created.evidence["git_sha"]
assert mutate_record(repo, "intake", "P-IN-0001", operation="route", route_to="P-WP-0002").status == "applied"
assert mutate_record(repo, "intake", "P-IN-0001", operation="note", note="Owner accepted.").status == "applied"
assert mutate_record(repo, "intake", "P-IN-0001", operation="close", outcome="promoted").status == "applied"
snapshot, index = observe_repository(repo)
record = next(item for item in index.work_records if item.kind == "intake")
assert snapshot["index"]["intake_count"] == 1
assert record.status == "closed"
assert record.extra["record"]["routed_to"] == "P-WP-0002"
assert record.extra["record"]["notes"][0]["content"] == "Owner accepted."
def test_decision_resolve_keeps_safe_guarantee(tmp_path: Path) -> None:
repo = _fixture(tmp_path)
created = mutate_record(
repo,
"decision",
"P-DEC-0001",
operation="create",
title="Place the projection",
data={"description": "Choose an aggregation owner."},
)
assert created.status == "applied"
incomplete = mutate_record(repo, "decision", "P-DEC-0001", operation="resolve", rationale="Boundary")
assert incomplete.status == "rejected"
resolved = mutate_record(
repo,
"decision",
"P-DEC-0001",
operation="resolve",
rationale="Repository authority stays local.",
decided_by="operator",
)
assert resolved.status == "applied"
snapshot, index = observe_repository(repo)
record = next(item for item in index.work_records if item.kind == "decision")
assert snapshot["index"]["decision_count"] == 1
assert record.status == "resolved"
assert record.extra["record"]["decided_by"] == "operator"
def test_dependency_and_human_flags_survive_indexing(tmp_path: Path) -> None:
repo = _fixture(tmp_path)
_snapshot, index = observe_repository(repo)
workplan = next(item for item in index.work_records if item.kind == "workplan")
task = next(item for item in index.work_records if item.kind == "task")
assert workplan.extra["depends_on"] == ["P-WP-0000"]
assert task.extra == {
"depends_on": ["P-WP-0000-T01"],
"needs_human": True,
"intervention_note": "Choose the API boundary.",
"blocking_reason": "Awaiting decision.",
}

View file

@ -47,6 +47,18 @@ def test_durable_scaffold_writes_intent(tmp_path: Path):
assert report.ok, report.to_dict()
def test_scaffold_rerun_is_an_applied_noop(tmp_path: Path):
dest = tmp_path / "stable-tool"
first = scaffold_repository(dest, flavor="tooling", commit=False)
before = {path.relative_to(dest): path.read_bytes() for path in dest.rglob("*") if path.is_file()}
second = scaffold_repository(dest, flavor="tooling", commit=False)
after = {path.relative_to(dest): path.read_bytes() for path in dest.rglob("*") if path.is_file()}
assert first.status == second.status == "applied"
assert second.evidence["noop"] is True
assert second.evidence["written"] == []
assert after == before
def test_cli_scaffold(tmp_path: Path):
dest = tmp_path / "cli-tool"
assert (