test: pin skill appendix sync and SKILL.md-only seeding (C-line, no behaviour change)
- test_skill_appendix_sync: all 9 appendix chapters 00..08 in order, body role-matrix set == appendix role-title set, authority statement on both sides, GitHub guarantee in body + named appendix chapters 00/04/07, seeded copy byte-identical with full appendix. Previously only 00/08 existence was asserted. - test_skill_seed_pinning: only SKILL.md copied (pack extras never land, never resurrected), tenant extras left alone, tenant-modified SKILL.md never overwritten across repeats, identical = noop, deleted SKILL.md re-seeded (documented boundary, not deletion preservation). - skill_seed.py: evaluation note only (why whole-dir copy is not done); no code change.
This commit is contained in:
1 parent
06710cf47b
commit
be41ad2c0a
3 files changed
+389
No files matched your search
@@ -0,0 +1,150 @@
|
||||
"""Pin skill_seed behaviour (C-line): SKILL.md-only, never overwrite.
|
||||
|
||||
These are helper-level tests (no DB, no Worker) for
|
||||
cloud.skill_seed.ensure_codex_home_skills, complementing the
|
||||
worker-level tests in test_skill_seed_worker.py (which already cover
|
||||
flag-off / flag-on / tenant-edit-survives through _tenant_dirs).
|
||||
|
||||
Two behaviours are pinned here, behaviour itself unchanged:
|
||||
|
||||
1. Only SKILL.md is ever copied. A skill directory may contain extra
|
||||
files (README, templates/, notes); none of them may land in the
|
||||
tenant home, and re-seeding must never resurrect them. Rationale
|
||||
(see the evaluation note in skill_seed.py): the pack today ships
|
||||
only SKILL.md per skill, so whole-directory copy has zero benefit
|
||||
today and would silently start copying future extras without
|
||||
review; per-file skip-if-differ cannot preserve a tenant deletion
|
||||
(missing destination is copied), so whole-dir copy would
|
||||
resurrect tenant-deleted auxiliary files on the next run.
|
||||
SKILL.md-only copy bounds that resurrection surface to the single
|
||||
SKILL.md file itself.
|
||||
2. A tenant-modified SKILL.md is never overwritten. It is reported
|
||||
in skills_skipped_differ and left byte-identical, across repeats,
|
||||
while other skills still seed normally.
|
||||
|
||||
Documented boundary, asserted explicitly rather than hidden: a
|
||||
tenant-DELETED SKILL.md is re-seeded on the next run (missing =
|
||||
copied). That is the existing, intended meaning of "missing is
|
||||
copied"; preserving a deletion would need a tombstone, which is new
|
||||
behaviour and out of scope for this convergence line. The test names
|
||||
it so a future reader does not mistake re-seeding for a bug -- or
|
||||
mistake the differ-guard for deletion preservation.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
from cloud.skill_seed import ensure_codex_home_skills, list_skills
|
||||
except ImportError:
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
|
||||
from cloud.skill_seed import ensure_codex_home_skills, list_skills
|
||||
|
||||
|
||||
def _pack(root):
|
||||
"""Pack with extras that must never reach the tenant home."""
|
||||
pack = Path(root) / "pack"
|
||||
skill_a = pack / "skill-a"
|
||||
(skill_a / "templates" / "roles").mkdir(parents=True, exist_ok=True)
|
||||
(skill_a / "SKILL.md").write_text("---\nname: skill-a\n---\nbody-a\n")
|
||||
(skill_a / "README.md").write_text("pack readme, must not seed\n")
|
||||
(skill_a / "notes.txt").write_text("pack notes, must not seed\n")
|
||||
(skill_a / "templates" / "roles" / "00-x.md").write_text("role\n")
|
||||
skill_b = pack / "skill-b"
|
||||
skill_b.mkdir(parents=True, exist_ok=True)
|
||||
(skill_b / "SKILL.md").write_text("---\nname: skill-b\n---\nbody-b\n")
|
||||
# Directory without SKILL.md: reported, never invented.
|
||||
(pack / "skill-broken").mkdir(parents=True, exist_ok=True)
|
||||
(pack / "skill-broken" / "other.txt").write_text("no SKILL.md here\n")
|
||||
return pack
|
||||
|
||||
|
||||
def _dest_tree(home):
|
||||
out = []
|
||||
root = Path(home) / "skills"
|
||||
if root.is_dir():
|
||||
for p in sorted(root.rglob("*")):
|
||||
if p.is_file():
|
||||
out.append(str(p.relative_to(root)))
|
||||
return out
|
||||
|
||||
|
||||
class SkillSeedPinningTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
|
||||
def tearDown(self):
|
||||
self.tmp.cleanup()
|
||||
|
||||
def test_only_skill_md_is_copied_never_extras(self):
|
||||
home = os.path.join(self.tmp.name, "home")
|
||||
out = ensure_codex_home_skills(home, _pack(self.tmp.name))
|
||||
self.assertEqual(sorted(out["skills_copied"]),
|
||||
["skill-a", "skill-b"])
|
||||
self.assertEqual(out["skills_skipped_missing"], ["skill-broken"])
|
||||
self.assertEqual(
|
||||
_dest_tree(home),
|
||||
["skill-a/SKILL.md", "skill-b/SKILL.md"])
|
||||
# Second run: still no extras, and no resurrection of anything
|
||||
# the tenant never had -- copied is empty because both SKILL.md
|
||||
# files are now identical.
|
||||
out2 = ensure_codex_home_skills(home, _pack(self.tmp.name))
|
||||
self.assertEqual(out2["skills_copied"], [])
|
||||
self.assertEqual(
|
||||
_dest_tree(home),
|
||||
["skill-a/SKILL.md", "skill-b/SKILL.md"])
|
||||
|
||||
def test_tenant_extra_files_in_dest_are_left_alone(self):
|
||||
home = os.path.join(self.tmp.name, "home")
|
||||
ensure_codex_home_skills(home, _pack(self.tmp.name))
|
||||
tenant_note = Path(home) / "skills" / "skill-a" / "tenant-notes.md"
|
||||
tenant_note.write_text("tenant's own file\n")
|
||||
ensure_codex_home_skills(home, _pack(self.tmp.name))
|
||||
self.assertEqual(tenant_note.read_text(), "tenant's own file\n")
|
||||
self.assertIn("skill-a/tenant-notes.md", _dest_tree(home))
|
||||
# Seeding never prunes the tenant directory either.
|
||||
self.assertIn("skill-a/SKILL.md", _dest_tree(home))
|
||||
|
||||
def test_tenant_modified_skill_never_overwritten_across_repeats(self):
|
||||
home = os.path.join(self.tmp.name, "home")
|
||||
pack = _pack(self.tmp.name)
|
||||
ensure_codex_home_skills(home, pack)
|
||||
dest = Path(home) / "skills" / "skill-a" / "SKILL.md"
|
||||
dest.write_text("tenant edited version\n")
|
||||
for _ in range(2):
|
||||
out = ensure_codex_home_skills(home, pack)
|
||||
self.assertEqual(out["skills_skipped_differ"], ["skill-a"])
|
||||
self.assertEqual(dest.read_text(), "tenant edited version\n")
|
||||
# The other skill is unaffected by one tenant edit.
|
||||
dest_b = Path(home) / "skills" / "skill-b" / "SKILL.md"
|
||||
self.assertEqual(dest_b.read_text(),
|
||||
"---\nname: skill-b\n---\nbody-b\n")
|
||||
|
||||
def test_identical_dest_is_noop_and_missing_is_recopied(self):
|
||||
home = os.path.join(self.tmp.name, "home")
|
||||
pack = _pack(self.tmp.name)
|
||||
ensure_codex_home_skills(home, pack)
|
||||
out = ensure_codex_home_skills(home, pack)
|
||||
self.assertEqual(out["skills_copied"], [])
|
||||
self.assertEqual(out["skills_skipped_differ"], [])
|
||||
# Documented boundary (see module docstring): a tenant-deleted
|
||||
# SKILL.md is re-seeded -- missing means copied. This is NOT
|
||||
# deletion preservation; only the differ-guard preserves edits.
|
||||
dest_b = Path(home) / "skills" / "skill-b" / "SKILL.md"
|
||||
dest_b.unlink()
|
||||
out2 = ensure_codex_home_skills(home, pack)
|
||||
self.assertEqual(out2["skills_copied"], ["skill-b"])
|
||||
self.assertTrue(dest_b.is_file())
|
||||
|
||||
def test_list_skills_ignores_broken_and_non_directories(self):
|
||||
pack = _pack(self.tmp.name)
|
||||
(pack / "stray.txt").write_text("not a skill\n")
|
||||
names = [n for n, _p in list_skills(pack)]
|
||||
self.assertEqual(names, ["skill-a", "skill-b"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user