Why: Preserve work merged by concurrent tasks while adopting versioned dependency workspaces. Evidence: SELF task receipts record conflict detection, all 96 task/recovery/cutover cases, all 33 evidence cases and 5 component identity cases. Documents links and requirements were replayed after preserving both planning entries. Impact: Keep upstream idempotent sync, local resume and source-external receipt rules; adapt fixed-guidelines readiness and ignored component identity validation. Remove only obsolete index links and retain their physical Git metadata. Refs: Authoritative main57bcb17ea212a531186b6e21617a7f3bea65a791; Documents4a26798000c39cc59ba0c2cc6328f5c8f10dcb32; dependency-workspace R15.
94 lines
4.8 KiB
Python
94 lines
4.8 KiB
Python
import importlib.util
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
|
|
SCRIPTS = Path(__file__).resolve().parents[1] / 'scripts'
|
|
|
|
class ComponentGovernanceTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temp = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.temp.cleanup)
|
|
self.root = Path(self.temp.name)
|
|
self.run_git(self.root, 'init', '-q')
|
|
self.run_git(self.root, 'config', 'user.name', 'Fixture')
|
|
self.run_git(self.root, 'config', 'user.email', 'fixture@example.test')
|
|
self.child = self.root / '.workspace/Provider'
|
|
self.child.mkdir(parents=True)
|
|
self.run_git(self.child, 'init', '-q')
|
|
self.run_git(self.child, 'config', 'user.name', 'Fixture')
|
|
self.run_git(self.child, 'config', 'user.email', 'fixture@example.test')
|
|
(self.child/'value').write_text('one')
|
|
self.run_git(self.child, 'add', 'value')
|
|
self.run_git(self.child, 'commit', '-qm', 'Add value')
|
|
(self.root/'.gitignore').write_text('.workspace/\n')
|
|
(self.root/'components.json').write_text(json.dumps({'schema':1,'components':[{
|
|
'id':'Provider','repo':self.child.as_uri(),'path':'.workspace/Provider',
|
|
'role':'documentation','modules':[],'target':'main'}]}))
|
|
self.run_git(self.root, 'add', '.gitignore', 'components.json')
|
|
self.run_git(self.root, 'commit', '-qm', 'Add component mapping')
|
|
self.old = Path.cwd()
|
|
os.chdir(self.root)
|
|
self.addCleanup(os.chdir,self.old)
|
|
|
|
def run_git(self,root,*args):
|
|
return subprocess.check_output(['git','-C',str(root),*args],stderr=subprocess.STDOUT,text=True)
|
|
|
|
def snapshot(self):
|
|
spec=importlib.util.spec_from_file_location('component_evidence',SCRIPTS/'evidence.py')
|
|
module=importlib.util.module_from_spec(spec); spec.loader.exec_module(module)
|
|
return module.snapshot()
|
|
|
|
def test_evidence_captures_ignored_component_revision_and_dirty_bytes(self):
|
|
first=self.snapshot()
|
|
(self.child/'value').write_text('two')
|
|
second=self.snapshot()
|
|
self.assertIn('components',first,'receipts must identify ordinary ignored component checkouts')
|
|
self.assertNotEqual(first['components'],second['components'])
|
|
self.run_git(self.child,'commit','-qam','Change value')
|
|
third=self.snapshot()
|
|
self.assertNotEqual(second['components'],third['components'])
|
|
self.assertEqual(first['head'],third['head'])
|
|
|
|
def test_before_sync_rejects_component_git_lock(self):
|
|
(self.child/'.git/index.lock').write_text('active owner')
|
|
result=subprocess.run(['python3',str(SCRIPTS/'check-worktree.py'),'before-sync'],cwd=self.root,capture_output=True,text=True)
|
|
self.assertNotEqual(result.returncode,0,'a hidden component writer must block synchronization')
|
|
self.assertTrue((self.child/'.git/index.lock').exists())
|
|
|
|
def test_startup_receipt_rejects_changed_ignored_component_objects(self):
|
|
spec=importlib.util.spec_from_file_location('component_evidence_validation',SCRIPTS/'evidence.py')
|
|
module=importlib.util.module_from_spec(spec); spec.loader.exec_module(module)
|
|
self.run_git(self.root,'update-ref','refs/remotes/origin/main','HEAD')
|
|
before=module.snapshot()
|
|
receipt=module.create_receipt('make precheck','[verdict] READY\n',0,before,before,
|
|
timestamp='2026-10-13T00:00:00+00:00',cwd=str(self.root),
|
|
git_common_dir=module.common_dir())
|
|
module.validate_precheck(receipt)
|
|
(self.child/'value').write_text('changed after startup')
|
|
with self.assertRaises(module.EvidenceError):
|
|
module.validate_precheck(receipt)
|
|
|
|
def test_new_layout_cannot_use_receipt_without_component_identity(self):
|
|
spec=importlib.util.spec_from_file_location('component_evidence_legacy',SCRIPTS/'evidence.py')
|
|
module=importlib.util.module_from_spec(spec); spec.loader.exec_module(module)
|
|
self.run_git(self.root,'update-ref','refs/remotes/origin/main','HEAD')
|
|
before=module.snapshot();before.pop('components')
|
|
receipt=module.create_receipt('make precheck','[verdict] READY\n',0,before,before,
|
|
timestamp='2026-10-13T00:00:00+00:00',cwd=str(self.root),
|
|
git_common_dir=module.common_dir())
|
|
with self.assertRaises(module.EvidenceError):
|
|
module.validate_precheck(receipt)
|
|
|
|
def test_receipts_reject_component_path_escape(self):
|
|
data=json.loads((self.root/'components.json').read_text())
|
|
data['components'][0]['path']='../outside'
|
|
(self.root/'components.json').write_text(json.dumps(data))
|
|
with self.assertRaises(Exception):
|
|
self.snapshot()
|
|
|
|
if __name__=='__main__': unittest.main()
|