intentdna 1.9.0 → 1.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +227 -87
  4. package/dist/cli/commands/run-lifecycle.d.ts +61 -11
  5. package/dist/cli/commands/run-lifecycle.js +184 -50
  6. package/dist/cli/commands/run-observer.d.ts +15 -0
  7. package/dist/cli/commands/run-observer.js +210 -0
  8. package/dist/cli/commands/run.d.ts +16 -20
  9. package/dist/cli/commands/run.js +461 -659
  10. package/dist/cli/commands/sync.js +16 -12
  11. package/dist/cli/index.js +14 -5
  12. package/dist/compiler/compile.d.ts +1 -0
  13. package/dist/compiler/compile.js +1 -1
  14. package/dist/compiler/controller.d.ts +16 -0
  15. package/dist/compiler/controller.js +693 -0
  16. package/dist/compiler/input-resolver.d.ts +2 -0
  17. package/dist/compiler/input-resolver.js +22 -6
  18. package/dist/hooks/cli.d.ts +45 -0
  19. package/dist/hooks/cli.js +263 -31
  20. package/dist/hooks/host-event.d.ts +57 -0
  21. package/dist/hooks/host-event.js +187 -0
  22. package/dist/hooks/index.d.ts +1 -0
  23. package/dist/hooks/index.js +1 -0
  24. package/dist/hooks/protocol.d.ts +7 -0
  25. package/dist/hooks/schema.d.ts +1 -0
  26. package/dist/hooks/schema.js +89 -1
  27. package/dist/hooks/state.d.ts +23 -1
  28. package/dist/hooks/state.js +9 -0
  29. package/dist/index.d.ts +1 -0
  30. package/dist/index.js +1 -0
  31. package/dist/mcp/index.js +2 -0
  32. package/dist/mcp/tools-run.d.ts +4 -0
  33. package/dist/mcp/tools-run.js +419 -0
  34. package/dist/mcp/tools-state.d.ts +1 -1
  35. package/dist/mcp/tools-state.js +13 -241
  36. package/dist/runtime/artifact-store.d.ts +146 -0
  37. package/dist/runtime/artifact-store.js +1436 -0
  38. package/dist/runtime/canonical-attempt-outcome.d.ts +12 -0
  39. package/dist/runtime/canonical-attempt-outcome.js +195 -0
  40. package/dist/runtime/canonical-json.d.ts +10 -0
  41. package/dist/runtime/canonical-json.js +105 -0
  42. package/dist/runtime/canonical-run-application.d.ts +93 -0
  43. package/dist/runtime/canonical-run-application.js +229 -0
  44. package/dist/runtime/canonical-run-service.d.ts +454 -0
  45. package/dist/runtime/canonical-run-service.js +4760 -0
  46. package/dist/runtime/canonical-runtime-composition.d.ts +22 -0
  47. package/dist/runtime/canonical-runtime-composition.js +34 -0
  48. package/dist/runtime/canonical-runtime-projection.d.ts +3 -0
  49. package/dist/runtime/canonical-runtime-projection.js +4 -0
  50. package/dist/runtime/canonical-target-compiler.d.ts +46 -0
  51. package/dist/runtime/canonical-target-compiler.js +514 -0
  52. package/dist/runtime/claude-agent-identity.d.ts +2 -0
  53. package/dist/runtime/claude-agent-identity.js +21 -0
  54. package/dist/runtime/executable-run-plan.d.ts +328 -0
  55. package/dist/runtime/executable-run-plan.js +1485 -0
  56. package/dist/runtime/execution-authority.d.ts +83 -0
  57. package/dist/runtime/execution-authority.js +98 -0
  58. package/dist/runtime/handoff-resolver.js +4 -1
  59. package/dist/runtime/harness-pull-adapter.d.ts +146 -0
  60. package/dist/runtime/harness-pull-adapter.js +315 -0
  61. package/dist/runtime/index.d.ts +39 -14
  62. package/dist/runtime/index.js +29 -7
  63. package/dist/runtime/local-execution-authority.d.ts +194 -0
  64. package/dist/runtime/local-execution-authority.js +2897 -0
  65. package/dist/runtime/local-execution-reconciliation.d.ts +20 -0
  66. package/dist/runtime/local-execution-reconciliation.js +107 -0
  67. package/dist/runtime/local-execution-supervisor-script.d.ts +7 -0
  68. package/dist/runtime/local-execution-supervisor-script.js +793 -0
  69. package/dist/runtime/local-provider-sandbox.d.ts +42 -0
  70. package/dist/runtime/local-provider-sandbox.js +154 -0
  71. package/dist/runtime/local-verifier-execution.d.ts +24 -0
  72. package/dist/runtime/local-verifier-execution.js +290 -0
  73. package/dist/runtime/local-windows-execution-supervisor-script.d.ts +6 -0
  74. package/dist/runtime/local-windows-execution-supervisor-script.js +788 -0
  75. package/dist/runtime/plan-store.d.ts +30 -0
  76. package/dist/runtime/plan-store.js +231 -0
  77. package/dist/runtime/process-tree.d.ts +9 -0
  78. package/dist/runtime/process-tree.js +97 -24
  79. package/dist/runtime/providers/codex.js +2 -2
  80. package/dist/runtime/push-driver.d.ts +184 -0
  81. package/dist/runtime/push-driver.js +1002 -0
  82. package/dist/runtime/result-store.d.ts +34 -3
  83. package/dist/runtime/result-store.js +1073 -2
  84. package/dist/runtime/run-binding.d.ts +68 -0
  85. package/dist/runtime/run-binding.js +278 -0
  86. package/dist/runtime/run-contracts.d.ts +575 -0
  87. package/dist/runtime/run-contracts.js +58 -7
  88. package/dist/runtime/run-controller.d.ts +1 -6
  89. package/dist/runtime/run-controller.js +0 -41
  90. package/dist/runtime/run-store.d.ts +85 -7
  91. package/dist/runtime/run-store.js +2528 -186
  92. package/dist/runtime/skill-adapter.d.ts +18 -7
  93. package/dist/runtime/skill-adapter.js +100 -742
  94. package/dist/runtime/structured-output-validator.d.ts +44 -0
  95. package/dist/runtime/structured-output-validator.js +171 -0
  96. package/dist/runtime/verifier-command-binding.d.ts +22 -0
  97. package/dist/runtime/verifier-command-binding.js +162 -0
  98. package/dist/runtime/verifier.d.ts +46 -6
  99. package/dist/runtime/verifier.js +355 -53
  100. package/dist/runtime/windows-job-keeper.d.ts +47 -0
  101. package/dist/runtime/windows-job-keeper.js +231 -0
  102. package/dist/runtime/worker-executor.d.ts +17 -1
  103. package/dist/runtime/worker-executor.js +26 -5
  104. package/dist/runtime/workflow-plan-adapter.d.ts +19 -0
  105. package/dist/runtime/workflow-plan-adapter.js +502 -6
  106. package/dist/runtime/workflow-runtime-manifest.d.ts +1 -0
  107. package/dist/runtime/workflow-runtime-manifest.js +5 -0
  108. package/dist/runtime/workspace-isolation.d.ts +29 -2
  109. package/dist/runtime/workspace-isolation.js +488 -11
  110. package/dist/runtime/workspace-observation.d.ts +44 -0
  111. package/dist/runtime/workspace-observation.js +216 -0
  112. package/dist/schema/controller-registry.d.ts +0 -7
  113. package/dist/schema/controller-registry.js +0 -8
  114. package/dist/schema/types.d.ts +84 -10
  115. package/dist/schema/types.js +2 -0
  116. package/dist/schema/validate.js +151 -1
  117. package/dist/schema/validators/controllers.d.ts +1 -1
  118. package/dist/schema/validators/controllers.js +126 -41
  119. package/dist/schema/workflow-authoring-contract.js +2 -0
  120. package/dist/schema/yaml-parser.js +1 -1
  121. package/dist/templates/flutter-rewrite-new.dna.yaml +1 -0
  122. package/dist/templates/flutter-rewrite.dna.yaml +87 -60
  123. package/dist/templates/safe-refactoring.dna.yaml +1 -0
  124. package/native/windows-job-keeper/README.md +80 -0
  125. package/native/windows-job-keeper/bin/aarch64/intentdna-windows-job-keeper.exe +0 -0
  126. package/native/windows-job-keeper/bin/x86_64/intentdna-windows-job-keeper.exe +0 -0
  127. package/native/windows-job-keeper/keeper.c +979 -0
  128. package/native/windows-job-keeper/manifest.json +33 -0
  129. package/package.json +6 -2
  130. package/scripts/build-windows-job-keeper.mjs +172 -0
  131. package/scripts/verify-windows-job-keeper.mjs +184 -0
@@ -170,9 +170,9 @@ verifier_policy:
170
170
  allow_commands:
171
171
  - "flutter analyze {{v2_path}}/core"
172
172
  - "node -e \"const fs=require('node:fs'); const cp=require('node:child_process'); const path=require('node:path'); const fail=m=>{console.error('INTENTDNA_DIAGNOSTIC:core_align_'+m);process.exit(1)}; const root=fs.realpathSync(process.cwd()); const inside=p=>{if(typeof p!=='string'||!p||path.isAbsolute(p))fail('path');const r=path.resolve(root,p);if(r!==root&&!r.startsWith(root+path.sep))fail('path');let stat=null;try{stat=fs.lstatSync(r)}catch(e){if(!e||e.code!=='ENOENT')fail('path_unreadable')}if(stat){if(stat.isSymbolicLink())fail('path_symlink');const real=fs.realpathSync(r);if(real!==root&&!real.startsWith(root+path.sep))fail('path')}return r}; const rel=p=>path.relative(root,inside(p)).split(path.sep).join('/')||'.'; const under=(p,base)=>p===base||p.startsWith(base+'/'); const reviewPath='.dna/reviews/core-align-review.json'; if(!fs.existsSync(inside(reviewPath)))fail('review_missing'); const d=JSON.parse(fs.readFileSync(inside(reviewPath),'utf8')); const head=cp.execFileSync('git',['rev-parse','HEAD'],{encoding:'utf8'}).trim(); if(d.reviewed_commit!==head||d.git_head!==head)fail('commit'); if(d.verdict!=='APPROVE')fail('review_verdict'); if(d.production_default_wiring_verified!==true)fail('production_default_wiring'); if(d.analyzer_passed!==true)fail('analyzer'); if(d.requires_non_core_changes!==false)fail('non_core_changes'); if(!Array.isArray(d.missing_paths)||d.missing_paths.length)fail('missing_paths'); if(typeof d.summary!=='string'||!d.summary.trim())fail('summary'); const updated=Date.parse(d.updated_at); const commitTime=Number(cp.execFileSync('git',['show','-s','--format=%ct',head],{encoding:'utf8'}).trim())*1000; const reviewMtime=fs.statSync(inside(reviewPath)).mtimeMs; if(!Number.isFinite(updated)||updated<commitTime||Math.abs(reviewMtime-updated)>300000)fail('review_stale'); const core=rel('{{v2_path}}/core'); if(!Array.isArray(d.evidence_paths)||!d.evidence_paths.length)fail('evidence'); for(const p of d.evidence_paths){const n=rel(p);const r=inside(p);if(!under(n,core)||!fs.existsSync(r)||!fs.statSync(r).isFile())fail('evidence')} const output=cp.execFileSync('git',['diff-tree','--root','--no-commit-id','--name-only','-r',head],{encoding:'utf8'}).trim(); const changed=output?output.split(/\\r?\\n/).filter(Boolean):[]; if(!changed.length)fail('empty_commit'); for(const p of changed){const n=rel(p);if(!under(n,core))fail('non_core_commit')} const dirty=cp.execFileSync('git',['status','--porcelain','--untracked-files=all','--',core],{encoding:'utf8'}).trim(); if(dirty)fail('uncommitted_core'); \""
