40 lines
4.1 KiB
Python
40 lines
4.1 KiB
Python
#!/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')
|