- 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.
151 lines
6.5 KiB
Python
151 lines
6.5 KiB
Python
"""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()
|