Files

40 lines
4.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Row287 semantic mutants. Each restoration is byte-checked; timeout/error is never a kill."""
from pathlib import Path
import hashlib, os, re, subprocess, sys
root=Path(__file__).resolve().parents[4]
os.chdir(root)
broker='packages/bus/src/broker.mjs'; grant='packages/bus/src/gitea-grant.mjs'; business='packages/business/src/business.mjs'
bus_test='packages/bus/tests/gitea-grant.test.mjs'; biz_test='packages/business/tests/business.test.mjs'
evidence=Path('agents/steelbeak/work/queue-287/evidence')
mutants=[
('M1',broker,"(CREATIVE_DEFINITIONS.includes(role.definition) || role.definition === STAFF_TO) && !libraryOnly", "role.definition === STAFF_TO && !libraryOnly",'C2-21 L7/L7b: research',bus_test),
('M2',broker,"level: CREATIVE_DEFINITIONS.includes(role.definition) ? 'write' : 'read'", "level: 'write'",'C2-21 L1/L2/L9: chief-of-staff',bus_test),
('M3',broker,"repo.permissions?.admin === false && repo.permissions?.push === (level === 'write') && repo.permissions?.pull === true", "repo.permissions?.pull === true",'C2-21 L4: read widened',bus_test),
('M4',broker," if (t.library) {", " if (false && t.library) {",'C2-21 L1/L2/L9: research',bus_test),
('M5',business,"projects[project].kind === 'library' && !CREATIVE_DEFINITIONS.includes(role.definition) && role.definition !== 'chief-of-staff'", "false",'C2-21: only creative',biz_test),
('M6',broker,"api.tokenCreate(user, tokenName, scopes)", "api.tokenCreate(user, tokenName)",'C2-21 L1/L2/L9: chief-of-staff',bus_test),
('M7',broker,"(CREATIVE_DEFINITIONS.includes(role.definition) || role.definition === STAFF_TO) && !libraryOnly", "CREATIVE_DEFINITIONS.includes(role.definition) && !libraryOnly",'C2-21 L7/L7b: chief-of-staff',bus_test),
('M8',broker," role.publish.length === 0 ||\n (projects.some((p) => b.projects?.[p]?.kind === 'library') &&\n !definition.credentials.some((c) => c.service === 'gitea' && Array.isArray(c.scopes) && c.scopes.length > 0))", " role.publish.length === 0",'C2-21: a library grant with no declared scopes',bus_test),
('M9',grant," if (r.json.length !== 0 || (r.total !== null && r.total !== 0)) deny('grant-admin-not-dedicated');", " // M9: omit dedicated-admin reach check.",'^X-G2:',bus_test),
('M10',broker," // Step 1, one transaction: spend and recheck. scope-changed ends spent (D6);", " await api.userCreate({ username: 'early-write', email: '[email protected]', full_name: 'Early write', password: 'fx287-early-q7', must_change_password: false });\n // Step 1, one transaction: spend and recheck. scope-changed ends spent (D6);",'C2-21 L10:',bus_test),
('M11',grant,"...(Object.keys(library).length ? { library } : {})", "...(false ? { library } : {})",'C2-21 L10:',bus_test),
]
results=[]
for key,file,old,new,pattern,test in mutants:
p=Path(file); original=p.read_bytes(); digest=hashlib.sha256(original).hexdigest(); source=original.decode()
if source.count(old)!=1: raise RuntimeError(f'{key}: expected one mutation site, got {source.count(old)}')
try:
p.write_text(source.replace(old,new,1))
out=subprocess.run(['timeout','-k','10','180','node','--test',f'--test-name-pattern={pattern}',test],stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True,env=os.environ)
(evidence/f'{key}.log').write_text(out.stdout)
passes=re.search(r'^ℹ pass (\d+)$',out.stdout,re.M); failures=re.search(r'^ℹ fail (\d+)$',out.stdout,re.M); skips=re.search(r'^ℹ skipped (\d+)$',out.stdout,re.M)
killed=out.returncode==1 and failures and int(failures[1])>0 and skips and int(skips[1])==0 and 'AssertionError' in out.stdout and not re.search(r'SyntaxError|ERR_MODULE_NOT_FOUND',out.stdout)
print(f'{key}: rc={out.returncode} pass={passes[1] if passes else "?"} fail={failures[1] if failures else "?"} skip={skips[1] if skips else "?"} {"KILLED" if killed else "INVALID/SURVIVED"}',flush=True)
results.append(f'{key} {"KILLED" if killed else "INVALID/SURVIVED"} rc={out.returncode}')
if not killed: sys.exit(1)
finally:
p.write_bytes(original)
if hashlib.sha256(p.read_bytes()).hexdigest()!=digest: raise RuntimeError(f'{key}: restoration mismatch')
(evidence/'mutants-summary.txt').write_text('\n'.join(results)+'\n')