fix registrar writebacks and own the State Hub access map
Ignore generated WORK-RECORDS.md/.custodian-brief.md in the registrar git precondition, commit identifier writebacks even when registration is incomplete, and name the remaining records in the error. Add config/state-hub-access.yaml as the single source for the AGENTS.md port map, rendered by rmgr scaffold and refreshed with --refresh-hub-access. Assistant: grok Assistant-Session: 01a04996-76e8-7f53-b971-1885cfbed436
This commit is contained in:
parent
77caca1ab8
commit
7ea7690dfc
10 changed files with 480 additions and 44 deletions
87
tests/test_hub_access.py
Normal file
87
tests/test_hub_access.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
from pathlib import Path
|
||||
|
||||
from repo_manager.cli import main
|
||||
from repo_manager.commands.scaffold import refresh_hub_access, scaffold_repository
|
||||
from repo_manager.hub_access import (
|
||||
apply_hub_access_block,
|
||||
live_urls,
|
||||
render_hub_access_block,
|
||||
retired_urls,
|
||||
)
|
||||
|
||||
|
||||
def test_canonical_map_matches_live_topology() -> None:
|
||||
urls = live_urls()
|
||||
assert "http://127.0.0.1:8000" in urls
|
||||
assert "http://10.43.68.154:8000" in urls
|
||||
assert "http://127.0.0.1:18000" not in urls
|
||||
assert "http://127.0.0.1:18000" in retired_urls()
|
||||
|
||||
|
||||
def test_rendered_table_does_not_instruct_retired_tunnel() -> None:
|
||||
block = render_hub_access_block()
|
||||
table, _, note = block.partition("\n\n")
|
||||
assert "`http://10.43.68.154:8000`" in table
|
||||
assert "`http://127.0.0.1:18000`" not in table
|
||||
assert "127.0.0.1:18000" in note
|
||||
|
||||
|
||||
def test_replace_unmarked_table_and_idempotent_refresh() -> None:
|
||||
original = (
|
||||
"# Agent instructions\n\n"
|
||||
"## State Hub Integration\n\n"
|
||||
"| Context | URL |\n"
|
||||
"|---------|-----|\n"
|
||||
"| Local workstation | `http://127.0.0.1:8000` |\n"
|
||||
"| Remote (railiance01, in-cluster) | `http://127.0.0.1:18000` |\n\n"
|
||||
"More prose.\n"
|
||||
)
|
||||
updated, action = apply_hub_access_block(original)
|
||||
assert action == "replaced-table"
|
||||
assert "127.0.0.1:18000" not in updated.split("Do not instruct")[0]
|
||||
again, second = apply_hub_access_block(updated)
|
||||
assert second == "noop"
|
||||
assert again == updated
|
||||
|
||||
|
||||
def test_scaffold_writes_canonical_access_table(tmp_path: Path) -> None:
|
||||
dest = tmp_path / "access-tool"
|
||||
result = scaffold_repository(dest, flavor="tooling", commit=False)
|
||||
assert result.status == "applied"
|
||||
agents = (dest / "AGENTS.md").read_text(encoding="utf-8")
|
||||
assert "<!-- BEGIN STATE-HUB-ACCESS -->" in agents
|
||||
assert "http://10.43.68.154:8000" in agents
|
||||
assert "| Remote (railiance01, in-cluster) | `http://127.0.0.1:18000` |" not in agents
|
||||
|
||||
|
||||
def test_refresh_hub_access_rewrites_stale_table(tmp_path: Path) -> None:
|
||||
dest = tmp_path / "stale-repo"
|
||||
dest.mkdir()
|
||||
(dest / "AGENTS.md").write_text(
|
||||
"# Agent instructions — stale-repo\n\n"
|
||||
"## State Hub Integration\n\n"
|
||||
"| Context | URL |\n"
|
||||
"|---------|-----|\n"
|
||||
"| Local workstation | `http://127.0.0.1:8000` |\n"
|
||||
"| Remote (railiance01, in-cluster) | `http://127.0.0.1:18000` |\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
result = refresh_hub_access(dest, commit=False)
|
||||
assert result.status == "applied"
|
||||
assert result.evidence["action"] == "replaced-table"
|
||||
text = (dest / "AGENTS.md").read_text(encoding="utf-8")
|
||||
assert "10.43.68.154:8000" in text
|
||||
assert "| Remote (railiance01, in-cluster) | `http://127.0.0.1:18000` |" not in text
|
||||
|
||||
|
||||
def test_cli_refresh_hub_access(tmp_path: Path) -> None:
|
||||
dest = tmp_path / "cli-refresh"
|
||||
dest.mkdir()
|
||||
(dest / "AGENTS.md").write_text("# Agent instructions\n", encoding="utf-8")
|
||||
assert (
|
||||
main(["scaffold", "--path", str(dest), "--refresh-hub-access", "--no-commit"])
|
||||
== 0
|
||||
)
|
||||
text = (dest / "AGENTS.md").read_text(encoding="utf-8")
|
||||
assert "<!-- BEGIN STATE-HUB-ACCESS -->" in text
|
||||
assert "10.43.68.154:8000" in text
|
||||
|
|
@ -177,6 +177,101 @@ def test_rejects_dirty_or_unsynced_repository(tmp_path: Path, monkeypatch) -> No
|
|||
dirty = rr.registrar_reconcile(repo, confirm_primary=True)
|
||||
assert dirty.status == "rejected"
|
||||
assert dirty.error and dirty.error["code"] == "git_precondition_failed"
|
||||
assert "note.txt" in (dirty.error.get("message") or "")
|
||||
|
||||
|
||||
def test_generated_index_alone_does_not_fail_git_precondition(tmp_path: Path, monkeypatch) -> None:
|
||||
repo = _fixture(tmp_path)
|
||||
(repo / "WORK-RECORDS.md").write_text("# generated\n", encoding="utf-8")
|
||||
monkeypatch.setattr(
|
||||
rr,
|
||||
"_check_primary",
|
||||
lambda _api, **_: ({"status": "ok", "db": "connected", "instance_role": "primary"}, None),
|
||||
)
|
||||
|
||||
def fake_run(command, *, env):
|
||||
workplan = repo / "workplans" / "DEMO-WP-0001.md"
|
||||
text = workplan.read_text(encoding="utf-8")
|
||||
text = text.replace(
|
||||
"status: active\n---",
|
||||
'status: active\nstate_hub_workstream_id: "11111111-1111-4111-8111-111111111111"\n---',
|
||||
)
|
||||
text = text.replace(
|
||||
"priority: high\n```",
|
||||
'priority: high\nstate_hub_task_id: "22222222-2222-4222-8222-222222222222"\n```',
|
||||
)
|
||||
workplan.write_text(text, encoding="utf-8")
|
||||
return subprocess.CompletedProcess(command, 0, "ok", "")
|
||||
|
||||
monkeypatch.setattr(rr, "_run_statehub", fake_run)
|
||||
result = rr.registrar_reconcile(repo, statehub_bin="statehub", confirm_primary=True)
|
||||
assert result.status == "applied"
|
||||
assert "WORK-RECORDS.md" in result.evidence.get("committed_paths", [])
|
||||
|
||||
|
||||
def test_incomplete_run_commits_writebacks_and_names_the_record(
|
||||
tmp_path: Path, monkeypatch
|
||||
) -> None:
|
||||
repo = _fixture(tmp_path)
|
||||
monkeypatch.setattr(
|
||||
rr,
|
||||
"_check_primary",
|
||||
lambda _api, **_: ({"status": "ok", "db": "connected", "instance_role": "primary"}, None),
|
||||
)
|
||||
|
||||
def fake_run(command, *, env):
|
||||
workplan = repo / "workplans" / "DEMO-WP-0001.md"
|
||||
text = workplan.read_text(encoding="utf-8")
|
||||
text = text.replace(
|
||||
"status: active\n---",
|
||||
'status: active\nstate_hub_workstream_id: "11111111-1111-4111-8111-111111111111"\n---',
|
||||
)
|
||||
workplan.write_text(text, encoding="utf-8")
|
||||
(repo / "WORK-RECORDS.md").write_text("# generated after mint\n", encoding="utf-8")
|
||||
return subprocess.CompletedProcess(
|
||||
command,
|
||||
1,
|
||||
"! task DEMO-WP-0001-T01 not created: 500 Internal Server Error: Internal Server Error",
|
||||
"",
|
||||
)
|
||||
|
||||
monkeypatch.setattr(rr, "_run_statehub", fake_run)
|
||||
result = rr.registrar_reconcile(repo, statehub_bin="statehub", confirm_primary=True)
|
||||
assert result.status == "failed"
|
||||
assert result.error and result.error["code"] == "registration_incomplete"
|
||||
assert "DEMO-WP-0001-T01" in (result.error.get("message") or "")
|
||||
assert result.error.get("records") == ["DEMO-WP-0001-T01"]
|
||||
assert "not created:" in (result.error.get("message") or "")
|
||||
subject = subprocess.run(
|
||||
["git", "log", "-1", "--format=%s"], cwd=repo, capture_output=True, text=True, check=True
|
||||
).stdout.strip()
|
||||
assert subject == "chore(registrar): assign State Hub identifiers"
|
||||
workplan = (repo / "workplans" / "DEMO-WP-0001.md").read_text(encoding="utf-8")
|
||||
assert "11111111-1111-4111-8111-111111111111" in workplan
|
||||
|
||||
|
||||
def test_bootstrap_source_invalid_names_the_record(tmp_path: Path, monkeypatch) -> None:
|
||||
repo = _fixture(tmp_path)
|
||||
monkeypatch.setattr(
|
||||
rr,
|
||||
"_check_primary",
|
||||
lambda _api, **_: ({"status": "ok", "db": "connected", "instance_role": "primary"}, None),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
rr,
|
||||
"_check_empty_repo_projection",
|
||||
lambda _api, _slug: ({"repo_id": "repo-1", "workplan_count": 0}, None),
|
||||
)
|
||||
result = rr.registrar_reconcile(
|
||||
repo,
|
||||
statehub_bin="statehub",
|
||||
confirm_primary=True,
|
||||
bootstrap_empty_projection=True,
|
||||
)
|
||||
assert result.status == "rejected"
|
||||
assert result.error and result.error["code"] == "bootstrap_source_invalid"
|
||||
assert "DEMO-WP-0001" in (result.error.get("message") or "")
|
||||
assert result.error.get("record") == "DEMO-WP-0001"
|
||||
|
||||
|
||||
def test_missing_scan_includes_intakes_and_decisions(tmp_path: Path) -> None:
|
||||
|
|
|
|||
|
|
@ -75,3 +75,5 @@ def test_cli_scaffold(tmp_path: Path):
|
|||
== 0
|
||||
)
|
||||
assert (dest / "INTENT.md").is_file()
|
||||
agents = (dest / "AGENTS.md").read_text(encoding="utf-8")
|
||||
assert "10.43.68.154:8000" in agents
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue