feat(automation): enforce assessment scope and document activation blockers

This commit is contained in:
cesnimda
2026-10-30 09:40:12 +01:00
parent 140e182516
commit 750d76317c
22 changed files with 7065 additions and 12 deletions
+31
View File
@@ -0,0 +1,31 @@
"""Read-only verification of a trusted, source-bound assessment receipt; no services."""
import hashlib
import json
from pathlib import Path
def verify(repository, receipt_path, expected_sha256):
repository = Path(repository).resolve()
raw = Path(receipt_path).read_bytes()
if len(raw) > 8 * 1024 * 1024 or hashlib.sha256(raw).hexdigest() != expected_sha256:
raise ValueError('Assessment receipt differs from the protected manifest pin')
receipt = json.loads(raw)
if (receipt.get('schemaVersion') != 1 or receipt.get('scope') != 'P10-00-ASSESSMENT-ONLY'
or receipt.get('integrity') != 'PASS' or not receipt.get('files')
or not receipt.get('nativeReports') or not receipt.get('retainedDecisions')):
raise ValueError('Incomplete assessment qualification receipt')
for name, expected in receipt['files'].items():
path = (repository / name).resolve()
if not path.is_relative_to(repository) or not path.is_file():
raise ValueError('Frozen assessment source missing or outside repository: ' + name)
if hashlib.sha256(path.read_bytes()).hexdigest() != expected:
raise ValueError('Frozen assessment source changed; fresh affected qualification required: ' + name)
evidence_root = Path(receipt_path).resolve().parent
for name, expected in receipt.get('evidenceFiles', {}).items():
path = (evidence_root / name).resolve()
if not path.is_relative_to(evidence_root) or not path.is_file() or hashlib.sha256(path.read_bytes()).hexdigest() != expected:
raise ValueError('Accepted handoff evidence changed or missing: ' + name)
return dict(kind='REUSED_SOURCE_BOUND_EVIDENCE', receiptSha256=expected_sha256,
nativeReports=receipt['nativeReports'],
retainedDecisions=receipt['retainedDecisions'],
limits='P10-00 assessment only. No fresh product runs or implementation qualification.')
+89 -8
View File
@@ -19,6 +19,7 @@ import time
import uuid
import xml.etree.ElementTree as ET
import local_mode
import assessment_adapter
class GateError(RuntimeError):
@@ -165,6 +166,13 @@ def validate_manifest(m, root=None):
if check['id'] in check_ids or not check.get('argv'):
raise GateError('Duplicate check or empty test command')
check_ids.add(check['id'])
if check.get('adapter') == 'assessment-evidence-inspection':
if not m.get('executionScope') or not re.fullmatch(r'[0-9a-f]{64}',check.get('receiptSha256','')):
raise GateError('Assessment adapter requires a restricted policy and pinned receipt')
if check.get('configured') is not False:
raise GateError('Evidence inspection cannot qualify the command-running composite adapter')
if root and sha_file(under(root,check['receipt'])) != check['receiptSha256']:
raise GateError('Assessment qualification receipt changed')
if not all(isinstance(a, str) and '\x00' not in a for a in check['argv']):
raise GateError('Command arguments must be strings')
if check.get('report') and check['report']['format'] not in ('trx', 'jest', 'playwright', 'junit'):
@@ -180,6 +188,17 @@ def validate_manifest(m, root=None):
raise GateError('Supplemental prompt missing/changed')
if sequences != sorted(set(sequences)):
raise GateError('Queue sequence must be unique and ascending')
scope = m.get('executionScope')
if scope:
stage = next((s for s in m['stages'] if s['id'] == scope.get('stage')), None)
cp = next((x for x in stage['checkpoints'] if x['id'] == scope.get('checkpoint')), None) if stage else None
if (scope.get('mode') != 'assessment-only' or scope.get('stage') != 'PHASE-10'
or scope.get('checkpoint') != 'P10-00' or not cp
or scope.get('reportDirectory') != 'docs/automation/stages/PHASE-10'
or 'candidate-specific-cleanup-scope' not in cp['approvalsAfter']):
raise GateError('Assessment policy requires an exact checkpoint and protected stop')
if not re.fullmatch(r'[0-9a-f]{40}', scope.get('handoffCommit','')) or not SAFE_BRANCH.fullmatch(scope.get('handoffBranch','')):
raise GateError('Invalid published handoff identity')
class StateStore:
@@ -553,6 +572,8 @@ class ProcessRunner:
return data
def run_check(self, engine, check, directory):
if check.get('adapter') == 'assessment-evidence-inspection':
raise GateError('Evidence inspection cannot replace command-running composite qualification')
if local_mode.single(engine.config):
local_mode.require_unattended(engine.root,engine.config)
working = engine.repo if check['cwd'] == '.' else under(engine.repo, check['cwd'])
@@ -581,6 +602,9 @@ class Engine:
self.root = Path(root).resolve()
self.manifest = manifest
self.config = config
if manifest.get('executionScope'):
self.config = dict(config)
self.config['assessmentReportDirectory'] = manifest['executionScope']['reportDirectory']
self.repo = Path(config['repository']).resolve()
self.store = StateStore(self.root)
self.runner = runner or ProcessRunner()
@@ -621,6 +645,8 @@ class Engine:
if kind in ('environment-qualified', 'roadmap-execution'):
request.update(stage='QUEUE', checkpoint='POLICY', baselineCommit=self.manifest['baselineCommit'],
candidateCommit=None, evidence=[], detail='Entire exact manifest; development only; no production authority')
if self.manifest.get('executionScope'):
request['executionScope'] = self.manifest['executionScope']
path = self.root / 'requests' / (digest(request) + '.json')
atomic(path, request)
approval = self.approval_verifier(request)
@@ -656,7 +682,7 @@ class Engine:
return True
def policy_hash(self):
files = ['controller.py', 'local_mode.py', 'Probe-JobjaktSandbox.ps1',
files = ['controller.py', 'local_mode.py', 'assessment_adapter.py', 'Probe-JobjaktSandbox.ps1',
'Approve-JobjaktBacklog.ps1', 'config.json', 'schemas/result.schema.json']
return digest({name: sha_file(self.root / name) for name in files if (self.root / name).exists()})
@@ -683,6 +709,8 @@ class Engine:
_,sid = local_mode.detect_identity()
local_mode.validate_identity(self.config,sid)
local_mode.require_unattended(self.root,self.config)
if self.manifest.get('executionScope') and not local_mode.single(self.config):
raise GateError('Restricted assessment filesystem profile currently qualifies single-user-local only')
certificate = under(self.root, self.config['publicKeyCertificate'])
if not certificate.is_file() or sha_file(certificate) != self.config['ownerCertificateSha256']:
raise GateError('Owner public certificate is missing or differs from protected configuration pin')
@@ -800,9 +828,17 @@ class Engine:
if ss['entryBaseline'] is None:
if self.git('status', '--porcelain', '--untracked-files=all'):
raise GateError('New stage requires a clean worktree, including untracked files')
self.verify_remote(self.manifest['baselineBranch'] if not any(
x['status'] == 'COMPLETED' for x in state['stages'].values())
else self.manifest['integrationBranch'], state['integrationCommit'])
scope = self.manifest.get('executionScope')
if scope and not any(x['status'] == 'COMPLETED' for x in state['stages'].values()):
self.verify_remote(scope['handoffBranch'], scope['handoffCommit'])
self.git('merge-base', '--is-ancestor', state['integrationCommit'], scope['handoffCommit'])
changed = self.git('diff','--name-only',state['integrationCommit'],scope['handoffCommit']).splitlines()
if any(not name.startswith('docs/automation/handoffs/pre-phase10/') for name in changed):
raise GateError('Published handoff changed outside the evidence envelope')
else:
self.verify_remote(self.manifest['baselineBranch'] if not any(
x['status'] == 'COMPLETED' for x in state['stages'].values())
else self.manifest['integrationBranch'], state['integrationCommit'])
baseline = state['integrationCommit']
branches = self.git('for-each-ref', '--format=%(refname:short)', 'refs/heads').splitlines()
if stage['branch'] in branches:
@@ -923,7 +959,8 @@ checkpoint; complete means ready for independent checks. Do not infer acceptance
return False
ss.pop('requestedDecision')
prompt = self.prompt(stage, cp, ss, state)
if not cp['checks'] or any(not check.get('configured', True) for check in cp['checks']):
if not cp['checks'] or any(check.get('adapter') == 'assessment-evidence-inspection'
or not check.get('configured', True) for check in cp['checks']):
raise GateError('Independent test adapters must be configured and owner-qualified before execution')
ss['attempts'] += 1
ss['sessions'] += 1
@@ -937,6 +974,7 @@ checkpoint; complete means ready for independent checks. Do not infer acceptance
sourceCommit=ss['checkpointBase'], manifestSha256=digest(self.manifest), at=utc()))
self.store.write(state) # durable RUNNING before launching child
result = self.runner.run_session(self, stage, cp, directory, prompt)
self.verify_assessment_writes()
ss['lastResult'] = result
state['recentSessions'].append(dict(id=session_id, stage=stage['id'], checkpoint=cp['id'],
outcome=result['outcome'], summary=redact(result['summary'])[:600], directory=str(directory)))
@@ -963,6 +1001,7 @@ checkpoint; complete means ready for independent checks. Do not infer acceptance
ss['verifiedChecks'] = []
for check in cp['checks']:
ss['verifiedChecks'].append(self.runner.run_check(self, check, directory / 'checks' / check['id']))
self.verify_assessment_writes()
ss['evidence'] = []
for name in cp['requiredArtifacts']:
artifact = under(self.repo, name)
@@ -998,6 +1037,17 @@ checkpoint; complete means ready for independent checks. Do not infer acceptance
self.store.write(state)
return self.finish_checkpoint(stage, cp, ss, state)
def verify_assessment_writes(self):
scope = self.manifest.get('executionScope')
if not scope:
return
prefix = scope['reportDirectory'] + '/'
changed = self.git('diff', '--name-only', 'HEAD', '-z').split('\0')
changed += self.git('ls-files', '--others', '--exclude-standard', '-z').split('\0')
deleted = self.git('diff', '--name-only', '--diff-filter=D', 'HEAD')
if deleted or any(name and (not name.startswith(prefix) or not name.endswith('.md')) for name in changed):
raise GateError('Read-only assessment modified a forbidden path or deleted a file')
def content_hash(self):
# Git blob hashes include tracked modifications and untracked source without reading secrets into logs.
files = self.git('ls-files', '--cached', '--others', '--exclude-standard', '-z').split('\0')
@@ -1086,6 +1136,9 @@ checkpoint; complete means ready for independent checks. Do not infer acceptance
self.store.write(state)
return
try:
scope = self.manifest.get('executionScope')
if scope and (stage['id'], cp['id']) != (scope['stage'], scope['checkpoint']):
raise GateError('Restricted assessment scope ends after P10-00; new owner-scoped policy required')
# Check source before requesting approval or modifying Git.
self.prompt(stage, cp, ss, state)
if ss['entryBaseline'] is None:
@@ -1179,7 +1232,7 @@ def install(source, root, repository, mode='dedicated-runner'):
if root.is_relative_to(repo) or repo.is_relative_to(root):
raise GateError('Install control plane outside worker checkout')
root.mkdir(parents=True)
for name in ('controller.py', 'local_mode.py', 'backlog.json', 'config.example.json'):
for name in ('controller.py', 'local_mode.py', 'assessment_adapter.py', 'backlog.json', 'config.example.json'):
shutil.copyfile(source / name, root / name)
for name in ('schemas', 'prompts'):
shutil.copytree(source / name, root / name)
@@ -1200,7 +1253,7 @@ def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('command', choices=['init', 'validate', 'dry-run', 'start', 'resume', 'status',
'pause', 'retry', 'current', 'logs', 'recover', 'configure',
'setup','auth','auth-status','qualify','approve','prepare','key','enable-unattended'])
'setup','auth','auth-status','qualify','approve','prepare','prepare-entry','key','enable-unattended'])
parser.add_argument('--control-root', type=Path, required=True)
parser.add_argument('--repository')
parser.add_argument('--stage')
@@ -1218,7 +1271,7 @@ def main(argv=None):
and not read_json(root/'backlog.json')['executionEnabled']
and not any(s['sessions'] for s in state['stages'].values())):
with RepoLock(Path(existing['repository'])):
for name in ('controller.py','local_mode.py'):
for name in ('controller.py','local_mode.py','assessment_adapter.py'):
shutil.copyfile(source/name,root/name)
for name in ('Run-JobjaktBacklog.ps1','Approve-JobjaktBacklog.ps1','Probe-JobjaktSandbox.ps1',
'Use-JobjaktBacklog.ps1','Register-JobjaktBacklogTask.ps1'):
@@ -1326,6 +1379,34 @@ def main(argv=None):
'-ApprovalPath',str(root/'approvals'/request.name),'-Thumbprint',thumb,'-ExpectedOwnerSid',sid,
'-Disposition',disposition,'-Decision',decision])
return result.returncode
elif args.command == 'prepare-entry':
with RepoLock(engine.repo):
validate_manifest(engine.manifest,root)
state=engine.store.read()
scope=engine.manifest.get('executionScope')
if not scope or not engine.manifest['executionEnabled']:
raise GateError('Adopt the signed restricted assessment policy first')
if (root/'active-process.json').exists() or state['recoveryRequired'] or any(s['sessions'] for s in state['stages'].values()):
raise GateError('Entry preparation requires an unused reconciled queue')
local_mode.require_unattended(root,engine.config)
if engine.git('status','--porcelain','--untracked-files=all') or engine.git('rev-parse','HEAD') != engine.manifest['baselineCommit']:
raise GateError('Assessment entry requires a clean exact baseline checkout')
engine.verify_remote(scope['handoffBranch'],scope['handoffCommit'])
engine.git('merge-base','--is-ancestor',engine.manifest['baselineCommit'],scope['handoffCommit'])
stage=next(s for s in engine.manifest['stages'] if s['id']==scope['stage'])
cp=next(x for x in stage['checkpoints'] if x['id']==scope['checkpoint'])
engine.prompt(stage,cp,state['stages'][stage['id']],state)
if not any(check['id']=='qualified-provider-browser-python-pdf-security-preservation'
and check.get('configured') is True for check in cp['checks']):
raise GateError('P10-00 composite adapter is not qualified')
for check in cp['checks']:
if check.get('adapter') == 'assessment-evidence-inspection' or not check.get('configured'):
raise GateError('P10-00 composite adapter is not qualified')
for kind in ['environment-qualified','roadmap-execution',*stage['requiredOwnerApprovals'],*cp['approvalsBefore']]:
if not engine.request(stage,cp,kind,state):
print('Protected entry request prepared; approve it and run prepare-entry again. No worker launched.')
return 3
print('All exact restricted entry approvals verified. Owner may deliberately run start. No worker launched.')
elif args.command == 'prepare':
with RepoLock(engine.repo):
state = engine.store.read()
+11 -2
View File
@@ -61,8 +61,17 @@ def validate_config(root, config):
def filesystem_rules(root, config):
repo = Path(config['repository']).resolve()
rules = {':root':'deny', ':minimal':'read', repo.as_posix():'write',
assessment = config.get('assessmentReportDirectory')
rules = {':root':'deny', ':minimal':'read', repo.as_posix():'read' if assessment else 'write',
Path(config['workerTemp']).resolve().as_posix():'write'}
if assessment:
if assessment != 'docs/automation/stages/PHASE-10':
raise ValueError('Invalid assessment report directory')
for name in (assessment, '.artifacts/orchestrator-probes'):
path = (repo/name).resolve()
if not path.is_relative_to(repo):
raise ValueError('Assessment output escapes repository')
rules[path.as_posix()] = 'write'
for name in config.get('toolReadRoots',[]):
path = Path(name).resolve()
if path == Path(path.anchor) or path == Path.home():
@@ -94,7 +103,7 @@ def permission_args(root, config, command):
def binding(root,config):
value = {k:v for k,v in config.items() if k != 'executionPolicy'}
for name in ('local_mode.py','controller.py','Probe-JobjaktSandbox.ps1'):
for name in ('local_mode.py','controller.py','assessment_adapter.py','Probe-JobjaktSandbox.ps1'):
source = Path(root)/name
if source.exists():
value[name+'Sha256'] = hashlib.sha256(source.read_bytes()).hexdigest()
+61
View File
@@ -0,0 +1,61 @@
// Trusted operator preparation. Validates published native evidence; runs no product suite.
import fs from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';
import zlib from 'node:zlib';
import { pathToFileURL } from 'node:url';
import { execFileSync } from 'node:child_process';
const [repository, destination] = process.argv.slice(2);
if (!repository || !destination) throw new Error('Usage: node prepare_assessment.mjs <published-handoff-checkout> <new-output-directory>');
const root = fs.realpathSync(repository), output = path.resolve(destination);
if (fs.existsSync(output)) throw new Error('Use a new receipt directory; preserve previous qualification');
const git = (...args) => execFileSync('rtk', ['proxy','git','-C',root,...args], {encoding:'utf8'}).trim();
const handoff = '5cb903b989742b7a96cb22ddf4c0e24993b18980';
const baseline = 'fc5b9d377199ce51f480a920a3772108094929e3';
if (git('rev-parse','HEAD') !== handoff || git('status','--porcelain')) throw new Error('Require the exact clean published handoff checkout');
const remote = git('ls-remote','--exit-code','origin','refs/heads/test/pre-phase10-ux-acceptance');
if (remote.split(/\s/)[0] !== handoff) throw new Error('Handoff publication differs');
if (git('rev-parse',handoff+'^') !== baseline) throw new Error('Baseline relationship differs');
const relative = 'docs/automation/handoffs/pre-phase10';
const directory = path.join(root,relative);
const {validatePackage, measureNative} = await import(pathToFileURL(path.join(directory,'verify.mjs')).href);
const verified = validatePackage();
if (verified.integrity !== 'PASS' || !verified.observedWorkingTreeClean) throw new Error('Handoff integrity failed');
const evidence = JSON.parse(fs.readFileSync(path.join(directory,'TEST_EVIDENCE.json')));
const artifact = id => {
const row=evidence.artifacts.find(item=>item.id===id);
if (!row) throw new Error('Missing native artifact');
const raw=fs.readFileSync(path.join(directory,row.path));
return row.path.endsWith('.gz') ? zlib.gunzipSync(raw) : raw;
};
const hash = raw => crypto.createHash('sha256').update(raw).digest('hex');
const files = {};
// Freeze all tracked non-envelope sources, including tests/config/security/preservation documentation.
for (const name of git('ls-files','-z').split('\0').filter(Boolean)) {
if (!name.startsWith(relative+'/')) files[name]=hash(fs.readFileSync(path.join(root,name)));
}
const nativeReports = evidence.reports.map(report=>{
let observation=JSON.parse(artifact(report.processArtifactId));
const parts=report.processPointer.slice(1).split('/').map(value=>value.replaceAll('~1','/').replaceAll('~0','~'));
for (const key of parts.slice(0,-1)) observation=observation[key];
return {...report, measured:report.nativeFormat ? measureNative(report.nativeFormat,artifact(report.artifactId).toString('utf8')) : null,
nativeProcess:observation};
});
fs.mkdirSync(output,{recursive:true});
const evidenceFiles={};
for (const name of git('ls-files',relative).split(/\r?\n/).filter(Boolean)) {
const local=path.relative(directory,path.join(root,name));
const target=path.join(output,'handoff',local);
fs.mkdirSync(path.dirname(target),{recursive:true});
fs.copyFileSync(path.join(root,name),target);
evidenceFiles['handoff/'+local.split(path.sep).join('/')]=hash(fs.readFileSync(target));
}
const receipt={schemaVersion:1,scope:'P10-00-ASSESSMENT-ONLY',integrity:'PASS',
generatedUtc:new Date().toISOString(),baselineCommit:baseline,handoffCommit:handoff,
testedCommit:evidence.testedCommit,files,evidenceFiles,nativeReports,
retainedDecisions:JSON.parse(fs.readFileSync(path.join(directory,'ACCEPTANCE.json'))).verdicts,
revisedS01:verified.revisedS01,verifierObservation:verified,
limits:'Source-bound reuse for unchanged read-only assessment. Original failures and conditional/blocked decisions retained. No fresh product, security, model, human or production acceptance.'};
fs.writeFileSync(path.join(output,'receipt.json'),JSON.stringify(receipt,null,2)+'\n');
console.log(JSON.stringify({integrity:'PASS',receipt:path.join(output,'receipt.json'),sha256:hash(fs.readFileSync(path.join(output,'receipt.json'))),frozenSources:Object.keys(files).length,nativeReports:nativeReports.length,productSuitesExecuted:0},null,2));
File diff suppressed because it is too large Load Diff
+80
View File
@@ -0,0 +1,80 @@
"""Synthetic native assessment qualification only. Never starts the development queue."""
import json
from pathlib import Path
import shutil
import sys
import uuid
import controller as c
import local_mode
def main():
root, worker, report_path = map(Path,sys.argv[1:])
config=c.read_json(root/'config.json')
policy=c.read_json(root/'proposed-p10-assessment.json')
config['repository']=str(worker.resolve())
engine=c.Engine(root,policy,config)
if c.read_json(root/'backlog.json')['executionEnabled'] or any(s['sessions'] for s in engine.store.read()['stages'].values()):
raise c.GateError('Qualification requires an unused disabled queue')
with c.RepoLock(worker):
prior=(root/'qualification.json').read_bytes() if (root/'qualification.json').exists() else None
try:
native=local_mode.qualify(engine)
finally:
if prior is not None:
(root/'qualification.json').write_bytes(prior)
else:
(root/'qualification.json').unlink(missing_ok=True)
if native['status'] != 'PASS':
c.atomic(report_path,dict(native=native,actualQueueStarted=False,assessment='NOT_RUN'))
raise c.GateError('Native signing isolation qualification failed; no further child launched')
token=uuid.uuid4().hex
probe=worker/'.artifacts/orchestrator-probes'/token
probe.mkdir(parents=True)
canary=worker/('.assessment-readonly-'+token)
canary.write_text('SYNTHETIC READ-ONLY CANARY')
output=worker/'docs/automation/stages/PHASE-10'/('native-probe-'+token+'.md')
output.parent.mkdir(parents=True,exist_ok=True)
logs=root/'probe'/('assessment-'+token)
receipt=root/'assessment-activation-01'
try:
shutil.copytree(receipt,probe/'receipt')
shutil.copyfile(Path(__file__).with_name('assessment_adapter.py'),probe/'assessment_adapter.py')
plan=dict(worker=str(worker.resolve()),receipt=str(probe/'receipt/receipt.json'),
pin=c.sha_file(receipt/'receipt.json'),canary=str(canary),output=str(output))
c.atomic(probe/'plan.json',plan)
(probe/'run.py').write_text('''import json, pathlib
import assessment_adapter
p=json.loads(pathlib.Path(__file__).with_name('plan.json').read_text())
r=assessment_adapter.verify(p['worker'],p['receipt'],p['pin'])
canary=pathlib.Path(p['canary'])
assert canary.read_text()=='SYNTHETIC READ-ONLY CANARY'
try:
with canary.open('r+b'): pass
except PermissionError: write_denied=True
else: write_denied=False
try: canary.unlink()
except PermissionError: delete_denied=True
else: delete_denied=False
pathlib.Path(p['output']).write_text('Synthetic native report creation')
assert write_denied and delete_denied
print(json.dumps(dict(evidenceIntegrity='PASS',sourceWriteDenied=write_denied,sourceDeleteDenied=delete_denied,reportWrite=True,kind=r['kind'],nativeReports=len(r['nativeReports']))))
''',encoding='utf-8')
argv=[config['codexExecutable'],'sandbox','-C',str(worker),
*local_mode.permission_args(root,engine.config,'sandbox'),
config['pythonExecutable'],'-B',str(probe/'run.py')]
engine.runner.supervise(engine,argv,logs,180)
observation=json.loads((logs/'stdout.log').read_text())
c.atomic(report_path,dict(native=native,assessment=observation,
candidateCommit=engine.git('rev-parse','HEAD'),actualQueueStarted=False,
compositeRuntime='BLOCKED_DOCKER_AND_SERVICE_BOUNDARY_UNQUALIFIED',logs=str(logs)))
finally:
canary.unlink(missing_ok=True)
output.unlink(missing_ok=True)
if probe.resolve().is_relative_to(worker.resolve()/'.artifacts/orchestrator-probes'):
shutil.rmtree(probe)
print('Synthetic native assessment checks complete; composite command runtime remains BLOCKED')
if __name__=='__main__':
main()
@@ -0,0 +1 @@
{"actualQueueStarted":false,"assessment":{"evidenceIntegrity":"PASS","kind":"REUSED_SOURCE_BOUND_EVIDENCE","nativeReports":34,"reportWrite":true,"sourceDeleteDenied":true,"sourceWriteDenied":true},"bindingMatchesProjectedProfile":true,"candidateCommit":"fc5b9d377199ce51f480a920a3772108094929e3","compositeRuntime":"BLOCKED_DOCKER_AND_SERVICE_BOUNDARY_UNQUALIFIED","logs":"C:\\Users\\Connor\\.jobjakt-backlog\\single-user-local\\probe\\assessment-b2c85d9a53474a7db172c73969586734","native":{"binding":"3b1ec3040285e5991dac5fc5e60cb362f7ea54691b9f790a6a496aadc12ec5db","failure":null,"issuedUtc":"2026-10-30T08:35:40.581635+00:00","ownerKeyDenied":true,"probes":{"files":[{"category":"approvals","readDenied":true,"writeDenied":true},{"category":"configuration","readDenied":true,"writeDenied":true},{"category":"key-file","readDenied":true,"writeDenied":true},{"category":"normal-codex","readDenied":true,"writeDenied":true},{"category":"other-repository","readDenied":true,"writeDenied":true},{"category":"personal","readDenied":true,"writeDenied":true},{"category":"production-canary","readDenied":true,"writeDenied":true}],"networkDenied":true,"ownerKeyDenied":true,"positiveControl":true,"signingKeyDenied":true},"scope":"Synthetic filesystem/network/signing canaries; real owner key probed","status":"PASS"}}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"activeProcessMarker":false,"actualSessions":0,"baselineCommit":"fc5b9d377199ce51f480a920a3772108094929e3","executionEnabled":false,"executionPolicy":"operator-controlled","executionWorktree":"C:\\Users\\Connor\\.codex\\worktrees\\p10-assessment\\Job tracker","handoffCommit":"5cb903b989742b7a96cb22ddf4c0e24993b18980","installedRepository":"C:\\Users\\Connor\\.codex\\worktrees\\autonomous-roadmap-01\\Job tracker","installedSourceMatches":true,"materialisedLineEndingOnlyFiles":47,"nativeQualification":{"binding":"b1bdcfd929af4829d316481c376e8c92d1b3182b61199080d2f915279f51ec7f","failure":null,"issuedUtc":"2026-10-30T08:35:24.011490+00:00","ownerKeyDenied":true,"probes":{"files":[{"category":"approvals","readDenied":true,"writeDenied":true},{"category":"configuration","readDenied":true,"writeDenied":true},{"category":"key-file","readDenied":true,"writeDenied":true},{"category":"normal-codex","readDenied":true,"writeDenied":true},{"category":"other-repository","readDenied":true,"writeDenied":true},{"category":"personal","readDenied":true,"writeDenied":true},{"category":"production-canary","readDenied":true,"writeDenied":true}],"networkDenied":true,"ownerKeyDenied":true,"positiveControl":true,"signingKeyDenied":true},"scope":"Synthetic filesystem/network/signing canaries; real owner key probed","status":"PASS"},"ownerKeyConfigured":true,"pending":[{"kind":"manifest-policy-update","request":"C:\\Users\\Connor\\.jobjakt-backlog\\single-user-local\\requests\\d637caf72c3fed5910f7c28e75dc82324d8febc12597ddb5627ae022af49a622.json"}],"publicCertificatePinMatches":true,"qualificationBindingMatches":true,"recoveryRequired":false}
@@ -0,0 +1 @@
{"exitCode":2,"modelSessionsAfter":0,"modelSessionsBefore":0,"stderr":"Adopt the signed restricted assessment policy first\n","stdout":""}
@@ -0,0 +1 @@
{"clean":true,"executionBranch":"codex/p10-assessment-ready","executionCommit":"fc5b9d377199ce51f480a920a3772108094929e3","handoffChanges":["docs/automation/handoffs/pre-phase10/ACCEPTANCE.json","docs/automation/handoffs/pre-phase10/ENTRY_PROPOSAL.json","docs/automation/handoffs/pre-phase10/TEST_EVIDENCE.json","docs/automation/handoffs/pre-phase10/evidence/p10-entry-proposal.gz","docs/automation/handoffs/pre-phase10/evidence/p10-final-gate-processes.gz","docs/automation/handoffs/pre-phase10/evidence/p10-proposal-check-source.gz","docs/automation/handoffs/pre-phase10/evidence/p10-protected-entry-native.gz"],"handoffParent":"fc5b9d377199ce51f480a920a3772108094929e3","policyFileMatchesInstalledProposal":true,"proposedManifestSha256":"0ff54dadbae7dde08e68c247e1365fd54f063e5b09066cb9e9cd577afe11a061","remoteHeads":["140e182516e86c8718054cf0907593fcd6a38aa7\trefs/heads/codex/autonomous-roadmap-single-user","5cb903b989742b7a96cb22ddf4c0e24993b18980\trefs/heads/test/pre-phase10-ux-acceptance"]}
@@ -0,0 +1,21 @@
{
"tests": 81,
"failures": 0,
"errors": 0,
"skipped": 0,
"sourceStable": true,
"sourceHashes": {
"automation/assessment_adapter.py": "6f6b4e7b96f1e9ca896e1d2a9ab26c12bf38d603dbf087acf61969f5d3906a21",
"automation/controller.py": "f2c05a3bccb43cc81f76e35e7adefd2e590934ae99f2a1fdc9206844e8732d88",
"automation/local_mode.py": "ccf4a5e8c208d17d87b47dd1aca8ebf540af27a274ae7333da9fb8075cb9b5b2",
"automation/prepare_queue.py": "6cad22305bc354ccbae2f031cb2b324596b3a7d42503b954c27f1e63e47a5f7d",
"automation/qualify_assessment.py": "0891520943779da0ff722d8deba62151d5c1dfad1329026310baab216459374d",
"automation/prepare_assessment.mjs": "a13f3d0458d7e4006cdfc07c3de2d563a6795a8cd4421be2b7de36fb70d68c10",
"automation/tests/test_assessment.py": "d3d30aaa808ead6927a444c1f91aa583835d3a787893b8f67db6b68261aa243c",
"automation/tests/test_controller.py": "458da3c60571e61aad42987ce156842efd73ef0e2aa0c7bce719d36d626d7e7d",
"automation/tests/test_local_mode.py": "524d40ede6379138f729c06876e43b6ef1cb15ee427947f2358a0439cb02ff69",
"scripts/Run-JobjaktBacklog.ps1": "2b11f5016560890262f0ca96bdba5a13240b0250c6bf6a96b43c33929cbcd070"
},
"elapsedSeconds": 161.283,
"scope": "Synthetic controller and adapter tests; no actual backlog or product suite"
}
@@ -0,0 +1,5 @@
{
"constraints": "PASS",
"metaSchemaValidation": "NOT_PERFORMED_DRAFT_2020_META_SCHEMA_UNAVAILABLE",
"errors": null
}
+86
View File
@@ -0,0 +1,86 @@
test_assessment_profile_keeps_product_read_only (test_assessment.ReadOnlyProfileTests.test_assessment_profile_keeps_product_read_only) ... ok
test_receipt_rejects_changed_product_or_changed_receipt (test_assessment.ReceiptTests.test_receipt_rejects_changed_product_or_changed_receipt) ... ok
test_assessment_reaches_protected_stop_and_cannot_advance (test_assessment.ScopeTests.test_assessment_reaches_protected_stop_and_cannot_advance) ... ok
test_assessment_reports_are_permitted (test_assessment.ScopeTests.test_assessment_reports_are_permitted) ... ok
test_entry_cannot_prepare_approvals_without_composite_adapter (test_assessment.ScopeTests.test_entry_cannot_prepare_approvals_without_composite_adapter) ... ok
test_evidence_inspection_cannot_be_marked_runtime_qualified (test_assessment.ScopeTests.test_evidence_inspection_cannot_be_marked_runtime_qualified) ... ok
test_generic_approval_cannot_launch_next_checkpoint (test_assessment.ScopeTests.test_generic_approval_cannot_launch_next_checkpoint) ... ok
test_inspection_requires_explicit_false_and_never_launches_worker (test_assessment.ScopeTests.test_inspection_requires_explicit_false_and_never_launches_worker) ... ok
test_native_isolation_failure_stops_before_additional_probe (test_assessment.ScopeTests.test_native_isolation_failure_stops_before_additional_probe) ... ok
test_policy_cannot_relabel_p10_01_as_assessment (test_assessment.ScopeTests.test_policy_cannot_relabel_p10_01_as_assessment) ... ok
test_scope_rejects_product_write_and_deletion (test_assessment.ScopeTests.test_scope_rejects_product_write_and_deletion) ... ok
test_scope_requires_protected_stop (test_assessment.ScopeTests.test_scope_requires_protected_stop) ... ok
test_accidental_main_checkout_rejected (test_controller.EngineTests.test_accidental_main_checkout_rejected) ... ok
test_after_approval_binds_tested_commit_without_rerunning_worker (test_controller.EngineTests.test_after_approval_binds_tested_commit_without_rerunning_worker) ... ok
test_changed_prompt_blocks (test_controller.EngineTests.test_changed_prompt_blocks) ... ok
test_conditional_can_advance_only_with_explicit_signed_deferral (test_controller.EngineTests.test_conditional_can_advance_only_with_explicit_signed_deferral) ... ok
test_conditional_requires_explicit_deferrable_gates (test_controller.EngineTests.test_conditional_requires_explicit_deferrable_gates) ... ok
test_corrupt_state_recovers_last_good_without_success (test_controller.EngineTests.test_corrupt_state_recovers_last_good_without_success) ... ok
test_cryptographically_approved_resume_e2e (test_controller.EngineTests.test_cryptographically_approved_resume_e2e) ... ok
test_dependency_cannot_be_bypassed (test_controller.EngineTests.test_dependency_cannot_be_bypassed) ... ok
test_design_revision_reruns_current_checkpoint_and_keeps_owner_selection (test_controller.EngineTests.test_design_revision_reruns_current_checkpoint_and_keeps_owner_selection) ... ok
test_disabled_manifest_never_executes (test_controller.EngineTests.test_disabled_manifest_never_executes) ... ok
test_double_corruption_fails_closed (test_controller.EngineTests.test_double_corruption_fails_closed) ... ok
test_duplicate_lock_rejected (test_controller.EngineTests.test_duplicate_lock_rejected) ... ok
test_execution_window_stops_before_worker (test_controller.EngineTests.test_execution_window_stops_before_worker) ... ok
test_failed_stage_stops_and_exhausts_retry_budget (test_controller.EngineTests.test_failed_stage_stops_and_exhausts_retry_budget) ... ok
test_failing_checks_never_commit_or_advance (test_controller.EngineTests.test_failing_checks_never_commit_or_advance) ... ok
test_interruption_is_not_success_and_resume_has_budget (test_controller.EngineTests.test_interruption_is_not_success_and_resume_has_budget) ... ok
test_manifest_initially_disabled (test_controller.EngineTests.test_manifest_initially_disabled) ... ok
test_missing_approval_does_not_run_model (test_controller.EngineTests.test_missing_approval_does_not_run_model) ... ok
test_missing_prompt_blocks (test_controller.EngineTests.test_missing_prompt_blocks) ... ok
test_model_cannot_approve_itself (test_controller.EngineTests.test_model_cannot_approve_itself) ... ok
test_pause_persists_and_resume_clears_it (test_controller.EngineTests.test_pause_persists_and_resume_clears_it) ... ok
test_push_failure_preserves_tested_commit (test_controller.EngineTests.test_push_failure_preserves_tested_commit) ... ok
test_push_verification_mismatch_stops (test_controller.EngineTests.test_push_verification_mismatch_stops) ... ok
test_recovered_running_without_marker_requires_reconciliation (test_controller.EngineTests.test_recovered_running_without_marker_requires_reconciliation) ... ok
test_restart_running_marker_cannot_mean_completed (test_controller.EngineTests.test_restart_running_marker_cannot_mean_completed) ... ok
test_same_repo_other_control_root_cannot_take_lock (test_controller.EngineTests.test_same_repo_other_control_root_cannot_take_lock) ... ok
test_success_orders_stages_and_verifies_remote (test_controller.EngineTests.test_success_orders_stages_and_verifies_remote) ... ok
test_tampered_evidence_cannot_be_approved (test_controller.EngineTests.test_tampered_evidence_cannot_be_approved) ... ok
test_unavailable_remote_preserves_commit_and_retries_publication_only (test_controller.EngineTests.test_unavailable_remote_preserves_commit_and_retries_publication_only) ... ok
test_unconfigured_test_adapter_blocks_before_model (test_controller.EngineTests.test_unconfigured_test_adapter_blocks_before_model) ... ok
test_unsigned_recovery_cannot_clear_running_guard (test_controller.EngineTests.test_unsigned_recovery_cannot_clear_running_guard) ... ok
test_forward_dependency_rejected (test_controller.QueueTests.test_forward_dependency_rejected) ... ok
test_production_branches_rejected (test_controller.QueueTests.test_production_branches_rejected) ... ok
test_real_queue_is_ordered_and_missing_sources_are_explicit (test_controller.QueueTests.test_real_queue_is_ordered_and_missing_sources_are_explicit) ... ok
test_empty_report_is_not_a_pass (test_controller.ReportTests.test_empty_report_is_not_a_pass) ... ok
test_json_log_redaction_preserves_event_structure (test_controller.ReportTests.test_json_log_redaction_preserves_event_structure) ... ok
test_path_escape_rejected (test_controller.ReportTests.test_path_escape_rejected) ... ok
test_playwright_retry_and_expected_failure_are_not_pass (test_controller.ReportTests.test_playwright_retry_and_expected_failure_are_not_pass) ... ok
test_redacts_common_credentials (test_controller.ReportTests.test_redacts_common_credentials) ... ok
test_report_membership_and_skip_enforcement (test_controller.ReportTests.test_report_membership_and_skip_enforcement) ... ok
test_cli_nonzero_exit_never_accepts (test_controller.SubprocessTests.test_cli_nonzero_exit_never_accepts) ... ok
test_cli_protocol_real_process_and_complete_prompt (test_controller.SubprocessTests.test_cli_protocol_real_process_and_complete_prompt) ... ok
test_killed_controller_terminates_worker_and_grandchild (test_controller.SubprocessTests.test_killed_controller_terminates_worker_and_grandchild) ... ok
test_marker_write_failure_does_not_leave_worker (test_controller.SubprocessTests.test_marker_write_failure_does_not_leave_worker) ... ok
test_no_newline_output_obeys_log_budget (test_controller.SubprocessTests.test_no_newline_output_obeys_log_budget) ... ok
test_prompt_stall_still_obeys_timeout (test_controller.SubprocessTests.test_prompt_stall_still_obeys_timeout) ... ok
test_real_check_report_must_match_expected_membership (test_controller.SubprocessTests.test_real_check_report_must_match_expected_membership) ... ok
test_worker_cannot_execute_before_job_assignment (test_controller.SubprocessTests.test_worker_cannot_execute_before_job_assignment) ... ok
test_pending_recovery_can_be_signed_from_idle_operator_terminal (test_local_mode.LocalFlowTests.test_pending_recovery_can_be_signed_from_idle_operator_terminal) ... ok
test_single_user_concurrent_controller_and_interrupted_guard (test_local_mode.LocalFlowTests.test_single_user_concurrent_controller_and_interrupted_guard) ... ok
test_single_user_main_checkout_is_still_prohibited (test_local_mode.LocalFlowTests.test_single_user_main_checkout_is_still_prohibited) ... ok
test_single_user_missing_approval_stops_before_worker (test_local_mode.LocalFlowTests.test_single_user_missing_approval_stops_before_worker) ... ok
test_single_user_pause_and_publication_recovery (test_local_mode.LocalFlowTests.test_single_user_pause_and_publication_recovery) ... ok
test_single_user_subprocess_protocol_and_ordered_publication (test_local_mode.LocalFlowTests.test_single_user_subprocess_protocol_and_ordered_publication) ... ok
test_auth_home_inside_worker_is_rejected (test_local_mode.LocalPolicyTests.test_auth_home_inside_worker_is_rejected) ... ok
test_blocked_status_rejects_otherwise_successful_probes (test_local_mode.LocalPolicyTests.test_blocked_status_rejects_otherwise_successful_probes) ... ok
test_different_or_missing_sid_rejected (test_local_mode.LocalPolicyTests.test_different_or_missing_sid_rejected) ... ok
test_existing_normal_auth_home_is_rejected (test_local_mode.LocalPolicyTests.test_existing_normal_auth_home_is_rejected) ... ok
test_failed_probe_and_unconfigured_owner_key_cannot_qualify (test_local_mode.LocalPolicyTests.test_failed_probe_and_unconfigured_owner_key_cannot_qualify) ... ok
test_missing_or_stale_qualification_never_enables_unattended (test_local_mode.LocalPolicyTests.test_missing_or_stale_qualification_never_enables_unattended) ... ok
test_mxc_is_explicit_without_legacy_or_unrestricted_fallback (test_local_mode.LocalPolicyTests.test_mxc_is_explicit_without_legacy_or_unrestricted_fallback) ... ok
test_noninteractive_approval_is_refused (test_local_mode.LocalPolicyTests.test_noninteractive_approval_is_refused) ... ok
test_operator_controlled_policy_cannot_launch_a_worker (test_local_mode.LocalPolicyTests.test_operator_controlled_policy_cannot_launch_a_worker) ... ok
test_plugins_are_not_an_implicit_same_user_capability (test_local_mode.LocalPolicyTests.test_plugins_are_not_an_implicit_same_user_capability) ... ok
test_probe_summary_rejects_partial_and_failed_boundaries (test_local_mode.LocalPolicyTests.test_probe_summary_rejects_partial_and_failed_boundaries) ... ok
test_profile_denies_root_control_auth_and_normal_configuration (test_local_mode.LocalPolicyTests.test_profile_denies_root_control_auth_and_normal_configuration) ... ok
test_same_chatgpt_auth_is_explicit_and_tokens_are_not_copied (test_local_mode.LocalPolicyTests.test_same_chatgpt_auth_is_explicit_and_tokens_are_not_copied) ... ok
test_same_windows_identity_supported (test_local_mode.LocalPolicyTests.test_same_windows_identity_supported) ... ok
test_tool_or_temp_roots_cannot_reopen_protected_parent (test_local_mode.LocalPolicyTests.test_tool_or_temp_roots_cannot_reopen_protected_parent) ... ok
----------------------------------------------------------------------
Ran 81 tests in 161.136s
OK
@@ -0,0 +1 @@
{"observations":[{"status":"BLOCKED","stderr":"Failed to resolve full path of the current executable [C:\\Users\\Connor\\.dotnet\\dotnet.exe]\n","stdout":"","tool":"accepted-dotnet"},{"status":"BLOCKED","stderr":"No pyvenv.cfg file\n","stdout":"","tool":"accepted-docling-python"},{"stderr":"","stdout":"v24.19.0\n","tool":"node"},{"stderr":"","stdout":"\nHost:\n Version: 9.0.20\n Architecture: x64\n Commit: 3879076d9a\n RID: win-x64\n\n.NET SDKs installed:\n No SDKs were found.\n\n.NET runtimes installed:\n Microsoft.NETCore.App 8.0.30 [C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App]\n Microsoft.NETCore.App 9.0.20 [C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App]\n Microsoft.WindowsDesktop.App 8.0.30 [C:\\Program Files\\dotnet\\shared\\Microsoft.WindowsDesktop.App]\n Microsoft.WindowsDesktop.App 9.0.20 [C:\\Program Files\\dotnet\\shared\\Microsoft.WindowsDesktop.App]\n\nOther architectures found:\n None\n\nEnvironment variables:\n Not set\n\nglobal.json file:\n C:\\Users\\Connor\\.codex\\worktrees\\autonomous-roadmap-01\\Job tracker\\global.json\n\nLearn more:\n https://aka.ms/dotnet/info\n\nDownload .NET:\n https://aka.ms/dotnet/download\n","tool":"dotnet"},{"stderr":"WARNING: Error loading config file: open C:\\Users\\Connor\\.docker\\config.json: Access is denied.\npermission denied while trying to connect to the docker API at npipe:////./pipe/docker_engine\n","stdout":"\n","tool":"docker"}],"profile":"Existing native MXC, no permissions widened","realProductSuitesExecuted":0}
+16
View File
@@ -11,6 +11,17 @@
"baselineBranch": {"$ref": "#/$defs/branch"},
"remote": {"type": "string", "pattern": "^[A-Za-z0-9_-]+$"},
"integrationBranch": {"$ref": "#/$defs/branch"},
"executionScope": {
"type": "object", "additionalProperties": false,
"required": ["stage", "checkpoint", "mode", "reportDirectory", "handoffCommit", "handoffBranch"],
"properties": {
"stage": {"const": "PHASE-10"}, "checkpoint": {"const": "P10-00"},
"mode": {"const": "assessment-only"},
"reportDirectory": {"const": "docs/automation/stages/PHASE-10"},
"handoffCommit": {"type": "string", "pattern": "^[0-9a-f]{40}$"},
"handoffBranch": {"$ref": "#/$defs/branch"}
}
},
"stages": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/stage"}}
},
"$defs": {
@@ -56,11 +67,16 @@
"check": {
"type": "object",
"required": ["id", "argv", "cwd", "report"],
"if": {"required": ["adapter"], "properties": {"adapter": {"const": "assessment-evidence-inspection"}}},
"then": {"required": ["configured"], "properties": {"configured": {"const": false}}},
"properties": {
"id": {"type": "string", "pattern": "^[A-Za-z0-9-]+$"},
"argv": {"type": "array", "minItems": 1, "items": {"type": "string"}},
"cwd": {"type": "string"},
"configured": {"type": "boolean"},
"adapter": {"enum": ["assessment-evidence-inspection"]},
"receipt": {"type": "string"},
"receiptSha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
"report": {"type": ["object", "null"], "properties": {
"format": {"enum": ["trx", "jest", "playwright", "junit"]},
"path": {"type": "string"},
+188
View File
@@ -0,0 +1,188 @@
"""Synthetic scope tests. No product backlog, model, signing or services."""
import copy
import json
from pathlib import Path
import sys
import tempfile
import unittest
from unittest import mock
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
import controller as c
import local_mode
import test_controller as fixture
def scope():
return dict(stage='PHASE-10', checkpoint='P10-00', mode='assessment-only',
reportDirectory='docs/automation/stages/PHASE-10',
handoffCommit='1'*40, handoffBranch='test/handoff')
class ScopeTests(unittest.TestCase):
def setUp(self):
fixture.EngineTests.setUp(self)
first=self.manifest['stages'][0]
first['id']='PHASE-10'
first['checkpoints'][0]['id']='P10-00'
self.manifest['stages'][1]['dependencies']=['PHASE-10']
state=self.engine.store.read()
state['stages']['PHASE-10']=state['stages'].pop('ONE')
state['manifestSha256']=c.digest(self.manifest)
self.engine.store.write(state)
tearDown = fixture.EngineTests.tearDown
git_at = staticmethod(fixture.EngineTests.git_at)
def test_scope_requires_protected_stop(self):
self.manifest['executionScope'] = scope()
with self.assertRaisesRegex(c.GateError, 'protected stop'):
c.validate_manifest(self.manifest)
def test_scope_rejects_product_write_and_deletion(self):
self.manifest['executionScope'] = scope()
(self.repo/'README').write_text('unauthorised')
with self.assertRaisesRegex(c.GateError, 'assessment'):
self.engine.verify_assessment_writes()
(self.repo/'README').unlink()
with self.assertRaises(c.GateError):
self.engine.verify_assessment_writes()
def test_assessment_reports_are_permitted(self):
self.manifest['executionScope'] = scope()
output=self.repo/scope()['reportDirectory']/'P10-00.md'
output.parent.mkdir(parents=True)
output.write_text('Synthetic assessment')
self.engine.verify_assessment_writes()
def test_generic_approval_cannot_launch_next_checkpoint(self):
first=self.manifest['stages'][0]
first['checkpoints'].append(copy.deepcopy(first['checkpoints'][0]))
first['checkpoints'][1]['id']='implementation'
self.manifest['executionScope']=scope()
state=self.engine.store.read()
state['manifestSha256']=c.digest(self.manifest)
state['stages']['PHASE-10']['checkpoint']=1
self.engine.store.write(state)
self.engine.run()
self.assertEqual(0,self.runner.calls)
self.assertIn('assessment', self.engine.store.read()['latestFailure'])
def test_assessment_reaches_protected_stop_and_cannot_advance(self):
output='docs/automation/stages/PHASE-10/P10-00.md'
class ReportRunner(fixture.SyntheticRunner):
def run_session(inner, engine, stage, checkpoint, directory, prompt):
inner.calls += 1
report=engine.repo/output
report.parent.mkdir(parents=True,exist_ok=True)
report.write_text('Synthetic assessment only')
return dict(outcome='complete',summary='synthetic',blockers=[],requestedApprovals=[],checkpoint=checkpoint['id'])
first=self.manifest['stages'][0]
cp=first['checkpoints'][0]
cp['requiredArtifacts']=[output]
cp['approvalsAfter']=['candidate-specific-cleanup-scope']
first['checkpoints'].append(copy.deepcopy(cp))
first['checkpoints'][1]['id']='implementation'
self.manifest['executionScope']={**scope(),'handoffCommit':self.manifest['baselineCommit'],'handoffBranch':'codex/base'}
state=self.engine.store.read()
state['manifestSha256']=c.digest(self.manifest)
self.engine.store.write(state)
self.engine.runner=ReportRunner()
self.engine.approval_verifier=lambda request: request['kind']!='candidate-specific-cleanup-scope'
self.engine.run()
self.assertEqual(1,self.engine.runner.calls)
self.assertEqual('candidate-specific-cleanup-scope', self.engine.store.read()['stages']['PHASE-10']['awaiting'][0]['kind'])
self.engine.approval_verifier=lambda request: True
self.engine.run()
self.assertEqual(1,self.engine.runner.calls)
self.assertIn('assessment',self.engine.store.read()['latestFailure'])
def test_policy_cannot_relabel_p10_01_as_assessment(self):
self.manifest['executionScope']={**scope(),'checkpoint':'P10-01'}
cp=self.manifest['stages'][0]['checkpoints'][0]
cp['id']='P10-01'
cp['approvalsAfter']=['candidate-specific-cleanup-scope']
with self.assertRaisesRegex(c.GateError,'protected stop'):
c.validate_manifest(self.manifest)
def test_evidence_inspection_cannot_be_marked_runtime_qualified(self):
self.manifest['executionScope']=scope()
cp=self.manifest['stages'][0]['checkpoints'][0]
cp['approvalsAfter']=['candidate-specific-cleanup-scope']
cp['checks'][0].update(adapter='assessment-evidence-inspection',configured=True,receiptSha256='a'*64)
with self.assertRaisesRegex(c.GateError,'cannot qualify'):
c.validate_manifest(self.manifest)
def test_inspection_requires_explicit_false_and_never_launches_worker(self):
self.manifest['executionScope']=scope()
cp=self.manifest['stages'][0]['checkpoints'][0]
cp['approvalsAfter']=['candidate-specific-cleanup-scope']
cp['checks'][0].update(adapter='assessment-evidence-inspection',receiptSha256='a'*64)
cp['checks'][0].pop('configured',None)
with self.assertRaisesRegex(c.GateError,'cannot qualify'):
c.validate_manifest(self.manifest)
state=self.engine.store.read()
with self.assertRaisesRegex(c.GateError,'adapters must be configured'):
self.engine.checkpoint(self.manifest['stages'][0],cp,state['stages']['PHASE-10'],state)
self.assertEqual(0,self.runner.calls)
def test_native_isolation_failure_stops_before_additional_probe(self):
import qualify_assessment
policy=copy.deepcopy(self.manifest)
policy['executionEnabled']=False
c.atomic(self.control/'backlog.json',policy)
c.atomic(self.control/'proposed-p10-assessment.json',policy)
c.atomic(self.control/'config.json',self.config)
report=self.root/'failed-native.json'
with mock.patch.object(qualify_assessment.local_mode,'qualify',return_value={'status':'FAIL'}), \
mock.patch.object(sys,'argv',['qualify',str(self.control),str(self.repo),str(report)]):
with self.assertRaisesRegex(c.GateError,'no further child'):
qualify_assessment.main()
self.assertEqual('NOT_RUN',c.read_json(report)['assessment'])
self.assertFalse((self.repo/'.artifacts').exists())
def test_entry_cannot_prepare_approvals_without_composite_adapter(self):
self.manifest['executionScope']={**scope(),'handoffCommit':self.manifest['baselineCommit'],'handoffBranch':'codex/base'}
cp=self.manifest['stages'][0]['checkpoints'][0]
cp['approvalsAfter']=['candidate-specific-cleanup-scope']
cp['checks']=[]
c.atomic(self.control/'backlog.json',self.manifest)
c.atomic(self.control/'config.json',self.config)
with mock.patch.object(c,'Engine',return_value=self.engine), \
mock.patch.object(local_mode,'require_unattended'):
with self.assertRaisesRegex(c.GateError,'composite adapter is not qualified'):
c.main(['prepare-entry','--control-root',str(self.control)])
self.assertEqual(0,self.runner.calls)
self.assertFalse((self.control/'requests').exists())
class ReadOnlyProfileTests(unittest.TestCase):
def test_assessment_profile_keeps_product_read_only(self):
with tempfile.TemporaryDirectory() as directory:
root=Path(directory)
config=dict(repository=str(root/'worker'), workerTemp=str(root/'temp'),
workerCodeXHome=str(root/'control/auth'), normalCodeXHome=str(root/'normal'),
assessmentReportDirectory='docs/automation/stages/PHASE-10')
rules=local_mode.filesystem_rules(root/'control',config)
self.assertEqual('read',rules[(root/'worker').resolve().as_posix()])
self.assertEqual('write',rules[(root/'worker/docs/automation/stages/PHASE-10').resolve().as_posix()])
class ReceiptTests(unittest.TestCase):
def test_receipt_rejects_changed_product_or_changed_receipt(self):
import assessment_adapter as adapter
with tempfile.TemporaryDirectory() as directory:
repo=Path(directory)
(repo/'product.txt').write_text('accepted synthetic source')
receipt=dict(schemaVersion=1, scope='P10-00-ASSESSMENT-ONLY',
integrity='PASS', files={'product.txt':c.sha_file(repo/'product.txt')},
nativeReports=[dict(id='synthetic',status='PASS',exitCode=0,
membershipSha256='a'*64)], retainedDecisions={'production':'NO-GO'})
path=repo/'receipt.json'
c.atomic(path,receipt)
pin=c.sha_file(path)
self.assertEqual('REUSED_SOURCE_BOUND_EVIDENCE',adapter.verify(repo,path,pin)['kind'])
(repo/'product.txt').write_text('changed')
with self.assertRaises(ValueError):
adapter.verify(repo,path,pin)
with self.assertRaises(ValueError):
adapter.verify(repo,path,'0'*64)