173
- - "node -e \"const fs=require('node:fs'); const cp=require('node:child_process'); const crypto=require('node:crypto'); const path=require('node:path'); const mod=process.env.ARGUMENTS||'$ARGUMENTS'; const fail=m=>{console.error('INTENTDNA_DIAGNOSTIC:fix_scope_'+m);process.exit(1)}; const root=fs.realpathSync(process.cwd()); const inside=p=>{if(typeof p!=='string'||!p||path.isAbsolute(p))fail('path_invalid');const r=path.resolve(root,p);if(r!==root&&!r.startsWith(root+path.sep))fail('path_escape');let stat=null;try{stat=fs.lstatSync(r)}catch(e){if(!e||e.code!=='ENOENT')fail('path_unreadable')}if(stat){if(stat.isSymbolicLink())fail('path_symlink');const real=fs.realpathSync(r);if(real!==root&&!real.startsWith(root+path.sep))fail('path_escape')}return r}; const rel=p=>path.relative(root,inside(p)).split(path.sep).join('/')||'.'; const under=(p,base)=>p===base||p.startsWith(base+'/'); const hash=v=>crypto.createHash('sha256').update(v).digest('hex'); const canon=v=>Array.isArray(v)?'['+v.map(canon).join(',')+']':v&&typeof v==='object'?'{'+Object.keys(v).sort().map(k=>JSON.stringify(k)+':'+canon(v[k])).join(',')+'}':JSON.stringify(v); const same=(a,b)=>Array.isArray(a)&&Array.isArray(b)&&a.length===b.length&&a.every((v,i)=>v===b[i]); const snapSame=(a,b)=>a&&b&&a.baseline_source_sha256===b.baseline_source_sha256&&a.diagnosis_artifact_sha256===b.diagnosis_artifact_sha256&&a.sidecar_semantic_sha256===b.sidecar_semantic_sha256&&a.diagnosis_semantic_sha256===b.diagnosis_semantic_sha256&&a.observed_failure_count===b.observed_failure_count&&a.work_item_count===b.work_item_count&&same(a.allowed_kinds,b.allowed_kinds); if(!/^[A-Za-z0-9_-]+$/.test(mod))fail('module'); const contractPath='.dna/specs/diagnosis-'+mod+'.contract.json'; const reviewPath='.dna/specs/diagnosis-'+mod+'.review.json'; const diagnosisPath='.dna/specs/diagnosis-'+mod+'.md'; const data=JSON.parse(fs.readFileSync(inside(contractPath),'utf8')); if(data.contract_version!=='diagnosis-contract/v2'||data.module!==mod||data.diagnosis_artifact!==diagnosisPath)fail('contract'); const snap=data.contract_snapshot||{}; const semantic=JSON.parse(JSON.stringify(data)); delete semantic.contract_snapshot.sidecar_semantic_sha256; delete semantic.contract_snapshot.diagnosis_artifact_sha256; delete semantic.staleness.diagnosis_artifact; if(snap.sidecar_semantic_sha256!==hash(canon(semantic)))fail('contract_snapshot'); const review=JSON.parse(fs.readFileSync(inside(reviewPath),'utf8')); if(review.contract_version!=='diagnosis-contract-review/v1'||review.contract_valid!==true||review.verdict!=='APPROVE')fail('review'); if(review.contract_artifact_reviewed!==contractPath||review.artifact_reviewed!==diagnosisPath||!snapSame(review.contract_snapshot,snap))fail('review_snapshot'); if(!Array.isArray(review.evidence_paths)||!review.evidence_paths.length)fail('review_evidence'); for(const p of review.evidence_paths){const r=inside(p);if(!fs.existsSync(r)||!fs.statSync(r).isFile())fail('review_evidence')} const cm=fs.statSync(inside(contractPath)).mtimeMs; const rm=fs.statSync(inside(reviewPath)).mtimeMs; const updated=Date.parse(review.updated_at); if(!Number.isFinite(updated)||rm<cm||updated+300000<cm||Math.abs(rm-updated)>300000)fail('review_stale'); const v1=rel('{{v1_path}}'); const v2=rel('{{v2_path}}'); const action=new Set(['BUG','UNIMPLEMENTED','INFRA','TEST_BUG']); const allowed=new Set(); for(const item of data.work_items||[]){if(!action.has(item.kind))continue;const evidence=new Set((item.v2_evidence_paths||[]).map(rel));for(const p of item.allowed_change_paths||[]){if(typeof p!=='string'||['*','?','[',']'].some(c=>p.includes(c)))fail('allowed_path_pattern');const n=rel(p);const r=inside(p);if(under(n,v1))fail('v1_change');if(!(under(n,v2)||under(n,'test')||under(n,'lib')))fail('allowed_path_root');if(fs.existsSync(r)?!fs.statSync(r).isFile():!path.basename(n).includes('.'))fail('allowed_path_not_file');if(!evidence.has(n))fail('allowed_path_not_evidence');allowed.add(n)}} if(!allowed.size)fail('allowed_paths'); const head=cp.execFileSync('git',['rev-parse','HEAD'],{encoding:'utf8'}).trim(); const output=cp.execFileSync('git',['diff-tree','--root','--no-commit-id','--name-only','-r',head],{encoding:'utf8'}).trim(); const changed=output?output.split(/\\r?\\n/).filter(Boolean):[]; if(!changed.length)fail('empty_commit'); for(const p of changed){const n=rel(p);if(under(n,v1))fail('v1_change');if(!allowed.has(n))fail('outside_diagnosis')} const dirty=cp.execFileSync('git',['status','--porcelain','--untracked-files=all','--',...allowed],{encoding:'utf8'}).trim(); if(dirty)fail('uncommitted_allowed_path'); \""
174
- - "node -e \"const fs=require('node:fs'); const cp=require('node:child_process'); const crypto=require('node:crypto'); const path=require('node:path'); const mod=process.env.ARGUMENTS||'$ARGUMENTS'; const allowed=['BUG','UNIMPLEMENTED','INFRA','REMOVED','TEST_BUG']; const action=['BUG','UNIMPLEMENTED','INFRA','TEST_BUG']; const fail=m=>{console.error('INTENTDNA_DIAGNOSTIC:'+m);process.exit(1)}; const same=(a,b)=>Array.isArray(a)&&a.length===b.length&&a.every((v,i)=>v===b[i]); const legacySame=(a,b)=>a&&b&&a.baseline_source_sha256===b.baseline_source_sha256&&a.diagnosis_artifact_sha256===b.diagnosis_artifact_sha256&&a.observed_failure_count===b.observed_failure_count&&a.work_item_count===b.work_item_count&&same(a.allowed_kinds,b.allowed_kinds); const modern=s=>s&&typeof s.sidecar_semantic_sha256==='string'&&typeof s.diagnosis_semantic_sha256==='string'; const root=fs.realpathSync(process.cwd()); const inside=p=>{if(typeof p!=='string'||!p||path.isAbsolute(p))fail('path_invalid');const r=path.resolve(root,p);if(r!==root&&!r.startsWith(root+path.sep))fail('path_escape');let stat=null;try{stat=fs.lstatSync(r)}catch(e){if(!e||e.code!=='ENOENT')fail('path_unreadable')}if(stat){if(stat.isSymbolicLink())fail('path_symlink');const real=fs.realpathSync(r);if(real!==root&&!real.startsWith(root+path.sep))fail('path_escape')}return r}; const rel=p=>path.relative(root,inside(p)).split(path.sep).join('/')||'.'; const under=(p,base)=>p===base||p.startsWith(base+'/'); const readJson=p=>JSON.parse(fs.readFileSync(inside(p),'utf8')); const hash=v=>crypto.createHash('sha256').update(v).digest('hex'); const fileHash=p=>hash(fs.readFileSync(inside(p))); const normalizedDiagnosisHash=p=>hash(fs.readFileSync(inside(p),'utf8').replace(/\\s+/g,' ').trim()); const canon=v=>Array.isArray(v)?'['+v.map(canon).join(',')+']':v&&typeof v==='object'?'{'+Object.keys(v).sort().map(k=>JSON.stringify(k)+':'+canon(v[k])).join(',')+'}':JSON.stringify(v); if(!/^[A-Za-z0-9_-]+$/.test(mod))fail('module'); const contractPath='.dna/specs/diagnosis-'+mod+'.contract.json'; const reviewPath='.dna/specs/diagnosis-'+mod+'.review.json'; const progressPath='.dna/fix-progress/'+mod+'.json'; const diagnosisPath='.dna/specs/diagnosis-'+mod+'.md'; const behaviorPath='docs/behavior/'+mod+'.md'; const v1Root=rel('{{v1_path}}'); const v2Root=rel('{{v2_path}}'); if(v1Root==='.'||v2Root==='.'||under(v1Root,v2Root)||under(v2Root,v1Root))fail('configured_roots'); const data=readJson(contractPath); if(data.contract_version!=='diagnosis-contract/v2')fail('contract_version'); if(data.module!==mod)fail('module_mismatch'); if(data.diagnosis_artifact!==diagnosisPath)fail('diagnosis_artifact_path'); if(!same(data.allowed_kinds,allowed))fail('allowed_kinds'); const observed=Array.isArray(data.observed_failures)?data.observed_failures:fail('observed_failures'); const work=Array.isArray(data.work_items)?data.work_items:fail('work_items'); if(!observed.length)fail('empty_observed_failures'); if(!work.length)fail('empty_work_items'); const idOk=id=>typeof id==='string'&&/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/.test(id); const ids=new Set(); for(const f of observed){if(!idOk(f&&f.id)||ids.has(f.id))fail('observed_failure_ids');ids.add(f.id)} const workIds=new Set(); for(const item of work){if(!idOk(item&&item.id)||workIds.has(item.id))fail('work_item_ids');workIds.add(item.id)} const failureById=new Map(); for(const f of observed){if(typeof f.test_name!=='string'||!f.test_name.trim())fail('observed_failure_test_name');if(typeof f.symptom!=='string'||!f.symptom.trim())fail('observed_failure_symptom');if(!Array.isArray(f.evidence_paths)||!f.evidence_paths.length)fail('observed_failure_evidence');for(const p of f.evidence_paths)rel(p);if(f.kind!==undefined)fail('observed_failure_kind_forbidden');failureById.set(f.id,f)} const mapped=new Set(); const allAllowed=new Set(); const v1Evidence=new Set(); for(const item of work){if(!allowed.includes(item.kind))fail('invalid_kind');if(item.kind==='PLACEHOLDER_CONTRACT')fail('placeholder_kind');if(!Array.isArray(item.observed_failure_ids)||!item.observed_failure_ids.length)fail('work_item_observed_failure_ids');if(!Array.isArray(item.evidence_paths)||!item.evidence_paths.length)fail('work_item_evidence');for(const p of item.evidence_paths)rel(p);if(action.includes(item.kind)){if(!Array.isArray(item.v1_evidence_paths)||!item.v1_evidence_paths.length)fail('work_item_v1_evidence');if(!Array.isArray(item.v2_evidence_paths)||!item.v2_evidence_paths.length)fail('work_item_v2_evidence');const v2Evidence=new Set(item.v2_evidence_paths.map(rel));for(const p of item.v1_evidence_paths){const n=rel(p);if(!under(n,v1Root))fail('work_item_v1_evidence_root');v1Evidence.add(n)}for(const p of item.v2_evidence_paths)rel(p);if(!Array.isArray(item.allowed_change_paths)||!item.allowed_change_paths.length)fail('work_item_allowed_change_paths');const approved=new Set();for(const p of item.allowed_change_paths){if(typeof p!=='string'||['*','?','[',']'].some(c=>p.includes(c)))fail('work_item_allowed_change_path_pattern');const n=rel(p);const r=inside(p);if(under(n,v1Root))fail('work_item_allowed_change_path_v1');if(!(under(n,v2Root)||under(n,'test')||under(n,'lib')))fail('work_item_allowed_change_path_root');if(fs.existsSync(r)?!fs.statSync(r).isFile():!path.basename(n).includes('.'))fail('work_item_allowed_change_path_not_file');if(!v2Evidence.has(n))fail('work_item_allowed_change_path_not_evidence');if(approved.has(n))fail('work_item_allowed_change_path_duplicate');approved.add(n);allAllowed.add(n)}}for(const id of item.observed_failure_ids){const f=failureById.get(id);if(!f)fail('unknown_observed_failure_id');if(action.includes(item.kind)&&(!f.test_name||!String(f.test_name).trim()))fail('action_test_evidence');mapped.add(id)}} if(mapped.size!==ids.size)fail('unmapped_observed_failures'); for(const f of observed.filter(x=>String(x.symptom).includes('PLACEHOLDER_CONTRACT'))){const kinds=new Set(work.filter(i=>i.observed_failure_ids.includes(f.id)).map(i=>i.kind));if(![...kinds].every(k=>k==='TEST_BUG'||k==='UNIMPLEMENTED')||kinds.size===0)fail('placeholder_mapping')} const counts=data.baseline&&data.baseline.counts; if(!data.baseline||!data.baseline.source_path||!counts)fail('baseline'); for(const k of ['failed','skipped','hung']){if(!Number.isInteger(counts[k])||counts[k]<0)fail('baseline_count_'+k)} if(counts.failed+counts.skipped+counts.hung!==observed.length)fail('baseline_count_mismatch'); const summary=data.summary_counts||{}; for(const kind of allowed){if(!Number.isInteger(summary[kind])||summary[kind]!==work.filter(x=>x.kind===kind).length)fail('summary_count_'+kind)} const snap=data.contract_snapshot||{}; const baselineHash=fileHash(data.baseline.source_path); const diagnosisHash=fileHash(diagnosisPath); if(snap.observed_failure_count!==observed.length||snap.work_item_count!==work.length||!same(snap.allowed_kinds,allowed))fail('contract_snapshot'); if(snap.baseline_source_sha256!==baselineHash)fail('baseline_sha256'); if(snap.diagnosis_artifact_sha256!==diagnosisHash)fail('diagnosis_sha256'); const stale=data.staleness; if(!stale||stale.is_stale!==false)fail('stale_contract'); const source=(entry,p,n,expected)=>{if(!entry||entry.path!==p)fail('staleness_'+n+'_path');if(entry.sha256!==expected)fail('staleness_'+n+'_sha256')}; source(stale.behavior_doc,behaviorPath,'behavior_doc',fileHash(behaviorPath)); source(stale.baseline,data.baseline.source_path,'baseline',baselineHash); source(stale.diagnosis_artifact,diagnosisPath,'diagnosis_artifact',diagnosisHash); const expectedSources=[...new Set(work.filter(i=>action.includes(i.kind)).flatMap(i=>[...i.v1_evidence_paths,...i.v2_evidence_paths]).map(rel))].sort(); if(!Array.isArray(stale.source_files)||stale.source_files.length!==expectedSources.length)fail('staleness_source_files'); let relaxed=false; let head=''; if(fs.existsSync(inside(progressPath))){const progressMtime=fs.statSync(inside(progressPath)).mtimeMs;const artifactMtime=Math.max(fs.statSync(inside(contractPath)).mtimeMs,fs.statSync(inside(diagnosisPath)).mtimeMs,fs.existsSync(inside(reviewPath))?fs.statSync(inside(reviewPath)).mtimeMs:0);if(progressMtime>=artifactMtime){const progress=readJson(progressPath);head=cp.execFileSync('git',['rev-parse','HEAD'],{encoding:'utf8'}).trim();if(progress.module!==mod||progress.verdict!=='CONTINUE'||progress.module_complete!==false||progress.diagnosis_invalid!==false)fail('progress_state');if(progress.diagnosis_artifact!==diagnosisPath||progress.review_artifact!=='.dna/reviews/'+mod+'-review.json')fail('progress_artifacts');if(progress.git_head!==head||progress.last_fix_commit!==head)fail('progress_commit');if(!Number.isInteger(progress.round)||progress.round<1||typeof progress.run_id!=='string'||!progress.run_id.trim())fail('progress_provenance');const progressTime=Date.parse(progress.updated_at);if(!Number.isFinite(progressTime)||progressTime+300000<artifactMtime)fail('progress_updated_at');relaxed=true}} const headState=p=>{try{const body=cp.execFileSync('git',['show',head+':'+p],{encoding:null,stdio:['ignore','pipe','ignore']});return {exists:true,sha256:hash(body)}}catch{return {exists:false,sha256:null}}}; const seenSources=new Set(); for(let i=0;i<expectedSources.length;i++){const p=expectedSources[i];const entry=stale.source_files[i];if(entry&&seenSources.has(entry.path))fail('staleness_source_file_duplicate');if(!entry||entry.path!==p)fail('staleness_source_file_path');seenSources.add(entry.path);if(typeof entry.exists!=='boolean')fail('staleness_source_file_exists');if(entry.exists?(typeof entry.sha256!=='string'||!/^[a-f0-9]{64}$/.test(entry.sha256)):entry.sha256!==null)fail('staleness_source_file_snapshot');if(v1Evidence.has(p)&&entry.exists!==true)fail('staleness_v1_source_missing');const r=inside(p);const currentExists=fs.existsSync(r);if(currentExists&&!fs.statSync(r).isFile())fail('staleness_source_file_not_file');const current={exists:currentExists,sha256:currentExists?fileHash(p):null};if(relaxed&&allAllowed.has(p)){const committed=headState(p);if(current.exists!==committed.exists||current.sha256!==committed.sha256)fail('staleness_source_file_uncommitted')}else if(current.exists!==entry.exists||current.sha256!==entry.sha256)fail('staleness_source_file_sha256')} const semantic=JSON.parse(JSON.stringify(data)); delete semantic.contract_snapshot.sidecar_semantic_sha256; delete semantic.contract_snapshot.diagnosis_artifact_sha256; delete semantic.staleness.diagnosis_artifact; const sidecarSemanticHash=hash(canon(semantic)); const diagnosisSemanticHash=normalizedDiagnosisHash(diagnosisPath); if(snap.sidecar_semantic_sha256!==sidecarSemanticHash||snap.diagnosis_semantic_sha256!==diagnosisSemanticHash)fail('contract_snapshot_semantic'); if(fs.existsSync(inside(reviewPath))){const prev=readJson(reviewPath);if(prev.verdict==='REQUEST_REANALYSIS'){const ps=prev.contract_snapshot||{};const unchanged=modern(ps)?ps.sidecar_semantic_sha256===sidecarSemanticHash&&ps.diagnosis_semantic_sha256===diagnosisSemanticHash:legacySame(ps,snap);if(unchanged)fail('reanalysis_unchanged');const cm=fs.statSync(inside(contractPath)).mtimeMs;const dm=fs.statSync(inside(diagnosisPath)).mtimeMs;const rm=fs.statSync(inside(reviewPath)).mtimeMs;if(cm<=rm||dm<=rm)fail('reanalysis_not_rewritten')}} \""
175
- - "node -e \"const fs=require('node:fs'); const path=require('node:path'); const mod=process.env.ARGUMENTS||'$ARGUMENTS'; const allowed=['BUG','UNIMPLEMENTED','INFRA','REMOVED','TEST_BUG']; const fail=m=>{console.error('INTENTDNA_DIAGNOSTIC:'+m);process.exit(1)}; const same=(a,b)=>Array.isArray(a)&&a.length===b.length&&a.every((v,i)=>v===b[i]); const snapSame=(a,b)=>a&&b&&a.baseline_source_sha256===b.baseline_source_sha256&&a.diagnosis_artifact_sha256===b.diagnosis_artifact_sha256&&a.sidecar_semantic_sha256===b.sidecar_semantic_sha256&&a.diagnosis_semantic_sha256===b.diagnosis_semantic_sha256&&a.observed_failure_count===b.observed_failure_count&&a.work_item_count===b.work_item_count&&same(a.allowed_kinds,b.allowed_kinds); const root=fs.realpathSync(process.cwd()); const inside=p=>{if(typeof p!=='string'||!p||path.isAbsolute(p))fail('path_invalid');const r=path.resolve(root,p);if(r!==root&&!r.startsWith(root+path.sep))fail('path_escape');let stat=null;try{stat=fs.lstatSync(r)}catch(e){if(!e||e.code!=='ENOENT')fail('path_unreadable')}if(stat){if(stat.isSymbolicLink())fail('path_symlink');const real=fs.realpathSync(r);if(real!==root&&!real.startsWith(root+path.sep))fail('path_escape')}return r}; const readJson=p=>JSON.parse(fs.readFileSync(inside(p),'utf8')); if(!/^[A-Za-z0-9_-]+$/.test(mod))fail('module'); const contractPath='.dna/specs/diagnosis-'+mod+'.contract.json'; const reviewPath='.dna/specs/diagnosis-'+mod+'.review.json'; const diagnosisPath='.dna/specs/diagnosis-'+mod+'.md'; const data=readJson(contractPath); const snap=data.contract_snapshot||{}; if(data.contract_version!=='diagnosis-contract/v2'||data.module!==mod)fail('contract_version'); const review=readJson(reviewPath); if(review.contract_version!=='diagnosis-contract-review/v1')fail('review_contract_version'); if(review.contract_valid!==true)fail('review_contract_valid'); if(review.verdict!=='APPROVE')fail('review_verdict'); if(review.contract_artifact_reviewed!==contractPath)fail('review_contract_artifact_path'); if(review.artifact_reviewed!==diagnosisPath)fail('review_artifact_path'); if(!snapSame(review.contract_snapshot,snap))fail('review_contract_snapshot'); const updated=Date.parse(review.updated_at); if(!Number.isFinite(updated))fail('review_updated_at'); if(!Array.isArray(review.evidence_paths)||!review.evidence_paths.length)fail('review_evidence_paths'); for(const p of review.evidence_paths){const r=inside(p);if(!fs.existsSync(r)||!fs.statSync(r).isFile())fail('review_evidence_paths')} const cm=fs.statSync(inside(contractPath)).mtimeMs; const dm=fs.statSync(inside(diagnosisPath)).mtimeMs; const rm=fs.statSync(inside(reviewPath)).mtimeMs; if(rm<cm||rm<dm||updated+300000<Math.max(cm,dm)||Math.abs(rm-updated)>300000)fail('review_stale'); \""
173
+ - "node -e \"const fs=require('node:fs'); const cp=require('node:child_process'); const crypto=require('node:crypto'); const path=require('node:path'); const mod=process.argv[1]; const fail=m=>{console.error('INTENTDNA_DIAGNOSTIC:fix_scope_'+m);process.exit(1)}; const root=fs.realpathSync(process.cwd()); const inside=p=>{if(typeof p!=='string'||!p||path.isAbsolute(p))fail('path_invalid');const r=path.resolve(root,p);if(r!==root&&!r.startsWith(root+path.sep))fail('path_escape');let stat=null;try{stat=fs.lstatSync(r)}catch(e){if(!e||e.code!=='ENOENT')fail('path_unreadable')}if(stat){if(stat.isSymbolicLink())fail('path_symlink');const real=fs.realpathSync(r);if(real!==root&&!real.startsWith(root+path.sep))fail('path_escape')}return r}; const rel=p=>path.relative(root,inside(p)).split(path.sep).join('/')||'.'; const under=(p,base)=>p===base||p.startsWith(base+'/'); const hash=v=>crypto.createHash('sha256').update(v).digest('hex'); const canon=v=>Array.isArray(v)?'['+v.map(canon).join(',')+']':v&&typeof v==='object'?'{'+Object.keys(v).sort().map(k=>JSON.stringify(k)+':'+canon(v[k])).join(',')+'}':JSON.stringify(v); const same=(a,b)=>Array.isArray(a)&&Array.isArray(b)&&a.length===b.length&&a.every((v,i)=>v===b[i]); const snapSame=(a,b)=>a&&b&&a.baseline_source_sha256===b.baseline_source_sha256&&a.diagnosis_artifact_sha256===b.diagnosis_artifact_sha256&&a.sidecar_semantic_sha256===b.sidecar_semantic_sha256&&a.diagnosis_semantic_sha256===b.diagnosis_semantic_sha256&&a.observed_failure_count===b.observed_failure_count&&a.work_item_count===b.work_item_count&&same(a.allowed_kinds,b.allowed_kinds); if(!/^[A-Za-z0-9_-]+$/.test(mod))fail('module'); const contractPath='.dna/specs/diagnosis-'+mod+'.contract.json'; const reviewPath='.dna/specs/diagnosis-'+mod+'.review.json'; const diagnosisPath='.dna/specs/diagnosis-'+mod+'.md'; const data=JSON.parse(fs.readFileSync(inside(contractPath),'utf8')); if(data.contract_version!=='diagnosis-contract/v2'||data.module!==mod||data.diagnosis_artifact!==diagnosisPath)fail('contract'); const snap=data.contract_snapshot||{}; const semantic=JSON.parse(JSON.stringify(data)); delete semantic.contract_snapshot.sidecar_semantic_sha256; delete semantic.contract_snapshot.diagnosis_artifact_sha256; delete semantic.staleness.diagnosis_artifact; if(snap.sidecar_semantic_sha256!==hash(canon(semantic)))fail('contract_snapshot'); const review=JSON.parse(fs.readFileSync(inside(reviewPath),'utf8')); if(review.contract_version!=='diagnosis-contract-review/v1'||review.contract_valid!==true||review.verdict!=='APPROVE')fail('review'); if(review.contract_artifact_reviewed!==contractPath||review.artifact_reviewed!==diagnosisPath||!snapSame(review.contract_snapshot,snap))fail('review_snapshot'); if(!Array.isArray(review.evidence_paths)||!review.evidence_paths.length)fail('review_evidence'); for(const p of review.evidence_paths){const r=inside(p);if(!fs.existsSync(r)||!fs.statSync(r).isFile())fail('review_evidence')} const cm=fs.statSync(inside(contractPath)).mtimeMs; const rm=fs.statSync(inside(reviewPath)).mtimeMs; const updated=Date.parse(review.updated_at); if(!Number.isFinite(updated)||rm<cm||updated+300000<cm||Math.abs(rm-updated)>300000)fail('review_stale'); const v1=rel(process.argv[2]); const v2=rel(process.argv[3]); const action=new Set(['BUG','UNIMPLEMENTED','INFRA','TEST_BUG']); const allowed=new Set(); for(const item of data.work_items||[]){if(!action.has(item.kind))continue;const evidence=new Set((item.v2_evidence_paths||[]).map(rel));for(const p of item.allowed_change_paths||[]){if(typeof p!=='string'||['*','?','[',']'].some(c=>p.includes(c)))fail('allowed_path_pattern');const n=rel(p);const r=inside(p);if(under(n,v1))fail('v1_change');if(!(under(n,v2)||under(n,'test')||under(n,'lib')))fail('allowed_path_root');if(fs.existsSync(r)?!fs.statSync(r).isFile():!path.basename(n).includes('.'))fail('allowed_path_not_file');if(!evidence.has(n))fail('allowed_path_not_evidence');allowed.add(n)}} if(!allowed.size)fail('allowed_paths'); const head=cp.execFileSync('git',['rev-parse','HEAD'],{encoding:'utf8'}).trim(); const output=cp.execFileSync('git',['diff-tree','--root','--no-commit-id','--name-only','-r',head],{encoding:'utf8'}).trim(); const changed=output?output.split(/\\r?\\n/).filter(Boolean):[]; if(!changed.length)fail('empty_commit'); for(const p of changed){const n=rel(p);if(under(n,v1))fail('v1_change');if(!allowed.has(n))fail('outside_diagnosis')} const dirty=cp.execFileSync('git',['status','--porcelain','--untracked-files=all','--',...allowed],{encoding:'utf8'}).trim(); if(dirty)fail('uncommitted_allowed_path'); \" \"$ARGUMENTS\" \"{{v1_path}}\" \"{{v2_path}}\""
174
+ - "node -e \"const fs=require('node:fs'); const cp=require('node:child_process'); const crypto=require('node:crypto'); const path=require('node:path'); const mod=process.argv[1]; const allowed=['BUG','UNIMPLEMENTED','INFRA','REMOVED','TEST_BUG']; const action=['BUG','UNIMPLEMENTED','INFRA','TEST_BUG']; const fail=m=>{console.error('INTENTDNA_DIAGNOSTIC:'+m);process.exit(1)}; const same=(a,b)=>Array.isArray(a)&&a.length===b.length&&a.every((v,i)=>v===b[i]); const legacySame=(a,b)=>a&&b&&a.baseline_source_sha256===b.baseline_source_sha256&&a.diagnosis_artifact_sha256===b.diagnosis_artifact_sha256&&a.observed_failure_count===b.observed_failure_count&&a.work_item_count===b.work_item_count&&same(a.allowed_kinds,b.allowed_kinds); const modern=s=>s&&typeof s.sidecar_semantic_sha256==='string'&&typeof s.diagnosis_semantic_sha256==='string'; const root=fs.realpathSync(process.cwd()); const inside=p=>{if(typeof p!=='string'||!p||path.isAbsolute(p))fail('path_invalid');const r=path.resolve(root,p);if(r!==root&&!r.startsWith(root+path.sep))fail('path_escape');let stat=null;try{stat=fs.lstatSync(r)}catch(e){if(!e||e.code!=='ENOENT')fail('path_unreadable')}if(stat){if(stat.isSymbolicLink())fail('path_symlink');const real=fs.realpathSync(r);if(real!==root&&!real.startsWith(root+path.sep))fail('path_escape')}return r}; const rel=p=>path.relative(root,inside(p)).split(path.sep).join('/')||'.'; const under=(p,base)=>p===base||p.startsWith(base+'/'); const readJson=p=>JSON.parse(fs.readFileSync(inside(p),'utf8')); const hash=v=>crypto.createHash('sha256').update(v).digest('hex'); const fileHash=p=>hash(fs.readFileSync(inside(p))); const normalizedDiagnosisHash=p=>hash(fs.readFileSync(inside(p),'utf8').replace(/\\s+/g,' ').trim()); const canon=v=>Array.isArray(v)?'['+v.map(canon).join(',')+']':v&&typeof v==='object'?'{'+Object.keys(v).sort().map(k=>JSON.stringify(k)+':'+canon(v[k])).join(',')+'}':JSON.stringify(v); if(!/^[A-Za-z0-9_-]+$/.test(mod))fail('module'); const contractPath='.dna/specs/diagnosis-'+mod+'.contract.json'; const reviewPath='.dna/specs/diagnosis-'+mod+'.review.json'; const progressPath='.dna/fix-progress/'+mod+'.json'; const diagnosisPath='.dna/specs/diagnosis-'+mod+'.md'; const behaviorPath='docs/behavior/'+mod+'.md'; const v1Root=rel(process.argv[2]); const v2Root=rel(process.argv[3]); if(v1Root==='.'||v2Root==='.'||under(v1Root,v2Root)||under(v2Root,v1Root))fail('configured_roots'); const data=readJson(contractPath); if(data.contract_version!=='diagnosis-contract/v2')fail('contract_version'); if(data.module!==mod)fail('module_mismatch'); if(data.diagnosis_artifact!==diagnosisPath)fail('diagnosis_artifact_path'); if(!same(data.allowed_kinds,allowed))fail('allowed_kinds'); const observed=Array.isArray(data.observed_failures)?data.observed_failures:fail('observed_failures'); const work=Array.isArray(data.work_items)?data.work_items:fail('work_items'); if(!observed.length)fail('empty_observed_failures'); if(!work.length)fail('empty_work_items'); const idOk=id=>typeof id==='string'&&/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/.test(id); const ids=new Set(); for(const f of observed){if(!idOk(f&&f.id)||ids.has(f.id))fail('observed_failure_ids');ids.add(f.id)} const workIds=new Set(); for(const item of work){if(!idOk(item&&item.id)||workIds.has(item.id))fail('work_item_ids');workIds.add(item.id)} const failureById=new Map(); for(const f of observed){if(typeof f.test_name!=='string'||!f.test_name.trim())fail('observed_failure_test_name');if(typeof f.symptom!=='string'||!f.symptom.trim())fail('observed_failure_symptom');if(!Array.isArray(f.evidence_paths)||!f.evidence_paths.length)fail('observed_failure_evidence');for(const p of f.evidence_paths)rel(p);if(f.kind!==undefined)fail('observed_failure_kind_forbidden');failureById.set(f.id,f)} const mapped=new Set(); const allAllowed=new Set(); const v1Evidence=new Set(); for(const item of work){if(!allowed.includes(item.kind))fail('invalid_kind');if(item.kind==='PLACEHOLDER_CONTRACT')fail('placeholder_kind');if(!Array.isArray(item.observed_failure_ids)||!item.observed_failure_ids.length)fail('work_item_observed_failure_ids');if(!Array.isArray(item.evidence_paths)||!item.evidence_paths.length)fail('work_item_evidence');for(const p of item.evidence_paths)rel(p);if(action.includes(item.kind)){if(!Array.isArray(item.v1_evidence_paths)||!item.v1_evidence_paths.length)fail('work_item_v1_evidence');if(!Array.isArray(item.v2_evidence_paths)||!item.v2_evidence_paths.length)fail('work_item_v2_evidence');const v2Evidence=new Set(item.v2_evidence_paths.map(rel));for(const p of item.v1_evidence_paths){const n=rel(p);if(!under(n,v1Root))fail('work_item_v1_evidence_root');v1Evidence.add(n)}for(const p of item.v2_evidence_paths)rel(p);if(!Array.isArray(item.allowed_change_paths)||!item.allowed_change_paths.length)fail('work_item_allowed_change_paths');const approved=new Set();for(const p of item.allowed_change_paths){if(typeof p!=='string'||['*','?','[',']'].some(c=>p.includes(c)))fail('work_item_allowed_change_path_pattern');const n=rel(p);const r=inside(p);if(under(n,v1Root))fail('work_item_allowed_change_path_v1');if(!(under(n,v2Root)||under(n,'test')||under(n,'lib')))fail('work_item_allowed_change_path_root');if(fs.existsSync(r)?!fs.statSync(r).isFile():!path.basename(n).includes('.'))fail('work_item_allowed_change_path_not_file');if(!v2Evidence.has(n))fail('work_item_allowed_change_path_not_evidence');if(approved.has(n))fail('work_item_allowed_change_path_duplicate');approved.add(n);allAllowed.add(n)}}for(const id of item.observed_failure_ids){const f=failureById.get(id);if(!f)fail('unknown_observed_failure_id');if(action.includes(item.kind)&&(!f.test_name||!String(f.test_name).trim()))fail('action_test_evidence');mapped.add(id)}} if(mapped.size!==ids.size)fail('unmapped_observed_failures'); for(const f of observed.filter(x=>String(x.symptom).includes('PLACEHOLDER_CONTRACT'))){const kinds=new Set(work.filter(i=>i.observed_failure_ids.includes(f.id)).map(i=>i.kind));if(![...kinds].every(k=>k==='TEST_BUG'||k==='UNIMPLEMENTED')||kinds.size===0)fail('placeholder_mapping')} const counts=data.baseline&&data.baseline.counts; if(!data.baseline||!data.baseline.source_path||!counts)fail('baseline'); for(const k of ['failed','skipped','hung']){if(!Number.isInteger(counts[k])||counts[k]<0)fail('baseline_count_'+k)} if(counts.failed+counts.skipped+counts.hung!==observed.length)fail('baseline_count_mismatch'); const summary=data.summary_counts||{}; for(const kind of allowed){if(!Number.isInteger(summary[kind])||summary[kind]!==work.filter(x=>x.kind===kind).length)fail('summary_count_'+kind)} const snap=data.contract_snapshot||{}; const baselineHash=fileHash(data.baseline.source_path); const diagnosisHash=fileHash(diagnosisPath); if(snap.observed_failure_count!==observed.length||snap.work_item_count!==work.length||!same(snap.allowed_kinds,allowed))fail('contract_snapshot'); if(snap.baseline_source_sha256!==baselineHash)fail('baseline_sha256'); if(snap.diagnosis_artifact_sha256!==diagnosisHash)fail('diagnosis_sha256'); const stale=data.staleness; if(!stale||stale.is_stale!==false)fail('stale_contract'); const source=(entry,p,n,expected)=>{if(!entry||entry.path!==p)fail('staleness_'+n+'_path');if(entry.sha256!==expected)fail('staleness_'+n+'_sha256')}; source(stale.behavior_doc,behaviorPath,'behavior_doc',fileHash(behaviorPath)); source(stale.baseline,data.baseline.source_path,'baseline',baselineHash); source(stale.diagnosis_artifact,diagnosisPath,'diagnosis_artifact',diagnosisHash); const expectedSources=[...new Set(work.filter(i=>action.includes(i.kind)).flatMap(i=>[...i.v1_evidence_paths,...i.v2_evidence_paths]).map(rel))].sort(); if(!Array.isArray(stale.source_files)||stale.source_files.length!==expectedSources.length)fail('staleness_source_files'); let relaxed=false; let head=''; if(fs.existsSync(inside(progressPath))){const progressMtime=fs.statSync(inside(progressPath)).mtimeMs;const artifactMtime=Math.max(fs.statSync(inside(contractPath)).mtimeMs,fs.statSync(inside(diagnosisPath)).mtimeMs,fs.existsSync(inside(reviewPath))?fs.statSync(inside(reviewPath)).mtimeMs:0);if(progressMtime>=artifactMtime){const progress=readJson(progressPath);head=cp.execFileSync('git',['rev-parse','HEAD'],{encoding:'utf8'}).trim();if(progress.module!==mod||progress.verdict!=='CONTINUE'||progress.module_complete!==false||progress.diagnosis_invalid!==false)fail('progress_state');if(progress.diagnosis_artifact!==diagnosisPath||progress.review_artifact!=='.dna/reviews/'+mod+'-review.json')fail('progress_artifacts');if(progress.git_head!==head||progress.last_fix_commit!==head)fail('progress_commit');if(!Number.isInteger(progress.round)||progress.round<1||typeof progress.run_id!=='string'||!progress.run_id.trim())fail('progress_provenance');const progressTime=Date.parse(progress.updated_at);if(!Number.isFinite(progressTime)||progressTime+300000<artifactMtime)fail('progress_updated_at');relaxed=true}} const headState=p=>{try{const body=cp.execFileSync('git',['show',head+':'+p],{encoding:null,stdio:['ignore','pipe','ignore']});return {exists:true,sha256:hash(body)}}catch{return {exists:false,sha256:null}}}; const seenSources=new Set(); for(let i=0;i<expectedSources.length;i++){const p=expectedSources[i];const entry=stale.source_files[i];if(entry&&seenSources.has(entry.path))fail('staleness_source_file_duplicate');if(!entry||entry.path!==p)fail('staleness_source_file_path');seenSources.add(entry.path);if(typeof entry.exists!=='boolean')fail('staleness_source_file_exists');if(entry.exists?(typeof entry.sha256!=='string'||!/^[a-f0-9]{64}$/.test(entry.sha256)):entry.sha256!==null)fail('staleness_source_file_snapshot');if(v1Evidence.has(p)&&entry.exists!==true)fail('staleness_v1_source_missing');const r=inside(p);const currentExists=fs.existsSync(r);if(currentExists&&!fs.statSync(r).isFile())fail('staleness_source_file_not_file');const current={exists:currentExists,sha256:currentExists?fileHash(p):null};if(relaxed&&allAllowed.has(p)){const committed=headState(p);if(current.exists!==committed.exists||current.sha256!==committed.sha256)fail('staleness_source_file_uncommitted')}else if(current.exists!==entry.exists||current.sha256!==entry.sha256)fail('staleness_source_file_sha256')} const semantic=JSON.parse(JSON.stringify(data)); delete semantic.contract_snapshot.sidecar_semantic_sha256; delete semantic.contract_snapshot.diagnosis_artifact_sha256; delete semantic.staleness.diagnosis_artifact; const sidecarSemanticHash=hash(canon(semantic)); const diagnosisSemanticHash=normalizedDiagnosisHash(diagnosisPath); if(snap.sidecar_semantic_sha256!==sidecarSemanticHash||snap.diagnosis_semantic_sha256!==diagnosisSemanticHash)fail('contract_snapshot_semantic'); if(fs.existsSync(inside(reviewPath))){const prev=readJson(reviewPath);if(prev.verdict==='REQUEST_REANALYSIS'){const ps=prev.contract_snapshot||{};const unchanged=modern(ps)?ps.sidecar_semantic_sha256===sidecarSemanticHash&&ps.diagnosis_semantic_sha256===diagnosisSemanticHash:legacySame(ps,snap);if(unchanged)fail('reanalysis_unchanged');const cm=fs.statSync(inside(contractPath)).mtimeMs;const dm=fs.statSync(inside(diagnosisPath)).mtimeMs;const rm=fs.statSync(inside(reviewPath)).mtimeMs;if(cm<=rm||dm<=rm)fail('reanalysis_not_rewritten')}} \" \"$ARGUMENTS\" \"{{v1_path}}\" \"{{v2_path}}\""
175
+ - "node -e \"const fs=require('node:fs'); const path=require('node:path'); const mod=process.argv[1]; const allowed=['BUG','UNIMPLEMENTED','INFRA','REMOVED','TEST_BUG']; const fail=m=>{console.error('INTENTDNA_DIAGNOSTIC:'+m);process.exit(1)}; const same=(a,b)=>Array.isArray(a)&&a.length===b.length&&a.every((v,i)=>v===b[i]); const snapSame=(a,b)=>a&&b&&a.baseline_source_sha256===b.baseline_source_sha256&&a.diagnosis_artifact_sha256===b.diagnosis_artifact_sha256&&a.sidecar_semantic_sha256===b.sidecar_semantic_sha256&&a.diagnosis_semantic_sha256===b.diagnosis_semantic_sha256&&a.observed_failure_count===b.observed_failure_count&&a.work_item_count===b.work_item_count&&same(a.allowed_kinds,b.allowed_kinds); const root=fs.realpathSync(process.cwd()); const inside=p=>{if(typeof p!=='string'||!p||path.isAbsolute(p))fail('path_invalid');const r=path.resolve(root,p);if(r!==root&&!r.startsWith(root+path.sep))fail('path_escape');let stat=null;try{stat=fs.lstatSync(r)}catch(e){if(!e||e.code!=='ENOENT')fail('path_unreadable')}if(stat){if(stat.isSymbolicLink())fail('path_symlink');const real=fs.realpathSync(r);if(real!==root&&!real.startsWith(root+path.sep))fail('path_escape')}return r}; const readJson=p=>JSON.parse(fs.readFileSync(inside(p),'utf8')); if(!/^[A-Za-z0-9_-]+$/.test(mod))fail('module'); const contractPath='.dna/specs/diagnosis-'+mod+'.contract.json'; const reviewPath='.dna/specs/diagnosis-'+mod+'.review.json'; const diagnosisPath='.dna/specs/diagnosis-'+mod+'.md'; const data=readJson(contractPath); const snap=data.contract_snapshot||{}; if(data.contract_version!=='diagnosis-contract/v2'||data.module!==mod)fail('contract_version'); const review=readJson(reviewPath); if(review.contract_version!=='diagnosis-contract-review/v1')fail('review_contract_version'); if(review.contract_valid!==true)fail('review_contract_valid'); if(review.verdict!=='APPROVE')fail('review_verdict'); if(review.contract_artifact_reviewed!==contractPath)fail('review_contract_artifact_path'); if(review.artifact_reviewed!==diagnosisPath)fail('review_artifact_path'); if(!snapSame(review.contract_snapshot,snap))fail('review_contract_snapshot'); const updated=Date.parse(review.updated_at); if(!Number.isFinite(updated))fail('review_updated_at'); if(!Array.isArray(review.evidence_paths)||!review.evidence_paths.length)fail('review_evidence_paths'); for(const p of review.evidence_paths){const r=inside(p);if(!fs.existsSync(r)||!fs.statSync(r).isFile())fail('review_evidence_paths')} const cm=fs.statSync(inside(contractPath)).mtimeMs; const dm=fs.statSync(inside(diagnosisPath)).mtimeMs; const rm=fs.statSync(inside(reviewPath)).mtimeMs; if(rm<cm||rm<dm||updated+300000<Math.max(cm,dm)||Math.abs(rm-updated)>300000)fail('review_stale'); \" \"$ARGUMENTS\""
176
176
  allow_builtin_asserts:
