Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55198ee511 | ||
|
|
05e4c83054 |
@@ -1407,16 +1407,6 @@ impl Workflow {
|
||||
.color(style::MUTED),
|
||||
);
|
||||
}
|
||||
content = content.push(
|
||||
text(format!(
|
||||
"{} changes · {} required boundary choices · {} unresolved conflicts · {} modeling warnings",
|
||||
d.plan.changes.len(),
|
||||
d.plan.conflicts.iter().filter(|c| d.boundary_choice(p, c)).count(),
|
||||
d.plan.conflicts.iter().filter(|c| !d.boundary_choice(p, c)).count(),
|
||||
d.plan.warnings.len()
|
||||
))
|
||||
.size(12),
|
||||
);
|
||||
}
|
||||
}
|
||||
let enabled = match self {
|
||||
@@ -1433,12 +1423,50 @@ impl Workflow {
|
||||
.color(iced::Color::from_rgb8(178, 54, 58)),
|
||||
);
|
||||
}
|
||||
column![
|
||||
scrollable(content.padding(iced::Padding {
|
||||
right: 14.,
|
||||
..Default::default()
|
||||
}))
|
||||
.height(480),
|
||||
let mut footer = column![].spacing(8);
|
||||
if let Self::Transition(d) = self {
|
||||
let boundary_choices = d
|
||||
.plan
|
||||
.conflicts
|
||||
.iter()
|
||||
.filter(|c| d.boundary_choice(p, c))
|
||||
.count();
|
||||
footer = footer.push(
|
||||
text(format!(
|
||||
"{} reviewed items · {} boundary choices · {} conflicts · {} warnings",
|
||||
d.plan.changes.len(),
|
||||
boundary_choices,
|
||||
d.plan.conflicts.len() - boundary_choices,
|
||||
d.plan.warnings.len()
|
||||
))
|
||||
.size(12),
|
||||
);
|
||||
let guidance = if d.destination_error(p).is_some() {
|
||||
Some("Choose a compatible diagram in Show result in, above the mappings.".into())
|
||||
} else if boundary_choices > 0 {
|
||||
Some("Choose the counterpart kind in each mapping that needs a system boundary decision.".into())
|
||||
} else if let Some(field) = d.plan.conflicts.iter().find_map(|c| c.field) {
|
||||
let field = match field {
|
||||
TransitionField::Name => "Name",
|
||||
TransitionField::Description => "Description",
|
||||
};
|
||||
Some(format!(
|
||||
"Review {field} below the mappings: choose Keep target, Take source, or merged text."
|
||||
))
|
||||
} else if !d.plan.conflicts.is_empty() {
|
||||
Some("Review the conflict messages below the mappings and complete the required decisions.".into())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(guidance) = guidance {
|
||||
footer = footer.push(
|
||||
text(guidance)
|
||||
.size(12)
|
||||
.color(iced::Color::from_rgb8(178, 54, 58)),
|
||||
);
|
||||
}
|
||||
}
|
||||
footer = footer.push(
|
||||
row![
|
||||
Space::new().width(Fill),
|
||||
button(text("Cancel").size(12))
|
||||
@@ -1458,7 +1486,15 @@ impl Workflow {
|
||||
.padding(10)
|
||||
.on_press_maybe(enabled.then_some(Message::Workflow(Event::Apply)))
|
||||
]
|
||||
.spacing(10)
|
||||
.spacing(10),
|
||||
);
|
||||
column![
|
||||
scrollable(content.padding(iced::Padding {
|
||||
right: 14.,
|
||||
..Default::default()
|
||||
}))
|
||||
.height(480),
|
||||
footer
|
||||
]
|
||||
.spacing(18)
|
||||
.into()
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# Closed lifecycle checker and footer gates
|
||||
|
||||
Run `python verify.py` with Python 3.10 or newer. Verification uses only these
|
||||
package files and the standard library; it does not execute archived source,
|
||||
binaries, Cargo, Git or network requests. `prepare.py [CHECKOUT]` reproducibly
|
||||
packages the explicitly named raw captures if those local originals are present.
|
||||
The input capsules are source snapshots, not a hermetic toolchain distribution.
|
||||
|
||||
Ten closed Cargo runs are preserved: footer-format-1, footer-windows-build-1,
|
||||
footer-windows-build-2, footer-clippy-1, lifecycle-producer-build-1,
|
||||
lifecycle-producer-generate-1, lifecycle-producer-build-2, footer-windows-build-3,
|
||||
footer-format-2 and lifecycle-producer-format-1. Each remains bound
|
||||
to its original command, source inventory, helper, logs and receipts. The first
|
||||
footer build has no JSON executable artifact records; the second captures them.
|
||||
Recorded HEAD d0b7c28 does not identify uncommitted footer/producer source bytes;
|
||||
the archived per-run input manifests do.
|
||||
|
||||
The three final gates use the same 715-file manifest, SHA-256
|
||||
`b5a62817a1b5df936b9d759241bdfc4e8283901c1d4b540b9e494995e1544d8d`.
|
||||
Compared with producer build2's captured inputs, only the footer literal changes
|
||||
from "changes" to "reviewed items". The verifier checks that exact byte delta.
|
||||
The final GUI build and both format checks pass. Earlier lint and oracle captures
|
||||
retain their original input scope; they are not presented as reruns of the final
|
||||
wording. The checker source is unchanged by that label refinement.
|
||||
|
||||
Producer build1 and its generation succeeded, but that older checker incorrectly
|
||||
rejected expected engineering warnings during saved-file admission. Build2
|
||||
contains the corrected admission logic. The 40 PASS falsifier trials are bound
|
||||
to build2's exact checker artifact receipt and producer source, not to build1.
|
||||
Build success alone does not demonstrate checker correctness. The earlier native
|
||||
admission failure is outside these selected captures, not fabricated as a Cargo
|
||||
failure or silently converted into successful native evidence.
|
||||
|
||||
The oracle archive retains all original fixtures, repeated generation outputs,
|
||||
mutated specimens, identity receipts, copied build evidence, runner/source,
|
||||
stdout/stderr and per-trial results. The 40 expected outcomes include accepted
|
||||
original/deleted/recreated/relinked states and deliberate rejection cases for
|
||||
retained prose/geometry corruption, source/trace identity drift, missing persisted
|
||||
IDs, absent identity receipts and receipt overwrite. These are synthetic checker
|
||||
tests, not native interaction. A PASS summary includes expected negative exits.
|
||||
|
||||
`manifest.json` maps every logical raw path to a stored member. Only byte-identical
|
||||
inputs.json/inputs.zip files are deduplicated; exact run mapping is retained.
|
||||
`files.json` pins outer files including the reproducible packager. Outer text is
|
||||
UTF-8 LF; all raw archived bytes are preserved. The verifier checks exact safe ZIP
|
||||
membership, hashes, nested input contents/counts, helpers, timestamps, receipt and
|
||||
binary-pin relationships, repeated fixture equality, watched specimen identities,
|
||||
all 40 outcomes and their recorded rejection text. It rereads evidence rather
|
||||
than rerunning the model checker, and does not authenticate historical execution.
|
||||
|
||||
Native trial1 lacks a closing exit receipt and is excluded. No successful native
|
||||
deletion, Undo, relinking, save/reopen, or full lifecycle acceptance is claimed.
|
||||
No runtime binaries, credentials or whole user profile are included. Recorded
|
||||
absolute paths are historical labels; verification maps them to archive members
|
||||
without accessing the original machine. Binary hashes remain receipt observations.
|
||||
Binary file not shown.
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"evidence.zip": {
|
||||
"bytes": 14254708,
|
||||
"sha256": "663bf83db4ead276063b321fd5e9857e29d2305e6046c2e87dbbac87a366b19c"
|
||||
},
|
||||
"manifest.json": {
|
||||
"bytes": 79071,
|
||||
"sha256": "5d2be3d68206871a560dd1a83064ff779e8aab6e11302d09aaf1a655502dd38e"
|
||||
},
|
||||
"README.md": {
|
||||
"bytes": 3696,
|
||||
"sha256": "0e33fe1a85a1ee17ebde57ed7994280dcb95d3a7c4338b78c1f7e0ae9fdcbb7d"
|
||||
},
|
||||
"verify.py": {
|
||||
"bytes": 10908,
|
||||
"sha256": "1e8ba1cc2759fbe76e14636a37925c9dfce9a6f14bf63569acb76498bd8380ad"
|
||||
},
|
||||
"prepare.py": {
|
||||
"bytes": 3572,
|
||||
"sha256": "147a63f26b7131076df52057f1cc263dfefdf99d937b2c2a819217a46c56105f"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,56 @@
|
||||
"""Repackage the explicitly closed captures; reads raw evidence, never runs Cargo."""
|
||||
from pathlib import Path
|
||||
import hashlib, json, sys, zipfile
|
||||
|
||||
OUT=Path(__file__).resolve().parent
|
||||
ROOT=Path(sys.argv[1]).resolve() if len(sys.argv)>1 else OUT.parents[3]
|
||||
GATES=ROOT/'.local/transition-gates'
|
||||
ORACLE=ROOT/'.local/transition-native/oracle-falsifiers-1'
|
||||
RUNS=['footer-format-1','footer-windows-build-1','footer-windows-build-2','footer-clippy-1',
|
||||
'lifecycle-producer-build-1','lifecycle-producer-generate-1','lifecycle-producer-build-2',
|
||||
'footer-windows-build-3','footer-format-2','lifecycle-producer-format-1']
|
||||
def pin(b):return {'bytes':len(b),'sha256':hashlib.sha256(b).hexdigest()}
|
||||
def main():
|
||||
payload={};logical={};run_specs={}
|
||||
def add(name,b):
|
||||
# Deduplicate only byte-identical input manifests and source archives.
|
||||
stored='capsules/'+pin(b)['sha256']+Path(name).suffix if Path(name).name in ('inputs.json','inputs.zip') else name
|
||||
if stored in payload:assert payload[stored]==b
|
||||
payload[stored]=b;logical[name]=stored
|
||||
for run in RUNS:
|
||||
result=json.loads((GATES/run/'result.json').read_bytes())
|
||||
assert 'finish' in result and result['inputsUnchanged'] is True,run
|
||||
for f in sorted((GATES/run).iterdir()):
|
||||
assert f.is_file(),f
|
||||
add('cargo/'+run+'/'+f.name,f.read_bytes())
|
||||
helpers=[f for f in GATES.glob('*.py') if pin(f.read_bytes())==result['helper']]
|
||||
assert helpers,run+' helper unavailable'
|
||||
helper='helpers/'+result['helper']['sha256']+'.py'
|
||||
add(helper,helpers[0].read_bytes())
|
||||
run_specs[run]={k:result[k] for k in ('command','head','exitCode','inputCount')}
|
||||
run_specs[run]['helper']=helper
|
||||
for f in sorted(ORACLE.rglob('*')):
|
||||
if f.is_file():add('oracle/'+f.relative_to(ORACLE).as_posix(),f.read_bytes())
|
||||
summary=json.loads((ORACLE/'summary.json').read_bytes())
|
||||
assert summary['status']=='PASS' and summary['trialCount']==40
|
||||
manifest={'schema':1,'captureCheckout':'d0b7c285018a516c6dc34c196d1359a861ddaeb3',
|
||||
'cargo':run_specs,'oracleExpectedTrials':summary['trials'],
|
||||
'oracleOriginalDirectory':str(ORACLE).replace('\\','/'),
|
||||
'logicalFiles':dict(sorted(logical.items())),
|
||||
'members':{n:pin(b) for n,b in sorted(payload.items())}}
|
||||
with zipfile.ZipFile(OUT/'evidence.zip','w',compression=zipfile.ZIP_DEFLATED,compresslevel=9) as z:
|
||||
for name,b in sorted(payload.items()):
|
||||
info=zipfile.ZipInfo(name,date_time=(2026,9,13,0,0,0));info.compress_type=zipfile.ZIP_DEFLATED
|
||||
z.writestr(info,b)
|
||||
(OUT/'manifest.json').write_bytes((json.dumps(manifest,indent=2)+'\n').encode())
|
||||
for name in ('README.md','verify.py','prepare.py'):
|
||||
(OUT/name).write_bytes((OUT/name).read_bytes().replace(b'\r\n',b'\n'))
|
||||
files={name:pin((OUT/name).read_bytes()) for name in ('evidence.zip','manifest.json','README.md','verify.py','prepare.py')}
|
||||
(OUT/'files.json').write_bytes((json.dumps(files,indent=2)+'\n').encode())
|
||||
with zipfile.ZipFile(OUT/'evidence.zip') as z:
|
||||
assert all(z.read(logical[n])==b for n,b in (
|
||||
[('cargo/'+r+'/'+f.name,f.read_bytes()) for r in RUNS for f in sorted((GATES/r).iterdir())]+
|
||||
[('oracle/'+f.relative_to(ORACLE).as_posix(),f.read_bytes()) for f in sorted(ORACLE.rglob('*')) if f.is_file()]))
|
||||
print(json.dumps(files,indent=2))
|
||||
print('Exact raw fidelity:',len(logical),'logical files;',len(payload),'stored members')
|
||||
if __name__=='__main__':main()
|
||||
@@ -0,0 +1,132 @@
|
||||
"""Read-only standard-library verification; no archived source/binary execution."""
|
||||
from pathlib import Path, PurePosixPath
|
||||
from datetime import datetime
|
||||
import hashlib, io, json, zipfile
|
||||
ROOT=Path(__file__).resolve().parent
|
||||
def pin(b):return {'bytes':len(b),'sha256':hashlib.sha256(b).hexdigest()}
|
||||
def need(ok,msg):
|
||||
if not ok:raise ValueError(msg)
|
||||
def safe(n):return not PurePosixPath(n).is_absolute() and '..' not in PurePosixPath(n).parts and '\\' not in n and ':' not in n
|
||||
def members(z,expected):
|
||||
names=z.namelist()
|
||||
need(len(names)==len(set(names)) and set(names)==set(expected),'archive membership')
|
||||
need(all(safe(n) for n in names),'unsafe archive member')
|
||||
def time_order(*values):return list(map(datetime.fromisoformat,values))==sorted(map(datetime.fromisoformat,values))
|
||||
def main():
|
||||
files=json.loads((ROOT/'files.json').read_bytes())
|
||||
need(set(files)=={'evidence.zip','manifest.json','README.md','verify.py','prepare.py'},'outer inventory')
|
||||
for n,p in files.items():need(pin((ROOT/n).read_bytes())==p,'outer hash '+n)
|
||||
m=json.loads((ROOT/'manifest.json').read_bytes())
|
||||
with zipfile.ZipFile(ROOT/'evidence.zip') as z:
|
||||
members(z,m['members'])
|
||||
for n,p in m['members'].items():need(pin(z.read(n))==p,'member hash '+n)
|
||||
logical=m['logicalFiles']
|
||||
need(all(safe(n) for n in logical) and set(logical.values())==set(m['members']),'logical mapping')
|
||||
def raw(n):return z.read(logical[n])
|
||||
def obj(n):return json.loads(raw(n))
|
||||
def directory(prefix):return {n[len(prefix):]:pin(raw(n)) for n in logical if n.startswith(prefix)}
|
||||
pairs=set()
|
||||
for run,spec in m['cargo'].items():
|
||||
p='cargo/'+run+'/'
|
||||
start=obj(p+'started.json');result=obj(p+'result.json');process=obj(p+'process.json')
|
||||
need(all(result[k]==v for k,v in start.items()),run+' start/result')
|
||||
need(result['command']==process['command']==spec['command'],run+' commands')
|
||||
need(result['head']==spec['head'] and result['exitCode']==spec['exitCode'],run+' recorded outcome')
|
||||
need(result['inputsUnchanged'] is True and result['seconds']>=0,run+' source/duration')
|
||||
need(process['pid']>0 and time_order(start['start'],process['observed'],result['finish']),run+' process times')
|
||||
for field,n in [('inputs','inputs.json'),('archive','inputs.zip'),('stdout','stdout.log'),('stderr','stderr.log')]:need(result[field]==pin(raw(p+n)),run+' '+field)
|
||||
need(result['helper']==pin(raw(spec['helper'])),run+' helper')
|
||||
inputs=obj(p+'inputs.json')
|
||||
need(len(inputs)==result['inputCount']==spec['inputCount'],run+' input count')
|
||||
need(sum(n.startswith('crates/vendor/iced_widget/') for n in inputs)==71,run+' vendor inventory')
|
||||
pair=(logical[p+'inputs.json'],logical[p+'inputs.zip'])
|
||||
if pair not in pairs:
|
||||
with zipfile.ZipFile(io.BytesIO(raw(p+'inputs.zip'))) as source:
|
||||
members(source,inputs)
|
||||
for n,expected in inputs.items():need(pin(source.read(n))==expected,run+' source '+n)
|
||||
pairs.add(pair)
|
||||
artifacts=[]
|
||||
for line in raw(p+'stdout.log').decode('utf-8',errors='replace').splitlines():
|
||||
try:item=json.loads(line)
|
||||
except json.JSONDecodeError:continue
|
||||
if isinstance(item,dict) and item.get('reason')=='compiler-artifact' and item.get('executable'):artifacts.append(item)
|
||||
need(artifacts==[a['cargo'] for a in result['artifacts']],run+' artifact relationships')
|
||||
print(run,'exit',result['exitCode'],'inputs',len(inputs),'artifact records',len(artifacts))
|
||||
# Successful build1/generation are not the corrected checker admission.
|
||||
older=obj('cargo/lifecycle-producer-build-1/inputs.json')
|
||||
newer=obj('cargo/lifecycle-producer-build-2/inputs.json')
|
||||
need(older==obj('cargo/lifecycle-producer-generate-1/inputs.json'),'generation uses older producer inputs')
|
||||
source_path='harness/experiments/transition-native-lifecycle-20260913/producer/src/main.rs'
|
||||
need(older[source_path]!=newer[source_path],'distinct producer revisions')
|
||||
final_runs=('footer-windows-build-3','footer-format-2','lifecycle-producer-format-1')
|
||||
final_inputs=obj('cargo/footer-windows-build-3/inputs.json')
|
||||
final_pin='b5a62817a1b5df936b9d759241bdfc4e8283901c1d4b540b9e494995e1544d8d'
|
||||
for run in final_runs:
|
||||
p='cargo/'+run+'/'
|
||||
need(pin(raw(p+'inputs.json'))['sha256']==final_pin and obj(p+'inputs.json')==final_inputs,run+' final source manifest')
|
||||
need(obj(p+'result.json')['exitCode']==0 and len(final_inputs)==715,run+' final successful gate')
|
||||
for run in final_runs[1:]:
|
||||
need(not raw('cargo/'+run+'/stdout.log') and not raw('cargo/'+run+'/stderr.log'),run+' empty format streams')
|
||||
final_build=obj('cargo/footer-windows-build-3/result.json')
|
||||
need(len(final_build['artifacts'])==1 and final_build['artifacts'][0]['cargo']['target']['name']=='arcadia-studio','final GUI artifact')
|
||||
changed={n for n in set(newer)|set(final_inputs) if newer.get(n)!=final_inputs.get(n)}
|
||||
need(changed=={'crates/oa-desktop/src/workflow.rs'},'final wording-only file scope')
|
||||
with zipfile.ZipFile(io.BytesIO(raw('cargo/lifecycle-producer-build-2/inputs.zip'))) as before, zipfile.ZipFile(io.BytesIO(raw('cargo/footer-windows-build-3/inputs.zip'))) as after:
|
||||
path='crates/oa-desktop/src/workflow.rs';a=before.read(path);b=after.read(path)
|
||||
old='{} changes · {} boundary choices · {} conflicts · {} warnings'.encode()
|
||||
new='{} reviewed items · {} boundary choices · {} conflicts · {} warnings'.encode()
|
||||
need(a.count(old)==1 and a.replace(old,new,1)==b,'exact final footer label delta')
|
||||
build=obj('cargo/lifecycle-producer-build-2/result.json')
|
||||
artifact=[a for a in build['artifacts'] if a['cargo']['target']['name']=='transition-native-lifecycle']
|
||||
need(len(artifact)==1,'corrected checker artifact')
|
||||
# Oracle copied build capsule must match the independently included gate.
|
||||
for name in ('result.json','started.json','process.json','inputs.json','inputs.zip','stdout.log','stderr.log'):
|
||||
need(raw('oracle/build-capture/'+name)==raw('cargo/lifecycle-producer-build-2/'+name),'oracle build capsule '+name)
|
||||
need(pin(raw('oracle/build-capture/build-helper.py'))==build['helper'],'oracle build helper')
|
||||
start=obj('oracle/started.json');summary=obj('oracle/summary.json')
|
||||
need(summary['status']=='PASS' and summary['trialCount']==40,'oracle completion')
|
||||
need(all(summary['context'][k]==v for k,v in start.items() if k!='start'),'oracle context')
|
||||
need(start['checker']==artifact[0]['readback'] and start['buildReceipt']==pin(raw('cargo/lifecycle-producer-build-2/result.json')),'oracle checker chain')
|
||||
need(start['runner']==pin(raw('oracle/oracle-falsifiers.py')),'oracle runner pin')
|
||||
for path,p in directory('oracle/producer/').items():need(p==newer['harness/experiments/transition-native-lifecycle-20260913/producer/'+path],'oracle producer source '+path)
|
||||
fixtures=directory('oracle/fixtures/')
|
||||
need(fixtures==start['fixtureInput']==directory('oracle/repeat-generation-a/')==directory('oracle/repeat-generation-b/'),'deterministic repeated fixtures')
|
||||
need(directory('oracle/specimens/')==summary['specimens'],'mutation inventory')
|
||||
rows=summary['trials']
|
||||
need(rows==m['oracleExpectedTrials'] and len(rows)==len({r['label'] for r in rows})==40,'forty distinct trials')
|
||||
actual_labels={n.split('/')[2] for n in logical if n.startswith('oracle/trials/')}
|
||||
need(actual_labels=={r['label'] for r in rows},'exact trial membership')
|
||||
# Original absolute names are mapped to archive members, never opened.
|
||||
original=m['oracleOriginalDirectory'].rstrip('/')+'/'
|
||||
def mapped(path):
|
||||
norm=path.replace('\\','/')
|
||||
need(norm.startswith(original),'unmapped trial input path')
|
||||
rel=norm[len(original):];need(safe(rel),'unsafe trial input mapping')
|
||||
return 'oracle/'+rel
|
||||
for row in rows:
|
||||
label=row['label'];p='oracle/trials/'+label+'/'
|
||||
r=obj(p+'result.json')
|
||||
need(r['label']==label and r['exitCode']==r['expectedExitCode']==row['exitCode']==row['expectedExitCode'],label+' outcome')
|
||||
need(time_order(start['start'],r['start'],r['finish'],summary['finish']),label+' time order')
|
||||
need(r['checkerBefore']==r['checkerAfter']==start['checker'] and r['runner']==start['runner'] and r['buildReceipt']==start['buildReceipt'],label+' provenance')
|
||||
for stream in ('stdout','stderr'):need(r[stream]==pin(raw(p+stream+'.log')),label+' '+stream)
|
||||
need(r['watchedBefore']==r['watchedAfter'],label+' unchanged inputs')
|
||||
for path,expected in r['watchedBefore'].items():need(pin(raw(mapped(path)))==expected,label+' watched input')
|
||||
cmd=r['command'];need(cmd[0]==artifact[0]['cargo']['executable'],label+' executable command')
|
||||
if cmd[1]=='check':
|
||||
need(mapped(cmd[3]) in logical,label+' check entry')
|
||||
opts=cmd[4:];need(len(opts)%2==0,label+' option arity')
|
||||
for flag,path in zip(opts[::2],opts[1::2]):
|
||||
need(flag in ('--identities','--write-identities') and mapped(path) in logical,label+' identity option')
|
||||
if r['exitCode']==0:
|
||||
report=obj(p+'stdout.log')
|
||||
need(report['status']=='PASS' and report['comparison']=='complete Project equality' and report['state']==cmd[2] and report['entry']==cmd[3],label+' positive report')
|
||||
if cmd[2].endswith(('-reopened','-noop')):need(report['trace_identity_pinned'] is True and '--identities' in opts,label+' continuity receipt')
|
||||
if '--write-identities' in opts:need(obj(p+'written-identities.json')==report['new_ids']==obj(mapped(opts[opts.index('--write-identities')+1])),label+' identity write')
|
||||
else:need(cmd[1]=='generate' and len(cmd)==3,label+' generation command')
|
||||
stderr=raw(p+'stderr.log').decode('utf-8',errors='replace')
|
||||
if r['expectedStderrFragment']:need(r['expectedStderrFragment'] in stderr,label+' rejection reason')
|
||||
elif r['exitCode']==0:need(not stderr,label+' successful stderr')
|
||||
print('PASS:',len(m['cargo']),'closed Cargo runs; 40 recorded oracle trials;',len(logical),'logical raw files;',len(m['members']),'stored members;',len(pairs),'source capsule pairs.')
|
||||
print('Offline consistency only: no binaries/source executed; no native lifecycle completion claim.')
|
||||
if __name__=='__main__':main()
|
||||
@@ -0,0 +1,45 @@
|
||||
# Closed native transition lifecycle captures
|
||||
|
||||
Keep this directory beside `../checks/`. Run `python verify.py` with Python 3.10
|
||||
or newer. Verification requires the exact pinned `../checks/evidence.zip` and
|
||||
these native package files, uses only the standard library, and executes no
|
||||
archived helper, application, checker, Cargo command or network request.
|
||||
`prepare.py [CHECKOUT]` rebuilds this archive from the named closed originals.
|
||||
|
||||
Trials lifecycle-2/3 capture OA deletion, recreation, first relink, a separate
|
||||
reopening and no-op check. Trials lifecycle-4/5 capture reverse SA deletion,
|
||||
Undo/Redo, recreation, first relink, reopening and no-op. All four GUI receipts
|
||||
close successfully with empty stdout/stderr and unchanged fixture/binary pins.
|
||||
Trial1 is excluded entirely because it lacked a closing process receipt. This
|
||||
package does not turn that interrupted history into successful evidence.
|
||||
|
||||
Trials 2/3 use footer-windows-build-2; trials 4/5 use footer-windows-build-3 with
|
||||
the refined "reviewed items" footer. Snapshot checks use the corrected producer
|
||||
build2. Exact build receipts, helpers, artifact records and complete source
|
||||
capsules are read from the pinned sibling checks archive; source proof is not
|
||||
self-contained in the native archive alone. Runtime binaries are excluded.
|
||||
|
||||
Every original frame, adjacent metadata, trial file, accepted identity receipt,
|
||||
saved source snapshot and helper is retained without editing. The launch-pinned
|
||||
captureHelper refers to the corresponding capture-lifecycle-N.js source. Each
|
||||
trial's separately recorded capture-function.js is the serialized function used
|
||||
in the persistent UI session; it is different bytes and was not that launch pin.
|
||||
The files document the distinction rather than asserting byte equivalence or
|
||||
authenticated execution. Frame labels/timestamps/application paths record states,
|
||||
not a complete action invocation log or independent PID attestation. Image/window
|
||||
dimensions vary; these captures are not all minimum-window checks.
|
||||
|
||||
The verifier checks exact safe archive membership, hashes, linked build/source
|
||||
capsules, trial/helper/stream relationships, frame membership and timestamps,
|
||||
snapshot helper and corrected-checker provenance, and the recorded complete
|
||||
Project comparison results. Accepted first-relink IDs match later reopen/no-op
|
||||
receipts in each direction, and both later saved source bundles are byte-identical
|
||||
to their first-relink bundle. The producer's full-model result is recorded evidence;
|
||||
the portable verifier does not rerun its Rust comparisons or derive model equality
|
||||
from screenshots. Snapshot receipts record start time but no completion timestamp;
|
||||
only their start can be located in the GUI process interval.
|
||||
|
||||
These finite workflows do not establish exhaustive usability/accessibility,
|
||||
performance, deployment readiness, or behavior on another platform. No credentials,
|
||||
whole user profile, or runtime binaries are packaged. Outer text is UTF-8 LF;
|
||||
archived raw bytes and historical local-path strings remain unchanged.
|
||||
Binary file not shown.
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"evidence.zip": {
|
||||
"bytes": 6888298,
|
||||
"sha256": "b53253fbe9b98db6a0028ed3629426460e7086c8943be0d13ab5ec540e7e4b68"
|
||||
},
|
||||
"manifest.json": {
|
||||
"bytes": 56348,
|
||||
"sha256": "f6bd0bb5e3d29d5e47f1dfb8f1cb7747a3c2e12aa62b6e4e81b68e68ce5ccf95"
|
||||
},
|
||||
"README.md": {
|
||||
"bytes": 3019,
|
||||
"sha256": "01aed34547156bede9c2fea75162c8fecda18203fa575c4cf81e28e440130a95"
|
||||
},
|
||||
"verify.py": {
|
||||
"bytes": 10474,
|
||||
"sha256": "5f93ff8b266c0c7c3e64630f64a522ff5f8f8bd41d7d0a14cc4856c16a529306"
|
||||
},
|
||||
"prepare.py": {
|
||||
"bytes": 2861,
|
||||
"sha256": "c1884b10724cc618b58abd84b93c03623ea9e80dbda4e5a8b866fff499e93fca"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,37 @@
|
||||
"""Package only closed lifecycle trials 2-5; never runs a GUI or checker."""
|
||||
from pathlib import Path
|
||||
import hashlib,json,sys,zipfile
|
||||
OUT=Path(__file__).resolve().parent
|
||||
ROOT=Path(sys.argv[1]).resolve() if len(sys.argv)>1 else OUT.parents[3]
|
||||
SRC=ROOT/'.local/transition-native'
|
||||
def pin(b):return {'bytes':len(b),'sha256':hashlib.sha256(b).hexdigest()}
|
||||
def main():
|
||||
payload={};runs={}
|
||||
for number in (2,3,4,5):
|
||||
run=f'lifecycle-{number}';folder=SRC/run
|
||||
result=json.loads((folder/'result.json').read_bytes())
|
||||
assert result['exitCode']==0 and 'finish' in result,run+' not closed successfully'
|
||||
for f in sorted(folder.rglob('*')):
|
||||
if f.is_file():payload['native/'+f.relative_to(SRC).as_posix()]=f.read_bytes()
|
||||
helpers={}
|
||||
for field,name in [('runner',f'run-lifecycle-{number}.py'),('launchCapture',f'capture-lifecycle-{number}.js'),('snapshot',f'check-lifecycle-snapshot-{number}.py')]:
|
||||
helpers[field]='helpers/'+name;payload[helpers[field]]=(SRC/name).read_bytes()
|
||||
captures=[n for n,b in payload.items() if n.startswith('native/'+run+'/') and n.endswith('.json') and isinstance(json.loads(b),dict) and 'images' in json.loads(b)]
|
||||
snapshots={f.name:json.loads((f/'result.json').read_bytes())['command'][2] for f in sorted((folder/'snapshots').iterdir()) if f.is_dir()}
|
||||
runs[run]={'helpers':helpers,'guiBuild':'footer-windows-build-2' if number<4 else 'footer-windows-build-3','captures':captures,'snapshots':snapshots}
|
||||
for f in sorted((SRC/'lifecycle-input-1').rglob('*')):
|
||||
if f.is_file():payload['fixtures/'+f.relative_to(SRC/'lifecycle-input-1').as_posix()]=f.read_bytes()
|
||||
checks=OUT.parent/'checks'
|
||||
payload['references/checks-manifest.json']=(checks/'manifest.json').read_bytes()
|
||||
manifest={'schema':1,'checksEvidence':pin((checks/'evidence.zip').read_bytes()),
|
||||
'checksRelativePath':'../checks/evidence.zip','originalNativeDirectory':str(SRC).replace('\\','/'),
|
||||
'runs':runs,'members':{n:pin(b) for n,b in sorted(payload.items())}}
|
||||
with zipfile.ZipFile(OUT/'evidence.zip','w',compression=zipfile.ZIP_DEFLATED,compresslevel=9) as z:
|
||||
for n,b in sorted(payload.items()):
|
||||
info=zipfile.ZipInfo(n,date_time=(2026,9,22,0,0,0));info.compress_type=zipfile.ZIP_DEFLATED;z.writestr(info,b)
|
||||
(OUT/'manifest.json').write_bytes((json.dumps(manifest,indent=2)+'\n').encode())
|
||||
for n in ('README.md','verify.py','prepare.py'):(OUT/n).write_bytes((OUT/n).read_bytes().replace(b'\r\n',b'\n'))
|
||||
files={n:pin((OUT/n).read_bytes()) for n in ('evidence.zip','manifest.json','README.md','verify.py','prepare.py')}
|
||||
(OUT/'files.json').write_bytes((json.dumps(files,indent=2)+'\n').encode())
|
||||
print(json.dumps(files,indent=2));print('Native raw members:',len(payload))
|
||||
if __name__=='__main__':main()
|
||||
@@ -0,0 +1,115 @@
|
||||
"""Read-only offline verification. Requires the pinned sibling checks/evidence.zip."""
|
||||
from pathlib import Path,PurePosixPath
|
||||
from datetime import datetime
|
||||
import hashlib,io,json,zipfile
|
||||
ROOT=Path(__file__).resolve().parent
|
||||
def pin(b):return {'bytes':len(b),'sha256':hashlib.sha256(b).hexdigest()}
|
||||
def need(ok,msg):
|
||||
if not ok:raise ValueError(msg)
|
||||
def safe(n):return not PurePosixPath(n).is_absolute() and '..' not in PurePosixPath(n).parts and '\\' not in n and ':' not in n
|
||||
def members(z,expected):
|
||||
names=z.namelist();need(len(names)==len(set(names)) and set(names)==set(expected),'exact ZIP membership');need(all(safe(n) for n in names),'safe ZIP members')
|
||||
def ordered(*times):return list(map(datetime.fromisoformat,times))==sorted(map(datetime.fromisoformat,times))
|
||||
def main():
|
||||
files=json.loads((ROOT/'files.json').read_bytes())
|
||||
need(set(files)=={'evidence.zip','manifest.json','README.md','verify.py','prepare.py'},'outer inventory')
|
||||
for n,p in files.items():need(pin((ROOT/n).read_bytes())==p,'outer hash '+n)
|
||||
m=json.loads((ROOT/'manifest.json').read_bytes())
|
||||
need(m['checksRelativePath']=='../checks/evidence.zip','fixed sibling evidence location')
|
||||
checks_bytes=(ROOT/'../checks/evidence.zip').read_bytes()
|
||||
need(pin(checks_bytes)==m['checksEvidence'],'pinned sibling checks archive')
|
||||
with zipfile.ZipFile(ROOT/'evidence.zip') as z,zipfile.ZipFile(io.BytesIO(checks_bytes)) as checks:
|
||||
members(z,m['members'])
|
||||
for n,p in m['members'].items():need(pin(z.read(n))==p,'native member hash '+n)
|
||||
def raw(n):return z.read(n)
|
||||
def obj(n):return json.loads(raw(n))
|
||||
def directory(prefix):return {n[len(prefix):]:pin(raw(n)) for n in m['members'] if n.startswith(prefix)}
|
||||
cm=obj('references/checks-manifest.json');members(checks,cm['members'])
|
||||
def craw(logical):
|
||||
n=cm['logicalFiles'][logical];b=checks.read(n);need(pin(b)==cm['members'][n],'checks member '+n);return b
|
||||
def cobj(n):return json.loads(craw(n))
|
||||
# Source capsules and receipt helper chains are independently checked here.
|
||||
builds={};artifacts={};pairs=set()
|
||||
for run in ('footer-windows-build-2','footer-windows-build-3','lifecycle-producer-build-2'):
|
||||
p='cargo/'+run+'/';r=cobj(p+'result.json');start=cobj(p+'started.json');process=cobj(p+'process.json')
|
||||
need(all(r[k]==v for k,v in start.items()) and r['exitCode']==0 and r['inputsUnchanged'] is True,run+' build closure')
|
||||
need(r['command']==process['command'] and ordered(r['start'],process['observed'],r['finish']),run+' build process')
|
||||
for field,n in [('inputs','inputs.json'),('archive','inputs.zip'),('stdout','stdout.log'),('stderr','stderr.log')]:need(r[field]==pin(craw(p+n)),run+' receipt '+field)
|
||||
need(r['helper']==pin(craw(cm['cargo'][run]['helper'])),run+' build helper')
|
||||
inputs=cobj(p+'inputs.json');need(len(inputs)==r['inputCount'] and sum(n.startswith('crates/vendor/iced_widget/') for n in inputs)==71,run+' input inventory')
|
||||
pair=(r['inputs']['sha256'],r['archive']['sha256'])
|
||||
if pair not in pairs:
|
||||
with zipfile.ZipFile(io.BytesIO(craw(p+'inputs.zip'))) as source:
|
||||
members(source,inputs)
|
||||
for n,expected in inputs.items():need(pin(source.read(n))==expected,run+' source '+n)
|
||||
pairs.add(pair)
|
||||
records=[]
|
||||
for line in craw(p+'stdout.log').decode('utf-8',errors='replace').splitlines():
|
||||
try:item=json.loads(line)
|
||||
except json.JSONDecodeError:continue
|
||||
if isinstance(item,dict) and item.get('reason')=='compiler-artifact' and item.get('executable'):records.append(item)
|
||||
need(records==[a['cargo'] for a in r['artifacts']],run+' artifact records')
|
||||
expected='transition-native-lifecycle' if run.startswith('lifecycle') else 'arcadia-studio'
|
||||
selected=[a for a in r['artifacts'] if a['cargo']['target']['name']==expected];need(len(selected)==1,run+' unique artifact')
|
||||
builds[run]=r;artifacts[run]=selected[0]
|
||||
original=m['originalNativeDirectory'].rstrip('/')+'/'
|
||||
def mapped(path):
|
||||
norm=path.replace('\\','/');need(norm.startswith(original),'snapshot path outside captured native root')
|
||||
rel=norm[len(original):];need(safe(rel),'unsafe snapshot path');return 'native/'+rel
|
||||
fixtures=directory('fixtures/');snapshots={};reports={};capture_count=image_count=0
|
||||
need(set(m['runs'])=={'lifecycle-2','lifecycle-3','lifecycle-4','lifecycle-5'},'closed trial membership')
|
||||
for run,spec in m['runs'].items():
|
||||
p='native/'+run+'/';r=obj(p+'result.json');start=obj(p+'started.json');process=obj(p+'process.json')
|
||||
build=builds[spec['guiBuild']];artifact=artifacts[spec['guiBuild']]
|
||||
need(all(r[k]==v for k,v in start.items()),run+' start/result relationship')
|
||||
need(r['exitCode']==0 and r['inputUnchanged'] is True and r['binary']==r['binaryAfter']==artifact['readback'],run+' native closure and binary pin')
|
||||
need(r['buildReceipt']==pin(craw('cargo/'+spec['guiBuild']+'/result.json')) and r['buildHead']==build['head'],run+' GUI build relationship')
|
||||
need(r['command']==[artifact['cargo']['executable']] and process['pid']>0,run+' native command/PID')
|
||||
need(ordered(build['finish'],r['start'],process['observed'],r['finish']),run+' native times')
|
||||
need(r['helper']==pin(raw(spec['helpers']['runner'])) and r['captureHelper']==pin(raw(spec['helpers']['launchCapture'])),run+' launch helper pins')
|
||||
need(p+'capture-function.js' in m['members'],run+' actual capture-function record')
|
||||
need(r['inputs']==pin(raw(p+'inputs.json')) and obj(p+'inputs.json')==fixtures,run+' unchanged fixture bytes')
|
||||
for stream in ('stdout','stderr'):need(not raw(p+stream+'.log') and r[stream]==pin(raw(p+stream+'.log')),run+' empty '+stream)
|
||||
images=[]
|
||||
actual_captures=[n for n in m['members'] if n.startswith(p) and n.endswith('.json') and isinstance(obj(n),dict) and 'images' in obj(n)]
|
||||
need(set(actual_captures)==set(spec['captures']) and len(actual_captures)==len(spec['captures']),run+' exact metadata coverage')
|
||||
for n in spec['captures']:
|
||||
frame=obj(n);need(ordered(r['start'],frame['time'],r['finish']),n+' frame timestamp')
|
||||
need(frame['window']['app'].replace('\\','/')=='process:'+r['command'][0].replace('\\','/'),n+' window executable path')
|
||||
for image in frame['images']:
|
||||
name=p+image['file'];b=raw(name)
|
||||
need(image['width']>0 and image['height']>0 and (b.startswith(b'\xff\xd8') or b.startswith(b'\x89PNG')),n+' image record')
|
||||
images.append(name)
|
||||
actual_images={n for n in m['members'] if n.startswith(p) and n.endswith(('.jpg','.png'))}
|
||||
need(len(images)==len(set(images)) and set(images)==actual_images,run+' all original images')
|
||||
capture_count+=len(spec['captures']);image_count+=len(images)
|
||||
actual_snapshots={n.split('/')[3] for n in m['members'] if n.startswith(p+'snapshots/')}
|
||||
need(actual_snapshots==set(spec['snapshots']),run+' snapshot inventory')
|
||||
for label,state in spec['snapshots'].items():
|
||||
q=p+'snapshots/'+label+'/';s=obj(q+'result.json');ss=obj(q+'started.json');report=obj(q+'stdout.log')
|
||||
checker=artifacts['lifecycle-producer-build-2']
|
||||
need(all(s[k]==v for k,v in ss.items()) and s['exitCode']==0 and s['binaryUnchanged'] is True and s['workingSourceUnchanged'] is True,label+' snapshot closure')
|
||||
need(ordered(r['start'],s['time'],r['finish']),label+' snapshot start during trial')
|
||||
need(s['binary']==checker['readback'] and s['buildReceipt']==pin(craw('cargo/lifecycle-producer-build-2/result.json')),label+' corrected checker build')
|
||||
need(s['helper']==pin(raw(spec['helpers']['snapshot'])),label+' snapshot helper pin')
|
||||
need(s['source']==directory(q+'source/'),label+' exact saved source bytes')
|
||||
for stream in ('stdout','stderr'):need(s[stream]==pin(raw(q+stream+'.log')),label+' '+stream)
|
||||
need(not raw(q+'stderr.log'),label+' empty snapshot stderr')
|
||||
cmd=s['command'];need(cmd[:3]==[checker['cargo']['executable'],'check',state] and mapped(cmd[3])==q+'source/original.arcadia',label+' checker command')
|
||||
need(report['status']=='PASS' and report['comparison']=='complete Project equality' and report['state']==state and report['entry']==cmd[3],label+' accepted model oracle')
|
||||
opts=cmd[4:];need(len(opts)%2==0,label+' option pairs')
|
||||
for flag,path in zip(opts[::2],opts[1::2]):
|
||||
need(flag in ('--identities','--write-identities'),label+' identity-only options')
|
||||
need(obj(mapped(path))==report['new_ids'],label+' exact identity receipt')
|
||||
if state.endswith(('-reopened','-noop')):need('--identities' in opts and report['trace_identity_pinned'] is True,label+' continuity identity pin')
|
||||
if state.endswith('-relinked'):need('--write-identities' in opts and mapped(report['identity_receipt'])==mapped(opts[opts.index('--write-identities')+1]),label+' initial accepted identities')
|
||||
snapshots[(run,label)]=s['source'];reports[(run,label)]=report
|
||||
print(run,'captures',len(spec['captures']),'images',len(images),'accepted snapshots',len(spec['snapshots']))
|
||||
for side,first,later in [('oa','lifecycle-2','lifecycle-3'),('sa','lifecycle-4','lifecycle-5')]:
|
||||
base=snapshots[(first,side+'-relinked')];ids=reports[(first,side+'-relinked')]['new_ids']
|
||||
for phase in ('reopened','noop'):
|
||||
key=(later,side+'-'+phase)
|
||||
need(snapshots[key]==base and reports[key]['new_ids']==ids,side+' saved bytes and identity continuity '+phase)
|
||||
print('PASS: 4 closed trials;',len(snapshots),'accepted full-model receipts;',capture_count,'capture records;',image_count,'original images;',len(pairs),'linked source capsules.')
|
||||
print('No UI history authentication or checker re-execution; snapshot receipts lack completion timestamps. Frame dimensions vary.')
|
||||
if __name__=='__main__':main()
|
||||
+434
@@ -0,0 +1,434 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.189"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93e7820bc0a80a0238e650327316f929ba18d5be054b647490a3a6a339f3e7c0"
|
||||
dependencies = [
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oa-core"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"num-bigint",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"same-file",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1_smol"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "transition-native-lifecycle"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"oa-core",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"js-sys",
|
||||
"serde_core",
|
||||
"sha1_smol",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "transition-native-lifecycle"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
[workspace]
|
||||
|
||||
[dependencies]
|
||||
oa-core = { path = "../../../../crates/oa-core" }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
@@ -0,0 +1,154 @@
|
||||
# Native transition lifecycle fixture and oracle
|
||||
|
||||
This isolated producer/checker prepares native OA↔SA acceptance work. It does
|
||||
not automate the application. The project owns no production code. It has its
|
||||
own Cargo workspace and depends on this checkout's `oa-core` by relative path.
|
||||
|
||||
## Commands
|
||||
|
||||
From the repository root, with the configured Rust toolchain and compiler gate:
|
||||
|
||||
```powershell
|
||||
cargo run --manifest-path harness/experiments/transition-native-lifecycle-20260913/producer/Cargo.toml -- generate .local/transition-native/lifecycle-input-1
|
||||
cargo run --manifest-path harness/experiments/transition-native-lifecycle-20260913/producer/Cargo.toml -- check original .local/transition-native/lifecycle-input-1/original.arcadia
|
||||
```
|
||||
|
||||
The parent owns compilation, execution and native verification. These commands
|
||||
are instructions, not evidence that they have run. Generation refuses to
|
||||
overwrite an existing deliverable; use a new output directory for a new run.
|
||||
All fixture identities are reproducible UUIDv5 values of fixed aliases in one
|
||||
fixed namespace. Generating into a second directory must reproduce file bytes.
|
||||
|
||||
`generate OUTDIR` validates and round-trips each expected project, then emits:
|
||||
|
||||
- `original.arcadia`, `original.json`, and `manifest.json` with owned removal IDs.
|
||||
- `oa-deleted.arcadia` / `.json` and `sa-deleted.arcadia` / `.json`.
|
||||
- `oa-recreated.arcadia` / `.json` and `sa-recreated.arcadia` / `.json`.
|
||||
- `oa-recreate-root.snippet` and `sa-recreate-root.snippet`.
|
||||
|
||||
The deleted/recreated files are independent expected artifacts. Opening them
|
||||
does not establish that the native deletion, Undo or editing operation works.
|
||||
Each snippet contains only its replacement root declaration, with the original
|
||||
alias, name and description, a deterministic new UUID, no children, and no
|
||||
placement. Generation verifies that inserting it immediately before the deleted
|
||||
project's final closing brace compiles to the exact expected recreated Project.
|
||||
|
||||
`check STATE SAVED_ENTRY [--placements PATH] [--identities PATH]
|
||||
[--write-identities PATH]` loads the actual saved entry and its reachable
|
||||
includes through the public disk loader/compiler. It rejects any source bundle
|
||||
changed by compilation, including declarations with missing UUIDs. An identity
|
||||
inserted only into the compiler's memory cannot prove a saved identity. States:
|
||||
|
||||
| State | Required saved model |
|
||||
| --- | --- |
|
||||
| `original` | Entire original Project, including every original UUID |
|
||||
| `oa-deleted`, `sa-deleted` | Explicit source-owned removals; opposite design and shared definitions retained exactly |
|
||||
| `oa-recreated`, `sa-recreated` | Deleted state plus only the original source root under its pinned replacement UUID |
|
||||
| `oa-relinked`, `sa-relinked` | First relink: recreated state plus exactly one new refinement trace, with both existing prose values acknowledged |
|
||||
| `oa-reopened`, `sa-reopened` | Same relinked model; requires `--identities` with its previously accepted trace UUID |
|
||||
| `oa-noop`, `sa-noop` | Same relinked model after unchanged retry; requires `--identities` with its previously accepted trace UUID |
|
||||
|
||||
The checker compares every Project field, including IDs, aliases, prose, typed
|
||||
bodies, ownership, item/port contracts, relations, trace baselines, diagram
|
||||
geometry/routes and artifacts. A mismatch reports its first differing JSON path.
|
||||
No deletion impact helper, transition planner, or observed post-deletion state
|
||||
constructs the expected removal set. Relinking expectations construct a new
|
||||
Trace directly. The source UUID is always the deterministic snippet UUID unless
|
||||
an explicit `--identities` file supplies another source UUID. Only the first
|
||||
`*-relinked` check can observe a fresh trace UUID. Use `--write-identities PATH`
|
||||
on that successful check to write a receipt, then pass that exact receipt using
|
||||
`--identities PATH` for reopening and no-op checks. Those stages refuse to run
|
||||
without a pinned trace UUID and reject any replacement of source, trace or
|
||||
declared placement identities. Receipt writes refuse overwrite and happen only
|
||||
after complete-model comparison succeeds.
|
||||
|
||||
An identity receipt is a JSON object with `source` and, after relinking, `trace`
|
||||
UUID strings. Optional declared placements add `placement:ALIAS` UUID entries.
|
||||
An explicitly authored source-only identity file can establish a different
|
||||
GUI-created root UUID for initial recreation/relinking. Do not re-extract it
|
||||
from later saves. The checker rejects unused identity entries and reports whether
|
||||
trace identity was pinned; the first relink establishes identity evidence but
|
||||
does not itself prove preservation across save/reopen.
|
||||
|
||||
Example using the already compiled producer executable:
|
||||
|
||||
```powershell
|
||||
transition-native-lifecycle check oa-relinked saved-first-relink.arcadia --write-identities first-relink.identities.json
|
||||
transition-native-lifecycle check oa-reopened saved-after-reopen.arcadia --identities first-relink.identities.json
|
||||
transition-native-lifecycle check oa-noop saved-after-noop.arcadia --identities first-relink.identities.json
|
||||
```
|
||||
|
||||
## Native workflow
|
||||
|
||||
| Layer | Root to select | Alias | Diagrams |
|
||||
| --- | --- | --- | --- |
|
||||
| OA | Coordinate distress response | `oa_response` | `oa_work`, `oa_overview` |
|
||||
| SA | Deliver coordinated response | `sa_response` | `sa_work`, `sa_overview` |
|
||||
|
||||
Both roots own two work children with an internal report exchange. The SA
|
||||
children own typed report input/output ports. Shared report definitions have
|
||||
Flow mechanisms and String payload fields. OA intentionally has no ports.
|
||||
Both layers have an expanded decomposition and overview diagram, authored
|
||||
placements, exchange routing/labels, and artifacts on roots, children,
|
||||
exchanges, placements and shared items. The initial correspondence and its
|
||||
artifact must disappear when either root is deleted. Shared item definitions,
|
||||
their fields/evidence, diagrams and the independent System remain.
|
||||
|
||||
1. Open `original.arcadia`. Save a native snapshot and check `original`.
|
||||
2. Graphically delete **Coordinate distress response** (`oa_response`). Save a
|
||||
snapshot and check `oa-deleted`. Inspect the complete retained SA design.
|
||||
3. Native Undo, save another snapshot and check `original`. This checks the
|
||||
actual saved result, including original IDs and all owned records.
|
||||
4. Delete OA again. In the native Textual model editor, insert the inspected
|
||||
`oa-recreate-root.snippet` immediately before the project's final `}`. Apply,
|
||||
save, and check `oa-recreated`. Do not open the generated recreated fixture
|
||||
as a substitute for this editing step.
|
||||
5. Select the newly recreated `oa_response` and start OA→SA transition. Choose
|
||||
existing `sa_response` explicitly. Select **Keep target** for both Name and
|
||||
Description; do not request child/required dependency creation. The existing
|
||||
target already has placements. Apply, save, and check `oa-relinked` with
|
||||
`--write-identities first-relink.identities.json`.
|
||||
6. Reopen the saved result and save a fresh snapshot; check `oa-reopened` with
|
||||
`--identities first-relink.identities.json`.
|
||||
Re-preview unchanged and inspect reuse/no changes. Applying any no-op and
|
||||
saving must pass `oa-noop` with the same original identity receipt. Do not
|
||||
substitute another unpinned `oa-relinked` observation for these later checks.
|
||||
7. For the reverse workflow, start again from `original.arcadia`, delete
|
||||
`sa_response`, use the SA snippet, and explicitly relink to retained
|
||||
`oa_response`, using the corresponding `sa-*` checker states.
|
||||
|
||||
These instructions separate source recreation in the text editor from relinking
|
||||
through the graphical transition dialog, avoiding unpredictable canvas placement
|
||||
side effects. The newly recreated root deliberately has no children or placement.
|
||||
|
||||
## Optional explicitly authored source placement
|
||||
|
||||
If a separate GUI recreation task intentionally creates a placement, pass
|
||||
`--placements` with an expectation file shaped like `oa-placement.example.json`. Set its diagram, alias,
|
||||
position, size and collapsed state to the intended interaction inputs. Only the
|
||||
new placement UUID is observed during the first check; all its other fields are
|
||||
compared with the authored expectation. It must reference the new source root
|
||||
and use the existing source-layer work/overview diagram. Undeclared placements,
|
||||
changed target geometry and extra artifacts fail comparison. The snippet
|
||||
workflow requires no placement expectation file. Later checks must supply the
|
||||
same placement specifications and the saved identity receipt; the placement UUID
|
||||
is then pinned rather than re-learned from the new saved model.
|
||||
|
||||
## Explicit incompleteness and limits
|
||||
|
||||
The architecture fixture deliberately omits performer allocations and
|
||||
process/chain/scenario contexts. Admission requires exactly `unallocated_leaf`
|
||||
(`allocation`) and `work_without_behavior_context` (no field) on each surviving
|
||||
leaf work UUID, and `exchange_without_behavior_context` (no field) on each
|
||||
surviving work exchange UUID. Errors, extra warnings, wrong warning owners and
|
||||
duplicate warnings fail. System and data contracts are modeled explicitly.
|
||||
The disk loader and source compiler may report these same engineering warnings.
|
||||
Their diagnostic multisets must exactly match the validated model warnings by
|
||||
severity, code, object UUID and field. Additional loading warnings, missing or
|
||||
duplicate warnings, and all source-loading errors remain failures.
|
||||
|
||||
Whole-model comparison and source/JSON round trips do not prove usability,
|
||||
authored comment/whitespace preservation, native Undo without saving, or HTTP
|
||||
retry behavior. The parent must record actual native interactions, executable
|
||||
provenance, saved snapshots and checker results. This tool does not perform
|
||||
native interaction or publish an acceptance result itself.
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
[
|
||||
{
|
||||
"diagram": "oa_work",
|
||||
"alias": "view_coordinate_distress_response",
|
||||
"position": { "x": 60.0, "y": 60.0 },
|
||||
"size": { "width": 224.0, "height": 94.0 },
|
||||
"collapsed": false
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,914 @@
|
||||
use oa_core::{
|
||||
language, source_files,
|
||||
source_project::{ProjectLimits, SourceBundle, compile_bundle},
|
||||
validation::{self, Severity},
|
||||
*,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::BTreeMap, fs, io::Write, path::Path};
|
||||
|
||||
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||
|
||||
fn id(alias: &str) -> Id {
|
||||
Id::new_v5(
|
||||
&Id::from_u128(0xc1192026_0913_5000_8000_000000000028),
|
||||
alias.as_bytes(),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Side {
|
||||
prefix: String,
|
||||
root: Id,
|
||||
elements: Vec<Id>,
|
||||
exchange: Id,
|
||||
placements: Vec<(Id, Id)>,
|
||||
artifacts: Vec<Id>,
|
||||
}
|
||||
|
||||
struct Fixture {
|
||||
project: Project,
|
||||
oa: Side,
|
||||
sa: Side,
|
||||
}
|
||||
|
||||
fn add(
|
||||
p: &mut Project,
|
||||
kind: Kind,
|
||||
layer: Layer,
|
||||
alias: &str,
|
||||
name: &str,
|
||||
parent: Option<Id>,
|
||||
) -> Id {
|
||||
let key = id(alias);
|
||||
assert!(
|
||||
p.elements
|
||||
.insert(
|
||||
key,
|
||||
Element {
|
||||
id: key,
|
||||
alias: alias.into(),
|
||||
name: name.into(),
|
||||
description: String::new(),
|
||||
parent,
|
||||
body: ElementBody::new(kind, layer).unwrap(),
|
||||
}
|
||||
)
|
||||
.is_none()
|
||||
);
|
||||
key
|
||||
}
|
||||
|
||||
fn artifact(p: &mut Project, owner: Id, alias: &str, name: &str) -> Id {
|
||||
let key = id(alias);
|
||||
p.artifact_links.insert(
|
||||
key,
|
||||
ArtifactLink {
|
||||
id: key,
|
||||
alias: alias.into(),
|
||||
name: name.into(),
|
||||
description: "Reviewed evidence must survive opposite-layer deletion.".into(),
|
||||
owner,
|
||||
relation: ArtifactRelation::References,
|
||||
uri: "git:response/design.md".into(),
|
||||
revision: Some("review-17".into()),
|
||||
fragment: Some("response-handling".into()),
|
||||
},
|
||||
);
|
||||
key
|
||||
}
|
||||
|
||||
fn port(p: &mut Project, alias: &str, owner: Id, item: Id, output: bool) -> Id {
|
||||
let key = id(alias);
|
||||
p.elements.insert(
|
||||
key,
|
||||
Element {
|
||||
id: key,
|
||||
alias: alias.into(),
|
||||
name: if output {
|
||||
"Report output"
|
||||
} else {
|
||||
"Report input"
|
||||
}
|
||||
.into(),
|
||||
description: "Retain the typed report contract.".into(),
|
||||
parent: Some(owner),
|
||||
body: ElementBody::shared_in(
|
||||
Layer::Sa,
|
||||
SharedBody::Port {
|
||||
contract: if output {
|
||||
PortContract::FunctionOutput {
|
||||
outgoing_items: vec![item],
|
||||
}
|
||||
} else {
|
||||
PortContract::FunctionInput {
|
||||
incoming_items: vec![item],
|
||||
}
|
||||
},
|
||||
legacy: None,
|
||||
},
|
||||
),
|
||||
},
|
||||
);
|
||||
key
|
||||
}
|
||||
|
||||
fn side(p: &mut Project, layer: Layer) -> Side {
|
||||
let (prefix, kind, root_name, send_name, receive_name, description) = match layer {
|
||||
Layer::Oa => (
|
||||
"oa",
|
||||
Kind::Activity,
|
||||
"Coordinate distress response",
|
||||
"Report distress",
|
||||
"Assess incident",
|
||||
"Coordinate responders while preserving operational responsibility.",
|
||||
),
|
||||
Layer::Sa => (
|
||||
"sa",
|
||||
Kind::Function,
|
||||
"Deliver coordinated response",
|
||||
"Transmit report",
|
||||
"Classify incident",
|
||||
"Technical design: preserve independent processing and report contracts.",
|
||||
),
|
||||
};
|
||||
let root = add(
|
||||
p,
|
||||
kind,
|
||||
layer,
|
||||
&format!("{prefix}_response"),
|
||||
root_name,
|
||||
None,
|
||||
);
|
||||
p.elements.get_mut(&root).unwrap().description = description.into();
|
||||
let send = add(
|
||||
p,
|
||||
kind,
|
||||
layer,
|
||||
&format!("{prefix}_send"),
|
||||
send_name,
|
||||
Some(root),
|
||||
);
|
||||
let receive = add(
|
||||
p,
|
||||
kind,
|
||||
layer,
|
||||
&format!("{prefix}_receive"),
|
||||
receive_name,
|
||||
Some(root),
|
||||
);
|
||||
p.elements.get_mut(&send).unwrap().description =
|
||||
"Keep the detailed sending responsibility.".into();
|
||||
p.elements.get_mut(&receive).unwrap().description =
|
||||
"Keep the detailed receiving responsibility.".into();
|
||||
let item = add(
|
||||
p,
|
||||
Kind::ExchangeItem,
|
||||
layer,
|
||||
&format!("{prefix}_report"),
|
||||
"Incident report",
|
||||
None,
|
||||
);
|
||||
*p.elements.get_mut(&item).unwrap().shared_mut().unwrap() = SharedBody::ExchangeItem {
|
||||
kind: ExchangeItemKind::Information,
|
||||
mechanism: ExchangeMechanism::Flow,
|
||||
supertype: None,
|
||||
};
|
||||
add(
|
||||
p,
|
||||
Kind::DataField,
|
||||
layer,
|
||||
&format!("{prefix}_report_text"),
|
||||
"Report text",
|
||||
Some(item),
|
||||
);
|
||||
let mut elements = vec![root, send, receive];
|
||||
let body = if layer == Layer::Sa {
|
||||
let output = port(p, "sa_send_output", send, item, true);
|
||||
let input = port(p, "sa_receive_input", receive, item, false);
|
||||
elements.extend([output, input]);
|
||||
RelationBody::FunctionalExchange {
|
||||
source: send,
|
||||
target: receive,
|
||||
source_port: output,
|
||||
target_port: input,
|
||||
items: vec![item],
|
||||
}
|
||||
} else {
|
||||
RelationBody::Interaction {
|
||||
source: send,
|
||||
target: receive,
|
||||
items: vec![item],
|
||||
}
|
||||
};
|
||||
let exchange_alias = format!("{prefix}_report_delivery");
|
||||
let exchange = id(&exchange_alias);
|
||||
p.relations.insert(
|
||||
exchange,
|
||||
Relation {
|
||||
id: exchange,
|
||||
alias: exchange_alias,
|
||||
name: "Deliver incident report".into(),
|
||||
description: "Preserve direction, item identity and endpoint contracts.".into(),
|
||||
body,
|
||||
},
|
||||
);
|
||||
let mut placements = vec![];
|
||||
let mut artifacts = vec![
|
||||
artifact(
|
||||
p,
|
||||
root,
|
||||
&format!("{prefix}_root_evidence"),
|
||||
"Response rationale",
|
||||
),
|
||||
artifact(
|
||||
p,
|
||||
send,
|
||||
&format!("{prefix}_send_evidence"),
|
||||
"Sending requirement",
|
||||
),
|
||||
artifact(
|
||||
p,
|
||||
exchange,
|
||||
&format!("{prefix}_exchange_evidence"),
|
||||
"Report contract evidence",
|
||||
),
|
||||
];
|
||||
for (suffix, title, projection) in [
|
||||
(
|
||||
"work",
|
||||
"Response decomposition",
|
||||
ProjectionKind::Decomposition,
|
||||
),
|
||||
("overview", "Response overview", ProjectionKind::Overview),
|
||||
] {
|
||||
let alias = format!("{prefix}_{suffix}");
|
||||
let diagram = id(&alias);
|
||||
let mut nodes = BTreeMap::new();
|
||||
for (index, (element, short)) in [(root, "root"), (send, "send"), (receive, "receive")]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
let node_alias = format!("{prefix}_{suffix}_{short}");
|
||||
let node_id = id(&node_alias);
|
||||
nodes.insert(
|
||||
node_id,
|
||||
NodePlacement {
|
||||
id: node_id,
|
||||
alias: node_alias,
|
||||
element,
|
||||
position: match index {
|
||||
0 => Position { x: 40., y: 40. },
|
||||
1 => Position { x: 80., y: 120. },
|
||||
_ => Position { x: 400., y: 120. },
|
||||
},
|
||||
size: if index == 0 {
|
||||
Size {
|
||||
width: 660.,
|
||||
height: 240.,
|
||||
}
|
||||
} else {
|
||||
Size {
|
||||
width: 224.,
|
||||
height: 94.,
|
||||
}
|
||||
},
|
||||
collapsed: false,
|
||||
port_anchors: vec![],
|
||||
},
|
||||
);
|
||||
placements.push((diagram, node_id));
|
||||
if index == 0 {
|
||||
artifacts.push(artifact(
|
||||
p,
|
||||
node_id,
|
||||
&format!("{prefix}_{suffix}_layout_evidence"),
|
||||
"Reviewed layout",
|
||||
));
|
||||
}
|
||||
}
|
||||
p.diagrams.insert(
|
||||
diagram,
|
||||
Diagram {
|
||||
id: diagram,
|
||||
alias,
|
||||
name: format!("{} {title}", layer),
|
||||
description: "Inspect the response root, its children and their original exchange."
|
||||
.into(),
|
||||
layer,
|
||||
projection,
|
||||
view_options: Default::default(),
|
||||
nodes,
|
||||
routes: vec![EdgeRoute {
|
||||
edge: exchange,
|
||||
waypoints: vec![Position { x: 360., y: 167. }],
|
||||
label: Some(EdgeLabel {
|
||||
fraction: 0.55,
|
||||
offset: Position { x: 0., y: -18. },
|
||||
}),
|
||||
}],
|
||||
},
|
||||
);
|
||||
}
|
||||
artifact(
|
||||
p,
|
||||
item,
|
||||
&format!("{prefix}_item_evidence"),
|
||||
"Shared report definition",
|
||||
);
|
||||
Side {
|
||||
prefix: prefix.into(),
|
||||
root,
|
||||
elements,
|
||||
exchange,
|
||||
placements,
|
||||
artifacts,
|
||||
}
|
||||
}
|
||||
|
||||
fn fixture() -> Fixture {
|
||||
let mut project = Project::empty();
|
||||
project.id = id("response_lifecycle");
|
||||
project.alias = "response_lifecycle".into();
|
||||
project.name = "Distress response: transition lifecycle".into();
|
||||
project.description =
|
||||
"Native acceptance fixture for preserving opposite-layer engineering work.".into();
|
||||
add(
|
||||
&mut project,
|
||||
Kind::System,
|
||||
Layer::Sa,
|
||||
"response_system",
|
||||
"Response support system",
|
||||
None,
|
||||
);
|
||||
let oa = side(&mut project, Layer::Oa);
|
||||
let sa = side(&mut project, Layer::Sa);
|
||||
let key = id("response_refinement");
|
||||
project.traces.insert(
|
||||
key,
|
||||
Trace {
|
||||
id: key,
|
||||
alias: "response_refinement".into(),
|
||||
name: "Reviewed response refinement".into(),
|
||||
description: "Operational and technical prose are intentionally different.".into(),
|
||||
oa: oa.root,
|
||||
sa: sa.root,
|
||||
kind: TraceKind::Refinement,
|
||||
baseline: Some(TraceBaseline {
|
||||
oa: project.properties(oa.root).unwrap(),
|
||||
sa: project.properties(sa.root).unwrap(),
|
||||
semantic: None,
|
||||
}),
|
||||
},
|
||||
);
|
||||
artifact(
|
||||
&mut project,
|
||||
key,
|
||||
"response_trace_evidence",
|
||||
"Review decision",
|
||||
);
|
||||
Fixture { project, oa, sa }
|
||||
}
|
||||
|
||||
// The removal oracle enumerates fixture-owned records. No deletion command,
|
||||
// impact helper, transition planner or observed post-deletion model is used.
|
||||
fn deleted(f: &Fixture, side: &Side) -> Project {
|
||||
let mut p = f.project.clone();
|
||||
for key in &side.elements {
|
||||
assert!(p.elements.remove(key).is_some());
|
||||
}
|
||||
assert!(p.relations.remove(&side.exchange).is_some());
|
||||
assert!(p.traces.remove(&id("response_refinement")).is_some());
|
||||
for key in side
|
||||
.artifacts
|
||||
.iter()
|
||||
.copied()
|
||||
.chain([id("response_trace_evidence")])
|
||||
{
|
||||
assert!(p.artifact_links.remove(&key).is_some());
|
||||
}
|
||||
for (diagram, node) in &side.placements {
|
||||
assert!(
|
||||
p.diagrams
|
||||
.get_mut(diagram)
|
||||
.unwrap()
|
||||
.nodes
|
||||
.remove(node)
|
||||
.is_some()
|
||||
);
|
||||
}
|
||||
for suffix in ["work", "overview"] {
|
||||
let diagram = p
|
||||
.diagrams
|
||||
.get_mut(&id(&format!("{}_{suffix}", side.prefix)))
|
||||
.unwrap();
|
||||
assert_eq!(diagram.routes.len(), 1);
|
||||
assert_eq!(diagram.routes[0].edge, side.exchange);
|
||||
diagram.routes.clear();
|
||||
}
|
||||
p
|
||||
}
|
||||
|
||||
fn accepted(p: &Project) -> Result<Vec<validation::Issue>> {
|
||||
// Intentional isolated architecture: no allocations, processes or scenarios.
|
||||
// Require the exact warning multiset by record UUID, code and field.
|
||||
let issues = validation::validate(p);
|
||||
if issues.iter().any(|i| i.severity != Severity::Warning) {
|
||||
return Err(format!("Structural validation failed: {issues:?}").into());
|
||||
}
|
||||
let mut expected = vec![];
|
||||
for e in p.elements.values().filter(|e| {
|
||||
matches!(e.kind(), Kind::Activity | Kind::Function)
|
||||
&& !p
|
||||
.elements
|
||||
.values()
|
||||
.any(|child| child.parent == Some(e.id) && child.kind() == e.kind())
|
||||
}) {
|
||||
expected.push(("unallocated_leaf", Some(e.id), Some("allocation")));
|
||||
expected.push(("work_without_behavior_context", Some(e.id), None));
|
||||
}
|
||||
for r in p.relations.values().filter(|r| {
|
||||
matches!(
|
||||
r.kind(),
|
||||
RelationKind::Interaction | RelationKind::FunctionalExchange
|
||||
)
|
||||
}) {
|
||||
expected.push(("exchange_without_behavior_context", Some(r.id), None));
|
||||
}
|
||||
let mut actual = issues
|
||||
.iter()
|
||||
.map(|i| (i.code.as_str(), i.element, i.field.as_deref()))
|
||||
.collect::<Vec<_>>();
|
||||
expected.sort();
|
||||
actual.sort();
|
||||
if expected != actual {
|
||||
return Err(
|
||||
format!("Unexpected warnings: expected {expected:?}; actual {actual:?}").into(),
|
||||
);
|
||||
}
|
||||
Ok(issues)
|
||||
}
|
||||
|
||||
fn compile(source: String) -> Result<Project> {
|
||||
let bundle = SourceBundle {
|
||||
entry: "project.arcadia".into(),
|
||||
files: BTreeMap::from([("project.arcadia".into(), source)]),
|
||||
};
|
||||
let result = compile_bundle(&bundle, ProjectLimits::default())
|
||||
.map_err(|e| format!("Source compile: {e:?}"))?;
|
||||
Ok(result.decoded.project)
|
||||
}
|
||||
|
||||
fn round_trip(p: &Project) -> Result<String> {
|
||||
accepted(p)?;
|
||||
let bytes = serde_json::to_vec_pretty(p)?;
|
||||
compare(p, &persistence::decode(&bytes)?)?;
|
||||
let source = language::format(p);
|
||||
compare(p, &compile(source.clone())?)?;
|
||||
Ok(source)
|
||||
}
|
||||
|
||||
fn recreated(f: &Fixture, side: &Side, new_id: Id) -> Project {
|
||||
assert!(!f.project.contains(new_id));
|
||||
let mut p = deleted(f, side);
|
||||
let mut root = f.project.elements[&side.root].clone();
|
||||
root.id = new_id;
|
||||
p.elements.insert(new_id, root);
|
||||
p
|
||||
}
|
||||
|
||||
fn snippet(f: &Fixture, side: &Side) -> String {
|
||||
let e = &f.project.elements[&side.root];
|
||||
let keyword = if side.prefix == "oa" {
|
||||
"oa_activity"
|
||||
} else {
|
||||
"sa_function"
|
||||
};
|
||||
format!(
|
||||
" {keyword} {} {} id \"{}\" {{\n description {};\n }}\n",
|
||||
e.alias,
|
||||
serde_json::to_string(&e.name).unwrap(),
|
||||
id(&format!("{}_recreated_response", side.prefix)),
|
||||
serde_json::to_string(&e.description).unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
fn generate(out: &Path) -> Result<()> {
|
||||
let f = fixture();
|
||||
let mut files: Vec<(String, Vec<u8>)> = vec![
|
||||
(
|
||||
"original.arcadia".into(),
|
||||
round_trip(&f.project)?.into_bytes(),
|
||||
),
|
||||
(
|
||||
"original.json".into(),
|
||||
serde_json::to_vec_pretty(&f.project)?,
|
||||
),
|
||||
(
|
||||
"manifest.json".into(),
|
||||
serde_json::to_vec_pretty(&serde_json::json!({
|
||||
"fixture": "transition-native-lifecycle-20260913", "oa": &f.oa, "sa": &f.sa,
|
||||
"trace": id("response_refinement"), "trace_artifact": id("response_trace_evidence"),
|
||||
"recreated_oa_id": id("oa_recreated_response"), "recreated_sa_id": id("sa_recreated_response")
|
||||
}))?,
|
||||
),
|
||||
];
|
||||
for side in [&f.oa, &f.sa] {
|
||||
let removal = deleted(&f, side);
|
||||
let removal_source = round_trip(&removal)?;
|
||||
let replacement = recreated(&f, side, id(&format!("{}_recreated_response", side.prefix)));
|
||||
let replacement_source = round_trip(&replacement)?;
|
||||
let fragment = snippet(&f, side);
|
||||
let (body, tail) = removal_source
|
||||
.rsplit_once('}')
|
||||
.ok_or("Missing project closing brace")?;
|
||||
compare(&replacement, &compile(format!("{body}{fragment}}}{tail}"))?)?;
|
||||
files.extend([
|
||||
(
|
||||
format!("{}-deleted.json", side.prefix),
|
||||
serde_json::to_vec_pretty(&removal)?,
|
||||
),
|
||||
(
|
||||
format!("{}-deleted.arcadia", side.prefix),
|
||||
removal_source.into_bytes(),
|
||||
),
|
||||
(
|
||||
format!("{}-recreated.json", side.prefix),
|
||||
serde_json::to_vec_pretty(&replacement)?,
|
||||
),
|
||||
(
|
||||
format!("{}-recreated.arcadia", side.prefix),
|
||||
replacement_source.into_bytes(),
|
||||
),
|
||||
(
|
||||
format!("{}-recreate-root.snippet", side.prefix),
|
||||
fragment.into_bytes(),
|
||||
),
|
||||
]);
|
||||
}
|
||||
if files.iter().any(|(name, _)| out.join(name).exists()) {
|
||||
return Err(
|
||||
"Generation refuses to overwrite fixture files; use a fresh output directory".into(),
|
||||
);
|
||||
}
|
||||
fs::create_dir_all(out)?;
|
||||
for (name, bytes) in files {
|
||||
fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(out.join(name))?
|
||||
.write_all(&bytes)?;
|
||||
}
|
||||
println!(
|
||||
"Generated and round-trip verified both directions in {}",
|
||||
out.display()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct PlacementExpectation {
|
||||
diagram: String,
|
||||
alias: String,
|
||||
position: Position,
|
||||
size: Size,
|
||||
collapsed: bool,
|
||||
}
|
||||
|
||||
fn first_difference(
|
||||
expected: &serde_json::Value,
|
||||
actual: &serde_json::Value,
|
||||
path: &str,
|
||||
) -> Option<String> {
|
||||
use serde_json::Value;
|
||||
if expected == actual {
|
||||
return None;
|
||||
}
|
||||
match (expected, actual) {
|
||||
(Value::Object(a), Value::Object(b)) => {
|
||||
for key in a.keys().chain(b.keys()) {
|
||||
match (a.get(key), b.get(key)) {
|
||||
(Some(x), Some(y)) => {
|
||||
if let Some(diff) = first_difference(x, y, &format!("{path}.{key}")) {
|
||||
return Some(diff);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Some(format!(
|
||||
"{path}.{key}: expected presence {}, actual presence {}",
|
||||
a.contains_key(key),
|
||||
b.contains_key(key)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(Value::Array(a), Value::Array(b)) if a.len() == b.len() => {
|
||||
for (index, (x, y)) in a.iter().zip(b).enumerate() {
|
||||
if let Some(diff) = first_difference(x, y, &format!("{path}[{index}]")) {
|
||||
return Some(diff);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => return Some(format!("{path}: expected {expected}, actual {actual}")),
|
||||
}
|
||||
Some(format!("{path}: values differ"))
|
||||
}
|
||||
|
||||
fn compare(expected: &Project, actual: &Project) -> Result<()> {
|
||||
if expected != actual {
|
||||
return Err(first_difference(
|
||||
&serde_json::to_value(expected)?,
|
||||
&serde_json::to_value(actual)?,
|
||||
"Project",
|
||||
)
|
||||
.unwrap_or_else(|| "Project differs".into())
|
||||
.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check(
|
||||
state: &str,
|
||||
entry: &Path,
|
||||
placement_path: Option<&Path>,
|
||||
identity_path: Option<&Path>,
|
||||
receipt_path: Option<&Path>,
|
||||
) -> Result<()> {
|
||||
let f = fixture();
|
||||
let identity_expectations: Option<BTreeMap<String, Id>> = identity_path
|
||||
.map(|path| -> Result<_> { Ok(serde_json::from_slice(&fs::read(path)?)?) })
|
||||
.transpose()?;
|
||||
let disk = source_files::load_project(entry, ProjectLimits::default())?;
|
||||
if disk
|
||||
.diagnostics
|
||||
.iter()
|
||||
.any(|located| located.diagnostic.severity != Severity::Warning)
|
||||
{
|
||||
return Err(format!("Source loading errors: {:?}", disk.diagnostics).into());
|
||||
}
|
||||
let disk_bundle = disk.reachable_bundle();
|
||||
let compiled = compile_bundle(&disk_bundle, ProjectLimits::default())
|
||||
.map_err(|e| format!("Saved source compile: {e:?}"))?;
|
||||
if compiled.bundle != disk_bundle {
|
||||
return Err("Saved source is not identity-stable: compilation inserted UUIDs or changed source; save explicit identities before checking".into());
|
||||
}
|
||||
let actual = &compiled.decoded.project;
|
||||
let validated_warnings = accepted(actual)?;
|
||||
let mut expected_diagnostics = validated_warnings
|
||||
.iter()
|
||||
.map(|issue| {
|
||||
(
|
||||
issue.severity == Severity::Warning,
|
||||
issue.code.as_str(),
|
||||
issue.element,
|
||||
issue.field.as_deref(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
expected_diagnostics.sort();
|
||||
// Loading/compilation may return the fixture's intentional engineering
|
||||
// warnings. Admit exactly the independently validated warning multiset,
|
||||
// retaining owner, field and severity checks; no extra discovery warning or
|
||||
// error is silently discarded. Source location spelling is not semantic.
|
||||
for (stage, diagnostics) in [
|
||||
("loading", &disk.diagnostics),
|
||||
("compilation", &compiled.diagnostics),
|
||||
] {
|
||||
let mut actual_diagnostics = diagnostics
|
||||
.iter()
|
||||
.map(|located| {
|
||||
let diagnostic = &located.diagnostic;
|
||||
(
|
||||
diagnostic.severity == Severity::Warning,
|
||||
diagnostic.code.as_str(),
|
||||
diagnostic.object_id,
|
||||
diagnostic.field.as_deref(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
actual_diagnostics.sort();
|
||||
if actual_diagnostics != expected_diagnostics {
|
||||
return Err(format!("Unexpected source {stage} diagnostics: expected {expected_diagnostics:?}; actual {actual_diagnostics:?}").into());
|
||||
}
|
||||
}
|
||||
let mut generated_ids = BTreeMap::<String, Id>::new();
|
||||
let expected = if state == "original" {
|
||||
if placement_path.is_some() || identity_path.is_some() || receipt_path.is_some() {
|
||||
return Err("original does not accept placement/identity options".into());
|
||||
}
|
||||
f.project.clone()
|
||||
} else {
|
||||
let (prefix, phase) = state
|
||||
.split_once('-')
|
||||
.ok_or("State must be original or oa-/sa-deleted/recreated/relinked/reopened/noop")?;
|
||||
let side = match prefix {
|
||||
"oa" => &f.oa,
|
||||
"sa" => &f.sa,
|
||||
_ => return Err("Unknown source layer".into()),
|
||||
};
|
||||
if phase == "deleted" {
|
||||
if placement_path.is_some() || identity_path.is_some() || receipt_path.is_some() {
|
||||
return Err("deleted does not accept placement/identity options".into());
|
||||
}
|
||||
deleted(&f, side)
|
||||
} else if matches!(phase, "recreated" | "relinked" | "reopened" | "noop") {
|
||||
if matches!(phase, "reopened" | "noop")
|
||||
&& identity_expectations
|
||||
.as_ref()
|
||||
.and_then(|ids| ids.get("trace"))
|
||||
.is_none()
|
||||
{
|
||||
return Err("Reopened/noop checks require --identities with the first accepted relink trace UUID".into());
|
||||
}
|
||||
let original_root = &f.project.elements[&side.root];
|
||||
let new_source = match &identity_expectations {
|
||||
Some(ids) => *ids
|
||||
.get("source")
|
||||
.ok_or("Identity expectations must explicitly include source")?,
|
||||
None => id(&format!("{prefix}_recreated_response")),
|
||||
};
|
||||
let roots = actual
|
||||
.elements
|
||||
.values()
|
||||
.filter(|e| e.alias == original_root.alias)
|
||||
.collect::<Vec<_>>();
|
||||
if roots.len() != 1 || roots[0].id != new_source || f.project.contains(new_source) {
|
||||
return Err(
|
||||
"Expected exactly one recreated root with the original alias and the pinned new UUID (snippet UUID unless --identities supplied)"
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
generated_ids.insert("source".into(), new_source);
|
||||
let mut candidate = recreated(&f, side, new_source);
|
||||
if let Some(path) = placement_path {
|
||||
let specs: Vec<PlacementExpectation> = serde_json::from_slice(&fs::read(path)?)?;
|
||||
for spec in specs {
|
||||
if ![format!("{prefix}_work"), format!("{prefix}_overview")]
|
||||
.contains(&spec.diagram)
|
||||
{
|
||||
return Err("A new source placement must use its existing source-layer work/overview diagram".into());
|
||||
}
|
||||
let diagram = id(&spec.diagram);
|
||||
let nodes = actual
|
||||
.diagrams
|
||||
.get(&diagram)
|
||||
.ok_or("Expected diagram is missing")?
|
||||
.nodes
|
||||
.values()
|
||||
.filter(|node| node.alias == spec.alias)
|
||||
.collect::<Vec<_>>();
|
||||
if nodes.len() != 1 || f.project.contains(nodes[0].id) {
|
||||
return Err(
|
||||
"Expected one newly identified declared source placement".into()
|
||||
);
|
||||
}
|
||||
let receipt_key = format!("placement:{}", spec.alias);
|
||||
let key = match &identity_expectations {
|
||||
Some(ids) => *ids.get(&receipt_key).ok_or(
|
||||
"Identity receipt lacks an explicitly declared placement UUID",
|
||||
)?,
|
||||
None => nodes[0].id,
|
||||
};
|
||||
if nodes[0].id != key {
|
||||
return Err(
|
||||
"Source placement UUID changed from its identity expectation".into(),
|
||||
);
|
||||
}
|
||||
if candidate
|
||||
.diagrams
|
||||
.get_mut(&diagram)
|
||||
.unwrap()
|
||||
.nodes
|
||||
.insert(
|
||||
key,
|
||||
NodePlacement {
|
||||
id: key,
|
||||
alias: spec.alias.clone(),
|
||||
element: new_source,
|
||||
position: spec.position,
|
||||
size: spec.size,
|
||||
collapsed: spec.collapsed,
|
||||
port_anchors: vec![],
|
||||
},
|
||||
)
|
||||
.is_some()
|
||||
{
|
||||
return Err("Duplicate placement expectation".into());
|
||||
}
|
||||
generated_ids.insert(receipt_key, key);
|
||||
}
|
||||
}
|
||||
if phase != "recreated" {
|
||||
if actual.traces.len() != 1 {
|
||||
return Err("Relinking must create exactly one trace".into());
|
||||
}
|
||||
let actual_key = actual.traces.values().next().unwrap().id;
|
||||
let key = identity_expectations
|
||||
.as_ref()
|
||||
.and_then(|ids| ids.get("trace"))
|
||||
.copied()
|
||||
.unwrap_or(actual_key);
|
||||
if actual_key != key {
|
||||
return Err(
|
||||
"Refinement trace UUID changed from its accepted identity receipt".into(),
|
||||
);
|
||||
}
|
||||
if f.project.contains(key) || candidate.contains(key) {
|
||||
return Err("Expected a fresh trace UUID".into());
|
||||
}
|
||||
let (oa, sa) = if prefix == "oa" {
|
||||
(new_source, f.sa.root)
|
||||
} else {
|
||||
(f.oa.root, new_source)
|
||||
};
|
||||
candidate.traces.insert(
|
||||
key,
|
||||
Trace {
|
||||
id: key,
|
||||
alias: "trace_oa_response_sa_response".into(),
|
||||
name: format!(
|
||||
"{} ↔ {}",
|
||||
candidate.elements[&oa].name, candidate.elements[&sa].name
|
||||
),
|
||||
description: String::new(),
|
||||
oa,
|
||||
sa,
|
||||
kind: TraceKind::Refinement,
|
||||
baseline: Some(TraceBaseline {
|
||||
oa: candidate.properties(oa).unwrap(),
|
||||
sa: candidate.properties(sa).unwrap(),
|
||||
semantic: None,
|
||||
}),
|
||||
},
|
||||
);
|
||||
generated_ids.insert("trace".into(), key);
|
||||
}
|
||||
candidate
|
||||
} else {
|
||||
return Err("Unknown lifecycle phase".into());
|
||||
}
|
||||
};
|
||||
// No actual body, prose, ownership, geometry, alias or baseline is copied
|
||||
// into the expected model: only explicitly permitted fresh identities.
|
||||
compare(&expected, actual)?;
|
||||
round_trip(&expected)?;
|
||||
if let Some(ids) = &identity_expectations {
|
||||
for (label, expected_id) in ids {
|
||||
if generated_ids.get(label) != Some(expected_id) {
|
||||
return Err(format!("Unused or mismatching identity expectation: {label}").into());
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(path) = receipt_path {
|
||||
// Publish a receipt only after complete-model validation succeeds. Never
|
||||
// replace earlier accepted identity evidence with a new observation.
|
||||
fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(path)?
|
||||
.write_all(&serde_json::to_vec_pretty(&generated_ids)?)?;
|
||||
}
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&serde_json::json!({
|
||||
"status": "PASS", "state": state, "entry": entry,
|
||||
"comparison": "complete Project equality", "new_ids": generated_ids,
|
||||
"trace_identity_pinned": identity_expectations.as_ref().is_some_and(|ids| ids.contains_key("trace")),
|
||||
"identity_receipt": receipt_path,
|
||||
"recreated_id_matches_snippet": generated_ids.get("source").map(|key|
|
||||
*key == id(if state.starts_with("oa-") { "oa_recreated_response" } else { "sa_recreated_response" }))
|
||||
}))?
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run() -> Result<()> {
|
||||
let args = std::env::args().skip(1).collect::<Vec<_>>();
|
||||
match args.as_slice() {
|
||||
[command, out] if command == "generate" => generate(Path::new(out)),
|
||||
[command, state, entry, options @ ..] if command == "check" => {
|
||||
if options.len() % 2 != 0 { return Err("Every check option requires a path".into()); }
|
||||
let (mut placements, mut identities, mut receipt) = (None, None, None);
|
||||
for pair in options.chunks_exact(2) {
|
||||
let slot = match pair[0].as_str() {
|
||||
"--placements" => &mut placements,
|
||||
"--identities" => &mut identities,
|
||||
"--write-identities" => &mut receipt,
|
||||
_ => return Err(format!("Unknown check option: {}", pair[0]).into()),
|
||||
};
|
||||
if slot.replace(Path::new(&pair[1])).is_some() { return Err("Duplicate check option".into()); }
|
||||
}
|
||||
check(state, Path::new(entry), placements, identities, receipt)
|
||||
},
|
||||
_ => Err("Usage: transition-native-lifecycle generate OUTDIR | check STATE SAVED_ENTRY [--placements PATH] [--identities PATH] [--write-identities PATH]".into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Err(error) = run() {
|
||||
eprintln!("FAIL: {error}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
# Independent bounded transition review
|
||||
|
||||
Reviewer: `transition_picker_review`, 2026-09-22. This review inspected code,
|
||||
original native screenshots and closed-run/snapshot receipts. The reviewer did
|
||||
not operate the application, build it, rerun the checker, or alter the specimens.
|
||||
Only this review note was written. The parent owns execution and publication.
|
||||
|
||||
The reviewed production baseline is `d0b7c285018a516c6dc34c196d1359a861ddaeb3`
|
||||
plus the captured, initially uncommitted footer changes in
|
||||
`crates/oa-desktop/src/workflow.rs`. HEAD alone does not identify those changes.
|
||||
Lifecycle trials 2 and 3 identify GUI SHA-256
|
||||
`3813e79d84cf4a83f2290639b93c3daaa8de427b0268b288f87e4f535fa1aea2`;
|
||||
trials 4 and 5 identify final-footer GUI SHA-256
|
||||
`f0bb021e350bcc55435d29e3daf6a4b3dd7389620df99f5d169f9cb669fd53d0`.
|
||||
Exact build/source inventories belong to the accompanying evidence packages.
|
||||
|
||||
## Code and evaluator findings
|
||||
|
||||
The picker shares the core layer/category/kind predicate, preserves selected
|
||||
identity independently of search results, and keeps query state outside the
|
||||
request and recovery schema. Query events return before replanning. Compatibility
|
||||
does not imply that dependencies or the complete transition plan are valid.
|
||||
Distinct traced IDs, rather than trace-record count, determine inferred reuse;
|
||||
ambiguous traces remain an explicit decision. Manual tracing remains separate.
|
||||
|
||||
The desktop tests compare the whole request, plan and recovery snapshot during
|
||||
search, including generated preview identities. The initial second-query test
|
||||
used a target ID rather than a second rendered source card; native corrected
|
||||
picker frame 17 separately demonstrates two visible source cards with independent
|
||||
queries. These checks do not prove keyboard access or every focus transition.
|
||||
|
||||
The core lifecycle tests and [native producer/checker](producer/src/main.rs)
|
||||
construct expected deletions from fixture-owned identities, not a deletion
|
||||
impact helper or observed deletion result. Expected recreation adds only the
|
||||
replacement root. Expected relinking adds a directly constructed refinement
|
||||
trace and reviewed baseline while preserving target prose, bodies, ownership,
|
||||
geometry, routes and artifacts. Whole-Project equality detects incidental changes.
|
||||
First-relink admission may observe the fresh trace UUID; reopen/no-op admission
|
||||
requires the previously written identity receipt and rejects replacement IDs.
|
||||
Source loading rejects compiler-inserted identities. Round trips compare the
|
||||
decoded model; they do not promise original comments or formatting preservation.
|
||||
No blocking conservation-oracle defect was found in the inspected paths.
|
||||
|
||||
## Native observations
|
||||
|
||||
Trial/frame labels below refer to original `*-0.jpg` files and matching JSON
|
||||
under `.local/transition-native/` at capture time. Receipt evidence is distinct
|
||||
from what screenshots visibly establish. Capture helpers and checksums provide
|
||||
provenance context, not authenticated historical action logs.
|
||||
|
||||
| Originals inspected | Visible observation |
|
||||
| --- | --- |
|
||||
| `picker-baseline-1` 10–11 | Activity menu includes incompatible ports; selecting one yields a kind conflict. |
|
||||
| `picker-corrected-2` 06–07, 10–12, 15, 17–19, 22–23 | Six compatible Functions are sorted; alias filtering preserves current selection until explicit selection; zero results preserve the selected target; two cards retain independent queries; field review remains required; clearing search retains Keep target; Cancel returns to the saved-status view. |
|
||||
| `lifecycle-2` 05, 11–12 | OA deletion is visible; a replacement root declaration is entered and applied through Textual model. |
|
||||
| `lifecycle-2` 17, 21, 27–30 | Recreation initially offers Create rather than silently relinking. Selecting the retained SA target requires Name and Description decisions. Sticky guidance advances from Name/two conflicts to Description/one, then clears after both Keep target decisions. Seven warnings do not block Apply. |
|
||||
| `lifecycle-2` 31–32 | Apply displays the retained SA root, children and exchange; subsequent frame reports all changes saved. |
|
||||
| `lifecycle-3` 02, 06–08 | A fresh process opens the saved model; unchanged transition infers reuse without field conflicts; Apply and save return to the retained SA design. |
|
||||
| `lifecycle-4` 12, 14, 16, 22–23 | SA deletion, Undo restoration, Redo deletion and replacement-root entry/application are visible. |
|
||||
| `lifecycle-4` 28, 30–38 | Reverse transition defaults to Create; alias search offers the retained OA Activity; explicit selection requires both prose decisions. Persistent guidance follows two/one/zero conflicts. Apply displays the retained OA decomposition and save reports completion. The final `reviewed items` wording is visible. |
|
||||
| `lifecycle-5` 02, 07–09 | A fresh process opens the retained OA design; unchanged SA-to-OA review infers the accepted counterpart without conflicts; Apply and save retain the visible OA design. |
|
||||
|
||||
Closed GUI receipts for lifecycle trials 2–5 record exit 0, empty stdout/stderr,
|
||||
and unchanged executable hashes. Snapshot receipts record exit 0 for OA deletion,
|
||||
recreation and relink; OA reopen/no-op; and SA deletion, original-state Undo,
|
||||
deletion-state Redo, recreation, relink and subsequent reopen/no-op. Checks operate on saved source copies
|
||||
and report the working source unchanged. SA Redo's source hash equals deletion's.
|
||||
|
||||
OA relink, reopen and no-op snapshots have identical hashes for both
|
||||
`original.arcadia` and `traceability.arcadia`. Reopen/no-op use source
|
||||
`8eb83649-2810-58e1-94cd-71e25422658e` and accepted trace
|
||||
`856eb758-33fc-4ebf-9a87-22ad40a89952`. SA first relink establishes source
|
||||
`074eab66-7092-5e7e-8b83-b31b95afdbcb` and trace
|
||||
`acc7969b-08c3-43f4-a31d-9140ca873ffe`. Trial 5 passes that exact first-relink
|
||||
receipt to both saved-source checks. Its reopen and no-op source hashes match the
|
||||
first relink for both files (`original.arcadia`: `e75b4cb55e5b205bd5784a1f35573931f1e64491b646f4ffb241f90288447b5d`;
|
||||
`traceability.arcadia`: `5046e2d2731c5d8fcbcb7fe2deede0677ec61de03b85890852fa20f778c57552`).
|
||||
This supports conservation and persisted identity across these finite reopen and
|
||||
unchanged-review workflows in both directions. No new blocking finding arose
|
||||
from the reviewed lifecycle evidence.
|
||||
|
||||
## Feedback correction and remaining limits
|
||||
|
||||
Corrected picker frames 12/15 initially left Apply disabled while its explanation
|
||||
was below the fold. The reviewed footer change moves counts outside the scrolling
|
||||
body and derives guidance from the same conflicts and destination validity used
|
||||
by Apply. It adds no persisted state and weakens no validation. Generic guidance
|
||||
was corrected from “adjust mapping or scope” because operation-omission rationale
|
||||
and component-port decisions require other controls. Final native trial 4 shows
|
||||
the Name/Description guidance and `reviewed items` count. Destination, boundary
|
||||
and general-conflict guidance were checked against code, not exercised in these
|
||||
native lifecycle frames.
|
||||
|
||||
All inspected originals are 1442×932. Resize attempts did not establish a smaller
|
||||
viewport, regardless of frame filenames; minimum-window behavior is unverified.
|
||||
The inherited picker is pointer-dependent. This review does not establish full
|
||||
keyboard/screen-reader accessibility, packaged launch, other platforms, arbitrary
|
||||
models, implicit creation applied natively, or relink Undo/Redo. Recreated roots
|
||||
were authored through Textual model and intentionally have no new placement;
|
||||
the empty source canvas is consistent with that fixture. Screenshots alone do
|
||||
not establish complete model conservation: the independent saved-source comparisons
|
||||
provide that bounded evidence.
|
||||
@@ -52,4 +52,83 @@ The corrected GUI trial ran 2026-09-13 15:22:22–15:27:13 UTC on `d46854c`. It
|
||||
|
||||
Independent read-only reviews found no blocking defect in the shared predicate, selection/query handling, recovery behavior, or lifecycle conservation evaluator. Two refinements were incorporated: empty-query feedback no longer asks to clear an already empty search, and query-isolation tests use two actual OA source cards. A separate review of original native frames confirmed the bounded observations above without a blocking visual regression. Remaining friction: disabled Apply can have its required field-review explanation below the fold, requiring a scroll. Keyboard-only picker operation and broader transition-dialog feedback remain incomplete.
|
||||
|
||||
The native trial establishes this finite Windows selection/review/cancel workflow. It does not establish native endpoint deletion/recreation/relink, saving an applied transition, packaged launch, or whole-project equality from screenshots. Those lifecycle operations have the core evidence above; the broader C1 native acceptance and integration/release decision remain open.
|
||||
That picker-only trial establishes the finite Windows selection/review/cancel workflow. The applied lifecycle follow-up below supplies separate native and saved-model evidence; neither trial is a packaged-release acceptance claim.
|
||||
|
||||
## Applied native lifecycle follow-up, 2026-09-22
|
||||
|
||||
The original disabled-Apply observation led to a small presentation correction:
|
||||
transition counts and the next blocking decision now remain beside Apply, outside
|
||||
the scrolling mappings. Guidance identifies destination, boundary-kind, field or
|
||||
other conflict decisions; it does not weaken Apply validation. The count says
|
||||
`reviewed items` because a reused item need not change the model. No new persisted
|
||||
state or transition semantics are introduced.
|
||||
|
||||
The [isolated producer/checker](experiments/transition-native-lifecycle-20260913/producer/README.md)
|
||||
defines a deterministic two-layer fixture and independently constructs expected
|
||||
deletion, recreation and relink states. It compares complete Projects, exact
|
||||
engineering-warning multisets, JSON/source round trips and persisted identities.
|
||||
An independent critic exposed two potential false passes: learning replacement
|
||||
identities again after reopening, and accepting identities inserted only into
|
||||
compiler memory. Both were corrected. First relink records accepted identities;
|
||||
reopen/reapply require that unchanged receipt. Forty synthetic falsification
|
||||
trials include deliberate prose/geometry corruption, identity drift, omitted
|
||||
persisted IDs and receipt misuse. They are checker evidence, not GUI actions.
|
||||
|
||||
The [closed Cargo and oracle package](experiments/transition-native-lifecycle-20260913/checks/README.md)
|
||||
retains ten successful invocations, including strict all-target Clippy, final
|
||||
desktop build and both format checks. Earlier checker build1 incorrectly rejected
|
||||
the fixture's expected engineering warnings during native admission; corrected
|
||||
build2 accepts only the exact expected diagnostic multiset. The original failed
|
||||
admission and an interrupted first GUI session were not counted as successful
|
||||
native trials. The final build differs from the linted footer only in the
|
||||
`changes` to `reviewed items` literal; the package verifies that exact delta.
|
||||
|
||||
Four fresh, closed Windows sessions provide the
|
||||
[native evidence](experiments/transition-native-lifecycle-20260913/native/README.md):
|
||||
|
||||
| Flow | Native operations and saved-source acceptance |
|
||||
| --- | --- |
|
||||
| OA source, trials 2–3 | Delete OA root; recreate only that root through Textual model; search `@sa_response`; explicitly select the retained SA function; keep target Name and Description; apply/save; restart/open/save; unchanged review/apply/save. Five full-Project checks pass. |
|
||||
| SA source, trials 4–5 | Delete SA root; Undo/save restores the complete original; Redo/save repeats the deletion; recreate only that root through Textual model; search `@oa_response`; explicitly select the retained OA activity; keep both prose fields; apply/save; restart/open/save; unchanged review/apply/save. Seven full-Project checks pass. |
|
||||
|
||||
The retained opposite-layer design includes children, internal exchanges, typed
|
||||
SA ports, shared contracts, authored diagram geometry/routes and artifact links.
|
||||
Recreated roots intentionally have no children or placement. The relink adds one
|
||||
trace and its reviewed baseline; it does not recreate deleted design work.
|
||||
For each direction, both saved source files remain byte-identical through relink,
|
||||
restart/save and unchanged reapply/save, with the first accepted source/trace IDs
|
||||
pinned. This is evidence about these concrete files, not a general promise to
|
||||
preserve arbitrary authored comments or whitespace.
|
||||
|
||||
All four processes exited zero with empty stdout/stderr and unchanged executable
|
||||
hashes. Trials 2–3 used GUI SHA256
|
||||
`3813e79d84cf4a83f2290639b93c3daaa8de427b0268b288f87e4f535fa1aea2`;
|
||||
trials 4–5 used the final 117,090,816-byte GUI SHA256
|
||||
`f0bb021e350bcc55435d29e3daf6a4b3dd7389620df99f5d169f9cb669fd53d0`.
|
||||
Build receipts record HEAD `d0b7c28` plus captured uncommitted input bytes;
|
||||
the 715-file final inventory, rather than that old HEAD alone, identifies the
|
||||
tested source. All 715 captured inputs were checked against exact staged Git
|
||||
blobs before source commit `05e4c83054dc8b2f156e50e7731a50ac19ae8767`.
|
||||
|
||||
The [independent review](experiments/transition-native-lifecycle-20260913/review.md)
|
||||
separates original-frame observations from saved-model comparisons and found no
|
||||
new blocking defect in this bounded workflow. Native frames show the persistent
|
||||
Name/Description instruction advancing with two, one and zero conflicts, while
|
||||
expected modeling warnings remain nonblocking. Destination, boundary and generic
|
||||
conflict guidance were reviewed in code but not exercised by these native trials.
|
||||
All main-window captures are 1442×932; attempted resizing did not establish
|
||||
minimum-window behavior. Keyboard/screen-reader accessibility, native implicit
|
||||
creation, relink Undo/Redo, arbitrary models and packaged launch remain outside
|
||||
this acceptance. Linux checks remain paused by the user. Integration and release
|
||||
status belong to issue 28; this is branch-specific evidence.
|
||||
|
||||
Run the read-only evidence verifiers with Python 3.10+:
|
||||
|
||||
```text
|
||||
python harness/experiments/transition-native-lifecycle-20260913/checks/verify.py
|
||||
python harness/experiments/transition-native-lifecycle-20260913/native/verify.py
|
||||
```
|
||||
|
||||
These verify archived bytes and recorded relationships without executing archived
|
||||
code or authenticating historical interaction. The producer README gives the
|
||||
commands and native steps to reproduce behavior independently.
|
||||
|
||||
Reference in New Issue
Block a user