fix(content-build): --check orphan detection; canon secrecy-0 + value-type guards
- --check now detects orphaned/stale build artifacts in build-owned dirs (client canon/ + topics/ fully owned; client npcs/ shared with legacy hand-authored files, only npc.* ids are treated as build-owned) - resolve() enforces the schema rule that canon entities must be secrecy 0 - resolve() and ladder_rungs() guard against malformed value TYPES (non-int secrecy, non-list related/rungs) raising BuildError instead of crashing Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HuHRPE7VfppUJEaoGBEUqZ
This commit is contained in:
@@ -52,3 +52,22 @@ def test_invalid_bible_fails_build(tmp_path):
|
||||
argv = ["--lore", str(lore), "--world", str(tmp_path / "w"),
|
||||
"--server", str(tmp_path / "s")]
|
||||
assert main(argv) == 1
|
||||
|
||||
|
||||
def test_check_detects_orphan_file(tmp_path):
|
||||
lore = _lore(tmp_path)
|
||||
world, server = tmp_path / "world", tmp_path / "server"
|
||||
argv = ["--lore", str(lore), "--world", str(world), "--server", str(server)]
|
||||
assert main(argv) == 0
|
||||
(world / "canon" / "ghost.json").write_text('{"id": "town.ghost"}\n')
|
||||
assert main(argv + ["--check"]) == 1
|
||||
|
||||
|
||||
def test_check_ignores_legacy_npc_files(tmp_path):
|
||||
lore = _lore(tmp_path)
|
||||
world, server = tmp_path / "world", tmp_path / "server"
|
||||
argv = ["--lore", str(lore), "--world", str(world), "--server", str(server)]
|
||||
assert main(argv) == 0
|
||||
(world / "npcs").mkdir(parents=True, exist_ok=True)
|
||||
(world / "npcs" / "legacy.json").write_text('{"id": "legacy"}\n') # bare id
|
||||
assert main(argv + ["--check"]) == 0 # legacy file is not a build orphan
|
||||
|
||||
@@ -131,3 +131,24 @@ def test_empty_rungs_rejected():
|
||||
secrecy=0, related=[], body="x", source="t:1", rungs=[])
|
||||
with pytest.raises(BuildError):
|
||||
resolve([empty])
|
||||
|
||||
|
||||
def test_canon_entity_nonzero_secrecy_rejected():
|
||||
bad = Entry(id="town.a", type="town", status="canon", secrecy=2,
|
||||
related=[], body="x", source="t:9")
|
||||
with pytest.raises(BuildError):
|
||||
resolve([ladder(), bad])
|
||||
|
||||
|
||||
def test_non_int_secrecy_raises():
|
||||
bad = Entry(id="town.a", type="town", status="canon", secrecy="high",
|
||||
related=[], body="x", source="t:9")
|
||||
with pytest.raises(BuildError):
|
||||
resolve([ladder(), bad])
|
||||
|
||||
|
||||
def test_non_list_rungs_raises():
|
||||
bad_ladder = Entry(id="rule.disposition-ladder", type="rule", status="canon",
|
||||
secrecy=0, related=[], body="x", source="t:1", rungs="trusted")
|
||||
with pytest.raises(BuildError):
|
||||
resolve([bad_ladder])
|
||||
|
||||
Reference in New Issue
Block a user