177
177
  - clean_working_tree
178
178
  roles:
@@ -363,59 +363,54 @@ controllers:
363
363
  fix_reviewer: fix_reviewer
364
364
  test_runner: test_runner
365
365
  stop_format: configured_stop_report
366
- policy:
367
- diagnosis_reuse_criteria:
368
- - "$diagnosis_artifact_path exists"
369
- - ".dna/specs/diagnosis-$ARGUMENTS.contract.json uses diagnosis-contract/v2 and passes the declared completion validator"
370
- - "$diagnosis_review_artifact_path exists and verdict is APPROVE"
371
- - "$diagnosis_review_artifact_path contract_valid is true and contract_artifact_reviewed equals .dna/specs/diagnosis-$ARGUMENTS.contract.json"
372
- - "every actionable work item has non-empty file-only allowed_change_paths backed by v2_evidence_paths"
373
- - "before round 1, every staleness.source_files existence/hash snapshot is exact and current"
374
- - "when resuming after a recorded fix commit, configured v1 evidence and unapproved v2 evidence must still match exactly; drift is allowed only for an allowed_change_paths file when progress provenance binds git_head and last_fix_commit to current HEAD and the working file equals HEAD"
375
- - "behavior doc is not newer than diagnosis spec"
376
- - "baseline artifact is not newer than diagnosis spec"
377
- - "baseline artifact is not newer than diagnosis contract sidecar"
378
- - "user did not explicitly pass reanalyze or scope changed"
379
- - "progress JSON does not say diagnosis_invalid"
380
- progress_provenance_checks:
381
- - "module equals the safe module argument"
382
- - "round equals the provided current_round"
383
- - "run_id equals the provided controller_run_id"
384
- - "diagnosis_artifact equals $diagnosis_artifact_path"
385
- - "review_artifact equals $review_artifact_path"
386
- - "git_head or last_fix_commit references the current round's fix commit when available"
387
- - "updated_at is not earlier than the current round start time"
388
- progress_provenance_failure: "if provenance is missing, stale, or mismatched, stop with Verdict BLOCKED and failure_reason PROGRESS_PROVENANCE_MISMATCH"
389
- verdict_branches:
390
- - "PASS -> success stop"
391
- - "CONTINUE -> next fresh context fix round"
392
- - "REQUEST_CHANGES -> stop with the configured stop report"
393
- - "BLOCKED -> stop with the configured stop report"
394
- - "DIAGNOSIS_CONTRACT_INVALID -> stop before fix with Verdict BLOCKED, failure_reason DIAGNOSIS_CONTRACT_INVALID, and progress diagnosis_invalid true"
395
- verdict_mapping:
396
- - "PASS / CONTINUE / REQUEST_CHANGES / BLOCKED mapping:"
397
- - "- PASS: success stop"
398
- - "- CONTINUE: start the next fresh context round"
399
- - "- REQUEST_CHANGES: stop, report reviewer/verifier requested changes"
400
- - "- BLOCKED: stop, report blocker"
401
- - "- DIAGNOSIS_CONTRACT_INVALID: stop before fix, propagate diagnosis_invalid true in progress JSON, and require /dna-frw-diagnosis reanalysis"
402
- - "- MAX_ROUNDS: stop, report max rounds reached and next focus"
403
- stop_report:
404
- - "On REQUEST_CHANGES, BLOCKED, or MAX_ROUNDS, output exactly these Chinese sections:"
405
- - "结论:state the direct stop trigger, current round, and current commit if one exists; cite verifier result_id, trace_id, or ArtifactManifest artifact id."
406
- - "为什么停止:explain the workflow principle in plain language; if this is a current-round new red, explain that continuing would build on a bad baseline; prose-only claims are not sufficient."
407
- - "是否回滚:say whether rollback/revert is recommended. If the commit direction looks wrong or the blast radius is unclear, recommend revert; if the failure is narrow and fixable, recommend fix-forward."
408
- - "下一步:give one concrete next action. If recommending fix-forward, include a copy-paste single-fix prompt with current commit, exact failure, allowed scope, forbidden actions, verification command, commit expectation, and stop conditions."
409
- constraints:
410
- - "One session targets one behavior module; diagnosis-approved supporting package paths are part of that module's repair scope."
411
- - "Do not require a capability id, capability catalog, or separate core-align invocation when the approved diagnosis already names the supporting paths."
412
- - "Do not invalidate a diagnosis solely because a prior verified round changed an approved v2 evidence file; require the progress/commit provenance checks before accepting that drift."
413
- - "Do not infer module completion from raw test output."
414
- - "The controller must schedule only from $progress_artifact_path."
415
- - "Manual /dna-frw-diagnosis and /dna-frw-fix flows remain separate and available."
416
- - "Manual /dna-frw-fix must run the same diagnosis contract preflight before edits and stop with DIAGNOSIS_CONTRACT_INVALID when the sidecar or independent review contract is invalid."
417
- - "Do not use the generic workflow policy block; branch only from the progress JSON verdict."
418
-
366
+ runtime_program:
367
+ schema_version: intentdna.controller_program.v1
368
+ diagnosis:
369
+ analyzer_step_id: analyze
370
+ reviewer_step_id: review
371
+ decision:
372
+ step_id: review
373
+ output_name: diagnosis-review-verdict
374
+ schema_ref: intentdna.frw.diagnosis-review.v1
375
+ json_pointer: /verdict
376
+ approved_value: APPROVE
377
+ request_changes_value: REQUEST_REANALYSIS
378
+ fix:
379
+ surgeon_step_id: fix_bugs
380
+ reviewer_step_id: review_changes
381
+ verifier_step_id: verify_report
382
+ decision:
383
+ step_id: verify_report
384
+ output_name: fix-progress-verdict
385
+ schema_ref: intentdna.frw.fix-progress.v1
386
+ json_pointer: /verdict
387
+ pass_value: PASS
388
+ continue_value: CONTINUE
389
+ request_changes_value: REQUEST_CHANGES
390
+ blocked_value: BLOCKED
391
+ diagnosis_contract_invalid_value: DIAGNOSIS_CONTRACT_INVALID
392
+ provenance_invalid_value: PROVENANCE_INVALID
393
+ reuse:
394
+ mode_input: diagnosis_source
395
+ legacy:
396
+ diagnosis_spec_reference_input: legacy_diagnosis_spec_path
397
+ diagnosis_spec_digest_input: legacy_diagnosis_spec_digest
398
+ diagnosis_contract_reference_input: legacy_diagnosis_contract_path
399
+ diagnosis_contract_digest_input: legacy_diagnosis_contract_digest
400
+ canonical:
401
+ source_plan_input: canonical_source_plan_id
402
+ diagnosis_result_input: canonical_diagnosis_result_id
403
+ review_result_input: canonical_diagnosis_review_result_id
404
+ approval_reuse:
405
+ enabled: true
406
+ policy_id: exact-diagnosis-approval-v1
407
+ require_exact_artifact_ids: true
408
+ require_exact_artifact_digests: true
409
+ outputs:
410
+ diagnosis_spec: diagnosis-spec
411
+ diagnosis_contract: diagnosis-contract
412
+ diagnosis_review: diagnosis-review
413
+ diagnosis_review_verdict: diagnosis-review-verdict
419
414
  workflows:
420
415
  behavior-lock:
421
416
  name: Behavior Lock
@@ -605,13 +600,15 @@ workflows:
605
600
  8. If this is a reanalysis after REQUEST_REANALYSIS or DIAGNOSIS_CONTRACT_INVALID, read the reviewer verdict first, update every section listed in sections_to_fix plus the summary table and contract sidecar, and report sections_updated in your final response. Reanalysis is not successful unless both the spec artifact and contract sidecar are rewritten.
606
601
  9. Before handing off to review, the producer-side diagnosis contract completion gate must pass. It rejects grouped observations that hide per-test failures, duplicate/bad ids, unmapped observed_failures, missing evidence, count mismatches, bad hashes, stale state, and no-material-change reanalysis after REQUEST_REANALYSIS. Reanalysis compares the canonical sidecar with its self-digest and raw diagnosis snapshot fields omitted, plus a whitespace-normalized diagnosis hash, so a real sidecar-only correction is allowed while formatting-only Markdown changes are not.
607
602
  completion:
608
- - command_success: "node -e \"const fs=require('node:fs'); const cp=require('node:child_process'); const crypto=require('node:crypto'); const path=require('node:path'); const mod=process.env.ARGUMENTS||'$ARGUMENTS'; const allowed=['BUG','UNIMPLEMENTED','INFRA','REMOVED','TEST_BUG']; const action=['BUG','UNIMPLEMENTED','INFRA','TEST_BUG']; const fail=m=>{console.error('INTENTDNA_DIAGNOSTIC:'+m);process.exit(1)}; const same=(a,b)=>Array.isArray(a)&&a.length===b.length&&a.every((v,i)=>v===b[i]); const legacySame=(a,b)=>a&&b&&a.baseline_source_sha256===b.baseline_source_sha256&&a.diagnosis_artifact_sha256===b.diagnosis_artifact_sha256&&a.observed_failure_count===b.observed_failure_count&&a.work_item_count===b.work_item_count&&same(a.allowed_kinds,b.allowed_kinds); const modern=s=>s&&typeof s.sidecar_semantic_sha256==='string'&&typeof s.diagnosis_semantic_sha256==='string'; const root=fs.realpathSync(process.cwd()); const inside=p=>{if(typeof p!=='string'||!p||path.isAbsolute(p))fail('path_invalid');const r=path.resolve(root,p);if(r!==root&&!r.startsWith(root+path.sep))fail('path_escape');let stat=null;try{stat=fs.lstatSync(r)}catch(e){if(!e||e.code!=='ENOENT')fail('path_unreadable')}if(stat){if(stat.isSymbolicLink())fail('path_symlink');const real=fs.realpathSync(r);if(real!==root&&!real.startsWith(root+path.sep))fail('path_escape')}return r}; const rel=p=>path.relative(root,inside(p)).split(path.sep).join('/')||'.'; const under=(p,base)=>p===base||p.startsWith(base+'/'); const readJson=p=>JSON.parse(fs.readFileSync(inside(p),'utf8')); const hash=v=>crypto.createHash('sha256').update(v).digest('hex'); const fileHash=p=>hash(fs.readFileSync(inside(p))); const normalizedDiagnosisHash=p=>hash(fs.readFileSync(inside(p),'utf8').replace(/\\s+/g,' ').trim()); const canon=v=>Array.isArray(v)?'['+v.map(canon).join(',')+']':v&&typeof v==='object'?'{'+Object.keys(v).sort().map(k=>JSON.stringify(k)+':'+canon(v[k])).join(',')+'}':JSON.stringify(v); if(!/^[A-Za-z0-9_-]+$/.test(mod))fail('module'); const contractPath='.dna/specs/diagnosis-'+mod+'.contract.json'; const reviewPath='.dna/specs/diagnosis-'+mod+'.review.json'; const progressPath='.dna/fix-progress/'+mod+'.json'; const diagnosisPath='.dna/specs/diagnosis-'+mod+'.md'; const behaviorPath='docs/behavior/'+mod+'.md'; const v1Root=rel('{{v1_path}}'); const v2Root=rel('{{v2_path}}'); if(v1Root==='.'||v2Root==='.'||under(v1Root,v2Root)||under(v2Root,v1Root))fail('configured_roots'); const data=readJson(contractPath); if(data.contract_version!=='diagnosis-contract/v2')fail('contract_version'); if(data.module!==mod)fail('module_mismatch'); if(data.diagnosis_artifact!==diagnosisPath)fail('diagnosis_artifact_path'); if(!same(data.allowed_kinds,allowed))fail('allowed_kinds'); const observed=Array.isArray(data.observed_failures)?data.observed_failures:fail('observed_failures'); const work=Array.isArray(data.work_items)?data.work_items:fail('work_items'); if(!observed.length)fail('empty_observed_failures'); if(!work.length)fail('empty_work_items'); const idOk=id=>typeof id==='string'&&/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/.test(id); const ids=new Set(); for(const f of observed){if(!idOk(f&&f.id)||ids.has(f.id))fail('observed_failure_ids');ids.add(f.id)} const workIds=new Set(); for(const item of work){if(!idOk(item&&item.id)||workIds.has(item.id))fail('work_item_ids');workIds.add(item.id)} const failureById=new Map(); for(const f of observed){if(typeof f.test_name!=='string'||!f.test_name.trim())fail('observed_failure_test_name');if(typeof f.symptom!=='string'||!f.symptom.trim())fail('observed_failure_symptom');if(!Array.isArray(f.evidence_paths)||!f.evidence_paths.length)fail('observed_failure_evidence');for(const p of f.evidence_paths)rel(p);if(f.kind!==undefined)fail('observed_failure_kind_forbidden');failureById.set(f.id,f)} const mapped=new Set(); const allAllowed=new Set(); const v1Evidence=new Set(); for(const item of work){if(!allowed.includes(item.kind))fail('invalid_kind');if(item.kind==='PLACEHOLDER_CONTRACT')fail('placeholder_kind');if(!Array.isArray(item.observed_failure_ids)||!item.observed_failure_ids.length)fail('work_item_observed_failure_ids');if(!Array.isArray(item.evidence_paths)||!item.evidence_paths.length)fail('work_item_evidence');for(const p of item.evidence_paths)rel(p);if(action.includes(item.kind)){if(!Array.isArray(item.v1_evidence_paths)||!item.v1_evidence_paths.length)fail('work_item_v1_evidence');if(!Array.isArray(item.v2_evidence_paths)||!item.v2_evidence_paths.length)fail('work_item_v2_evidence');const v2Evidence=new Set(item.v2_evidence_paths.map(rel));for(const p of item.v1_evidence_paths){const n=rel(p);if(!under(n,v1Root))fail('work_item_v1_evidence_root');v1Evidence.add(n)}for(const p of item.v2_evidence_paths)rel(p);if(!Array.isArray(item.allowed_change_paths)||!item.allowed_change_paths.length)fail('work_item_allowed_change_paths');const approved=new Set();for(const p of item.allowed_change_paths){if(typeof p!=='string'||['*','?','[',']'].some(c=>p.includes(c)))fail('work_item_allowed_change_path_pattern');const n=rel(p);const r=inside(p);if(under(n,v1Root))fail('work_item_allowed_change_path_v1');if(!(under(n,v2Root)||under(n,'test')||under(n,'lib')))fail('work_item_allowed_change_path_root');if(fs.existsSync(r)?!fs.statSync(r).isFile():!path.basename(n).includes('.'))fail('work_item_allowed_change_path_not_file');if(!v2Evidence.has(n))fail('work_item_allowed_change_path_not_evidence');if(approved.has(n))fail('work_item_allowed_change_path_duplicate');approved.add(n);allAllowed.add(n)}}for(const id of item.observed_failure_ids){const f=failureById.get(id);if(!f)fail('unknown_observed_failure_id');if(action.includes(item.kind)&&(!f.test_name||!String(f.test_name).trim()))fail('action_test_evidence');mapped.add(id)}} if(mapped.size!==ids.size)fail('unmapped_observed_failures'); for(const f of observed.filter(x=>String(x.symptom).includes('PLACEHOLDER_CONTRACT'))){const kinds=new Set(work.filter(i=>i.observed_failure_ids.includes(f.id)).map(i=>i.kind));if(![...kinds].every(k=>k==='TEST_BUG'||k==='UNIMPLEMENTED')||kinds.size===0)fail('placeholder_mapping')} const counts=data.baseline&&data.baseline.counts; if(!data.baseline||!data.baseline.source_path||!counts)fail('baseline'); for(const k of ['failed','skipped','hung']){if(!Number.isInteger(counts[k])||counts[k]<0)fail('baseline_count_'+k)} if(counts.failed+counts.skipped+counts.hung!==observed.length)fail('baseline_count_mismatch'); const summary=data.summary_counts||{}; for(const kind of allowed){if(!Number.isInteger(summary[kind])||summary[kind]!==work.filter(x=>x.kind===kind).length)fail('summary_count_'+kind)} const snap=data.contract_snapshot||{}; const baselineHash=fileHash(data.baseline.source_path); const diagnosisHash=fileHash(diagnosisPath); if(snap.observed_failure_count!==observed.length||snap.work_item_count!==work.length||!same(snap.allowed_kinds,allowed))fail('contract_snapshot'); if(snap.baseline_source_sha256!==baselineHash)fail('baseline_sha256'); if(snap.diagnosis_artifact_sha256!==diagnosisHash)fail('diagnosis_sha256'); const stale=data.staleness; if(!stale||stale.is_stale!==false)fail('stale_contract'); const source=(entry,p,n,expected)=>{if(!entry||entry.path!==p)fail('staleness_'+n+'_path');if(entry.sha256!==expected)fail('staleness_'+n+'_sha256')}; source(stale.behavior_doc,behaviorPath,'behavior_doc',fileHash(behaviorPath)); source(stale.baseline,data.baseline.source_path,'baseline',baselineHash); source(stale.diagnosis_artifact,diagnosisPath,'diagnosis_artifact',diagnosisHash); const expectedSources=[...new Set(work.filter(i=>action.includes(i.kind)).flatMap(i=>[...i.v1_evidence_paths,...i.v2_evidence_paths]).map(rel))].sort(); if(!Array.isArray(stale.source_files)||stale.source_files.length!==expectedSources.length)fail('staleness_source_files'); let relaxed=false; let head=''; if(fs.existsSync(inside(progressPath))){const progressMtime=fs.statSync(inside(progressPath)).mtimeMs;const artifactMtime=Math.max(fs.statSync(inside(contractPath)).mtimeMs,fs.statSync(inside(diagnosisPath)).mtimeMs,fs.existsSync(inside(reviewPath))?fs.statSync(inside(reviewPath)).mtimeMs:0);if(progressMtime>=artifactMtime){const progress=readJson(progressPath);head=cp.execFileSync('git',['rev-parse','HEAD'],{encoding:'utf8'}).trim();if(progress.module!==mod||progress.verdict!=='CONTINUE'||progress.module_complete!==false||progress.diagnosis_invalid!==false)fail('progress_state');if(progress.diagnosis_artifact!==diagnosisPath||progress.review_artifact!=='.dna/reviews/'+mod+'-review.json')fail('progress_artifacts');if(progress.git_head!==head||progress.last_fix_commit!==head)fail('progress_commit');if(!Number.isInteger(progress.round)||progress.round<1||typeof progress.run_id!=='string'||!progress.run_id.trim())fail('progress_provenance');const progressTime=Date.parse(progress.updated_at);if(!Number.isFinite(progressTime)||progressTime+300000<artifactMtime)fail('progress_updated_at');relaxed=true}} const headState=p=>{try{const body=cp.execFileSync('git',['show',head+':'+p],{encoding:null,stdio:['ignore','pipe','ignore']});return {exists:true,sha256:hash(body)}}catch{return {exists:false,sha256:null}}}; const seenSources=new Set(); for(let i=0;i<expectedSources.length;i++){const p=expectedSources[i];const entry=stale.source_files[i];if(entry&&seenSources.has(entry.path))fail('staleness_source_file_duplicate');if(!entry||entry.path!==p)fail('staleness_source_file_path');seenSources.add(entry.path);if(typeof entry.exists!=='boolean')fail('staleness_source_file_exists');if(entry.exists?(typeof entry.sha256!=='string'||!/^[a-f0-9]{64}$/.test(entry.sha256)):entry.sha256!==null)fail('staleness_source_file_snapshot');if(v1Evidence.has(p)&&entry.exists!==true)fail('staleness_v1_source_missing');const r=inside(p);const currentExists=fs.existsSync(r);if(currentExists&&!fs.statSync(r).isFile())fail('staleness_source_file_not_file');const current={exists:currentExists,sha256:currentExists?fileHash(p):null};if(relaxed&&allAllowed.has(p)){const committed=headState(p);if(current.exists!==committed.exists||current.sha256!==committed.sha256)fail('staleness_source_file_uncommitted')}else if(current.exists!==entry.exists||current.sha256!==entry.sha256)fail('staleness_source_file_sha256')} const semantic=JSON.parse(JSON.stringify(data)); delete semantic.contract_snapshot.sidecar_semantic_sha256; delete semantic.contract_snapshot.diagnosis_artifact_sha256; delete semantic.staleness.diagnosis_artifact; const sidecarSemanticHash=hash(canon(semantic)); const diagnosisSemanticHash=normalizedDiagnosisHash(diagnosisPath); if(snap.sidecar_semantic_sha256!==sidecarSemanticHash||snap.diagnosis_semantic_sha256!==diagnosisSemanticHash)fail('contract_snapshot_semantic'); if(fs.existsSync(inside(reviewPath))){const prev=readJson(reviewPath);if(prev.verdict==='REQUEST_REANALYSIS'){const ps=prev.contract_snapshot||{};const unchanged=modern(ps)?ps.sidecar_semantic_sha256===sidecarSemanticHash&&ps.diagnosis_semantic_sha256===diagnosisSemanticHash:legacySame(ps,snap);if(unchanged)fail('reanalysis_unchanged');const cm=fs.statSync(inside(contractPath)).mtimeMs;const dm=fs.statSync(inside(diagnosisPath)).mtimeMs;const rm=fs.statSync(inside(reviewPath)).mtimeMs;if(cm<=rm||dm<=rm)fail('reanalysis_not_rewritten')}} \""
603
+ - command_success: "node -e \"const fs=require('node:fs'); const cp=require('node:child_process'); const crypto=require('node:crypto'); const path=require('node:path'); const mod=process.argv[1]; const allowed=['BUG','UNIMPLEMENTED','INFRA','REMOVED','TEST_BUG']; const action=['BUG','UNIMPLEMENTED','INFRA','TEST_BUG']; const fail=m=>{console.error('INTENTDNA_DIAGNOSTIC:'+m);process.exit(1)}; const same=(a,b)=>Array.isArray(a)&&a.length===b.length&&a.every((v,i)=>v===b[i]); const legacySame=(a,b)=>a&&b&&a.baseline_source_sha256===b.baseline_source_sha256&&a.diagnosis_artifact_sha256===b.diagnosis_artifact_sha256&&a.observed_failure_count===b.observed_failure_count&&a.work_item_count===b.work_item_count&&same(a.allowed_kinds,b.allowed_kinds); const modern=s=>s&&typeof s.sidecar_semantic_sha256==='string'&&typeof s.diagnosis_semantic_sha256==='string'; const root=fs.realpathSync(process.cwd()); const inside=p=>{if(typeof p!=='string'||!p||path.isAbsolute(p))fail('path_invalid');const r=path.resolve(root,p);if(r!==root&&!r.startsWith(root+path.sep))fail('path_escape');let stat=null;try{stat=fs.lstatSync(r)}catch(e){if(!e||e.code!=='ENOENT')fail('path_unreadable')}if(stat){if(stat.isSymbolicLink())fail('path_symlink');const real=fs.realpathSync(r);if(real!==root&&!real.startsWith(root+path.sep))fail('path_escape')}return r}; const rel=p=>path.relative(root,inside(p)).split(path.sep).join('/')||'.'; const under=(p,base)=>p===base||p.startsWith(base+'/'); const readJson=p=>JSON.parse(fs.readFileSync(inside(p),'utf8')); const hash=v=>crypto.createHash('sha256').update(v).digest('hex'); const fileHash=p=>hash(fs.readFileSync(inside(p))); const normalizedDiagnosisHash=p=>hash(fs.readFileSync(inside(p),'utf8').replace(/\\s+/g,' ').trim()); const canon=v=>Array.isArray(v)?'['+v.map(canon).join(',')+']':v&&typeof v==='object'?'{'+Object.keys(v).sort().map(k=>JSON.stringify(k)+':'+canon(v[k])).join(',')+'}':JSON.stringify(v); if(!/^[A-Za-z0-9_-]+$/.test(mod))fail('module'); const contractPath='.dna/specs/diagnosis-'+mod+'.contract.json'; const reviewPath='.dna/specs/diagnosis-'+mod+'.review.json'; const progressPath='.dna/fix-progress/'+mod+'.json'; const diagnosisPath='.dna/specs/diagnosis-'+mod+'.md'; const behaviorPath='docs/behavior/'+mod+'.md'; const v1Root=rel(process.argv[2]); const v2Root=rel(process.argv[3]); if(v1Root==='.'||v2Root==='.'||under(v1Root,v2Root)||under(v2Root,v1Root))fail('configured_roots'); const data=readJson(contractPath); if(data.contract_version!=='diagnosis-contract/v2')fail('contract_version'); if(data.module!==mod)fail('module_mismatch'); if(data.diagnosis_artifact!==diagnosisPath)fail('diagnosis_artifact_path'); if(!same(data.allowed_kinds,allowed))fail('allowed_kinds'); const observed=Array.isArray(data.observed_failures)?data.observed_failures:fail('observed_failures'); const work=Array.isArray(data.work_items)?data.work_items:fail('work_items'); if(!observed.length)fail('empty_observed_failures'); if(!work.length)fail('empty_work_items'); const idOk=id=>typeof id==='string'&&/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/.test(id); const ids=new Set(); for(const f of observed){if(!idOk(f&&f.id)||ids.has(f.id))fail('observed_failure_ids');ids.add(f.id)} const workIds=new Set(); for(const item of work){if(!idOk(item&&item.id)||workIds.has(item.id))fail('work_item_ids');workIds.add(item.id)} const failureById=new Map(); for(const f of observed){if(typeof f.test_name!=='string'||!f.test_name.trim())fail('observed_failure_test_name');if(typeof f.symptom!=='string'||!f.symptom.trim())fail('observed_failure_symptom');if(!Array.isArray(f.evidence_paths)||!f.evidence_paths.length)fail('observed_failure_evidence');for(const p of f.evidence_paths)rel(p);if(f.kind!==undefined)fail('observed_failure_kind_forbidden');failureById.set(f.id,f)} const mapped=new Set(); const allAllowed=new Set(); const v1Evidence=new Set(); for(const item of work){if(!allowed.includes(item.kind))fail('invalid_kind');if(item.kind==='PLACEHOLDER_CONTRACT')fail('placeholder_kind');if(!Array.isArray(item.observed_failure_ids)||!item.observed_failure_ids.length)fail('work_item_observed_failure_ids');if(!Array.isArray(item.evidence_paths)||!item.evidence_paths.length)fail('work_item_evidence');for(const p of item.evidence_paths)rel(p);if(action.includes(item.kind)){if(!Array.isArray(item.v1_evidence_paths)||!item.v1_evidence_paths.length)fail('work_item_v1_evidence');if(!Array.isArray(item.v2_evidence_paths)||!item.v2_evidence_paths.length)fail('work_item_v2_evidence');const v2Evidence=new Set(item.v2_evidence_paths.map(rel));for(const p of item.v1_evidence_paths){const n=rel(p);if(!under(n,v1Root))fail('work_item_v1_evidence_root');v1Evidence.add(n)}for(const p of item.v2_evidence_paths)rel(p);if(!Array.isArray(item.allowed_change_paths)||!item.allowed_change_paths.length)fail('work_item_allowed_change_paths');const approved=new Set();for(const p of item.allowed_change_paths){if(typeof p!=='string'||['*','?','[',']'].some(c=>p.includes(c)))fail('work_item_allowed_change_path_pattern');const n=rel(p);const r=inside(p);if(under(n,v1Root))fail('work_item_allowed_change_path_v1');if(!(under(n,v2Root)||under(n,'test')||under(n,'lib')))fail('work_item_allowed_change_path_root');if(fs.existsSync(r)?!fs.statSync(r).isFile():!path.basename(n).includes('.'))fail('work_item_allowed_change_path_not_file');if(!v2Evidence.has(n))fail('work_item_allowed_change_path_not_evidence');if(approved.has(n))fail('work_item_allowed_change_path_duplicate');approved.add(n);allAllowed.add(n)}}for(const id of item.observed_failure_ids){const f=failureById.get(id);if(!f)fail('unknown_observed_failure_id');if(action.includes(item.kind)&&(!f.test_name||!String(f.test_name).trim()))fail('action_test_evidence');mapped.add(id)}} if(mapped.size!==ids.size)fail('unmapped_observed_failures'); for(const f of observed.filter(x=>String(x.symptom).includes('PLACEHOLDER_CONTRACT'))){const kinds=new Set(work.filter(i=>i.observed_failure_ids.includes(f.id)).map(i=>i.kind));if(![...kinds].every(k=>k==='TEST_BUG'||k==='UNIMPLEMENTED')||kinds.size===0)fail('placeholder_mapping')} const counts=data.baseline&&data.baseline.counts; if(!data.baseline||!data.baseline.source_path||!counts)fail('baseline'); for(const k of ['failed','skipped','hung']){if(!Number.isInteger(counts[k])||counts[k]<0)fail('baseline_count_'+k)} if(counts.failed+counts.skipped+counts.hung!==observed.length)fail('baseline_count_mismatch'); const summary=data.summary_counts||{}; for(const kind of allowed){if(!Number.isInteger(summary[kind])||summary[kind]!==work.filter(x=>x.kind===kind).length)fail('summary_count_'+kind)} const snap=data.contract_snapshot||{}; const baselineHash=fileHash(data.baseline.source_path); const diagnosisHash=fileHash(diagnosisPath); if(snap.observed_failure_count!==observed.length||snap.work_item_count!==work.length||!same(snap.allowed_kinds,allowed))fail('contract_snapshot'); if(snap.baseline_source_sha256!==baselineHash)fail('baseline_sha256'); if(snap.diagnosis_artifact_sha256!==diagnosisHash)fail('diagnosis_sha256'); const stale=data.staleness; if(!stale||stale.is_stale!==false)fail('stale_contract'); const source=(entry,p,n,expected)=>{if(!entry||entry.path!==p)fail('staleness_'+n+'_path');if(entry.sha256!==expected)fail('staleness_'+n+'_sha256')}; source(stale.behavior_doc,behaviorPath,'behavior_doc',fileHash(behaviorPath)); source(stale.baseline,data.baseline.source_path,'baseline',baselineHash); source(stale.diagnosis_artifact,diagnosisPath,'diagnosis_artifact',diagnosisHash); const expectedSources=[...new Set(work.filter(i=>action.includes(i.kind)).flatMap(i=>[...i.v1_evidence_paths,...i.v2_evidence_paths]).map(rel))].sort(); if(!Array.isArray(stale.source_files)||stale.source_files.length!==expectedSources.length)fail('staleness_source_files'); let relaxed=false; let head=''; if(fs.existsSync(inside(progressPath))){const progressMtime=fs.statSync(inside(progressPath)).mtimeMs;const artifactMtime=Math.max(fs.statSync(inside(contractPath)).mtimeMs,fs.statSync(inside(diagnosisPath)).mtimeMs,fs.existsSync(inside(reviewPath))?fs.statSync(inside(reviewPath)).mtimeMs:0);if(progressMtime>=artifactMtime){const progress=readJson(progressPath);head=cp.execFileSync('git',['rev-parse','HEAD'],{encoding:'utf8'}).trim();if(progress.module!==mod||progress.verdict!=='CONTINUE'||progress.module_complete!==false||progress.diagnosis_invalid!==false)fail('progress_state');if(progress.diagnosis_artifact!==diagnosisPath||progress.review_artifact!=='.dna/reviews/'+mod+'-review.json')fail('progress_artifacts');if(progress.git_head!==head||progress.last_fix_commit!==head)fail('progress_commit');if(!Number.isInteger(progress.round)||progress.round<1||typeof progress.run_id!=='string'||!progress.run_id.trim())fail('progress_provenance');const progressTime=Date.parse(progress.updated_at);if(!Number.isFinite(progressTime)||progressTime+300000<artifactMtime)fail('progress_updated_at');relaxed=true}} const headState=p=>{try{const body=cp.execFileSync('git',['show',head+':'+p],{encoding:null,stdio:['ignore','pipe','ignore']});return {exists:true,sha256:hash(body)}}catch{return {exists:false,sha256:null}}}; const seenSources=new Set(); for(let i=0;i<expectedSources.length;i++){const p=expectedSources[i];const entry=stale.source_files[i];if(entry&&seenSources.has(entry.path))fail('staleness_source_file_duplicate');if(!entry||entry.path!==p)fail('staleness_source_file_path');seenSources.add(entry.path);if(typeof entry.exists!=='boolean')fail('staleness_source_file_exists');if(entry.exists?(typeof entry.sha256!=='string'||!/^[a-f0-9]{64}$/.test(entry.sha256)):entry.sha256!==null)fail('staleness_source_file_snapshot');if(v1Evidence.has(p)&&entry.exists!==true)fail('staleness_v1_source_missing');const r=inside(p);const currentExists=fs.existsSync(r);if(currentExists&&!fs.statSync(r).isFile())fail('staleness_source_file_not_file');const current={exists:currentExists,sha256:currentExists?fileHash(p):null};if(relaxed&&allAllowed.has(p)){const committed=headState(p);if(current.exists!==committed.exists||current.sha256!==committed.sha256)fail('staleness_source_file_uncommitted')}else if(current.exists!==entry.exists||current.sha256!==entry.sha256)fail('staleness_source_file_sha256')} const semantic=JSON.parse(JSON.stringify(data)); delete semantic.contract_snapshot.sidecar_semantic_sha256; delete semantic.contract_snapshot.diagnosis_artifact_sha256; delete semantic.staleness.diagnosis_artifact; const sidecarSemanticHash=hash(canon(semantic)); const diagnosisSemanticHash=normalizedDiagnosisHash(diagnosisPath); if(snap.sidecar_semantic_sha256!==sidecarSemanticHash||snap.diagnosis_semantic_sha256!==diagnosisSemanticHash)fail('contract_snapshot_semantic'); if(fs.existsSync(inside(reviewPath))){const prev=readJson(reviewPath);if(prev.verdict==='REQUEST_REANALYSIS'){const ps=prev.contract_snapshot||{};const unchanged=modern(ps)?ps.sidecar_semantic_sha256===sidecarSemanticHash&&ps.diagnosis_semantic_sha256===diagnosisSemanticHash:legacySame(ps,snap);if(unchanged)fail('reanalysis_unchanged');const cm=fs.statSync(inside(contractPath)).mtimeMs;const dm=fs.statSync(inside(diagnosisPath)).mtimeMs;const rm=fs.statSync(inside(reviewPath)).mtimeMs;if(cm<=rm||dm<=rm)fail('reanalysis_not_rewritten')}} \" \"$ARGUMENTS\" \"{{v1_path}}\" \"{{v2_path}}\""
609
604
  handoff:
610
605
  produces:
611
606
  - type: file
607
+ artifact_id: diagnosis-spec
612
608
  path: ".dna/specs/diagnosis-$ARGUMENTS.md"
613
609
  description: "Diagnosis spec for module"
614
610
  - type: file
611
+ artifact_id: diagnosis-contract
615
612
  path: ".dna/specs/diagnosis-$ARGUMENTS.contract.json"
616
613
  description: "Structured diagnosis contract sidecar mapping baseline observed failures to work items"
617
614
 
@@ -674,22 +671,44 @@ workflows:
674
671
 
675
672
  APPROVE → diagnosis complete.
676
673
  REQUEST_REANALYSIS → sections_to_fix must be passed back to analyze. If contract_valid is false, set failure_reason to DIAGNOSIS_CONTRACT_INVALID and include the exact invalid rule in sections_to_fix. docs/behavior/blocked_items.md is historical context only and must not be treated as the current run verdict.
674
+ canonical_runtime:
675
+ prompt_handoff_bindings:
676
+ - diagnosis-spec
677
+ - diagnosis-contract
678
+ handoff_sources:
679
+ diagnosis-spec: analyze
680
+ diagnosis-contract: analyze
681
+ require_attested_independent_submit: true
682
+ completion:
683
+ - command_success: "test -s \"{{handoff.review:diagnosis-spec.materialized_path}}\" && test -s \"{{handoff.review:diagnosis-contract.materialized_path}}\""
684
+ - command_success: "node -e \"const fs=require('node:fs'); const crypto=require('node:crypto'); const mod=process.argv[1]; const fail=m=>{console.error('INTENTDNA_DIAGNOSTIC:'+m);process.exit(1)}; const read=p=>JSON.parse(fs.readFileSync(p,'utf8')); const hash=p=>crypto.createHash('sha256').update(fs.readFileSync(p)).digest('hex'); if(!/^[A-Za-z0-9_-]+$/.test(mod))fail('module'); const diagnosisInput=process.argv[2]; const contractInput=process.argv[3]; const reviewPath='.dna/specs/diagnosis-'+mod+'.review.json'; for(const p of [diagnosisInput,contractInput,reviewPath]){if(!fs.existsSync(p)||!fs.statSync(p).isFile())fail('artifact_missing')} const contract=read(contractInput); const review=read(reviewPath); if(contract.contract_version!=='diagnosis-contract/v2'||contract.module!==mod)fail('contract_identity'); if(review.contract_version!=='diagnosis-contract-review/v1'||review.contract_valid!==true||review.verdict!=='APPROVE')fail('review_verdict'); if(review.contract_artifact_reviewed!==contractInput||review.artifact_reviewed!==diagnosisInput)fail('review_snapshot_binding'); const snap=contract.contract_snapshot||{}; const reviewed=review.contract_snapshot||{}; const keys=['baseline_source_sha256','diagnosis_artifact_sha256','sidecar_semantic_sha256','diagnosis_semantic_sha256','observed_failure_count','work_item_count']; for(const key of keys){if(reviewed[key]!==snap[key])fail('review_contract_snapshot')} if(JSON.stringify(reviewed.allowed_kinds)!==JSON.stringify(snap.allowed_kinds))fail('review_allowed_kinds'); if(hash(diagnosisInput)!==snap.diagnosis_artifact_sha256)fail('diagnosis_snapshot_digest'); const updated=Date.parse(review.updated_at); if(!Number.isFinite(updated))fail('review_updated_at'); const reviewMtime=fs.statSync(reviewPath).mtimeMs; const inputMtime=Math.max(fs.statSync(diagnosisInput).mtimeMs,fs.statSync(contractInput).mtimeMs); if(reviewMtime<inputMtime||updated+300000<inputMtime||Math.abs(reviewMtime-updated)>300000)fail('review_stale'); if(!Array.isArray(review.evidence_paths)||!review.evidence_paths.length)fail('review_evidence_paths'); \" \"$ARGUMENTS\" \"{{handoff.review:diagnosis-spec.materialized_path}}\" \"{{handoff.review:diagnosis-contract.materialized_path}}\""
685
+ outputs:
686
+ - type: state
687
+ artifact_id: diagnosis-review-verdict
688
+ schema_ref: intentdna.frw.diagnosis-review.v1
689
+ description: "Canonical diagnosis review verdict used only by the typed controller"
690
+ prompt_suffix: |
691
+ Canonical runtime output contract: return the diagnosis review JSON as a structured output named diagnosis-review-verdict with schema_ref intentdna.frw.diagnosis-review.v1 in the final outputs envelope. This is in addition to writing the declared review file.
677
692
  completion:
678
- - command_success: "node -e \"const fs=require('node:fs'); const cp=require('node:child_process'); const crypto=require('node:crypto'); const path=require('node:path'); const mod=process.env.ARGUMENTS||'$ARGUMENTS'; const allowed=['BUG','UNIMPLEMENTED','INFRA','REMOVED','TEST_BUG']; const action=['BUG','UNIMPLEMENTED','INFRA','TEST_BUG']; const fail=m=>{console.error('INTENTDNA_DIAGNOSTIC:'+m);process.exit(1)}; const same=(a,b)=>Array.isArray(a)&&a.length===b.length&&a.every((v,i)=>v===b[i]); const legacySame=(a,b)=>a&&b&&a.baseline_source_sha256===b.baseline_source_sha256&&a.diagnosis_artifact_sha256===b.diagnosis_artifact_sha256&&a.observed_failure_count===b.observed_failure_count&&a.work_item_count===b.work_item_count&&same(a.allowed_kinds,b.allowed_kinds); const modern=s=>s&&typeof s.sidecar_semantic_sha256==='string'&&typeof s.diagnosis_semantic_sha256==='string'; const root=fs.realpathSync(process.cwd()); const inside=p=>{if(typeof p!=='string'||!p||path.isAbsolute(p))fail('path_invalid');const r=path.resolve(root,p);if(r!==root&&!r.startsWith(root+path.sep))fail('path_escape');let stat=null;try{stat=fs.lstatSync(r)}catch(e){if(!e||e.code!=='ENOENT')fail('path_unreadable')}if(stat){if(stat.isSymbolicLink())fail('path_symlink');const real=fs.realpathSync(r);if(real!==root&&!real.startsWith(root+path.sep))fail('path_escape')}return r}; const rel=p=>path.relative(root,inside(p)).split(path.sep).join('/')||'.'; const under=(p,base)=>p===base||p.startsWith(base+'/'); const readJson=p=>JSON.parse(fs.readFileSync(inside(p),'utf8')); const hash=v=>crypto.createHash('sha256').update(v).digest('hex'); const fileHash=p=>hash(fs.readFileSync(inside(p))); const normalizedDiagnosisHash=p=>hash(fs.readFileSync(inside(p),'utf8').replace(/\\s+/g,' ').trim()); const canon=v=>Array.isArray(v)?'['+v.map(canon).join(',')+']':v&&typeof v==='object'?'{'+Object.keys(v).sort().map(k=>JSON.stringify(k)+':'+canon(v[k])).join(',')+'}':JSON.stringify(v); if(!/^[A-Za-z0-9_-]+$/.test(mod))fail('module'); const contractPath='.dna/specs/diagnosis-'+mod+'.contract.json'; const reviewPath='.dna/specs/diagnosis-'+mod+'.review.json'; const progressPath='.dna/fix-progress/'+mod+'.json'; const diagnosisPath='.dna/specs/diagnosis-'+mod+'.md'; const behaviorPath='docs/behavior/'+mod+'.md'; const v1Root=rel('{{v1_path}}'); const v2Root=rel('{{v2_path}}'); if(v1Root==='.'||v2Root==='.'||under(v1Root,v2Root)||under(v2Root,v1Root))fail('configured_roots'); const data=readJson(contractPath); if(data.contract_version!=='diagnosis-contract/v2')fail('contract_version'); if(data.module!==mod)fail('module_mismatch'); if(data.diagnosis_artifact!==diagnosisPath)fail('diagnosis_artifact_path'); if(!same(data.allowed_kinds,allowed))fail('allowed_kinds'); const observed=Array.isArray(data.observed_failures)?data.observed_failures:fail('observed_failures'); const work=Array.isArray(data.work_items)?data.work_items:fail('work_items'); if(!observed.length)fail('empty_observed_failures'); if(!work.length)fail('empty_work_items'); const idOk=id=>typeof id==='string'&&/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/.test(id); const ids=new Set(); for(const f of observed){if(!idOk(f&&f.id)||ids.has(f.id))fail('observed_failure_ids');ids.add(f.id)} const workIds=new Set(); for(const item of work){if(!idOk(item&&item.id)||workIds.has(item.id))fail('work_item_ids');workIds.add(item.id)} const failureById=new Map(); for(const f of observed){if(typeof f.test_name!=='string'||!f.test_name.trim())fail('observed_failure_test_name');if(typeof f.symptom!=='string'||!f.symptom.trim())fail('observed_failure_symptom');if(!Array.isArray(f.evidence_paths)||!f.evidence_paths.length)fail('observed_failure_evidence');for(const p of f.evidence_paths)rel(p);if(f.kind!==undefined)fail('observed_failure_kind_forbidden');failureById.set(f.id,f)} const mapped=new Set(); const allAllowed=new Set(); const v1Evidence=new Set(); for(const item of work){if(!allowed.includes(item.kind))fail('invalid_kind');if(item.kind==='PLACEHOLDER_CONTRACT')fail('placeholder_kind');if(!Array.isArray(item.observed_failure_ids)||!item.observed_failure_ids.length)fail('work_item_observed_failure_ids');if(!Array.isArray(item.evidence_paths)||!item.evidence_paths.length)fail('work_item_evidence');for(const p of item.evidence_paths)rel(p);if(action.includes(item.kind)){if(!Array.isArray(item.v1_evidence_paths)||!item.v1_evidence_paths.length)fail('work_item_v1_evidence');if(!Array.isArray(item.v2_evidence_paths)||!item.v2_evidence_paths.length)fail('work_item_v2_evidence');const v2Evidence=new Set(item.v2_evidence_paths.map(rel));for(const p of item.v1_evidence_paths){const n=rel(p);if(!under(n,v1Root))fail('work_item_v1_evidence_root');v1Evidence.add(n)}for(const p of item.v2_evidence_paths)rel(p);if(!Array.isArray(item.allowed_change_paths)||!item.allowed_change_paths.length)fail('work_item_allowed_change_paths');const approved=new Set();for(const p of item.allowed_change_paths){if(typeof p!=='string'||['*','?','[',']'].some(c=>p.includes(c)))fail('work_item_allowed_change_path_pattern');const n=rel(p);const r=inside(p);if(under(n,v1Root))fail('work_item_allowed_change_path_v1');if(!(under(n,v2Root)||under(n,'test')||under(n,'lib')))fail('work_item_allowed_change_path_root');if(fs.existsSync(r)?!fs.statSync(r).isFile():!path.basename(n).includes('.'))fail('work_item_allowed_change_path_not_file');if(!v2Evidence.has(n))fail('work_item_allowed_change_path_not_evidence');if(approved.has(n))fail('work_item_allowed_change_path_duplicate');approved.add(n);allAllowed.add(n)}}for(const id of item.observed_failure_ids){const f=failureById.get(id);if(!f)fail('unknown_observed_failure_id');if(action.includes(item.kind)&&(!f.test_name||!String(f.test_name).trim()))fail('action_test_evidence');mapped.add(id)}} if(mapped.size!==ids.size)fail('unmapped_observed_failures'); for(const f of observed.filter(x=>String(x.symptom).includes('PLACEHOLDER_CONTRACT'))){const kinds=new Set(work.filter(i=>i.observed_failure_ids.includes(f.id)).map(i=>i.kind));if(![...kinds].every(k=>k==='TEST_BUG'||k==='UNIMPLEMENTED')||kinds.size===0)fail('placeholder_mapping')} const counts=data.baseline&&data.baseline.counts; if(!data.baseline||!data.baseline.source_path||!counts)fail('baseline'); for(const k of ['failed','skipped','hung']){if(!Number.isInteger(counts[k])||counts[k]<0)fail('baseline_count_'+k)} if(counts.failed+counts.skipped+counts.hung!==observed.length)fail('baseline_count_mismatch'); const summary=data.summary_counts||{}; for(const kind of allowed){if(!Number.isInteger(summary[kind])||summary[kind]!==work.filter(x=>x.kind===kind).length)fail('summary_count_'+kind)} const snap=data.contract_snapshot||{}; const baselineHash=fileHash(data.baseline.source_path); const diagnosisHash=fileHash(diagnosisPath); if(snap.observed_failure_count!==observed.length||snap.work_item_count!==work.length||!same(snap.allowed_kinds,allowed))fail('contract_snapshot'); if(snap.baseline_source_sha256!==baselineHash)fail('baseline_sha256'); if(snap.diagnosis_artifact_sha256!==diagnosisHash)fail('diagnosis_sha256'); const stale=data.staleness; if(!stale||stale.is_stale!==false)fail('stale_contract'); const source=(entry,p,n,expected)=>{if(!entry||entry.path!==p)fail('staleness_'+n+'_path');if(entry.sha256!==expected)fail('staleness_'+n+'_sha256')}; source(stale.behavior_doc,behaviorPath,'behavior_doc',fileHash(behaviorPath)); source(stale.baseline,data.baseline.source_path,'baseline',baselineHash); source(stale.diagnosis_artifact,diagnosisPath,'diagnosis_artifact',diagnosisHash); const expectedSources=[...new Set(work.filter(i=>action.includes(i.kind)).flatMap(i=>[...i.v1_evidence_paths,...i.v2_evidence_paths]).map(rel))].sort(); if(!Array.isArray(stale.source_files)||stale.source_files.length!==expectedSources.length)fail('staleness_source_files'); let relaxed=false; let head=''; if(fs.existsSync(inside(progressPath))){const progressMtime=fs.statSync(inside(progressPath)).mtimeMs;const artifactMtime=Math.max(fs.statSync(inside(contractPath)).mtimeMs,fs.statSync(inside(diagnosisPath)).mtimeMs,fs.existsSync(inside(reviewPath))?fs.statSync(inside(reviewPath)).mtimeMs:0);if(progressMtime>=artifactMtime){const progress=readJson(progressPath);head=cp.execFileSync('git',['rev-parse','HEAD'],{encoding:'utf8'}).trim();if(progress.module!==mod||progress.verdict!=='CONTINUE'||progress.module_complete!==false||progress.diagnosis_invalid!==false)fail('progress_state');if(progress.diagnosis_artifact!==diagnosisPath||progress.review_artifact!=='.dna/reviews/'+mod+'-review.json')fail('progress_artifacts');if(progress.git_head!==head||progress.last_fix_commit!==head)fail('progress_commit');if(!Number.isInteger(progress.round)||progress.round<1||typeof progress.run_id!=='string'||!progress.run_id.trim())fail('progress_provenance');const progressTime=Date.parse(progress.updated_at);if(!Number.isFinite(progressTime)||progressTime+300000<artifactMtime)fail('progress_updated_at');relaxed=true}} const headState=p=>{try{const body=cp.execFileSync('git',['show',head+':'+p],{encoding:null,stdio:['ignore','pipe','ignore']});return {exists:true,sha256:hash(body)}}catch{return {exists:false,sha256:null}}}; const seenSources=new Set(); for(let i=0;i<expectedSources.length;i++){const p=expectedSources[i];const entry=stale.source_files[i];if(entry&&seenSources.has(entry.path))fail('staleness_source_file_duplicate');if(!entry||entry.path!==p)fail('staleness_source_file_path');seenSources.add(entry.path);if(typeof entry.exists!=='boolean')fail('staleness_source_file_exists');if(entry.exists?(typeof entry.sha256!=='string'||!/^[a-f0-9]{64}$/.test(entry.sha256)):entry.sha256!==null)fail('staleness_source_file_snapshot');if(v1Evidence.has(p)&&entry.exists!==true)fail('staleness_v1_source_missing');const r=inside(p);const currentExists=fs.existsSync(r);if(currentExists&&!fs.statSync(r).isFile())fail('staleness_source_file_not_file');const current={exists:currentExists,sha256:currentExists?fileHash(p):null};if(relaxed&&allAllowed.has(p)){const committed=headState(p);if(current.exists!==committed.exists||current.sha256!==committed.sha256)fail('staleness_source_file_uncommitted')}else if(current.exists!==entry.exists||current.sha256!==entry.sha256)fail('staleness_source_file_sha256')} const semantic=JSON.parse(JSON.stringify(data)); delete semantic.contract_snapshot.sidecar_semantic_sha256; delete semantic.contract_snapshot.diagnosis_artifact_sha256; delete semantic.staleness.diagnosis_artifact; const sidecarSemanticHash=hash(canon(semantic)); const diagnosisSemanticHash=normalizedDiagnosisHash(diagnosisPath); if(snap.sidecar_semantic_sha256!==sidecarSemanticHash||snap.diagnosis_semantic_sha256!==diagnosisSemanticHash)fail('contract_snapshot_semantic'); if(fs.existsSync(inside(reviewPath))){const prev=readJson(reviewPath);if(prev.verdict==='REQUEST_REANALYSIS'){const ps=prev.contract_snapshot||{};const unchanged=modern(ps)?ps.sidecar_semantic_sha256===sidecarSemanticHash&&ps.diagnosis_semantic_sha256===diagnosisSemanticHash:legacySame(ps,snap);if(unchanged)fail('reanalysis_unchanged');const cm=fs.statSync(inside(contractPath)).mtimeMs;const dm=fs.statSync(inside(diagnosisPath)).mtimeMs;const rm=fs.statSync(inside(reviewPath)).mtimeMs;if(cm<=rm||dm<=rm)fail('reanalysis_not_rewritten')}} \""
679
- - command_success: "node -e \"const fs=require('node:fs'); const path=require('node:path'); const mod=process.env.ARGUMENTS||'$ARGUMENTS'; const allowed=['BUG','UNIMPLEMENTED','INFRA','REMOVED','TEST_BUG']; const fail=m=>{console.error('INTENTDNA_DIAGNOSTIC:'+m);process.exit(1)}; const same=(a,b)=>Array.isArray(a)&&a.length===b.length&&a.every((v,i)=>v===b[i]); const snapSame=(a,b)=>a&&b&&a.baseline_source_sha256===b.baseline_source_sha256&&a.diagnosis_artifact_sha256===b.diagnosis_artifact_sha256&&a.sidecar_semantic_sha256===b.sidecar_semantic_sha256&&a.diagnosis_semantic_sha256===b.diagnosis_semantic_sha256&&a.observed_failure_count===b.observed_failure_count&&a.work_item_count===b.work_item_count&&same(a.allowed_kinds,b.allowed_kinds); const root=fs.realpathSync(process.cwd()); const inside=p=>{if(typeof p!=='string'||!p||path.isAbsolute(p))fail('path_invalid');const r=path.resolve(root,p);if(r!==root&&!r.startsWith(root+path.sep))fail('path_escape');let stat=null;try{stat=fs.lstatSync(r)}catch(e){if(!e||e.code!=='ENOENT')fail('path_unreadable')}if(stat){if(stat.isSymbolicLink())fail('path_symlink');const real=fs.realpathSync(r);if(real!==root&&!real.startsWith(root+path.sep))fail('path_escape')}return r}; const readJson=p=>JSON.parse(fs.readFileSync(inside(p),'utf8')); if(!/^[A-Za-z0-9_-]+$/.test(mod))fail('module'); const contractPath='.dna/specs/diagnosis-'+mod+'.contract.json'; const reviewPath='.dna/specs/diagnosis-'+mod+'.review.json'; const diagnosisPath='.dna/specs/diagnosis-'+mod+'.md'; const data=readJson(contractPath); const snap=data.contract_snapshot||{}; if(data.contract_version!=='diagnosis-contract/v2'||data.module!==mod)fail('contract_version'); const review=readJson(reviewPath); if(review.contract_version!=='diagnosis-contract-review/v1')fail('review_contract_version'); if(review.contract_valid!==true)fail('review_contract_valid'); if(review.verdict!=='APPROVE')fail('review_verdict'); if(review.contract_artifact_reviewed!==contractPath)fail('review_contract_artifact_path'); if(review.artifact_reviewed!==diagnosisPath)fail('review_artifact_path'); if(!snapSame(review.contract_snapshot,snap))fail('review_contract_snapshot'); const updated=Date.parse(review.updated_at); if(!Number.isFinite(updated))fail('review_updated_at'); if(!Array.isArray(review.evidence_paths)||!review.evidence_paths.length)fail('review_evidence_paths'); for(const p of review.evidence_paths){const r=inside(p);if(!fs.existsSync(r)||!fs.statSync(r).isFile())fail('review_evidence_paths')} const cm=fs.statSync(inside(contractPath)).mtimeMs; const dm=fs.statSync(inside(diagnosisPath)).mtimeMs; const rm=fs.statSync(inside(reviewPath)).mtimeMs; if(rm<cm||rm<dm||updated+300000<Math.max(cm,dm)||Math.abs(rm-updated)>300000)fail('review_stale'); \""
693
+ - command_success: "node -e \"const fs=require('node:fs'); const cp=require('node:child_process'); const crypto=require('node:crypto'); const path=require('node:path'); const mod=process.argv[1]; const allowed=['BUG','UNIMPLEMENTED','INFRA','REMOVED','TEST_BUG']; const action=['BUG','UNIMPLEMENTED','INFRA','TEST_BUG']; const fail=m=>{console.error('INTENTDNA_DIAGNOSTIC:'+m);process.exit(1)}; const same=(a,b)=>Array.isArray(a)&&a.length===b.length&&a.every((v,i)=>v===b[i]); const legacySame=(a,b)=>a&&b&&a.baseline_source_sha256===b.baseline_source_sha256&&a.diagnosis_artifact_sha256===b.diagnosis_artifact_sha256&&a.observed_failure_count===b.observed_failure_count&&a.work_item_count===b.work_item_count&&same(a.allowed_kinds,b.allowed_kinds); const modern=s=>s&&typeof s.sidecar_semantic_sha256==='string'&&typeof s.diagnosis_semantic_sha256==='string'; const root=fs.realpathSync(process.cwd()); const inside=p=>{if(typeof p!=='string'||!p||path.isAbsolute(p))fail('path_invalid');const r=path.resolve(root,p);if(r!==root&&!r.startsWith(root+path.sep))fail('path_escape');let stat=null;try{stat=fs.lstatSync(r)}catch(e){if(!e||e.code!=='ENOENT')fail('path_unreadable')}if(stat){if(stat.isSymbolicLink())fail('path_symlink');const real=fs.realpathSync(r);if(real!==root&&!real.startsWith(root+path.sep))fail('path_escape')}return r}; const rel=p=>path.relative(root,inside(p)).split(path.sep).join('/')||'.'; const under=(p,base)=>p===base||p.startsWith(base+'/'); const readJson=p=>JSON.parse(fs.readFileSync(inside(p),'utf8')); const hash=v=>crypto.createHash('sha256').update(v).digest('hex'); const fileHash=p=>hash(fs.readFileSync(inside(p))); const normalizedDiagnosisHash=p=>hash(fs.readFileSync(inside(p),'utf8').replace(/\\s+/g,' ').trim()); const canon=v=>Array.isArray(v)?'['+v.map(canon).join(',')+']':v&&typeof v==='object'?'{'+Object.keys(v).sort().map(k=>JSON.stringify(k)+':'+canon(v[k])).join(',')+'}':JSON.stringify(v); if(!/^[A-Za-z0-9_-]+$/.test(mod))fail('module'); const contractPath='.dna/specs/diagnosis-'+mod+'.contract.json'; const reviewPath='.dna/specs/diagnosis-'+mod+'.review.json'; const progressPath='.dna/fix-progress/'+mod+'.json'; const diagnosisPath='.dna/specs/diagnosis-'+mod+'.md'; const behaviorPath='docs/behavior/'+mod+'.md'; const v1Root=rel(process.argv[2]); const v2Root=rel(process.argv[3]); if(v1Root==='.'||v2Root==='.'||under(v1Root,v2Root)||under(v2Root,v1Root))fail('configured_roots'); const data=readJson(contractPath); if(data.contract_version!=='diagnosis-contract/v2')fail('contract_version'); if(data.module!==mod)fail('module_mismatch'); if(data.diagnosis_artifact!==diagnosisPath)fail('diagnosis_artifact_path'); if(!same(data.allowed_kinds,allowed))fail('allowed_kinds'); const observed=Array.isArray(data.observed_failures)?data.observed_failures:fail('observed_failures'); const work=Array.isArray(data.work_items)?data.work_items:fail('work_items'); if(!observed.length)fail('empty_observed_failures'); if(!work.length)fail('empty_work_items'); const idOk=id=>typeof id==='string'&&/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/.test(id); const ids=new Set(); for(const f of observed){if(!idOk(f&&f.id)||ids.has(f.id))fail('observed_failure_ids');ids.add(f.id)} const workIds=new Set(); for(const item of work){if(!idOk(item&&item.id)||workIds.has(item.id))fail('work_item_ids');workIds.add(item.id)} const failureById=new Map(); for(const f of observed){if(typeof f.test_name!=='string'||!f.test_name.trim())fail('observed_failure_test_name');if(typeof f.symptom!=='string'||!f.symptom.trim())fail('observed_failure_symptom');if(!Array.isArray(f.evidence_paths)||!f.evidence_paths.length)fail('observed_failure_evidence');for(const p of f.evidence_paths)rel(p);if(f.kind!==undefined)fail('observed_failure_kind_forbidden');failureById.set(f.id,f)} const mapped=new Set(); const allAllowed=new Set(); const v1Evidence=new Set(); for(const item of work){if(!allowed.includes(item.kind))fail('invalid_kind');if(item.kind==='PLACEHOLDER_CONTRACT')fail('placeholder_kind');if(!Array.isArray(item.observed_failure_ids)||!item.observed_failure_ids.length)fail('work_item_observed_failure_ids');if(!Array.isArray(item.evidence_paths)||!item.evidence_paths.length)fail('work_item_evidence');for(const p of item.evidence_paths)rel(p);if(action.includes(item.kind)){if(!Array.isArray(item.v1_evidence_paths)||!item.v1_evidence_paths.length)fail('work_item_v1_evidence');if(!Array.isArray(item.v2_evidence_paths)||!item.v2_evidence_paths.length)fail('work_item_v2_evidence');const v2Evidence=new Set(item.v2_evidence_paths.map(rel));for(const p of item.v1_evidence_paths){const n=rel(p);if(!under(n,v1Root))fail('work_item_v1_evidence_root');v1Evidence.add(n)}for(const p of item.v2_evidence_paths)rel(p);if(!Array.isArray(item.allowed_change_paths)||!item.allowed_change_paths.length)fail('work_item_allowed_change_paths');const approved=new Set();for(const p of item.allowed_change_paths){if(typeof p!=='string'||['*','?','[',']'].some(c=>p.includes(c)))fail('work_item_allowed_change_path_pattern');const n=rel(p);const r=inside(p);if(under(n,v1Root))fail('work_item_allowed_change_path_v1');if(!(under(n,v2Root)||under(n,'test')||under(n,'lib')))fail('work_item_allowed_change_path_root');if(fs.existsSync(r)?!fs.statSync(r).isFile():!path.basename(n).includes('.'))fail('work_item_allowed_change_path_not_file');if(!v2Evidence.has(n))fail('work_item_allowed_change_path_not_evidence');if(approved.has(n))fail('work_item_allowed_change_path_duplicate');approved.add(n);allAllowed.add(n)}}for(const id of item.observed_failure_ids){const f=failureById.get(id);if(!f)fail('unknown_observed_failure_id');if(action.includes(item.kind)&&(!f.test_name||!String(f.test_name).trim()))fail('action_test_evidence');mapped.add(id)}} if(mapped.size!==ids.size)fail('unmapped_observed_failures'); for(const f of observed.filter(x=>String(x.symptom).includes('PLACEHOLDER_CONTRACT'))){const kinds=new Set(work.filter(i=>i.observed_failure_ids.includes(f.id)).map(i=>i.kind));if(![...kinds].every(k=>k==='TEST_BUG'||k==='UNIMPLEMENTED')||kinds.size===0)fail('placeholder_mapping')} const counts=data.baseline&&data.baseline.counts; if(!data.baseline||!data.baseline.source_path||!counts)fail('baseline'); for(const k of ['failed','skipped','hung']){if(!Number.isInteger(counts[k])||counts[k]<0)fail('baseline_count_'+k)} if(counts.failed+counts.skipped+counts.hung!==observed.length)fail('baseline_count_mismatch'); const summary=data.summary_counts||{}; for(const kind of allowed){if(!Number.isInteger(summary[kind])||summary[kind]!==work.filter(x=>x.kind===kind).length)fail('summary_count_'+kind)} const snap=data.contract_snapshot||{}; const baselineHash=fileHash(data.baseline.source_path); const diagnosisHash=fileHash(diagnosisPath); if(snap.observed_failure_count!==observed.length||snap.work_item_count!==work.length||!same(snap.allowed_kinds,allowed))fail('contract_snapshot'); if(snap.baseline_source_sha256!==baselineHash)fail('baseline_sha256'); if(snap.diagnosis_artifact_sha256!==diagnosisHash)fail('diagnosis_sha256'); const stale=data.staleness; if(!stale||stale.is_stale!==false)fail('stale_contract'); const source=(entry,p,n,expected)=>{if(!entry||entry.path!==p)fail('staleness_'+n+'_path');if(entry.sha256!==expected)fail('staleness_'+n+'_sha256')}; source(stale.behavior_doc,behaviorPath,'behavior_doc',fileHash(behaviorPath)); source(stale.baseline,data.baseline.source_path,'baseline',baselineHash); source(stale.diagnosis_artifact,diagnosisPath,'diagnosis_artifact',diagnosisHash); const expectedSources=[...new Set(work.filter(i=>action.includes(i.kind)).flatMap(i=>[...i.v1_evidence_paths,...i.v2_evidence_paths]).map(rel))].sort(); if(!Array.isArray(stale.source_files)||stale.source_files.length!==expectedSources.length)fail('staleness_source_files'); let relaxed=false; let head=''; if(fs.existsSync(inside(progressPath))){const progressMtime=fs.statSync(inside(progressPath)).mtimeMs;const artifactMtime=Math.max(fs.statSync(inside(contractPath)).mtimeMs,fs.statSync(inside(diagnosisPath)).mtimeMs,fs.existsSync(inside(reviewPath))?fs.statSync(inside(reviewPath)).mtimeMs:0);if(progressMtime>=artifactMtime){const progress=readJson(progressPath);head=cp.execFileSync('git',['rev-parse','HEAD'],{encoding:'utf8'}).trim();if(progress.module!==mod||progress.verdict!=='CONTINUE'||progress.module_complete!==false||progress.diagnosis_invalid!==false)fail('progress_state');if(progress.diagnosis_artifact!==diagnosisPath||progress.review_artifact!=='.dna/reviews/'+mod+'-review.json')fail('progress_artifacts');if(progress.git_head!==head||progress.last_fix_commit!==head)fail('progress_commit');if(!Number.isInteger(progress.round)||progress.round<1||typeof progress.run_id!=='string'||!progress.run_id.trim())fail('progress_provenance');const progressTime=Date.parse(progress.updated_at);if(!Number.isFinite(progressTime)||progressTime+300000<artifactMtime)fail('progress_updated_at');relaxed=true}} const headState=p=>{try{const body=cp.execFileSync('git',['show',head+':'+p],{encoding:null,stdio:['ignore','pipe','ignore']});return {exists:true,sha256:hash(body)}}catch{return {exists:false,sha256:null}}}; const seenSources=new Set(); for(let i=0;i<expectedSources.length;i++){const p=expectedSources[i];const entry=stale.source_files[i];if(entry&&seenSources.has(entry.path))fail('staleness_source_file_duplicate');if(!entry||entry.path!==p)fail('staleness_source_file_path');seenSources.add(entry.path);if(typeof entry.exists!=='boolean')fail('staleness_source_file_exists');if(entry.exists?(typeof entry.sha256!=='string'||!/^[a-f0-9]{64}$/.test(entry.sha256)):entry.sha256!==null)fail('staleness_source_file_snapshot');if(v1Evidence.has(p)&&entry.exists!==true)fail('staleness_v1_source_missing');const r=inside(p);const currentExists=fs.existsSync(r);if(currentExists&&!fs.statSync(r).isFile())fail('staleness_source_file_not_file');const current={exists:currentExists,sha256:currentExists?fileHash(p):null};if(relaxed&&allAllowed.has(p)){const committed=headState(p);if(current.exists!==committed.exists||current.sha256!==committed.sha256)fail('staleness_source_file_uncommitted')}else if(current.exists!==entry.exists||current.sha256!==entry.sha256)fail('staleness_source_file_sha256')} const semantic=JSON.parse(JSON.stringify(data)); delete semantic.contract_snapshot.sidecar_semantic_sha256; delete semantic.contract_snapshot.diagnosis_artifact_sha256; delete semantic.staleness.diagnosis_artifact; const sidecarSemanticHash=hash(canon(semantic)); const diagnosisSemanticHash=normalizedDiagnosisHash(diagnosisPath); if(snap.sidecar_semantic_sha256!==sidecarSemanticHash||snap.diagnosis_semantic_sha256!==diagnosisSemanticHash)fail('contract_snapshot_semantic'); if(fs.existsSync(inside(reviewPath))){const prev=readJson(reviewPath);if(prev.verdict==='REQUEST_REANALYSIS'){const ps=prev.contract_snapshot||{};const unchanged=modern(ps)?ps.sidecar_semantic_sha256===sidecarSemanticHash&&ps.diagnosis_semantic_sha256===diagnosisSemanticHash:legacySame(ps,snap);if(unchanged)fail('reanalysis_unchanged');const cm=fs.statSync(inside(contractPath)).mtimeMs;const dm=fs.statSync(inside(diagnosisPath)).mtimeMs;const rm=fs.statSync(inside(reviewPath)).mtimeMs;if(cm<=rm||dm<=rm)fail('reanalysis_not_rewritten')}} \" \"$ARGUMENTS\" \"{{v1_path}}\" \"{{v2_path}}\""
694
+ - command_success: "node -e \"const fs=require('node:fs'); const path=require('node:path'); const mod=process.argv[1]; const allowed=['BUG','UNIMPLEMENTED','INFRA','REMOVED','TEST_BUG']; const fail=m=>{console.error('INTENTDNA_DIAGNOSTIC:'+m);process.exit(1)}; const same=(a,b)=>Array.isArray(a)&&a.length===b.length&&a.every((v,i)=>v===b[i]); const snapSame=(a,b)=>a&&b&&a.baseline_source_sha256===b.baseline_source_sha256&&a.diagnosis_artifact_sha256===b.diagnosis_artifact_sha256&&a.sidecar_semantic_sha256===b.sidecar_semantic_sha256&&a.diagnosis_semantic_sha256===b.diagnosis_semantic_sha256&&a.observed_failure_count===b.observed_failure_count&&a.work_item_count===b.work_item_count&&same(a.allowed_kinds,b.allowed_kinds); const root=fs.realpathSync(process.cwd()); const inside=p=>{if(typeof p!=='string'||!p||path.isAbsolute(p))fail('path_invalid');const r=path.resolve(root,p);if(r!==root&&!r.startsWith(root+path.sep))fail('path_escape');let stat=null;try{stat=fs.lstatSync(r)}catch(e){if(!e||e.code!=='ENOENT')fail('path_unreadable')}if(stat){if(stat.isSymbolicLink())fail('path_symlink');const real=fs.realpathSync(r);if(real!==root&&!real.startsWith(root+path.sep))fail('path_escape')}return r}; const readJson=p=>JSON.parse(fs.readFileSync(inside(p),'utf8')); if(!/^[A-Za-z0-9_-]+$/.test(mod))fail('module'); const contractPath='.dna/specs/diagnosis-'+mod+'.contract.json'; const reviewPath='.dna/specs/diagnosis-'+mod+'.review.json'; const diagnosisPath='.dna/specs/diagnosis-'+mod+'.md'; const data=readJson(contractPath); const snap=data.contract_snapshot||{}; if(data.contract_version!=='diagnosis-contract/v2'||data.module!==mod)fail('contract_version'); const review=readJson(reviewPath); if(review.contract_version!=='diagnosis-contract-review/v1')fail('review_contract_version'); if(review.contract_valid!==true)fail('review_contract_valid'); if(review.verdict!=='APPROVE')fail('review_verdict'); if(review.contract_artifact_reviewed!==contractPath)fail('review_contract_artifact_path'); if(review.artifact_reviewed!==diagnosisPath)fail('review_artifact_path'); if(!snapSame(review.contract_snapshot,snap))fail('review_contract_snapshot'); const updated=Date.parse(review.updated_at); if(!Number.isFinite(updated))fail('review_updated_at'); if(!Array.isArray(review.evidence_paths)||!review.evidence_paths.length)fail('review_evidence_paths'); for(const p of review.evidence_paths){const r=inside(p);if(!fs.existsSync(r)||!fs.statSync(r).isFile())fail('review_evidence_paths')} const cm=fs.statSync(inside(contractPath)).mtimeMs; const dm=fs.statSync(inside(diagnosisPath)).mtimeMs; const rm=fs.statSync(inside(reviewPath)).mtimeMs; if(rm<cm||rm<dm||updated+300000<Math.max(cm,dm)||Math.abs(rm-updated)>300000)fail('review_stale'); \" \"$ARGUMENTS\""
680
695
  handoff:
681
696
  consumes:
682
697
  - type: file
698
+ artifact_id: diagnosis-spec
683
699
  path: ".dna/specs/diagnosis-$ARGUMENTS.md"
684
700
  description: "Diagnosis spec from analyzer"
685
701
  - type: file
702
+ artifact_id: diagnosis-contract
686
703
  path: ".dna/specs/diagnosis-$ARGUMENTS.contract.json"
687
704
  description: "Diagnosis contract sidecar from analyzer"
688
705
  produces:
689
706
  - type: file
707
+ artifact_id: diagnosis-review
690
708
  path: ".dna/specs/diagnosis-$ARGUMENTS.review.json"
691
709
  description: "Durable diagnosis review verdict with independent contract validation (APPROVE or REQUEST_REANALYSIS)"
692
710
  - type: summary
711
+ artifact_id: diagnosis-review-summary
693
712
  required: false
694
713
  description: "Review verdict (APPROVE or REQUEST_REANALYSIS)"
695
714
 
@@ -756,7 +775,7 @@ workflows:
756
775
  After each fix: run tests ONCE for that specific file unless the shared boundary rule requires the affected module behavior suite.
757
776
  Commit only the round's coherent changes, with message including what changed, why, and which tests were targeted. The completion gate compares every file in the current HEAD commit with the approved allowed_change_paths set and rejects any extra path.
758
777
  completion:
759
- - command_success: "node -e \"const fs=require('node:fs'); const cp=require('node:child_process'); const crypto=require('node:crypto'); const path=require('node:path'); const mod=process.env.ARGUMENTS||'$ARGUMENTS'; const fail=m=>{console.error('INTENTDNA_DIAGNOSTIC:fix_scope_'+m);process.exit(1)}; const root=fs.realpathSync(process.cwd()); const inside=p=>{if(typeof p!=='string'||!p||path.isAbsolute(p))fail('path_invalid');const r=path.resolve(root,p);if(r!==root&&!r.startsWith(root+path.sep))fail('path_escape');let stat=null;try{stat=fs.lstatSync(r)}catch(e){if(!e||e.code!=='ENOENT')fail('path_unreadable')}if(stat){if(stat.isSymbolicLink())fail('path_symlink');const real=fs.realpathSync(r);if(real!==root&&!real.startsWith(root+path.sep))fail('path_escape')}return r}; const rel=p=>path.relative(root,inside(p)).split(path.sep).join('/')||'.'; const under=(p,base)=>p===base||p.startsWith(base+'/'); const hash=v=>crypto.createHash('sha256').update(v).digest('hex'); const canon=v=>Array.isArray(v)?'['+v.map(canon).join(',')+']':v&&typeof v==='object'?'{'+Object.keys(v).sort().map(k=>JSON.stringify(k)+':'+canon(v[k])).join(',')+'}':JSON.stringify(v); const same=(a,b)=>Array.isArray(a)&&Array.isArray(b)&&a.length===b.length&&a.every((v,i)=>v===b[i]); const snapSame=(a,b)=>a&&b&&a.baseline_source_sha256===b.baseline_source_sha256&&a.diagnosis_artifact_sha256===b.diagnosis_artifact_sha256&&a.sidecar_semantic_sha256===b.sidecar_semantic_sha256&&a.diagnosis_semantic_sha256===b.diagnosis_semantic_sha256&&a.observed_failure_count===b.observed_failure_count&&a.work_item_count===b.work_item_count&&same(a.allowed_kinds,b.allowed_kinds); if(!/^[A-Za-z0-9_-]+$/.test(mod))fail('module'); const contractPath='.dna/specs/diagnosis-'+mod+'.contract.json'; const reviewPath='.dna/specs/diagnosis-'+mod+'.review.json'; const diagnosisPath='.dna/specs/diagnosis-'+mod+'.md'; const data=JSON.parse(fs.readFileSync(inside(contractPath),'utf8')); if(data.contract_version!=='diagnosis-contract/v2'||data.module!==mod||data.diagnosis_artifact!==diagnosisPath)fail('contract'); const snap=data.contract_snapshot||{}; const semantic=JSON.parse(JSON.stringify(data)); delete semantic.contract_snapshot.sidecar_semantic_sha256; delete semantic.contract_snapshot.diagnosis_artifact_sha256; delete semantic.staleness.diagnosis_artifact; if(snap.sidecar_semantic_sha256!==hash(canon(semantic)))fail('contract_snapshot'); const review=JSON.parse(fs.readFileSync(inside(reviewPath),'utf8')); if(review.contract_version!=='diagnosis-contract-review/v1'||review.contract_valid!==true||review.verdict!=='APPROVE')fail('review'); if(review.contract_artifact_reviewed!==contractPath||review.artifact_reviewed!==diagnosisPath||!snapSame(review.contract_snapshot,snap))fail('review_snapshot'); if(!Array.isArray(review.evidence_paths)||!review.evidence_paths.length)fail('review_evidence'); for(const p of review.evidence_paths){const r=inside(p);if(!fs.existsSync(r)||!fs.statSync(r).isFile())fail('review_evidence')} const cm=fs.statSync(inside(contractPath)).mtimeMs; const rm=fs.statSync(inside(reviewPath)).mtimeMs; const updated=Date.parse(review.updated_at); if(!Number.isFinite(updated)||rm<cm||updated+300000<cm||Math.abs(rm-updated)>300000)fail('review_stale'); const v1=rel('{{v1_path}}'); const v2=rel('{{v2_path}}'); const action=new Set(['BUG','UNIMPLEMENTED','INFRA','TEST_BUG']); const allowed=new Set(); for(const item of data.work_items||[]){if(!action.has(item.kind))continue;const evidence=new Set((item.v2_evidence_paths||[]).map(rel));for(const p of item.allowed_change_paths||[]){if(typeof p!=='string'||['*','?','[',']'].some(c=>p.includes(c)))fail('allowed_path_pattern');const n=rel(p);const r=inside(p);if(under(n,v1))fail('v1_change');if(!(under(n,v2)||under(n,'test')||under(n,'lib')))fail('allowed_path_root');if(fs.existsSync(r)?!fs.statSync(r).isFile():!path.basename(n).includes('.'))fail('allowed_path_not_file');if(!evidence.has(n))fail('allowed_path_not_evidence');allowed.add(n)}} if(!allowed.size)fail('allowed_paths'); const head=cp.execFileSync('git',['rev-parse','HEAD'],{encoding:'utf8'}).trim(); const output=cp.execFileSync('git',['diff-tree','--root','--no-commit-id','--name-only','-r',head],{encoding:'utf8'}).trim(); const changed=output?output.split(/\\r?\\n/).filter(Boolean):[]; if(!changed.length)fail('empty_commit'); for(const p of changed){const n=rel(p);if(under(n,v1))fail('v1_change');if(!allowed.has(n))fail('outside_diagnosis')} const dirty=cp.execFileSync('git',['status','--porcelain','--untracked-files=all','--',...allowed],{encoding:'utf8'}).trim(); if(dirty)fail('uncommitted_allowed_path'); \""
778
+ - command_success: "node -e \"const fs=require('node:fs'); const cp=require('node:child_process'); const crypto=require('node:crypto'); const path=require('node:path'); const mod=process.argv[1]; const fail=m=>{console.error('INTENTDNA_DIAGNOSTIC:fix_scope_'+m);process.exit(1)}; const root=fs.realpathSync(process.cwd()); const inside=p=>{if(typeof p!=='string'||!p||path.isAbsolute(p))fail('path_invalid');const r=path.resolve(root,p);if(r!==root&&!r.startsWith(root+path.sep))fail('path_escape');let stat=null;try{stat=fs.lstatSync(r)}catch(e){if(!e||e.code!=='ENOENT')fail('path_unreadable')}if(stat){if(stat.isSymbolicLink())fail('path_symlink');const real=fs.realpathSync(r);if(real!==root&&!real.startsWith(root+path.sep))fail('path_escape')}return r}; const rel=p=>path.relative(root,inside(p)).split(path.sep).join('/')||'.'; const under=(p,base)=>p===base||p.startsWith(base+'/'); const hash=v=>crypto.createHash('sha256').update(v).digest('hex'); const canon=v=>Array.isArray(v)?'['+v.map(canon).join(',')+']':v&&typeof v==='object'?'{'+Object.keys(v).sort().map(k=>JSON.stringify(k)+':'+canon(v[k])).join(',')+'}':JSON.stringify(v); const same=(a,b)=>Array.isArray(a)&&Array.isArray(b)&&a.length===b.length&&a.every((v,i)=>v===b[i]); const snapSame=(a,b)=>a&&b&&a.baseline_source_sha256===b.baseline_source_sha256&&a.diagnosis_artifact_sha256===b.diagnosis_artifact_sha256&&a.sidecar_semantic_sha256===b.sidecar_semantic_sha256&&a.diagnosis_semantic_sha256===b.diagnosis_semantic_sha256&&a.observed_failure_count===b.observed_failure_count&&a.work_item_count===b.work_item_count&&same(a.allowed_kinds,b.allowed_kinds); if(!/^[A-Za-z0-9_-]+$/.test(mod))fail('module'); const contractPath='.dna/specs/diagnosis-'+mod+'.contract.json'; const reviewPath='.dna/specs/diagnosis-'+mod+'.review.json'; const diagnosisPath='.dna/specs/diagnosis-'+mod+'.md'; const data=JSON.parse(fs.readFileSync(inside(contractPath),'utf8')); if(data.contract_version!=='diagnosis-contract/v2'||data.module!==mod||data.diagnosis_artifact!==diagnosisPath)fail('contract'); const snap=data.contract_snapshot||{}; const semantic=JSON.parse(JSON.stringify(data)); delete semantic.contract_snapshot.sidecar_semantic_sha256; delete semantic.contract_snapshot.diagnosis_artifact_sha256; delete semantic.staleness.diagnosis_artifact; if(snap.sidecar_semantic_sha256!==hash(canon(semantic)))fail('contract_snapshot'); const review=JSON.parse(fs.readFileSync(inside(reviewPath),'utf8')); if(review.contract_version!=='diagnosis-contract-review/v1'||review.contract_valid!==true||review.verdict!=='APPROVE')fail('review'); if(review.contract_artifact_reviewed!==contractPath||review.artifact_reviewed!==diagnosisPath||!snapSame(review.contract_snapshot,snap))fail('review_snapshot'); if(!Array.isArray(review.evidence_paths)||!review.evidence_paths.length)fail('review_evidence'); for(const p of review.evidence_paths){const r=inside(p);if(!fs.existsSync(r)||!fs.statSync(r).isFile())fail('review_evidence')} const cm=fs.statSync(inside(contractPath)).mtimeMs; const rm=fs.statSync(inside(reviewPath)).mtimeMs; const updated=Date.parse(review.updated_at); if(!Number.isFinite(updated)||rm<cm||updated+300000<cm||Math.abs(rm-updated)>300000)fail('review_stale'); const v1=rel(process.argv[2]); const v2=rel(process.argv[3]); const action=new Set(['BUG','UNIMPLEMENTED','INFRA','TEST_BUG']); const allowed=new Set(); for(const item of data.work_items||[]){if(!action.has(item.kind))continue;const evidence=new Set((item.v2_evidence_paths||[]).map(rel));for(const p of item.allowed_change_paths||[]){if(typeof p!=='string'||['*','?','[',']'].some(c=>p.includes(c)))fail('allowed_path_pattern');const n=rel(p);const r=inside(p);if(under(n,v1))fail('v1_change');if(!(under(n,v2)||under(n,'test')||under(n,'lib')))fail('allowed_path_root');if(fs.existsSync(r)?!fs.statSync(r).isFile():!path.basename(n).includes('.'))fail('allowed_path_not_file');if(!evidence.has(n))fail('allowed_path_not_evidence');allowed.add(n)}} if(!allowed.size)fail('allowed_paths'); const head=cp.execFileSync('git',['rev-parse','HEAD'],{encoding:'utf8'}).trim(); const output=cp.execFileSync('git',['diff-tree','--root','--no-commit-id','--name-only','-r',head],{encoding:'utf8'}).trim(); const changed=output?output.split(/\\r?\\n/).filter(Boolean):[]; if(!changed.length)fail('empty_commit'); for(const p of changed){const n=rel(p);if(under(n,v1))fail('v1_change');if(!allowed.has(n))fail('outside_diagnosis')} const dirty=cp.execFileSync('git',['status','--porcelain','--untracked-files=all','--',...allowed],{encoding:'utf8'}).trim(); if(dirty)fail('uncommitted_allowed_path'); \" \"$ARGUMENTS\" \"{{v1_path}}\" \"{{v2_path}}\""
760
779
  handoff:
761
780
  consumes:
762
781
  - type: file
@@ -954,6 +973,14 @@ workflows:
954
973
  - CONTINUE: this round made progress or review found safe, actionable, in-diagnosis issues that should continue in the next fix invocation
955
974
  - REQUEST_CHANGES: verification found issues in this round's changes
956
975
  - BLOCKED: review blocks verification, user confirmation is needed, evidence conflicts, unsafe/out-of-scope changes, or repeated no-progress prevents safe continuation
976
+ canonical_runtime:
977
+ outputs:
978
+ - type: state
979
+ artifact_id: fix-progress-verdict
980
+ schema_ref: intentdna.frw.fix-progress.v1
981
+ description: "Canonical typed fix-loop verdict and provenance summary"
982
+ prompt_suffix: |
983
+ Canonical runtime output contract: return the exact progress JSON as a structured output named fix-progress-verdict with schema_ref intentdna.frw.fix-progress.v1 in the final outputs envelope. The verdict may also be DIAGNOSIS_CONTRACT_INVALID or PROVENANCE_INVALID when the corresponding typed failure is detected. This is in addition to writing the declared progress files.
957
984
  handoff:
958
985
  consumes:
959
986
  - type: file
@@ -90,6 +90,7 @@ variables:
90
90
  test_command:
91
91
  description: Command that proves behavior has not regressed
92
92
  default: npm test
93
+ runtime_type: trusted_verifier_command
93
94
  target_scope:
94
95
  description: Code paths allowed for the rewrite step
95
96
  default: src/**
@@ -0,0 +1,80 @@
1
+ # Windows Job Keeper
2
+
3
+ This helper owns one unnamed Windows Job Object and launches exactly one pre-resolved
4
+ provider executable into it. It has no non-system runtime dependency.
5
+
6
+ ## Input protocol
7
+
8
+ The helper reads a strict, bounded UTF-8 line protocol from standard input. Paths
9
+ and arguments are lowercase or uppercase hexadecimal UTF-8 so newlines and other
10
+ delimiters cannot be ambiguous. NUL and invalid UTF-8 are rejected.
11
+
12
+ ```text
13
+ IDNA_WINDOWS_JOB_KEEPER_V1
14
+ exe:<hex UTF-8 absolute executable path>
15
+ cwd:<hex UTF-8 working directory, or empty>
16
+ stdin:<hex UTF-8 absolute input file path, or empty for NUL>
17
+ stdout:<hex UTF-8 absolute new output file path>
18
+ stderr:<hex UTF-8 absolute new error file path>
19
+ mode:argv
20
+ argc:<decimal 0..4096>
21
+ arg:<hex UTF-8 argument 0>
22
+ ...
23
+ envc:<decimal 0..4096>
24
+ env:<hex UTF-8 key=value entry 0>
25
+ ...
26
+ natural_drain_grace_ms:<decimal 0..300000>
27
+ forced_drain_grace_ms:<decimal 0..300000>
28
+ end
29
+ ```
30
+
31
+ For verbatim command-line mode, replace the `mode:argv`, `argc`, and `arg` records
32
+ with:
33
+
34
+ ```text
35
+ mode:verbatim
36
+ command:<hex UTF-8 exact command line>
37
+ ```
38
+
39
+ `argv` mode constructs a Windows CRT-compatible command line from the resolved
40
+ executable and argument vector. `verbatim` mode passes the trusted resolver's exact
41
+ command line to `CreateProcessW`; the decoded command is limited to 32766 UTF-16
42
+ code units because the Windows limit includes the terminating NUL. It is not quoted
43
+ or rewritten by the helper.
44
+
45
+ Environment entries represent the complete effective provider environment; the
46
+ helper never inherits its own ambient environment. Each entry must have a nonempty
47
+ key followed by `=`, keys cannot contain `=`, and case-insensitive duplicate keys
48
+ are rejected. Entries are sorted case-insensitively and materialized as a bounded
49
+ double-NUL UTF-16 block for `CREATE_UNICODE_ENVIRONMENT`. The block is limited to
50
+ 32767 UTF-16 code units.
51
+
52
+ The helper opens provider stdin with `OPEN_EXISTING`; stdout and stderr use
53
+ `CREATE_NEW` and reject an existing destination. Handles are inheritable and only
54
+ those three pass through `PROC_THREAD_ATTRIBUTE_HANDLE_LIST`. Keeper stdin remains
55
+ a separate control channel managed by the parent.
56
+
57
+ After `end`, the helper creates the suspended provider, assigns it to the Job, and
58
+ emits `contained_ready`. It does not run the provider until it receives exactly
59
+ `release\n`, allowing the parent to durably persist containment identity first.
60
+ After release, the only valid record is `terminate\n`. EOF at either gate means the
61
+ parent disappeared and kills the Job. Any other record is a protocol error. The
62
+ complete config is limited to 8 MiB and each line to 1 MiB.
63
+
64
+ ## Output protocol
65
+
66
+ Standard output contains NDJSON facts. `contained_ready` includes keeper and root
67
+ PIDs and creation `FILETIME`s plus a SHA-256 identifier for the non-exported Job
68
+ nonce. The Job itself is unnamed and cannot be opened by identifier. `released`
69
+ confirms the suspended root was resumed. `completed` includes the
70
+ observed Windows root exit code and is emitted only after Job accounting reports
71
+ zero active processes. Errors are NDJSON on standard error; error cleanup never
72
+ emits `completed`.
73
+
74
+ The provider starts suspended, is assigned to a Job configured with
75
+ `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`, and is resumed only after its identity is
76
+ captured and the release gate passes. A normal root exit lets descendants finish
77
+ naturally for `natural_drain_grace_ms`; expiry, terminate, protocol failure, or
78
+ parent EOF terminates the Job. Forced cleanup must reach zero active processes
79
+ within `forced_drain_grace_ms` or fails closed without a completed fact. Closing or
80
+ crashing the helper closes the Job handle and terminates every member.